<commit_before>// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/layer_tree_host.h"

#include "cc/animation_curve.h"
#include "cc/layer.h"
#include "cc/layer_animation_controller.h"
#include "cc/layer_impl.h"
#include "cc/test/fake_content_layer.h"
#include "cc/test/fake_content_layer_client.h"
#include "cc/test/layer_tree_test_common.h"
#include "cc/timing_function.h"

namespace cc {
namespace {

class LayerTreeHostAnimationTest : public ThreadedTest {
 public:
  virtual void setupTree() OVERRIDE {
    ThreadedTest::setupTree();
    m_layerTreeHost->rootLayer()->setLayerAnimationDelegate(this);
  }
};

// Makes sure that setNeedsAnimate does not cause the commitRequested() state to
// be set.
class LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested()
      : num_commits_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void animate(base::TimeTicks monotonicTime) OVERRIDE {
    // We skip the first commit becasue its the commit that populates the
    // impl thread with a tree. After the second commit, the test is done.
    if (num_commits_ != 1)
      return;

    m_layerTreeHost->setNeedsAnimate();
    // Right now, commitRequested is going to be true, because during
    // beginFrame, we force commitRequested to true to prevent requests from
    // hitting the impl thread. But, when the next didCommit happens, we should
    // verify that commitRequested has gone back to false.
  }

  virtual void didCommit() OVERRIDE {
    if (!num_commits_) {
      EXPECT_FALSE(m_layerTreeHost->commitRequested());
      m_layerTreeHost->setNeedsAnimate();
      EXPECT_FALSE(m_layerTreeHost->commitRequested());
    }

    // Verifies that the setNeedsAnimate we made in ::animate did not
    // trigger commitRequested.
    EXPECT_FALSE(m_layerTreeHost->commitRequested());
    endTest();
    num_commits_++;
  }

  virtual void afterTest() OVERRIDE {}

 private:
  int num_commits_;
};

MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested)

// Trigger a frame with setNeedsCommit. Then, inside the resulting animate
// callback, requet another frame using setNeedsAnimate. End the test when
// animate gets called yet-again, indicating that the proxy is correctly
// handling the case where setNeedsAnimate() is called inside the begin frame
// flow.
class LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback()
      : num_animates_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void animate(base::TimeTicks) OVERRIDE {
    if (!num_animates_) {
      m_layerTreeHost->setNeedsAnimate();
      num_animates_++;
      return;
    }
    endTest();
  }

  virtual void afterTest() OVERRIDE {}

 private:
  int num_animates_;
};

MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback)

// Add a layer animation and confirm that
// LayerTreeHostImpl::updateAnimationState does get called and continues to
// get called.
class LayerTreeHostAnimationTestAddAnimation :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestAddAnimation()
      : num_animates_(0)
      , received_animation_started_notification_(false)
      , start_time_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postAddInstantAnimationToMainThread();
  }

  virtual void updateAnimationState(
      LayerTreeHostImpl* impl_host,
      bool hasUnfinishedAnimation) OVERRIDE {
    if (!num_animates_) {
      // The animation had zero duration so layerTreeHostImpl should no
      // longer need to animate its layers.
      EXPECT_FALSE(hasUnfinishedAnimation);
      num_animates_++;
      return;
    }
    EXPECT_LT(0, start_time_);
    EXPECT_TRUE(received_animation_started_notification_);
    endTest();
  }

  virtual void notifyAnimationStarted(double wall_clock_time) OVERRIDE {
    received_animation_started_notification_ = true;
    start_time_ = wall_clock_time;
  }

  virtual void afterTest() OVERRIDE {}

private:
  int num_animates_;
  bool received_animation_started_notification_;
  double start_time_;
};

MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestAddAnimation)

// Add a layer animation to a layer, but continually fail to draw. Confirm that
// after a while, we do eventually force a draw.
class LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws()
      : started_animating_(false) {
  }

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
  }

  virtual void animateLayers(
      LayerTreeHostImpl* host_impl,
      base::TimeTicks monotonicTime) OVERRIDE {
    started_animating_ = true;
  }

  virtual void drawLayersOnThread(LayerTreeHostImpl*) OVERRIDE {
    if (started_animating_)
      endTest();
  }

  virtual bool prepareToDrawOnThread(
      LayerTreeHostImpl* host_impl,
      LayerTreeHostImpl::FrameData& frame,
      bool result) OVERRIDE {
    return false;
  }

  virtual void afterTest() OVERRIDE {}

 private:
  bool started_animating_;
};

// Starvation can only be an issue with the MT compositor.
MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws)

// Ensures that animations continue to be ticked when we are backgrounded.
class LayerTreeHostAnimationTestTickAnimationWhileBackgrounded :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestTickAnimationWhileBackgrounded()
      : num_animates_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
  }

  // Use willAnimateLayers to set visible false before the animation runs and
  // causes a commit, so we block the second visible animate in single-thread
  // mode.
  virtual void willAnimateLayers(
      LayerTreeHostImpl* host_impl,
      base::TimeTicks monotonicTime) OVERRIDE {
    if (num_animates_ < 2) {
      if (!num_animates_) {
        // We have a long animation running. It should continue to tick even
        // if we are not visible.
        postSetVisibleToMainThread(false);
      }
      num_animates_++;
      return;
    }
    endTest();
  }

  virtual void afterTest() OVERRIDE {}

 private:
  int num_animates_;
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestTickAnimationWhileBackgrounded)

// Ensures that animations continue to be ticked when we are backgrounded.
class LayerTreeHostAnimationTestAddAnimationWithTimingFunction :
    public LayerTreeHostAnimationTest {
public:
  LayerTreeHostAnimationTestAddAnimationWithTimingFunction() {}

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
  }

  virtual void animateLayers(
      LayerTreeHostImpl* host_impl,
      base::TimeTicks monotonicTime) OVERRIDE {
    LayerAnimationController* controller =
        m_layerTreeHost->rootLayer()->layerAnimationController();
    Animation* animation =
        controller->getAnimation(0, Animation::Opacity);
    if (!animation)
      return;

    const FloatAnimationCurve* curve =
        animation->curve()->toFloatAnimationCurve();
    float startOpacity = curve->getValue(0);
    float endOpacity = curve->getValue(curve->duration());
    float linearly_interpolated_opacity =
        0.25 * endOpacity + 0.75 * startOpacity;
    double time = curve->duration() * 0.25;
    // If the linear timing function associated with this animation was not
    // picked up, then the linearly interpolated opacity would be different
    // because of the default ease timing function.
    EXPECT_FLOAT_EQ(linearly_interpolated_opacity, curve->getValue(time));

    LayerAnimationController* controller_impl =
        host_impl->rootLayer()->layerAnimationController();
    Animation* animation_impl =
        controller_impl->getAnimation(0, Animation::Opacity);

    controller->removeAnimation(animation->id());
    controller_impl->removeAnimation(animation_impl->id());
    endTest();
  }

  virtual void afterTest() OVERRIDE {}
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestAddAnimationWithTimingFunction)

// Ensures that main thread animations have their start times synchronized with
// impl thread animations.
class LayerTreeHostAnimationTestSynchronizeAnimationStartTimes :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestSynchronizeAnimationStartTimes()
      : main_start_time_(-1),
        impl_start_time_(-1) {
  }

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
  }

  virtual void notifyAnimationStarted(double time) OVERRIDE {
    LayerAnimationController* controller =
        m_layerTreeHost->rootLayer()->layerAnimationController();
    Animation* animation =
        controller->getAnimation(0, Animation::Opacity);
    main_start_time_ = animation->startTime();
    controller->removeAnimation(animation->id());

    if (impl_start_time_ > 0)
      endTest();
  }

  virtual void updateAnimationState(
      LayerTreeHostImpl* impl_host,
      bool hasUnfinishedAnimation) OVERRIDE {
    LayerAnimationController* controller =
        impl_host->rootLayer()->layerAnimationController();
    Animation* animation =
        controller->getAnimation(0, Animation::Opacity);
    if (!animation)
      return;

    impl_start_time_ = animation->startTime();
    controller->removeAnimation(animation->id());

    if (main_start_time_ > 0)
      endTest();
  }

  virtual void afterTest() OVERRIDE {
    EXPECT_FLOAT_EQ(impl_start_time_, main_start_time_);
  }

 private:
  double main_start_time_;
  double impl_start_time_;
};

SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestSynchronizeAnimationStartTimes)

// Ensures that notifyAnimationFinished is called.
class LayerTreeHostAnimationTestAnimationFinishedEvents :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestAnimationFinishedEvents() {}

  virtual void beginTest() OVERRIDE {
    postAddInstantAnimationToMainThread();
  }

  virtual void notifyAnimationFinished(double time) OVERRIDE {
    LayerAnimationController* controller =
        m_layerTreeHost->rootLayer()->layerAnimationController();
    Animation* animation =
        controller->getAnimation(0, Animation::Opacity);
    controller->removeAnimation(animation->id());
    endTest();
  }

  virtual void afterTest() OVERRIDE {}
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestAnimationFinishedEvents)

// Ensures that when opacity is being animated, this value does not cause the
// subtree to be skipped.
class LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity()
      : update_check_layer_(FakeContentLayer::Create(&client_)) {
  }

  virtual void setupTree() OVERRIDE {
    update_check_layer_->setOpacity(0);
    m_layerTreeHost->setRootLayer(update_check_layer_);
    LayerTreeHostAnimationTest::setupTree();
  }

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(update_check_layer_.get());
  }

  virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE {
    endTest();
  }

  virtual void afterTest() OVERRIDE {
    // update() should have been called once, proving that the layer was not
    // skipped.
    EXPECT_EQ(1, update_check_layer_->update_count());

    // clear update_check_layer_ so LayerTreeHost dies.
    update_check_layer_ = NULL;
  }

 private:
  FakeContentLayerClient client_;
  scoped_refptr<FakeContentLayer> update_check_layer_;
};

MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity)

// Layers added to tree with existing active animations should have the
// animation correctly recognized.
class LayerTreeHostAnimationTestLayerAddedWithAnimation :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestLayerAddedWithAnimation() { }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void didCommit() OVERRIDE {
    if (m_layerTreeHost->commitNumber() == 1) {
      scoped_refptr<Layer> layer = Layer::create();
      layer->setLayerAnimationDelegate(this);

      // Any valid AnimationCurve will do here.
      scoped_ptr<AnimationCurve> curve(EaseTimingFunction::create());
      scoped_ptr<Animation> animation(
          Animation::create(curve.Pass(), 1, 1,
                                  Animation::Opacity));
      layer->layerAnimationController()->addAnimation(animation.Pass());

      // We add the animation *before* attaching the layer to the tree.
      m_layerTreeHost->rootLayer()->addChild(layer);
    }
  }

  virtual void animateLayers(
      LayerTreeHostImpl* impl_host,
      base::TimeTicks monotonic_time) OVERRIDE {
    endTest();
  }

  virtual void afterTest() OVERRIDE {}
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestLayerAddedWithAnimation)

class LayerTreeHostAnimationTestCompositeAndReadbackAnimateCount :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestCompositeAndReadbackAnimateCount()
      : animated_commit_(-1) {
  }

  virtual void animate(base::TimeTicks) OVERRIDE {
    // We shouldn't animate on the compositeAndReadback-forced commit, but we
    // should for the setNeedsCommit-triggered commit.
    animated_commit_ = m_layerTreeHost->commitNumber();
    EXPECT_NE(2, animated_commit_);
  }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void didCommit() OVERRIDE {
    switch (m_layerTreeHost->commitNumber()) {
      case 1:
        m_layerTreeHost->setNeedsCommit();
        break;
      case 2: {
        char pixels[4];
        m_layerTreeHost->compositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1));
        break;
      }
      case 3:
        // This is finishing the readback's commit.
        break;
      case 4:
        // This is finishing the followup commit.
        endTest();
        break;
      default:
        NOTREACHED();
    }
  }

  virtual void afterTest() OVERRIDE {
    EXPECT_EQ(3, animated_commit_);
  }

 private:
  int animated_commit_;
};

MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestCompositeAndReadbackAnimateCount)

class LayerTreeHostAnimationTestContinuousAnimate :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestContinuousAnimate()
      : num_commit_complete_(0),
        num_draw_layers_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void animate(base::TimeTicks) OVERRIDE {
    if (num_draw_layers_ == 2)
      return;
    m_layerTreeHost->setNeedsAnimate();
  }

  virtual void layout() OVERRIDE {
    m_layerTreeHost->rootLayer()->setNeedsDisplay();
  }

  virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE {
    if (num_draw_layers_ == 1)
      num_commit_complete_++;
  }

  virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
    num_draw_layers_++;
    if (num_draw_layers_ == 2)
      endTest();
  }

  virtual void afterTest() OVERRIDE {
    // Check that we didn't commit twice between first and second draw.
    EXPECT_EQ(1, num_commit_complete_);
  }

 private:
  int num_commit_complete_;
  int num_draw_layers_;
};

MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestContinuousAnimate)

}  // namespace
}  // namespace cc
<commit_msg>Add missing nullity check to LayerTreeHostAnimationTestAnimationFinishedEvents<commit_after>// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/layer_tree_host.h"

#include "cc/animation_curve.h"
#include "cc/layer.h"
#include "cc/layer_animation_controller.h"
#include "cc/layer_impl.h"
#include "cc/test/fake_content_layer.h"
#include "cc/test/fake_content_layer_client.h"
#include "cc/test/layer_tree_test_common.h"
#include "cc/timing_function.h"

namespace cc {
namespace {

class LayerTreeHostAnimationTest : public ThreadedTest {
 public:
  virtual void setupTree() OVERRIDE {
    ThreadedTest::setupTree();
    m_layerTreeHost->rootLayer()->setLayerAnimationDelegate(this);
  }
};

// Makes sure that setNeedsAnimate does not cause the commitRequested() state to
// be set.
class LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested()
      : num_commits_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void animate(base::TimeTicks monotonicTime) OVERRIDE {
    // We skip the first commit becasue its the commit that populates the
    // impl thread with a tree. After the second commit, the test is done.
    if (num_commits_ != 1)
      return;

    m_layerTreeHost->setNeedsAnimate();
    // Right now, commitRequested is going to be true, because during
    // beginFrame, we force commitRequested to true to prevent requests from
    // hitting the impl thread. But, when the next didCommit happens, we should
    // verify that commitRequested has gone back to false.
  }

  virtual void didCommit() OVERRIDE {
    if (!num_commits_) {
      EXPECT_FALSE(m_layerTreeHost->commitRequested());
      m_layerTreeHost->setNeedsAnimate();
      EXPECT_FALSE(m_layerTreeHost->commitRequested());
    }

    // Verifies that the setNeedsAnimate we made in ::animate did not
    // trigger commitRequested.
    EXPECT_FALSE(m_layerTreeHost->commitRequested());
    endTest();
    num_commits_++;
  }

  virtual void afterTest() OVERRIDE {}

 private:
  int num_commits_;
};

MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested)

// Trigger a frame with setNeedsCommit. Then, inside the resulting animate
// callback, requet another frame using setNeedsAnimate. End the test when
// animate gets called yet-again, indicating that the proxy is correctly
// handling the case where setNeedsAnimate() is called inside the begin frame
// flow.
class LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback()
      : num_animates_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void animate(base::TimeTicks) OVERRIDE {
    if (!num_animates_) {
      m_layerTreeHost->setNeedsAnimate();
      num_animates_++;
      return;
    }
    endTest();
  }

  virtual void afterTest() OVERRIDE {}

 private:
  int num_animates_;
};

MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback)

// Add a layer animation and confirm that
// LayerTreeHostImpl::updateAnimationState does get called and continues to
// get called.
class LayerTreeHostAnimationTestAddAnimation :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestAddAnimation()
      : num_animates_(0)
      , received_animation_started_notification_(false)
      , start_time_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postAddInstantAnimationToMainThread();
  }

  virtual void updateAnimationState(
      LayerTreeHostImpl* impl_host,
      bool hasUnfinishedAnimation) OVERRIDE {
    if (!num_animates_) {
      // The animation had zero duration so layerTreeHostImpl should no
      // longer need to animate its layers.
      EXPECT_FALSE(hasUnfinishedAnimation);
      num_animates_++;
      return;
    }

    if (received_animation_started_notification_) {
      EXPECT_LT(0, start_time_);
      endTest();
    }
  }

  virtual void notifyAnimationStarted(double wall_clock_time) OVERRIDE {
    received_animation_started_notification_ = true;
    start_time_ = wall_clock_time;
    if (num_animates_) {
      EXPECT_LT(0, start_time_);
      endTest();
    }
  }

  virtual void afterTest() OVERRIDE {}

private:
  int num_animates_;
  bool received_animation_started_notification_;
  double start_time_;
};

MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestAddAnimation)

// Add a layer animation to a layer, but continually fail to draw. Confirm that
// after a while, we do eventually force a draw.
class LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws()
      : started_animating_(false) {
  }

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
  }

  virtual void animateLayers(
      LayerTreeHostImpl* host_impl,
      base::TimeTicks monotonicTime) OVERRIDE {
    started_animating_ = true;
  }

  virtual void drawLayersOnThread(LayerTreeHostImpl*) OVERRIDE {
    if (started_animating_)
      endTest();
  }

  virtual bool prepareToDrawOnThread(
      LayerTreeHostImpl* host_impl,
      LayerTreeHostImpl::FrameData& frame,
      bool result) OVERRIDE {
    return false;
  }

  virtual void afterTest() OVERRIDE {}

 private:
  bool started_animating_;
};

// Starvation can only be an issue with the MT compositor.
MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws)

// Ensures that animations continue to be ticked when we are backgrounded.
class LayerTreeHostAnimationTestTickAnimationWhileBackgrounded :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestTickAnimationWhileBackgrounded()
      : num_animates_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
  }

  // Use willAnimateLayers to set visible false before the animation runs and
  // causes a commit, so we block the second visible animate in single-thread
  // mode.
  virtual void willAnimateLayers(
      LayerTreeHostImpl* host_impl,
      base::TimeTicks monotonicTime) OVERRIDE {
    if (num_animates_ < 2) {
      if (!num_animates_) {
        // We have a long animation running. It should continue to tick even
        // if we are not visible.
        postSetVisibleToMainThread(false);
      }
      num_animates_++;
      return;
    }
    endTest();
  }

  virtual void afterTest() OVERRIDE {}

 private:
  int num_animates_;
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestTickAnimationWhileBackgrounded)

// Ensures that animations continue to be ticked when we are backgrounded.
class LayerTreeHostAnimationTestAddAnimationWithTimingFunction :
    public LayerTreeHostAnimationTest {
public:
  LayerTreeHostAnimationTestAddAnimationWithTimingFunction() {}

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
  }

  virtual void animateLayers(
      LayerTreeHostImpl* host_impl,
      base::TimeTicks monotonicTime) OVERRIDE {
    LayerAnimationController* controller =
        m_layerTreeHost->rootLayer()->layerAnimationController();
    Animation* animation =
        controller->getAnimation(0, Animation::Opacity);
    if (!animation)
      return;

    const FloatAnimationCurve* curve =
        animation->curve()->toFloatAnimationCurve();
    float startOpacity = curve->getValue(0);
    float endOpacity = curve->getValue(curve->duration());
    float linearly_interpolated_opacity =
        0.25 * endOpacity + 0.75 * startOpacity;
    double time = curve->duration() * 0.25;
    // If the linear timing function associated with this animation was not
    // picked up, then the linearly interpolated opacity would be different
    // because of the default ease timing function.
    EXPECT_FLOAT_EQ(linearly_interpolated_opacity, curve->getValue(time));

    LayerAnimationController* controller_impl =
        host_impl->rootLayer()->layerAnimationController();
    Animation* animation_impl =
        controller_impl->getAnimation(0, Animation::Opacity);

    controller->removeAnimation(animation->id());
    controller_impl->removeAnimation(animation_impl->id());
    endTest();
  }

  virtual void afterTest() OVERRIDE {}
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestAddAnimationWithTimingFunction)

// Ensures that main thread animations have their start times synchronized with
// impl thread animations.
class LayerTreeHostAnimationTestSynchronizeAnimationStartTimes :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestSynchronizeAnimationStartTimes()
      : main_start_time_(-1),
        impl_start_time_(-1) {
  }

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
  }

  virtual void notifyAnimationStarted(double time) OVERRIDE {
    LayerAnimationController* controller =
        m_layerTreeHost->rootLayer()->layerAnimationController();
    Animation* animation =
        controller->getAnimation(0, Animation::Opacity);
    main_start_time_ = animation->startTime();
    controller->removeAnimation(animation->id());

    if (impl_start_time_ > 0)
      endTest();
  }

  virtual void updateAnimationState(
      LayerTreeHostImpl* impl_host,
      bool hasUnfinishedAnimation) OVERRIDE {
    LayerAnimationController* controller =
        impl_host->rootLayer()->layerAnimationController();
    Animation* animation =
        controller->getAnimation(0, Animation::Opacity);
    if (!animation)
      return;

    impl_start_time_ = animation->startTime();
    controller->removeAnimation(animation->id());

    if (main_start_time_ > 0)
      endTest();
  }

  virtual void afterTest() OVERRIDE {
    EXPECT_FLOAT_EQ(impl_start_time_, main_start_time_);
  }

 private:
  double main_start_time_;
  double impl_start_time_;
};

SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestSynchronizeAnimationStartTimes)

// Ensures that notifyAnimationFinished is called.
class LayerTreeHostAnimationTestAnimationFinishedEvents :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestAnimationFinishedEvents() {}

  virtual void beginTest() OVERRIDE {
    postAddInstantAnimationToMainThread();
  }

  virtual void notifyAnimationFinished(double time) OVERRIDE {
    LayerAnimationController* controller =
        m_layerTreeHost->rootLayer()->layerAnimationController();
    Animation* animation =
        controller->getAnimation(0, Animation::Opacity);
    if (animation)
      controller->removeAnimation(animation->id());
    endTest();
  }

  virtual void afterTest() OVERRIDE {}
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestAnimationFinishedEvents)

// Ensures that when opacity is being animated, this value does not cause the
// subtree to be skipped.
class LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity()
      : update_check_layer_(FakeContentLayer::Create(&client_)) {
  }

  virtual void setupTree() OVERRIDE {
    update_check_layer_->setOpacity(0);
    m_layerTreeHost->setRootLayer(update_check_layer_);
    LayerTreeHostAnimationTest::setupTree();
  }

  virtual void beginTest() OVERRIDE {
    postAddAnimationToMainThread(update_check_layer_.get());
  }

  virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE {
    endTest();
  }

  virtual void afterTest() OVERRIDE {
    // update() should have been called once, proving that the layer was not
    // skipped.
    EXPECT_EQ(1, update_check_layer_->update_count());

    // clear update_check_layer_ so LayerTreeHost dies.
    update_check_layer_ = NULL;
  }

 private:
  FakeContentLayerClient client_;
  scoped_refptr<FakeContentLayer> update_check_layer_;
};

MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity)

// Layers added to tree with existing active animations should have the
// animation correctly recognized.
class LayerTreeHostAnimationTestLayerAddedWithAnimation :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestLayerAddedWithAnimation() { }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void didCommit() OVERRIDE {
    if (m_layerTreeHost->commitNumber() == 1) {
      scoped_refptr<Layer> layer = Layer::create();
      layer->setLayerAnimationDelegate(this);

      // Any valid AnimationCurve will do here.
      scoped_ptr<AnimationCurve> curve(EaseTimingFunction::create());
      scoped_ptr<Animation> animation(
          Animation::create(curve.Pass(), 1, 1,
                                  Animation::Opacity));
      layer->layerAnimationController()->addAnimation(animation.Pass());

      // We add the animation *before* attaching the layer to the tree.
      m_layerTreeHost->rootLayer()->addChild(layer);
    }
  }

  virtual void animateLayers(
      LayerTreeHostImpl* impl_host,
      base::TimeTicks monotonic_time) OVERRIDE {
    endTest();
  }

  virtual void afterTest() OVERRIDE {}
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeHostAnimationTestLayerAddedWithAnimation)

class LayerTreeHostAnimationTestCompositeAndReadbackAnimateCount :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestCompositeAndReadbackAnimateCount()
      : animated_commit_(-1) {
  }

  virtual void animate(base::TimeTicks) OVERRIDE {
    // We shouldn't animate on the compositeAndReadback-forced commit, but we
    // should for the setNeedsCommit-triggered commit.
    animated_commit_ = m_layerTreeHost->commitNumber();
    EXPECT_NE(2, animated_commit_);
  }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void didCommit() OVERRIDE {
    switch (m_layerTreeHost->commitNumber()) {
      case 1:
        m_layerTreeHost->setNeedsCommit();
        break;
      case 2: {
        char pixels[4];
        m_layerTreeHost->compositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1));
        break;
      }
      case 3:
        // This is finishing the readback's commit.
        break;
      case 4:
        // This is finishing the followup commit.
        endTest();
        break;
      default:
        NOTREACHED();
    }
  }

  virtual void afterTest() OVERRIDE {
    EXPECT_EQ(3, animated_commit_);
  }

 private:
  int animated_commit_;
};

MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestCompositeAndReadbackAnimateCount)

class LayerTreeHostAnimationTestContinuousAnimate :
    public LayerTreeHostAnimationTest {
 public:
  LayerTreeHostAnimationTestContinuousAnimate()
      : num_commit_complete_(0),
        num_draw_layers_(0) {
  }

  virtual void beginTest() OVERRIDE {
    postSetNeedsCommitToMainThread();
  }

  virtual void animate(base::TimeTicks) OVERRIDE {
    if (num_draw_layers_ == 2)
      return;
    m_layerTreeHost->setNeedsAnimate();
  }

  virtual void layout() OVERRIDE {
    m_layerTreeHost->rootLayer()->setNeedsDisplay();
  }

  virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE {
    if (num_draw_layers_ == 1)
      num_commit_complete_++;
  }

  virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
    num_draw_layers_++;
    if (num_draw_layers_ == 2)
      endTest();
  }

  virtual void afterTest() OVERRIDE {
    // Check that we didn't commit twice between first and second draw.
    EXPECT_EQ(1, num_commit_complete_);
  }

 private:
  int num_commit_complete_;
  int num_draw_layers_;
};

MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestContinuousAnimate)

}  // namespace
}  // namespace cc
<|endoftext|>


<commit_before>//
//  kern_start.cpp
//  Shiki
//
//  Copyright © 2016-2017 vit9696. All rights reserved.
//

#include <Headers/plugin_start.hpp>
#include <Headers/kern_api.hpp>
#include <Headers/kern_file.hpp>

#include "kern_resources.hpp"

enum ShikiGVAPatches {
	ForceOnlineRenderer = 1,
	AllowNonBGRA = 2,
	ForceCompatibleRenderer = 4,
	VDAExecutableWhitelist = 8,
    KillAppleGVA = 16
};

// 32 bytes should be reasonable for a safe comparison
static constexpr size_t NVPatchSize {32};
static uint8_t nvPatchFind[NVPatchSize] {};
static uint8_t nvPatchReplace[NVPatchSize] {};
static UserPatcher::BinaryModPatch *nvPatch {nullptr};

static void disableSection(uint32_t section) {
	for (size_t i = 0; i < ADDPR(procInfoSize); i++) {
		if (ADDPR(procInfo)[i].section == section) {
			ADDPR(procInfo)[i].section = SectionUnused;
		}
	}

	for (size_t i = 0; i < ADDPR(binaryModSize); i++) {
		auto patches = ADDPR(binaryMod)[i].patches;
		for (size_t j = 0; j < ADDPR(binaryMod)[i].count; j++) {
			if (patches[j].section == section) {
				patches[j].section = SectionUnused;
			}
		}
	}
}

static void shikiNvidiaPatch(void *, KernelPatcher &) {
	// We need to do magic here and prepare a patch for AppleGVA that would force
	// the assumption that NVIDIA is compatible with SKL/KBL processors.

	if (nvPatch) {
		size_t gvaSize {0};
		auto gvaBuf = FileIO::readFileToBuffer("/System/Library/PrivateFrameworks/AppleGVA.framework/Versions/A/AppleGVA", gvaSize);
		if (gvaBuf && gvaSize > PAGE_SIZE) {
			// We perform a two step search, firstly we find the relevant starting point, then we find the patch place
			size_t currOff {0}, endOff {0};

			// The real patch place should be very close
			static constexpr size_t MaxPatchOff {128};

			// 10.12 and newer support many Intel GPUs, which makes the check place very traceable
			if (getKernelVersion() > KernelVersion::ElCapitan) {
				// We need to find the initial check, which we expect to be or with stack var, because of the following:
				// - there are several GPUs that we need to test against (2xSKL, 2xKBL, thus not enough registers)
				// - the check is built as one of Intel and AMD (thus a number of ors in a row)
				static constexpr uint8_t OrOpcode[] {0xFF, 0xFF, 0x48, 0x0B /* XX, XX, XX, XX, XX */};

				endOff = gvaSize - sizeof(OrOpcode) + 5;
				while (currOff != endOff && memcmp(&gvaBuf[currOff], OrOpcode, sizeof(OrOpcode)))
					currOff++;

				if (currOff != endOff && endOff - currOff > MaxPatchOff)
					DBGLOG("shiki", "found pre or instructions at %lu (%02X %02X %02X %02X %02X %02X %02X)", currOff, gvaBuf[currOff+2],
					       gvaBuf[currOff+3], gvaBuf[currOff+4], gvaBuf[currOff+5], gvaBuf[currOff+6], gvaBuf[currOff+7], gvaBuf[currOff+8]);

				else
					currOff = endOff = 0;
			} else {
				// 10.11 is much more problematic, but the good thing is it no longer updates, we use an idea of
				// - few Intel GPUs
				// - test and jmp taking 3 and 2 bytes correspondingly
				static constexpr uint8_t TestOpcode[] {0x74, 0x05, 0x4D, 0x85 /* XX */};

				endOff = gvaSize - sizeof(TestOpcode) + 1;
				while (currOff != endOff && memcmp(&gvaBuf[currOff], TestOpcode, sizeof(TestOpcode)))
					currOff++;

				if (currOff != endOff && endOff - currOff > MaxPatchOff)
					DBGLOG("shiki", "found pre test instructions at %lu (%02X %02X %02X %02X %02X)", currOff, gvaBuf[currOff],
						   gvaBuf[currOff+1], gvaBuf[currOff+2], gvaBuf[currOff+3], gvaBuf[currOff+4]);

				else
					currOff = endOff = 0;
			}

			if (currOff && endOff) {
				// Now we need to get to 2 jmps, which we expect to be 0F 84 xx xx xx xx, because of the following:
				// - acceleration availability takes the main branch, and its code is large (thus 4 byte relative jmp)
				// - we have non-zero comparators (thus je will be used)
				static constexpr size_t JmpOpcodeSize {6};
				static constexpr size_t JmpOpcodeNum {2};
				static constexpr uint8_t JmpOpcode[] {0x0F, 0x84 /* XX, XX, XX, XX */};

				size_t jmpOffsets[JmpOpcodeNum] {};
				bool jmpOffsetsOk {true};

				endOff = currOff + MaxPatchOff - NVPatchSize;

				for (size_t i = 0; i < arrsize(jmpOffsets); i++) {
					while (currOff != endOff && memcmp(&gvaBuf[currOff], JmpOpcode, sizeof(JmpOpcode)))
						currOff++;

					if (currOff != endOff) {
						DBGLOG("shiki", "found %lu jmp instruction at %lu (%02X %02X %02X %02X %02X %02X)", i, currOff, gvaBuf[currOff],
							   gvaBuf[currOff+1], gvaBuf[currOff+2], gvaBuf[currOff+3], gvaBuf[currOff+4], gvaBuf[currOff+5]);

						if (i == 0 || (jmpOffsets[0] + (NVPatchSize - JmpOpcodeSize) >= jmpOffsets[i])) {
							DBGLOG("shiki", "offset %lu is within the bounds", i);
							jmpOffsets[i] = currOff;
						} else {
							jmpOffsetsOk = false;
						}
						currOff++;
					} else {
						jmpOffsetsOk = false;
					}
				}

				if (jmpOffsetsOk && currOff != endOff) {
					DBGLOG("shiki", "preparing find & replace nvidia buffers");
					lilu_os_memcpy(nvPatchFind, &gvaBuf[jmpOffsets[0]], NVPatchSize);
					lilu_os_memcpy(nvPatchReplace, &gvaBuf[jmpOffsets[0]], NVPatchSize);

					static constexpr uint8_t NopOpcode {0x90};
					for (size_t i = 0; i < arrsize(jmpOffsets); i++)
						memset(&nvPatchReplace[jmpOffsets[i] - jmpOffsets[0]], NopOpcode, JmpOpcodeSize);

					DBGLOG("shiki", "find %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", nvPatchFind[0], nvPatchFind[1], nvPatchFind[2],
						   nvPatchFind[3], nvPatchFind[4], nvPatchFind[5], nvPatchFind[6], nvPatchFind[7], nvPatchFind[8], nvPatchFind[9]);

					DBGLOG("shiki", "repl %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", nvPatchReplace[0], nvPatchReplace[1], nvPatchReplace[2],
						   nvPatchReplace[3], nvPatchReplace[4], nvPatchReplace[5], nvPatchReplace[6], nvPatchReplace[7], nvPatchReplace[8], nvPatchReplace[9]);

					// Ok, everything looks sane enough
					nvPatch->section = SectionNVDA;
				}
			} else {
				SYSLOG("shiki", "unable to find or instructions");
			}
		} else {
			SYSLOG("shiki", "failed to read AppleGVA framework %lu", gvaSize);
		}

		if (gvaBuf) Buffer::deleter(gvaBuf);
	} else {
		SYSLOG("shiki", "failed to find suitable nvpatch");
	}
}

static void shikiStart() {
	// Attempt to support fps.1_0 in Safari
	char tmp[16];
	bool patchStreamVideo = PE_parse_boot_argn("-shikifps", tmp, sizeof(tmp));
	bool leaveForceAccelRenderer = true;
	bool leaveBGRASupport = true;
	bool leaveNvidiaUnlock = true;
	bool leaveExecutableWhiteList = true;
    bool leaveAppleGVAAlone = true;

	if (PE_parse_boot_argn("shikigva", tmp, sizeof(tmp))) {
		leaveForceAccelRenderer = !(tmp[0] & ForceOnlineRenderer);
		leaveBGRASupport = !(tmp[0] & AllowNonBGRA);
		leaveNvidiaUnlock = !(tmp[0] & ForceCompatibleRenderer);
		leaveExecutableWhiteList = !(tmp[0] & VDAExecutableWhitelist);
        leaveAppleGVAAlone = !(tmp[0] & KillAppleGVA);
	}

	if (PE_parse_boot_argn("-shikigva", tmp, sizeof(tmp))) {
		SYSLOG("shiki", "-shikigva is deprecated use shikigva=1 instead");
		leaveForceAccelRenderer = false;
	}

	DBGLOG("shiki", "stream %d accel %d bgra %d nvidia %d/%d gva %d", patchStreamVideo, !leaveForceAccelRenderer,
		   !leaveBGRASupport, !leaveNvidiaUnlock, !leaveExecutableWhiteList, !leaveAppleGVAAlone);

	// Disable unused SectionFSTREAM
	if (!patchStreamVideo)
		disableSection(SectionNSTREAM);

	if (leaveForceAccelRenderer)
		disableSection(SectionOFFLINE);

	if (leaveBGRASupport)
		disableSection(SectionBGRA);

    if (leaveAppleGVAAlone)
        disableSection(SectionKILLGVA);

	for (size_t i = 0; i < ADDPR(binaryModSize); i++) {
		auto patches = ADDPR(binaryMod)[i].patches;
		for (size_t j = 0; j < ADDPR(binaryMod)[i].count; j++) {
			if (patches[j].section == SectionNVDA) {
				nvPatch = &patches[j];
				DBGLOG("shiki", "found nvidia patch at %lu:%lu with size %lu", i, j, nvPatch->size);
				break;
			}
		}
	}

	bool nvPatchOk = false;
	if (nvPatch && !leaveNvidiaUnlock && getKernelVersion() >= KernelVersion::ElCapitan) {
		// Do not enable until we are certain it works
		nvPatch->section = SectionUnused;
		nvPatch->find    = nvPatchFind;
		nvPatch->replace = nvPatchReplace;
		nvPatch->size    = NVPatchSize;
		auto err = lilu.onPatcherLoad(shikiNvidiaPatch);
		if (err == LiluAPI::Error::NoError)
			nvPatchOk = true;
		else
			SYSLOG("shiki", "unable to attach to patcher load %d", err);
	}

	if (leaveExecutableWhiteList || !nvPatchOk)
		disableSection(SectionNVDA);

	auto err = lilu.onProcLoad(ADDPR(procInfo), ADDPR(procInfoSize), nullptr, nullptr, ADDPR(binaryMod), ADDPR(binaryModSize));
	if (err != LiluAPI::Error::NoError)
		SYSLOG("shiki", "unable to attach to process load %d", err);
}

static const char *bootargOff[] {
	"-shikioff"
};

static const char *bootargDebug[] {
	"-shikidbg"
};

static const char *bootargBeta[] {
	"-shikibeta"
};

PluginConfiguration ADDPR(config) {
	xStringify(PRODUCT_NAME),
	parseModuleVersion(xStringify(MODULE_VERSION)),
	LiluAPI::AllowNormal,
	bootargOff,
	arrsize(bootargOff),
	bootargDebug,
	arrsize(bootargDebug),
	bootargBeta,
	arrsize(bootargBeta),
	KernelVersion::Mavericks,
	KernelVersion::HighSierra,
	shikiStart
};
<commit_msg>Fixed indentation<commit_after>//
//  kern_start.cpp
//  Shiki
//
//  Copyright © 2016-2017 vit9696. All rights reserved.
//

#include <Headers/plugin_start.hpp>
#include <Headers/kern_api.hpp>
#include <Headers/kern_file.hpp>

#include "kern_resources.hpp"

enum ShikiGVAPatches {
	ForceOnlineRenderer = 1,
	AllowNonBGRA = 2,
	ForceCompatibleRenderer = 4,
	VDAExecutableWhitelist = 8,
	KillAppleGVA = 16
};

// 32 bytes should be reasonable for a safe comparison
static constexpr size_t NVPatchSize {32};
static uint8_t nvPatchFind[NVPatchSize] {};
static uint8_t nvPatchReplace[NVPatchSize] {};
static UserPatcher::BinaryModPatch *nvPatch {nullptr};

static void disableSection(uint32_t section) {
	for (size_t i = 0; i < ADDPR(procInfoSize); i++) {
		if (ADDPR(procInfo)[i].section == section) {
			ADDPR(procInfo)[i].section = SectionUnused;
		}
	}

	for (size_t i = 0; i < ADDPR(binaryModSize); i++) {
		auto patches = ADDPR(binaryMod)[i].patches;
		for (size_t j = 0; j < ADDPR(binaryMod)[i].count; j++) {
			if (patches[j].section == section) {
				patches[j].section = SectionUnused;
			}
		}
	}
}

static void shikiNvidiaPatch(void *, KernelPatcher &) {
	// We need to do magic here and prepare a patch for AppleGVA that would force
	// the assumption that NVIDIA is compatible with SKL/KBL processors.

	if (nvPatch) {
		size_t gvaSize {0};
		auto gvaBuf = FileIO::readFileToBuffer("/System/Library/PrivateFrameworks/AppleGVA.framework/Versions/A/AppleGVA", gvaSize);
		if (gvaBuf && gvaSize > PAGE_SIZE) {
			// We perform a two step search, firstly we find the relevant starting point, then we find the patch place
			size_t currOff {0}, endOff {0};

			// The real patch place should be very close
			static constexpr size_t MaxPatchOff {128};

			// 10.12 and newer support many Intel GPUs, which makes the check place very traceable
			if (getKernelVersion() > KernelVersion::ElCapitan) {
				// We need to find the initial check, which we expect to be or with stack var, because of the following:
				// - there are several GPUs that we need to test against (2xSKL, 2xKBL, thus not enough registers)
				// - the check is built as one of Intel and AMD (thus a number of ors in a row)
				static constexpr uint8_t OrOpcode[] {0xFF, 0xFF, 0x48, 0x0B /* XX, XX, XX, XX, XX */};

				endOff = gvaSize - sizeof(OrOpcode) + 5;
				while (currOff != endOff && memcmp(&gvaBuf[currOff], OrOpcode, sizeof(OrOpcode)))
					currOff++;

				if (currOff != endOff && endOff - currOff > MaxPatchOff)
					DBGLOG("shiki", "found pre or instructions at %lu (%02X %02X %02X %02X %02X %02X %02X)", currOff, gvaBuf[currOff+2],
						   gvaBuf[currOff+3], gvaBuf[currOff+4], gvaBuf[currOff+5], gvaBuf[currOff+6], gvaBuf[currOff+7], gvaBuf[currOff+8]);

				else
					currOff = endOff = 0;
			} else {
				// 10.11 is much more problematic, but the good thing is it no longer updates, we use an idea of
				// - few Intel GPUs
				// - test and jmp taking 3 and 2 bytes correspondingly
				static constexpr uint8_t TestOpcode[] {0x74, 0x05, 0x4D, 0x85 /* XX */};

				endOff = gvaSize - sizeof(TestOpcode) + 1;
				while (currOff != endOff && memcmp(&gvaBuf[currOff], TestOpcode, sizeof(TestOpcode)))
					currOff++;

				if (currOff != endOff && endOff - currOff > MaxPatchOff)
					DBGLOG("shiki", "found pre test instructions at %lu (%02X %02X %02X %02X %02X)", currOff, gvaBuf[currOff],
						   gvaBuf[currOff+1], gvaBuf[currOff+2], gvaBuf[currOff+3], gvaBuf[currOff+4]);

				else
					currOff = endOff = 0;
			}

			if (currOff && endOff) {
				// Now we need to get to 2 jmps, which we expect to be 0F 84 xx xx xx xx, because of the following:
				// - acceleration availability takes the main branch, and its code is large (thus 4 byte relative jmp)
				// - we have non-zero comparators (thus je will be used)
				static constexpr size_t JmpOpcodeSize {6};
				static constexpr size_t JmpOpcodeNum {2};
				static constexpr uint8_t JmpOpcode[] {0x0F, 0x84 /* XX, XX, XX, XX */};

				size_t jmpOffsets[JmpOpcodeNum] {};
				bool jmpOffsetsOk {true};

				endOff = currOff + MaxPatchOff - NVPatchSize;

				for (size_t i = 0; i < arrsize(jmpOffsets); i++) {
					while (currOff != endOff && memcmp(&gvaBuf[currOff], JmpOpcode, sizeof(JmpOpcode)))
						currOff++;

					if (currOff != endOff) {
						DBGLOG("shiki", "found %lu jmp instruction at %lu (%02X %02X %02X %02X %02X %02X)", i, currOff, gvaBuf[currOff],
							   gvaBuf[currOff+1], gvaBuf[currOff+2], gvaBuf[currOff+3], gvaBuf[currOff+4], gvaBuf[currOff+5]);

						if (i == 0 || (jmpOffsets[0] + (NVPatchSize - JmpOpcodeSize) >= jmpOffsets[i])) {
							DBGLOG("shiki", "offset %lu is within the bounds", i);
							jmpOffsets[i] = currOff;
						} else {
							jmpOffsetsOk = false;
						}
						currOff++;
					} else {
						jmpOffsetsOk = false;
					}
				}

				if (jmpOffsetsOk && currOff != endOff) {
					DBGLOG("shiki", "preparing find & replace nvidia buffers");
					lilu_os_memcpy(nvPatchFind, &gvaBuf[jmpOffsets[0]], NVPatchSize);
					lilu_os_memcpy(nvPatchReplace, &gvaBuf[jmpOffsets[0]], NVPatchSize);

					static constexpr uint8_t NopOpcode {0x90};
					for (size_t i = 0; i < arrsize(jmpOffsets); i++)
						memset(&nvPatchReplace[jmpOffsets[i] - jmpOffsets[0]], NopOpcode, JmpOpcodeSize);

					DBGLOG("shiki", "find %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", nvPatchFind[0], nvPatchFind[1], nvPatchFind[2],
						   nvPatchFind[3], nvPatchFind[4], nvPatchFind[5], nvPatchFind[6], nvPatchFind[7], nvPatchFind[8], nvPatchFind[9]);

					DBGLOG("shiki", "repl %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", nvPatchReplace[0], nvPatchReplace[1], nvPatchReplace[2],
						   nvPatchReplace[3], nvPatchReplace[4], nvPatchReplace[5], nvPatchReplace[6], nvPatchReplace[7], nvPatchReplace[8], nvPatchReplace[9]);

					// Ok, everything looks sane enough
					nvPatch->section = SectionNVDA;
				}
			} else {
				SYSLOG("shiki", "unable to find or instructions");
			}
		} else {
			SYSLOG("shiki", "failed to read AppleGVA framework %lu", gvaSize);
		}

		if (gvaBuf) Buffer::deleter(gvaBuf);
	} else {
		SYSLOG("shiki", "failed to find suitable nvpatch");
	}
}

static void shikiStart() {
	// Attempt to support fps.1_0 in Safari
	char tmp[16];
	bool patchStreamVideo = PE_parse_boot_argn("-shikifps", tmp, sizeof(tmp));
	bool leaveForceAccelRenderer = true;
	bool leaveBGRASupport = true;
	bool leaveNvidiaUnlock = true;
	bool leaveExecutableWhiteList = true;
	bool leaveAppleGVAAlone = true;

	if (PE_parse_boot_argn("shikigva", tmp, sizeof(tmp))) {
		leaveForceAccelRenderer = !(tmp[0] & ForceOnlineRenderer);
		leaveBGRASupport = !(tmp[0] & AllowNonBGRA);
		leaveNvidiaUnlock = !(tmp[0] & ForceCompatibleRenderer);
		leaveExecutableWhiteList = !(tmp[0] & VDAExecutableWhitelist);
		leaveAppleGVAAlone = !(tmp[0] & KillAppleGVA);
	}

	if (PE_parse_boot_argn("-shikigva", tmp, sizeof(tmp))) {
		SYSLOG("shiki", "-shikigva is deprecated use shikigva=1 instead");
		leaveForceAccelRenderer = false;
	}

	DBGLOG("shiki", "stream %d accel %d bgra %d nvidia %d/%d gva %d", patchStreamVideo, !leaveForceAccelRenderer,
		   !leaveBGRASupport, !leaveNvidiaUnlock, !leaveExecutableWhiteList, !leaveAppleGVAAlone);

	// Disable unused SectionFSTREAM
	if (!patchStreamVideo)
		disableSection(SectionNSTREAM);

	if (leaveForceAccelRenderer)
		disableSection(SectionOFFLINE);

	if (leaveBGRASupport)
		disableSection(SectionBGRA);

	if (leaveAppleGVAAlone)
		disableSection(SectionKILLGVA);

	for (size_t i = 0; i < ADDPR(binaryModSize); i++) {
		auto patches = ADDPR(binaryMod)[i].patches;
		for (size_t j = 0; j < ADDPR(binaryMod)[i].count; j++) {
			if (patches[j].section == SectionNVDA) {
				nvPatch = &patches[j];
				DBGLOG("shiki", "found nvidia patch at %lu:%lu with size %lu", i, j, nvPatch->size);
				break;
			}
		}
	}

	bool nvPatchOk = false;
	if (nvPatch && !leaveNvidiaUnlock && getKernelVersion() >= KernelVersion::ElCapitan) {
		// Do not enable until we are certain it works
		nvPatch->section = SectionUnused;
		nvPatch->find    = nvPatchFind;
		nvPatch->replace = nvPatchReplace;
		nvPatch->size    = NVPatchSize;
		auto err = lilu.onPatcherLoad(shikiNvidiaPatch);
		if (err == LiluAPI::Error::NoError)
			nvPatchOk = true;
		else
			SYSLOG("shiki", "unable to attach to patcher load %d", err);
	}

	if (leaveExecutableWhiteList || !nvPatchOk)
		disableSection(SectionNVDA);

	auto err = lilu.onProcLoad(ADDPR(procInfo), ADDPR(procInfoSize), nullptr, nullptr, ADDPR(binaryMod), ADDPR(binaryModSize));
	if (err != LiluAPI::Error::NoError)
		SYSLOG("shiki", "unable to attach to process load %d", err);
}

static const char *bootargOff[] {
	"-shikioff"
};

static const char *bootargDebug[] {
	"-shikidbg"
};

static const char *bootargBeta[] {
	"-shikibeta"
};

PluginConfiguration ADDPR(config) {
	xStringify(PRODUCT_NAME),
	parseModuleVersion(xStringify(MODULE_VERSION)),
	LiluAPI::AllowNormal,
	bootargOff,
	arrsize(bootargOff),
	bootargDebug,
	arrsize(bootargDebug),
	bootargBeta,
	arrsize(bootargBeta),
	KernelVersion::Mavericks,
	KernelVersion::HighSierra,
	shikiStart
};
<|endoftext|>


<commit_before>#include "General.h"

#include <shellapi.h>

#include "../../3RVX/LanguageTranslator.h"
#include "../../3RVX/Logger.h"
#include "../../3RVX/Settings.h"
#include "../../3RVX/SkinInfo.h"

#include "../resource.h"

const wchar_t General::REGKEY_NAME[] = L"3RVX";
const wchar_t General::REGKEY_RUN[]
    = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";

void General::Initialize() {
    INIT_CONTROL(GRP_BEHAVIOR, GroupBox, _behaviorGroup);
    INIT_CONTROL(CHK_STARTUP, Checkbox, _startup);
    INIT_CONTROL(CHK_NOTIFY, Checkbox, _notifyIcon);
    INIT_CONTROL(CHK_SOUNDS, Checkbox, _sounds);

    INIT_CONTROL(GRP_SKIN, GroupBox, _skinGroup);
    INIT_CONTROL(CMB_SKIN, ComboBox, _skin);
    _skin.OnSelectionChange = [this]() {
        LoadSkinInfo(_skin.Selection());
        return true;
    };
    INIT_CONTROL(LBL_AUTHOR, Label, _author);
    INIT_CONTROL(BTN_WEBSITE, Button, _website);
    _website.OnClick = [this]() {
        if (_url != L"") {
            ShellExecute(NULL, L"open", _url.c_str(),
                NULL, NULL, SW_SHOWNORMAL);
        }
        return true;
    };

    INIT_CONTROL(GRP_LANGUAGE, GroupBox, _languageGroup);
    INIT_CONTROL(CMB_LANG, ComboBox, _language);
}

void General::LoadSettings() {
    Settings *settings = Settings::Instance();
    LanguageTranslator *lt = settings->Translator();
    _startup.Checked(RunOnStartup());
    _notifyIcon.Checked(settings->NotifyIconEnabled());
    _sounds.Checked(settings->SoundEffectsEnabled());

    /* Determine which skins are available */
    std::list<std::wstring> skins = FindSkins(Settings::SkinDir().c_str());
    for (std::wstring skin : skins) {
        _skin.AddItem(skin);
    }

    /* Update the combo box with the current skin */
    std::wstring current = settings->CurrentSkin();
    int idx = _skin.Select(current);
    if (idx == CB_ERR) {
        _skin.Select(Settings::DefaultSkin);
    }
    LoadSkinInfo(current);

    /* Populate the language box */
    std::list<std::wstring> languages = FindLanguages(
        settings->LanguagesDir().c_str());
    for (std::wstring language : languages) {
        int ext = language.find(L".xml");
        if (ext == language.npos) {
            continue;
        }

        _language.AddItem(language.substr(0, ext));
    }
    std::wstring currentLang = settings->LanguageName();
    _language.Select(currentLang);
}

void General::SaveSettings() {
    if (_hWnd == NULL) {
        return;
    }

    CLOG(L"Saving: General");
    Settings *settings = Settings::Instance();

    RunOnStartup(_startup.Checked());
    settings->NotifyIconEnabled(_notifyIcon.Checked());
    settings->SoundEffectsEnabled(_sounds.Checked());

    settings->CurrentSkin(_skin.Selection());
}

bool General::RunOnStartup() {
    long res;
    HKEY key = NULL;
    bool run = false;

    res = RegOpenKeyEx(HKEY_CURRENT_USER, REGKEY_RUN, NULL, KEY_READ, &key);
    if (res == ERROR_SUCCESS) {
        res = RegQueryValueEx(key, REGKEY_NAME, NULL, NULL, NULL, NULL);
        run = (res == ERROR_SUCCESS);
        RegCloseKey(key);
    }

    return run;
}

bool General::RunOnStartup(bool enable) {
    long res;
    HKEY key = NULL;
    bool ok = false;

    std::wstring path = Settings::AppDir() + L"\\3RVX.exe";

    res = RegOpenKeyEx(HKEY_CURRENT_USER, REGKEY_RUN,
        NULL, KEY_ALL_ACCESS, &key);
    if (res == ERROR_SUCCESS) {
        if (enable) {
            res = RegSetValueEx(key, REGKEY_NAME, NULL, REG_SZ,
                (LPBYTE) path.c_str(), (path.size() + 1) * sizeof(TCHAR));
            ok = (res == ERROR_SUCCESS);
        } else {
            res = RegDeleteValue(key, REGKEY_NAME);
            ok = (res == ERROR_SUCCESS);
        }
        RegCloseKey(key);
    }

    return ok;
}

std::list<std::wstring> General::FindSkins(std::wstring dir) {
    std::list<std::wstring> skins;
    WIN32_FIND_DATA ffd;
    HANDLE hFind;

    CLOG(L"Finding skins in: %s", dir.c_str());
    dir += L"\\*";
    hFind = FindFirstFile(dir.c_str(), &ffd);
    if (hFind == INVALID_HANDLE_VALUE) {
        CLOG(L"FindFirstFile() failed");
        return skins;
    }

    do {
        std::wstring fName(ffd.cFileName);
        if (fName.at(0) == L'.') {
            continue;
        }
        QCLOG(L"%s", fName.c_str());
        skins.push_back(fName);
    } while (FindNextFile(hFind, &ffd));
    FindClose(hFind);

    return skins;
}

void General::LoadSkinInfo(std::wstring skinName) {
    std::wstring skinXML = Settings::Instance()->SkinXML(skinName);
    SkinInfo s(skinXML);

    std::wstring transAuthor
        = Settings::Instance()->Translator()->TranslateAndReplace(
        L"Author: {1}", s.Author());
    _author.Text(transAuthor);

    std::wstring url = s.URL();
    if (url == L"") {
        _website.Disable();
    } else {
        _url = s.URL();
        _website.Enable();
    }
}

std::list<std::wstring> General::FindLanguages(std::wstring dir) {
    std::list<std::wstring> languages;
    WIN32_FIND_DATA ffd;
    HANDLE hFind;

    CLOG(L"Finding language translations in: %s", dir.c_str());
    dir += L"\\*.xml";
    hFind = FindFirstFile(dir.c_str(), &ffd);
    if (hFind == INVALID_HANDLE_VALUE) {
        CLOG(L"FindFirstFile() failed");
        return languages;
    }

    do {
        std::wstring fName(ffd.cFileName);

        if (fName.at(0) == L'.') {
            continue;
        }

        if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            continue;
        }

        QCLOG(L"%s", fName.c_str());
        languages.push_back(fName);
    } while (FindNextFile(hFind, &ffd));
    FindClose(hFind);

    return languages;
}<commit_msg>Save language selection<commit_after>#include "General.h"

#include <shellapi.h>

#include "../../3RVX/LanguageTranslator.h"
#include "../../3RVX/Logger.h"
#include "../../3RVX/Settings.h"
#include "../../3RVX/SkinInfo.h"

#include "../resource.h"

const wchar_t General::REGKEY_NAME[] = L"3RVX";
const wchar_t General::REGKEY_RUN[]
    = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";

void General::Initialize() {
    INIT_CONTROL(GRP_BEHAVIOR, GroupBox, _behaviorGroup);
    INIT_CONTROL(CHK_STARTUP, Checkbox, _startup);
    INIT_CONTROL(CHK_NOTIFY, Checkbox, _notifyIcon);
    INIT_CONTROL(CHK_SOUNDS, Checkbox, _sounds);

    INIT_CONTROL(GRP_SKIN, GroupBox, _skinGroup);
    INIT_CONTROL(CMB_SKIN, ComboBox, _skin);
    _skin.OnSelectionChange = [this]() {
        LoadSkinInfo(_skin.Selection());
        return true;
    };
    INIT_CONTROL(LBL_AUTHOR, Label, _author);
    INIT_CONTROL(BTN_WEBSITE, Button, _website);
    _website.OnClick = [this]() {
        if (_url != L"") {
            ShellExecute(NULL, L"open", _url.c_str(),
                NULL, NULL, SW_SHOWNORMAL);
        }
        return true;
    };

    INIT_CONTROL(GRP_LANGUAGE, GroupBox, _languageGroup);
    INIT_CONTROL(CMB_LANG, ComboBox, _language);
}

void General::LoadSettings() {
    Settings *settings = Settings::Instance();
    LanguageTranslator *lt = settings->Translator();
    _startup.Checked(RunOnStartup());
    _notifyIcon.Checked(settings->NotifyIconEnabled());
    _sounds.Checked(settings->SoundEffectsEnabled());

    /* Determine which skins are available */
    std::list<std::wstring> skins = FindSkins(Settings::SkinDir().c_str());
    for (std::wstring skin : skins) {
        _skin.AddItem(skin);
    }

    /* Update the combo box with the current skin */
    std::wstring current = settings->CurrentSkin();
    int idx = _skin.Select(current);
    if (idx == CB_ERR) {
        _skin.Select(Settings::DefaultSkin);
    }
    LoadSkinInfo(current);

    /* Populate the language box */
    std::list<std::wstring> languages = FindLanguages(
        settings->LanguagesDir().c_str());
    for (std::wstring language : languages) {
        int ext = language.find(L".xml");
        if (ext == language.npos) {
            continue;
        }

        _language.AddItem(language.substr(0, ext));
    }
    std::wstring currentLang = settings->LanguageName();
    _language.Select(currentLang);
}

void General::SaveSettings() {
    if (_hWnd == NULL) {
        return;
    }

    CLOG(L"Saving: General");
    Settings *settings = Settings::Instance();

    RunOnStartup(_startup.Checked());
    settings->NotifyIconEnabled(_notifyIcon.Checked());
    settings->SoundEffectsEnabled(_sounds.Checked());

    settings->CurrentSkin(_skin.Selection());

    settings->LanguageName(_language.Selection());
}

bool General::RunOnStartup() {
    long res;
    HKEY key = NULL;
    bool run = false;

    res = RegOpenKeyEx(HKEY_CURRENT_USER, REGKEY_RUN, NULL, KEY_READ, &key);
    if (res == ERROR_SUCCESS) {
        res = RegQueryValueEx(key, REGKEY_NAME, NULL, NULL, NULL, NULL);
        run = (res == ERROR_SUCCESS);
        RegCloseKey(key);
    }

    return run;
}

bool General::RunOnStartup(bool enable) {
    long res;
    HKEY key = NULL;
    bool ok = false;

    std::wstring path = Settings::AppDir() + L"\\3RVX.exe";

    res = RegOpenKeyEx(HKEY_CURRENT_USER, REGKEY_RUN,
        NULL, KEY_ALL_ACCESS, &key);
    if (res == ERROR_SUCCESS) {
        if (enable) {
            res = RegSetValueEx(key, REGKEY_NAME, NULL, REG_SZ,
                (LPBYTE) path.c_str(), (path.size() + 1) * sizeof(TCHAR));
            ok = (res == ERROR_SUCCESS);
        } else {
            res = RegDeleteValue(key, REGKEY_NAME);
            ok = (res == ERROR_SUCCESS);
        }
        RegCloseKey(key);
    }

    return ok;
}

std::list<std::wstring> General::FindSkins(std::wstring dir) {
    std::list<std::wstring> skins;
    WIN32_FIND_DATA ffd;
    HANDLE hFind;

    CLOG(L"Finding skins in: %s", dir.c_str());
    dir += L"\\*";
    hFind = FindFirstFile(dir.c_str(), &ffd);
    if (hFind == INVALID_HANDLE_VALUE) {
        CLOG(L"FindFirstFile() failed");
        return skins;
    }

    do {
        std::wstring fName(ffd.cFileName);
        if (fName.at(0) == L'.') {
            continue;
        }
        QCLOG(L"%s", fName.c_str());
        skins.push_back(fName);
    } while (FindNextFile(hFind, &ffd));
    FindClose(hFind);

    return skins;
}

void General::LoadSkinInfo(std::wstring skinName) {
    std::wstring skinXML = Settings::Instance()->SkinXML(skinName);
    SkinInfo s(skinXML);

    std::wstring transAuthor
        = Settings::Instance()->Translator()->TranslateAndReplace(
        L"Author: {1}", s.Author());
    _author.Text(transAuthor);

    std::wstring url = s.URL();
    if (url == L"") {
        _website.Disable();
    } else {
        _url = s.URL();
        _website.Enable();
    }
}

std::list<std::wstring> General::FindLanguages(std::wstring dir) {
    std::list<std::wstring> languages;
    WIN32_FIND_DATA ffd;
    HANDLE hFind;

    CLOG(L"Finding language translations in: %s", dir.c_str());
    dir += L"\\*.xml";
    hFind = FindFirstFile(dir.c_str(), &ffd);
    if (hFind == INVALID_HANDLE_VALUE) {
        CLOG(L"FindFirstFile() failed");
        return languages;
    }

    do {
        std::wstring fName(ffd.cFileName);

        if (fName.at(0) == L'.') {
            continue;
        }

        if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            continue;
        }

        QCLOG(L"%s", fName.c_str());
        languages.push_back(fName);
    } while (FindNextFile(hFind, &ffd));
    FindClose(hFind);

    return languages;
}<|endoftext|>


<commit_before>#include "KeyGrabber.h"

#include <string>

KeyGrabber *KeyGrabber::instance = NULL;

KeyGrabber *KeyGrabber::Instance() {
    if (instance == NULL) {
        instance = new KeyGrabber();
    }

    return instance;
}

bool KeyGrabber::Hook() {
    _mouseHook = SetWindowsHookEx(WH_MOUSE_LL,
        LowLevelMouseProc, NULL, NULL);

    _keyHook = SetWindowsHookEx(WH_KEYBOARD_LL,
        LowLevelKeyboardProc, NULL, NULL);

    return _mouseHook && _keyHook;
}

bool KeyGrabber::Unhook() {
    BOOL unMouse = UnhookWindowsHookEx(_mouseHook);
    BOOL unKey = UnhookWindowsHookEx(_keyHook);
    return unMouse && unKey;
}

void KeyGrabber::Grab(HWND hwnd) {
    _updateHwnd = hwnd;
    Hook();
}

bool KeyGrabber::IsModifier(DWORD vk) {
    switch (vk) {
    case VK_MENU:
    case VK_LMENU:
    case VK_RMENU:
    case VK_CONTROL:
    case VK_LCONTROL:
    case VK_RCONTROL:
    case VK_SHIFT:
    case VK_LSHIFT:
    case VK_RSHIFT:
    case VK_LWIN:
    case VK_RWIN:
        return true;
    }
    return false;
}

int KeyGrabber::Modifiers() {
    int mods = 0;
    mods += (GetKeyState(VK_MENU) & 0x8000) << 1;
    mods += (GetKeyState(VK_CONTROL) & 0x8000) << 2;
    mods += (GetKeyState(VK_SHIFT) & 0x8000) << 3;
    mods += (GetKeyState(VK_LWIN) & 0x8000) << 4;
    mods += (GetKeyState(VK_RWIN) & 0x8000) << 4;
    return mods;
}

std::wstring KeyGrabber::ModString(int modifiers) {
    std::wstring str = L"";
    if (modifiers & HKM_MOD_ALT) {
        str += VKToString(VK_MENU) + L" + ";
    }
    if (modifiers & HKM_MOD_CTRL) {
        str += VKToString(VK_CONTROL) + L" + ";
    }
    if (modifiers & HKM_MOD_SHF) {
        str += VKToString(VK_SHIFT) + L" + ";
    }
    if (modifiers & HKM_MOD_WIN) {
        str += L"Win + ";
    }

    return str;
}

std::wstring KeyGrabber::VKToString(
    unsigned int vk, bool extendedKey, bool distinguishLeftRight) {

    BOOL dontCare = distinguishLeftRight ? FALSE : TRUE;
    int extended = extendedKey ? 0x1 : 0x0;

    unsigned int scanCode = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
    scanCode = scanCode << 16;
    scanCode |= extended << 24;
    scanCode |= dontCare << 25;
    wchar_t buf[256] = {};
    GetKeyNameText(scanCode, buf, 256);
    return std::wstring(buf);
}

LRESULT CALLBACK
KeyGrabber::KeyProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }

    if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
        KBDLLHOOKSTRUCT *kbInfo = (KBDLLHOOKSTRUCT*) lParam;

        DWORD vk = kbInfo->vkCode;
        if (IsModifier(kbInfo->vkCode)) {
            /* Ignore modifier keys */
            return CallNextHookEx(NULL, nCode, wParam, lParam);
        }

        /* GetKeyNameText expects the following:
         * 16-23: scan code
         *    24: extended key flag
         *    25: 'do not care' bit (don't distinguish between L/R keys) */
        BOOL dontCare = TRUE;
        LONG newlParam;
        newlParam = (kbInfo->scanCode << 16);
        newlParam |= (kbInfo->flags & 0x1) << 24;
        newlParam |= dontCare << 25;
        if (kbInfo->vkCode == VK_RSHIFT) {
            /* For some reason, the right shift key ends up having its extended
             * key flag set and then prints the wrong thing. This doesn't matter
             * here, but we'll fix it in case we need this info later. */
            newlParam ^= 0x1000000;
        }

        wchar_t buf[256] = {};
        GetKeyNameText(newlParam, buf, 256);
        int mods = Modifiers();
        std::wstring modStr = ModString(mods) + buf;
        SetWindowText(_updateHwnd, modStr.c_str());

        /* Prevent other applications from receiving this event */
        return (LRESULT) 1;
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    return KeyGrabber::instance->MouseProc(nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    return KeyGrabber::instance->KeyProc(nCode, wParam, lParam);
}<commit_msg>Respond to key down events instead of up<commit_after>#include "KeyGrabber.h"

#include <string>

KeyGrabber *KeyGrabber::instance = NULL;

KeyGrabber *KeyGrabber::Instance() {
    if (instance == NULL) {
        instance = new KeyGrabber();
    }

    return instance;
}

bool KeyGrabber::Hook() {
    _mouseHook = SetWindowsHookEx(WH_MOUSE_LL,
        LowLevelMouseProc, NULL, NULL);

    _keyHook = SetWindowsHookEx(WH_KEYBOARD_LL,
        LowLevelKeyboardProc, NULL, NULL);

    return _mouseHook && _keyHook;
}

bool KeyGrabber::Unhook() {
    BOOL unMouse = UnhookWindowsHookEx(_mouseHook);
    BOOL unKey = UnhookWindowsHookEx(_keyHook);
    return unMouse && unKey;
}

void KeyGrabber::Grab(HWND hwnd) {
    _updateHwnd = hwnd;
    Hook();
}

bool KeyGrabber::IsModifier(DWORD vk) {
    switch (vk) {
    case VK_MENU:
    case VK_LMENU:
    case VK_RMENU:
    case VK_CONTROL:
    case VK_LCONTROL:
    case VK_RCONTROL:
    case VK_SHIFT:
    case VK_LSHIFT:
    case VK_RSHIFT:
    case VK_LWIN:
    case VK_RWIN:
        return true;
    }
    return false;
}

int KeyGrabber::Modifiers() {
    int mods = 0;
    mods += (GetKeyState(VK_MENU) & 0x8000) << 1;
    mods += (GetKeyState(VK_CONTROL) & 0x8000) << 2;
    mods += (GetKeyState(VK_SHIFT) & 0x8000) << 3;
    mods += (GetKeyState(VK_LWIN) & 0x8000) << 4;
    mods += (GetKeyState(VK_RWIN) & 0x8000) << 4;
    return mods;
}

std::wstring KeyGrabber::ModString(int modifiers) {
    std::wstring str = L"";
    if (modifiers & HKM_MOD_ALT) {
        str += VKToString(VK_MENU) + L" + ";
    }
    if (modifiers & HKM_MOD_CTRL) {
        str += VKToString(VK_CONTROL) + L" + ";
    }
    if (modifiers & HKM_MOD_SHF) {
        str += VKToString(VK_SHIFT) + L" + ";
    }
    if (modifiers & HKM_MOD_WIN) {
        str += L"Win + ";
    }

    return str;
}

std::wstring KeyGrabber::VKToString(
    unsigned int vk, bool extendedKey, bool distinguishLeftRight) {

    BOOL dontCare = distinguishLeftRight ? FALSE : TRUE;
    int extended = extendedKey ? 0x1 : 0x0;

    unsigned int scanCode = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
    scanCode = scanCode << 16;
    scanCode |= extended << 24;
    scanCode |= dontCare << 25;
    wchar_t buf[256] = {};
    GetKeyNameText(scanCode, buf, 256);
    return std::wstring(buf);
}

LRESULT CALLBACK
KeyGrabber::KeyProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }

    if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
        KBDLLHOOKSTRUCT *kbInfo = (KBDLLHOOKSTRUCT*) lParam;

        DWORD vk = kbInfo->vkCode;
        if (IsModifier(kbInfo->vkCode)) {
            /* Ignore modifier keys */
            return CallNextHookEx(NULL, nCode, wParam, lParam);
        }

        /* GetKeyNameText expects the following:
         * 16-23: scan code
         *    24: extended key flag
         *    25: 'do not care' bit (don't distinguish between L/R keys) */
        BOOL dontCare = TRUE;
        LONG newlParam;
        newlParam = (kbInfo->scanCode << 16);
        newlParam |= (kbInfo->flags & 0x1) << 24;
        newlParam |= dontCare << 25;
        if (kbInfo->vkCode == VK_RSHIFT) {
            /* For some reason, the right shift key ends up having its extended
             * key flag set and then prints the wrong thing. This doesn't matter
             * here, but we'll fix it in case we need this info later. */
            newlParam ^= 0x1000000;
        }

        wchar_t buf[256] = {};
        GetKeyNameText(newlParam, buf, 256);
        int mods = Modifiers();
        std::wstring modStr = ModString(mods) + buf;
        SetWindowText(_updateHwnd, modStr.c_str());

        /* Prevent other applications from receiving this event */
        return (LRESULT) 1;
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    return KeyGrabber::instance->MouseProc(nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    return KeyGrabber::instance->KeyProc(nCode, wParam, lParam);
}<|endoftext|>


<commit_before>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: optctl.cxx,v $
 *
 *  $Revision: 1.10 $
 *
 *  last change: $Author: hr $ $Date: 2007-06-27 17:25:32 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_svx.hxx"

#ifdef SVX_DLLIMPLEMENTATION
#undef SVX_DLLIMPLEMENTATION
#endif

#ifndef _SVX_OPTCTL_HXX
#include "optctl.hxx"
#endif
#ifndef _SVX_DIALMGR_HXX
#include <svx/dialmgr.hxx>
#endif

#ifndef _SVX_OPTCTL_HRC
#include "optctl.hrc"
#endif
#ifndef _SVX_DIALOGS_HRC
#include <svx/dialogs.hrc>
#endif

#ifndef _SVTOOLS_CTLOPTIONS_HXX
#include <svtools/ctloptions.hxx>
#endif

// class SvxCTLOptionsPage -----------------------------------------------------

IMPL_LINK( SvxCTLOptionsPage, SequenceCheckingCB_Hdl, void*, EMPTYARG )
{
    sal_Bool bIsSequenceChecking = m_aSequenceCheckingCB.IsChecked();
    m_aRestrictedCB.Enable( bIsSequenceChecking );
    m_aTypeReplaceCB.Enable( bIsSequenceChecking );
    // #i48117#: by default restricted and type&replace have to be switched on
    if(bIsSequenceChecking)
    {
        m_aTypeReplaceCB.Check( sal_True );
        m_aRestrictedCB.Check( sal_True );
    }
    return 0;
}

SvxCTLOptionsPage::SvxCTLOptionsPage( Window* pParent, const SfxItemSet& rSet ) :

    SfxTabPage( pParent, SVX_RES( RID_SVXPAGE_OPTIONS_CTL ), rSet ),

    m_aSequenceCheckingFL   ( this, SVX_RES( FL_SEQUENCECHECKING ) ),
    m_aSequenceCheckingCB   ( this, SVX_RES( CB_SEQUENCECHECKING ) ),
    m_aRestrictedCB         ( this, SVX_RES( CB_RESTRICTED ) ),
    m_aTypeReplaceCB        ( this, SVX_RES( CB_TYPE_REPLACE ) ),
    m_aCursorControlFL      ( this, SVX_RES( FL_CURSORCONTROL ) ),
    m_aMovementFT           ( this, SVX_RES( FT_MOVEMENT ) ),
    m_aMovementLogicalRB    ( this, SVX_RES( RB_MOVEMENT_LOGICAL ) ),
    m_aMovementVisualRB     ( this, SVX_RES( RB_MOVEMENT_VISUAL ) ),
    m_aGeneralFL            ( this, SVX_RES( FL_GENERAL ) ),
    m_aNumeralsFT           ( this, SVX_RES( FT_NUMERALS ) ),
    m_aNumeralsLB           ( this, SVX_RES( LB_NUMERALS ) )

{
    FreeResource();

    m_aSequenceCheckingCB.SetClickHdl( LINK( this, SvxCTLOptionsPage, SequenceCheckingCB_Hdl ) );

    m_aNumeralsLB.SetDropDownLineCount( m_aNumeralsLB.GetEntryCount() );
}
// -----------------------------------------------------------------------------
SvxCTLOptionsPage::~SvxCTLOptionsPage()
{
}
// -----------------------------------------------------------------------------
SfxTabPage* SvxCTLOptionsPage::Create( Window* pParent, const SfxItemSet& rAttrSet )
{
    return new SvxCTLOptionsPage( pParent, rAttrSet );
}
// -----------------------------------------------------------------------------
BOOL SvxCTLOptionsPage::FillItemSet( SfxItemSet& )
{
    BOOL bModified = FALSE;
    SvtCTLOptions aCTLOptions;

    // Sequence checking
    BOOL bChecked = m_aSequenceCheckingCB.IsChecked();
    if ( bChecked != m_aSequenceCheckingCB.GetSavedValue() )
    {
        aCTLOptions.SetCTLSequenceChecking( bChecked );
        bModified = TRUE;
    }

    bChecked = m_aRestrictedCB.IsChecked();
    if( bChecked != m_aRestrictedCB.GetSavedValue() )
    {
        aCTLOptions.SetCTLSequenceCheckingRestricted( bChecked );
        bModified = TRUE;
    }
    bChecked = m_aTypeReplaceCB.IsChecked();
    if( bChecked != m_aTypeReplaceCB.GetSavedValue())
    {
        aCTLOptions.SetCTLSequenceCheckingTypeAndReplace(bChecked);
        bModified = TRUE;
    }

    BOOL bLogicalChecked = m_aMovementLogicalRB.IsChecked();
    BOOL bVisualChecked = m_aMovementVisualRB.IsChecked();
    if ( bLogicalChecked != m_aMovementLogicalRB.GetSavedValue() ||
         bVisualChecked != m_aMovementVisualRB.GetSavedValue() )
    {
        SvtCTLOptions::CursorMovement eMovement =
            bLogicalChecked ? SvtCTLOptions::MOVEMENT_LOGICAL : SvtCTLOptions::MOVEMENT_VISUAL;
        aCTLOptions.SetCTLCursorMovement( eMovement );
        bModified = TRUE;
    }

    USHORT nPos = m_aNumeralsLB.GetSelectEntryPos();
    if ( nPos != m_aNumeralsLB.GetSavedValue() )
    {
        aCTLOptions.SetCTLTextNumerals( (SvtCTLOptions::TextNumerals)nPos );
        bModified = TRUE;
    }

    return bModified;
}
// -----------------------------------------------------------------------------
void SvxCTLOptionsPage::Reset( const SfxItemSet& )
{
    SvtCTLOptions aCTLOptions;

    m_aSequenceCheckingCB.Check( aCTLOptions.IsCTLSequenceChecking() );
    m_aRestrictedCB.Check( aCTLOptions.IsCTLSequenceCheckingRestricted() );
    m_aTypeReplaceCB.Check( aCTLOptions.IsCTLSequenceCheckingTypeAndReplace() );

    SvtCTLOptions::CursorMovement eMovement = aCTLOptions.GetCTLCursorMovement();
    switch ( eMovement )
    {
        case SvtCTLOptions::MOVEMENT_LOGICAL :
            m_aMovementLogicalRB.Check();
            break;

        case SvtCTLOptions::MOVEMENT_VISUAL :
            m_aMovementVisualRB.Check();
            break;

        default:
            DBG_ERRORFILE( "SvxCTLOptionsPage::Reset(): invalid movement enum" );
    }

    USHORT nPos = (USHORT)aCTLOptions.GetCTLTextNumerals();
    DBG_ASSERT( nPos < m_aNumeralsLB.GetEntryCount(), "SvxCTLOptionsPage::Reset(): invalid numerals enum" );
    m_aNumeralsLB.SelectEntryPos( nPos );

    m_aSequenceCheckingCB.SaveValue();
    m_aRestrictedCB.SaveValue();
    m_aTypeReplaceCB.SaveValue();
    m_aMovementLogicalRB.SaveValue();
    m_aMovementVisualRB.SaveValue();
    m_aNumeralsLB.SaveValue();

    sal_Bool bIsSequenceChecking = m_aSequenceCheckingCB.IsChecked();
    m_aRestrictedCB.Enable( bIsSequenceChecking );
    m_aTypeReplaceCB.Enable( bIsSequenceChecking );
}

<commit_msg>INTEGRATION: CWS changefileheader (1.10.368); FILE MERGED 2008/04/01 15:50:27 thb 1.10.368.3: #i85898# Stripping all external header guards 2008/04/01 12:48:17 thb 1.10.368.2: #i85898# Stripping all external header guards 2008/03/31 14:19:57 rt 1.10.368.1: #i87441# Change license header to LPGL v3.<commit_after>/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: optctl.cxx,v $
 * $Revision: 1.11 $
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_svx.hxx"

#ifdef SVX_DLLIMPLEMENTATION
#undef SVX_DLLIMPLEMENTATION
#endif
#include "optctl.hxx"
#include <svx/dialmgr.hxx>

#ifndef _SVX_OPTCTL_HRC
#include "optctl.hrc"
#endif
#ifndef _SVX_DIALOGS_HRC
#include <svx/dialogs.hrc>
#endif
#include <svtools/ctloptions.hxx>

// class SvxCTLOptionsPage -----------------------------------------------------

IMPL_LINK( SvxCTLOptionsPage, SequenceCheckingCB_Hdl, void*, EMPTYARG )
{
    sal_Bool bIsSequenceChecking = m_aSequenceCheckingCB.IsChecked();
    m_aRestrictedCB.Enable( bIsSequenceChecking );
    m_aTypeReplaceCB.Enable( bIsSequenceChecking );
    // #i48117#: by default restricted and type&replace have to be switched on
    if(bIsSequenceChecking)
    {
        m_aTypeReplaceCB.Check( sal_True );
        m_aRestrictedCB.Check( sal_True );
    }
    return 0;
}

SvxCTLOptionsPage::SvxCTLOptionsPage( Window* pParent, const SfxItemSet& rSet ) :

    SfxTabPage( pParent, SVX_RES( RID_SVXPAGE_OPTIONS_CTL ), rSet ),

    m_aSequenceCheckingFL   ( this, SVX_RES( FL_SEQUENCECHECKING ) ),
    m_aSequenceCheckingCB   ( this, SVX_RES( CB_SEQUENCECHECKING ) ),
    m_aRestrictedCB         ( this, SVX_RES( CB_RESTRICTED ) ),
    m_aTypeReplaceCB        ( this, SVX_RES( CB_TYPE_REPLACE ) ),
    m_aCursorControlFL      ( this, SVX_RES( FL_CURSORCONTROL ) ),
    m_aMovementFT           ( this, SVX_RES( FT_MOVEMENT ) ),
    m_aMovementLogicalRB    ( this, SVX_RES( RB_MOVEMENT_LOGICAL ) ),
    m_aMovementVisualRB     ( this, SVX_RES( RB_MOVEMENT_VISUAL ) ),
    m_aGeneralFL            ( this, SVX_RES( FL_GENERAL ) ),
    m_aNumeralsFT           ( this, SVX_RES( FT_NUMERALS ) ),
    m_aNumeralsLB           ( this, SVX_RES( LB_NUMERALS ) )

{
    FreeResource();

    m_aSequenceCheckingCB.SetClickHdl( LINK( this, SvxCTLOptionsPage, SequenceCheckingCB_Hdl ) );

    m_aNumeralsLB.SetDropDownLineCount( m_aNumeralsLB.GetEntryCount() );
}
// -----------------------------------------------------------------------------
SvxCTLOptionsPage::~SvxCTLOptionsPage()
{
}
// -----------------------------------------------------------------------------
SfxTabPage* SvxCTLOptionsPage::Create( Window* pParent, const SfxItemSet& rAttrSet )
{
    return new SvxCTLOptionsPage( pParent, rAttrSet );
}
// -----------------------------------------------------------------------------
BOOL SvxCTLOptionsPage::FillItemSet( SfxItemSet& )
{
    BOOL bModified = FALSE;
    SvtCTLOptions aCTLOptions;

    // Sequence checking
    BOOL bChecked = m_aSequenceCheckingCB.IsChecked();
    if ( bChecked != m_aSequenceCheckingCB.GetSavedValue() )
    {
        aCTLOptions.SetCTLSequenceChecking( bChecked );
        bModified = TRUE;
    }

    bChecked = m_aRestrictedCB.IsChecked();
    if( bChecked != m_aRestrictedCB.GetSavedValue() )
    {
        aCTLOptions.SetCTLSequenceCheckingRestricted( bChecked );
        bModified = TRUE;
    }
    bChecked = m_aTypeReplaceCB.IsChecked();
    if( bChecked != m_aTypeReplaceCB.GetSavedValue())
    {
        aCTLOptions.SetCTLSequenceCheckingTypeAndReplace(bChecked);
        bModified = TRUE;
    }

    BOOL bLogicalChecked = m_aMovementLogicalRB.IsChecked();
    BOOL bVisualChecked = m_aMovementVisualRB.IsChecked();
    if ( bLogicalChecked != m_aMovementLogicalRB.GetSavedValue() ||
         bVisualChecked != m_aMovementVisualRB.GetSavedValue() )
    {
        SvtCTLOptions::CursorMovement eMovement =
            bLogicalChecked ? SvtCTLOptions::MOVEMENT_LOGICAL : SvtCTLOptions::MOVEMENT_VISUAL;
        aCTLOptions.SetCTLCursorMovement( eMovement );
        bModified = TRUE;
    }

    USHORT nPos = m_aNumeralsLB.GetSelectEntryPos();
    if ( nPos != m_aNumeralsLB.GetSavedValue() )
    {
        aCTLOptions.SetCTLTextNumerals( (SvtCTLOptions::TextNumerals)nPos );
        bModified = TRUE;
    }

    return bModified;
}
// -----------------------------------------------------------------------------
void SvxCTLOptionsPage::Reset( const SfxItemSet& )
{
    SvtCTLOptions aCTLOptions;

    m_aSequenceCheckingCB.Check( aCTLOptions.IsCTLSequenceChecking() );
    m_aRestrictedCB.Check( aCTLOptions.IsCTLSequenceCheckingRestricted() );
    m_aTypeReplaceCB.Check( aCTLOptions.IsCTLSequenceCheckingTypeAndReplace() );

    SvtCTLOptions::CursorMovement eMovement = aCTLOptions.GetCTLCursorMovement();
    switch ( eMovement )
    {
        case SvtCTLOptions::MOVEMENT_LOGICAL :
            m_aMovementLogicalRB.Check();
            break;

        case SvtCTLOptions::MOVEMENT_VISUAL :
            m_aMovementVisualRB.Check();
            break;

        default:
            DBG_ERRORFILE( "SvxCTLOptionsPage::Reset(): invalid movement enum" );
    }

    USHORT nPos = (USHORT)aCTLOptions.GetCTLTextNumerals();
    DBG_ASSERT( nPos < m_aNumeralsLB.GetEntryCount(), "SvxCTLOptionsPage::Reset(): invalid numerals enum" );
    m_aNumeralsLB.SelectEntryPos( nPos );

    m_aSequenceCheckingCB.SaveValue();
    m_aRestrictedCB.SaveValue();
    m_aTypeReplaceCB.SaveValue();
    m_aMovementLogicalRB.SaveValue();
    m_aMovementVisualRB.SaveValue();
    m_aNumeralsLB.SaveValue();

    sal_Bool bIsSequenceChecking = m_aSequenceCheckingCB.IsChecked();
    m_aRestrictedCB.Enable( bIsSequenceChecking );
    m_aTypeReplaceCB.Enable( bIsSequenceChecking );
}

<|endoftext|>


<commit_before>#include "KeyGrabber.h"

#include <string>

KeyGrabber *KeyGrabber::instance = NULL;

KeyGrabber *KeyGrabber::Instance() {
    if (instance == NULL) {
        instance = new KeyGrabber();
    }

    return instance;
}

bool KeyGrabber::Hook() {
    _mouseHook = SetWindowsHookEx(WH_MOUSE_LL,
        LowLevelMouseProc, NULL, NULL);

    _keyHook = SetWindowsHookEx(WH_KEYBOARD_LL,
        LowLevelKeyboardProc, NULL, NULL);

    return _mouseHook && _keyHook;
}

bool KeyGrabber::Unhook() {
    BOOL unMouse = UnhookWindowsHookEx(_mouseHook);
    BOOL unKey = UnhookWindowsHookEx(_keyHook);
    return unMouse && unKey;
}

void KeyGrabber::Grab(HWND hwnd) {
    _updateHwnd = hwnd;
    Hook();
}

bool KeyGrabber::IsModifier(DWORD vk) {
    switch (vk) {
    case VK_MENU:
    case VK_LMENU:
    case VK_RMENU:
    case VK_CONTROL:
    case VK_LCONTROL:
    case VK_RCONTROL:
    case VK_SHIFT:
    case VK_LSHIFT:
    case VK_RSHIFT:
    case VK_LWIN:
    case VK_RWIN:
        return true;
    }
    return false;
}

int KeyGrabber::Modifiers() {
    int mods = 0;
    mods += (GetKeyState(VK_MENU) & 0x8000) << 1;
    mods += (GetKeyState(VK_CONTROL) & 0x8000) << 2;
    mods += (GetKeyState(VK_SHIFT) & 0x8000) << 3;
    mods += (GetKeyState(VK_LWIN) & 0x8000) << 4;
    mods += (GetKeyState(VK_RWIN) & 0x8000) << 4;
    return mods;
}

std::wstring KeyGrabber::ModString(int modifiers) {
    std::wstring str = L"";
    if (modifiers & HKM_MOD_ALT) {
        str += VKToString(VK_MENU) + L" + ";
    }
    if (modifiers & HKM_MOD_CTRL) {
        str += VKToString(VK_CONTROL) + L" + ";
    }
    if (modifiers & HKM_MOD_SHF) {
        str += VKToString(VK_SHIFT) + L" + ";
    }
    if (modifiers & HKM_MOD_WIN) {
        str += L"Win + ";
    }

    return str;
}

std::wstring KeyGrabber::VKToString(
    unsigned int vk, bool extendedKey, bool distinguishLeftRight) {

    BOOL dontCare = distinguishLeftRight ? FALSE : TRUE;
    int extended = extendedKey ? 0x1 : 0x0;

    unsigned int scanCode = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
    scanCode = scanCode << 16;
    scanCode |= extended << 24;
    scanCode |= dontCare << 25;
    wchar_t buf[256] = {};
    GetKeyNameText(scanCode, buf, 256);
    return std::wstring(buf);
}

LRESULT CALLBACK
KeyGrabber::KeyProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }

    if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
        KBDLLHOOKSTRUCT *kbInfo = (KBDLLHOOKSTRUCT*) lParam;

        DWORD vk = kbInfo->vkCode;
        if (IsModifier(kbInfo->vkCode)) {
            /* Ignore modifier keys */
            return CallNextHookEx(NULL, nCode, wParam, lParam);
        }

        /* GetKeyNameText expects the following:
         * 16-23: scan code
         *    24: extended key flag
         *    25: 'do not care' bit (don't distinguish between L/R keys) */
        BOOL dontCare = TRUE;
        LONG newlParam;
        newlParam = (kbInfo->scanCode << 16);
        newlParam |= (kbInfo->flags & 0x1) << 24;
        newlParam |= dontCare << 25;
        if (kbInfo->vkCode == VK_RSHIFT) {
            /* For some reason, the right shift key ends up having its extended
             * key flag set and then prints the wrong thing. This doesn't matter
             * here, but we'll fix it in case we need this info later. */
            newlParam ^= 0x1000000;
        }

        wchar_t buf[256] = {};
        GetKeyNameText(newlParam, buf, 256);
        int mods = Modifiers();
        std::wstring modStr = ModString(mods) + buf;
        SetWindowText(_updateHwnd, modStr.c_str());

        /* Prevent other applications from receiving this event */
        return (LRESULT) 1;
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    return KeyGrabber::instance->MouseProc(nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    return KeyGrabber::instance->KeyProc(nCode, wParam, lParam);
}<commit_msg>Need to use GetAsyncKeyState() since the messages won't be processed when we're handling WM_KEYDOWN et al<commit_after>#include "KeyGrabber.h"

#include <string>

KeyGrabber *KeyGrabber::instance = NULL;

KeyGrabber *KeyGrabber::Instance() {
    if (instance == NULL) {
        instance = new KeyGrabber();
    }

    return instance;
}

bool KeyGrabber::Hook() {
    _mouseHook = SetWindowsHookEx(WH_MOUSE_LL,
        LowLevelMouseProc, NULL, NULL);

    _keyHook = SetWindowsHookEx(WH_KEYBOARD_LL,
        LowLevelKeyboardProc, NULL, NULL);

    return _mouseHook && _keyHook;
}

bool KeyGrabber::Unhook() {
    BOOL unMouse = UnhookWindowsHookEx(_mouseHook);
    BOOL unKey = UnhookWindowsHookEx(_keyHook);
    return unMouse && unKey;
}

void KeyGrabber::Grab(HWND hwnd) {
    _updateHwnd = hwnd;
    Hook();
}

bool KeyGrabber::IsModifier(DWORD vk) {
    switch (vk) {
    case VK_MENU:
    case VK_LMENU:
    case VK_RMENU:
    case VK_CONTROL:
    case VK_LCONTROL:
    case VK_RCONTROL:
    case VK_SHIFT:
    case VK_LSHIFT:
    case VK_RSHIFT:
    case VK_LWIN:
    case VK_RWIN:
        return true;
    }
    return false;
}

int KeyGrabber::Modifiers() {
    int mods = 0;
    mods += (GetAsyncKeyState(VK_MENU) & 0x8000) << 1;
    mods += (GetAsyncKeyState(VK_CONTROL) & 0x8000) << 2;
    mods += (GetAsyncKeyState(VK_SHIFT) & 0x8000) << 3;
    mods += (GetAsyncKeyState(VK_LWIN) & 0x8000) << 4;
    mods += (GetAsyncKeyState(VK_RWIN) & 0x8000) << 4;
    return mods;
}

std::wstring KeyGrabber::ModString(int modifiers) {
    std::wstring str = L"";
    if (modifiers & HKM_MOD_ALT) {
        str += VKToString(VK_MENU) + L" + ";
    }
    if (modifiers & HKM_MOD_CTRL) {
        str += VKToString(VK_CONTROL) + L" + ";
    }
    if (modifiers & HKM_MOD_SHF) {
        str += VKToString(VK_SHIFT) + L" + ";
    }
    if (modifiers & HKM_MOD_WIN) {
        str += L"Win + ";
    }

    return str;
}

std::wstring KeyGrabber::VKToString(
    unsigned int vk, bool extendedKey, bool distinguishLeftRight) {

    BOOL dontCare = distinguishLeftRight ? FALSE : TRUE;
    int extended = extendedKey ? 0x1 : 0x0;

    unsigned int scanCode = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
    scanCode = scanCode << 16;
    scanCode |= extended << 24;
    scanCode |= dontCare << 25;
    wchar_t buf[256] = {};
    GetKeyNameText(scanCode, buf, 256);
    return std::wstring(buf);
}

LRESULT CALLBACK
KeyGrabber::KeyProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }

    if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
        KBDLLHOOKSTRUCT *kbInfo = (KBDLLHOOKSTRUCT*) lParam;

        DWORD vk = kbInfo->vkCode;
        if (IsModifier(kbInfo->vkCode)) {
            /* Ignore modifier keys */
            return CallNextHookEx(NULL, nCode, wParam, lParam);
        }

        /* GetKeyNameText expects the following:
         * 16-23: scan code
         *    24: extended key flag
         *    25: 'do not care' bit (don't distinguish between L/R keys) */
        BOOL dontCare = TRUE;
        LONG newlParam;
        newlParam = (kbInfo->scanCode << 16);
        newlParam |= (kbInfo->flags & 0x1) << 24;
        newlParam |= dontCare << 25;
        if (kbInfo->vkCode == VK_RSHIFT) {
            /* For some reason, the right shift key ends up having its extended
             * key flag set and then prints the wrong thing. This doesn't matter
             * here, but we'll fix it in case we need this info later. */
            newlParam ^= 0x1000000;
        }

        wchar_t buf[256] = {};
        GetKeyNameText(newlParam, buf, 256);
        int mods = Modifiers();
        std::wstring modStr = ModString(mods) + buf;
        SetWindowText(_updateHwnd, modStr.c_str());

        /* Prevent other applications from receiving this event */
        return (LRESULT) 1;
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    return KeyGrabber::instance->MouseProc(nCode, wParam, lParam);
}

LRESULT CALLBACK
KeyGrabber::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    return KeyGrabber::instance->KeyProc(nCode, wParam, lParam);
}<|endoftext|>