| // Copyright 2013 The Flutter 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 "flutter/shell/platform/windows/keyboard_key_handler.h" |
| |
| #include <rapidjson/document.h> |
| #include <memory> |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| namespace flutter { |
| namespace testing { |
| |
| namespace { |
| |
| static constexpr int kHandledScanCode = 20; |
| static constexpr int kHandledScanCode2 = 22; |
| static constexpr int kUnhandledScanCode = 21; |
| |
| constexpr uint64_t kScanCodeShiftRight = 0x36; |
| constexpr uint64_t kScanCodeControlLeft = 0x1D; |
| constexpr uint64_t kScanCodeAltLeft = 0x38; |
| |
| typedef std::function<void(bool)> Callback; |
| typedef std::function<void(Callback&)> CallbackHandler; |
| void dont_respond(Callback& callback) {} |
| void respond_true(Callback& callback) { |
| callback(true); |
| } |
| void respond_false(Callback& callback) { |
| callback(false); |
| } |
| |
| // A testing |KeyHandlerDelegate| that records all calls |
| // to |KeyboardHook| and can be customized with whether |
| // the hook is handled in async. |
| class MockKeyHandlerDelegate |
| : public KeyboardKeyHandler::KeyboardKeyHandlerDelegate { |
| public: |
| class KeyboardHookCall { |
| public: |
| int delegate_id; |
| int key; |
| int scancode; |
| int action; |
| char32_t character; |
| bool extended; |
| bool was_down; |
| std::function<void(bool)> callback; |
| }; |
| |
| // Create a |MockKeyHandlerDelegate|. |
| // |
| // The |delegate_id| is an arbitrary ID to tell between delegates |
| // that will be recorded in |KeyboardHookCall|. |
| // |
| // The |hook_history| will store every call to |KeyboardHookCall| that are |
| // handled asynchronously. |
| // |
| // The |is_async| is a function that the class calls upon every |
| // |KeyboardHookCall| to decide whether the call is handled asynchronously. |
| // Defaults to always returning true (async). |
| MockKeyHandlerDelegate(int delegate_id, |
| std::list<KeyboardHookCall>* hook_history) |
| : delegate_id(delegate_id), |
| hook_history(hook_history), |
| callback_handler(dont_respond) {} |
| virtual ~MockKeyHandlerDelegate() = default; |
| |
| virtual void KeyboardHook(int key, |
| int scancode, |
| int action, |
| char32_t character, |
| bool extended, |
| bool was_down, |
| std::function<void(bool)> callback) { |
| hook_history->push_back(KeyboardHookCall{ |
| .delegate_id = delegate_id, |
| .key = key, |
| .scancode = scancode, |
| .character = character, |
| .extended = extended, |
| .was_down = was_down, |
| .callback = std::move(callback), |
| }); |
| callback_handler(hook_history->back().callback); |
| } |
| |
| CallbackHandler callback_handler; |
| int delegate_id; |
| std::list<KeyboardHookCall>* hook_history; |
| }; |
| |
| class TestKeyboardKeyHandler : public KeyboardKeyHandler { |
| public: |
| explicit TestKeyboardKeyHandler(EventDispatcher redispatch_event) |
| : KeyboardKeyHandler(redispatch_event) {} |
| |
| bool HasRedispatched() { return RedispatchedCount() > 0; } |
| }; |
| |
| } // namespace |
| |
| TEST(KeyboardKeyHandlerTest, SingleDelegateWithAsyncResponds) { |
| std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history; |
| |
| // Capture the scancode of the last redispatched event |
| int redispatch_scancode = 0; |
| bool delegate_handled = false; |
| TestKeyboardKeyHandler handler([&redispatch_scancode](UINT cInputs, |
| LPINPUT pInputs, |
| int cbSize) -> UINT { |
| EXPECT_TRUE(cbSize > 0); |
| redispatch_scancode = pInputs->ki.wScan; |
| return 1; |
| }); |
| // Add one delegate |
| auto delegate = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history); |
| handler.AddDelegate(std::move(delegate)); |
| |
| /// Test 1: One event that is handled by the framework |
| |
| // Dispatch a key event |
| delegate_handled = handler.KeyboardHook(nullptr, 64, kHandledScanCode, |
| WM_KEYDOWN, L'a', false, true); |
| EXPECT_EQ(delegate_handled, true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.back().delegate_id, 1); |
| EXPECT_EQ(hook_history.back().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.back().was_down, true); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| hook_history.back().callback(true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| hook_history.clear(); |
| |
| /// Test 2: Two events that are unhandled by the framework |
| |
| delegate_handled = handler.KeyboardHook(nullptr, 64, kHandledScanCode, |
| WM_KEYDOWN, L'a', false, false); |
| EXPECT_EQ(delegate_handled, true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.back().delegate_id, 1); |
| EXPECT_EQ(hook_history.back().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.back().was_down, false); |
| |
| // Dispatch another key event |
| delegate_handled = handler.KeyboardHook(nullptr, 65, kHandledScanCode2, |
| WM_KEYUP, L'b', false, true); |
| EXPECT_EQ(delegate_handled, true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 2); |
| EXPECT_EQ(hook_history.back().delegate_id, 1); |
| EXPECT_EQ(hook_history.back().scancode, kHandledScanCode2); |
| EXPECT_EQ(hook_history.back().was_down, true); |
| |
| // Resolve the second event first to test out-of-order response |
| hook_history.back().callback(false); |
| EXPECT_EQ(redispatch_scancode, kHandledScanCode2); |
| |
| // Resolve the first event then |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kHandledScanCode); |
| |
| EXPECT_EQ(handler.KeyboardHook(nullptr, 64, kHandledScanCode, WM_KEYDOWN, |
| L'a', false, false), |
| false); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, 65, kHandledScanCode2, WM_KEYUP, L'b', |
| false, false), |
| false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| hook_history.clear(); |
| redispatch_scancode = 0; |
| } |
| |
| TEST(KeyboardKeyHandlerTest, SingleDelegateWithSyncResponds) { |
| std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history; |
| |
| // Capture the scancode of the last redispatched event |
| int redispatch_scancode = 0; |
| bool delegate_handled = false; |
| TestKeyboardKeyHandler handler([&redispatch_scancode](UINT cInputs, |
| LPINPUT pInputs, |
| int cbSize) -> UINT { |
| EXPECT_TRUE(cbSize > 0); |
| redispatch_scancode = pInputs->ki.wScan; |
| return 1; |
| }); |
| // Add one delegate |
| auto delegate = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history); |
| CallbackHandler& delegate_handler = delegate->callback_handler; |
| handler.AddDelegate(std::move(delegate)); |
| |
| /// Test 1: One event that is handled by the framework |
| |
| // Dispatch a key event |
| delegate_handler = respond_true; |
| delegate_handled = handler.KeyboardHook(nullptr, 64, kHandledScanCode, |
| WM_KEYDOWN, L'a', false, false); |
| EXPECT_EQ(delegate_handled, true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.back().delegate_id, 1); |
| EXPECT_EQ(hook_history.back().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.back().was_down, false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| hook_history.clear(); |
| |
| /// Test 2: An event unhandled by the framework |
| |
| delegate_handler = respond_false; |
| delegate_handled = handler.KeyboardHook(nullptr, 64, kHandledScanCode, |
| WM_KEYDOWN, L'a', false, false); |
| EXPECT_EQ(delegate_handled, true); |
| EXPECT_EQ(redispatch_scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.back().delegate_id, 1); |
| EXPECT_EQ(hook_history.back().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.back().was_down, false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), true); |
| |
| // Resolve the event |
| EXPECT_EQ(handler.KeyboardHook(nullptr, 64, kHandledScanCode, WM_KEYDOWN, |
| L'a', false, false), |
| false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| hook_history.clear(); |
| redispatch_scancode = 0; |
| } |
| |
| TEST(KeyboardKeyHandlerTest, WithTwoAsyncDelegates) { |
| std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history; |
| |
| // Capture the scancode of the last redispatched event |
| int redispatch_scancode = 0; |
| bool delegate_handled = false; |
| TestKeyboardKeyHandler handler([&redispatch_scancode](UINT cInputs, |
| LPINPUT pInputs, |
| int cbSize) -> UINT { |
| EXPECT_TRUE(cbSize > 0); |
| redispatch_scancode = pInputs->ki.wScan; |
| return 1; |
| }); |
| |
| auto delegate1 = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history); |
| CallbackHandler& delegate1_handler = delegate1->callback_handler; |
| handler.AddDelegate(std::move(delegate1)); |
| |
| auto delegate2 = std::make_unique<MockKeyHandlerDelegate>(2, &hook_history); |
| CallbackHandler& delegate2_handler = delegate2->callback_handler; |
| handler.AddDelegate(std::move(delegate2)); |
| |
| /// Test 1: One delegate responds true, the other false |
| |
| delegate_handled = handler.KeyboardHook(nullptr, 64, kHandledScanCode, |
| WM_KEYDOWN, L'a', false, false); |
| EXPECT_EQ(delegate_handled, true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 2); |
| EXPECT_EQ(hook_history.front().delegate_id, 1); |
| EXPECT_EQ(hook_history.front().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.front().was_down, false); |
| EXPECT_EQ(hook_history.back().delegate_id, 2); |
| EXPECT_EQ(hook_history.back().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.back().was_down, false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| |
| hook_history.back().callback(true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, 0); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| |
| /// Test 2: All delegates respond false |
| |
| delegate_handled = handler.KeyboardHook(nullptr, 64, kHandledScanCode, |
| WM_KEYDOWN, L'a', false, false); |
| EXPECT_EQ(delegate_handled, true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 2); |
| EXPECT_EQ(hook_history.front().delegate_id, 1); |
| EXPECT_EQ(hook_history.front().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.front().was_down, false); |
| EXPECT_EQ(hook_history.back().delegate_id, 2); |
| EXPECT_EQ(hook_history.back().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.back().was_down, false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, 0); |
| |
| hook_history.back().callback(false); |
| EXPECT_EQ(redispatch_scancode, kHandledScanCode); |
| |
| EXPECT_EQ(handler.HasRedispatched(), true); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, 64, kHandledScanCode, WM_KEYDOWN, |
| L'a', false, false), |
| false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| hook_history.clear(); |
| redispatch_scancode = 0; |
| |
| /// Test 3: All delegates responds true |
| |
| delegate_handled = handler.KeyboardHook(nullptr, 64, kHandledScanCode, |
| WM_KEYDOWN, L'a', false, false); |
| EXPECT_EQ(delegate_handled, true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 2); |
| EXPECT_EQ(hook_history.front().delegate_id, 1); |
| EXPECT_EQ(hook_history.front().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.front().was_down, false); |
| EXPECT_EQ(hook_history.back().delegate_id, 2); |
| EXPECT_EQ(hook_history.back().scancode, kHandledScanCode); |
| EXPECT_EQ(hook_history.back().was_down, false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| |
| hook_history.back().callback(true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| // Only resolve after everyone has responded |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| |
| hook_history.front().callback(true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| } |
| |
| // Regression test for a crash in an earlier implementation. |
| // |
| // In real life, the framework responds slowly. The next real event might |
| // arrive earlier than the framework response, and if the 2nd event is identical |
| // to the one waiting for response, an earlier implementation will crash upon |
| // the response. |
| TEST(KeyboardKeyHandlerTest, WithSlowFrameworkResponse) { |
| std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history; |
| |
| // Capture the scancode of the last redispatched event |
| int redispatch_scancode = 0; |
| bool delegate_handled = false; |
| TestKeyboardKeyHandler handler([&redispatch_scancode](UINT cInputs, |
| LPINPUT pInputs, |
| int cbSize) -> UINT { |
| EXPECT_TRUE(cbSize > 0); |
| redispatch_scancode = pInputs->ki.wScan; |
| return 1; |
| }); |
| |
| auto delegate1 = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history); |
| CallbackHandler& delegate1_handler = delegate1->callback_handler; |
| handler.AddDelegate(std::move(delegate1)); |
| |
| // The first native event. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, 64, kHandledScanCode, WM_KEYDOWN, |
| L'a', false, true), |
| true); |
| |
| // The second identical native event, received between the first and its |
| // framework response. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, 64, kHandledScanCode, WM_KEYDOWN, |
| L'a', false, true), |
| true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 2); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| |
| // The first response. |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kHandledScanCode); |
| EXPECT_EQ(handler.HasRedispatched(), true); |
| |
| // Redispatch the first event. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, 64, kHandledScanCode, WM_KEYDOWN, |
| L'a', false, false), |
| false); |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| |
| // The second response. |
| hook_history.back().callback(false); |
| EXPECT_EQ(redispatch_scancode, kHandledScanCode); |
| EXPECT_EQ(handler.HasRedispatched(), true); |
| |
| // Redispatch the second event. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, 64, kHandledScanCode, WM_KEYDOWN, |
| L'a', false, false), |
| false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| } |
| |
| // A key down event for shift right must not be redispatched even if |
| // the framework returns unhandled. |
| // |
| // The reason for this test is documented in |IsKeyDownShiftRight|. |
| TEST(KeyboardKeyHandlerTest, NeverRedispatchShiftRightKeyDown) { |
| std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history; |
| |
| // Capture the scancode of the last redispatched event |
| int redispatch_scancode = 0; |
| bool delegate_handled = false; |
| TestKeyboardKeyHandler handler([&redispatch_scancode](UINT cInputs, |
| LPINPUT pInputs, |
| int cbSize) -> UINT { |
| EXPECT_TRUE(cbSize > 0); |
| redispatch_scancode = pInputs->ki.wScan; |
| return 1; |
| }); |
| |
| auto delegate = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history); |
| delegate->callback_handler = respond_false; |
| handler.AddDelegate(std::move(delegate)); |
| |
| // Press ShiftRight and the delegate responds false. |
| |
| delegate_handled = handler.KeyboardHook( |
| nullptr, VK_RSHIFT, kScanCodeShiftRight, WM_KEYDOWN, 0, false, false); |
| EXPECT_EQ(delegate_handled, true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 1); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| } |
| |
| TEST(KeyboardKeyHandlerTest, AltGr) { |
| std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history; |
| |
| // Capture the scancode of the last redispatched event |
| int redispatch_scancode = 0; |
| TestKeyboardKeyHandler handler([&redispatch_scancode](UINT cInputs, |
| LPINPUT pInputs, |
| int cbSize) -> UINT { |
| EXPECT_TRUE(cbSize > 0); |
| redispatch_scancode = pInputs->ki.wScan; |
| return 1; |
| }); |
| |
| auto delegate = std::make_unique<MockKeyHandlerDelegate>(1, &hook_history); |
| delegate->callback_handler = dont_respond; |
| handler.AddDelegate(std::move(delegate)); |
| |
| // Sequence 1: Tap AltGr. |
| |
| // The key down event causes a ControlLeft down and a AltRight (extended |
| // AltLeft) down. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYDOWN, 0, false, false), |
| true); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, |
| WM_KEYDOWN, 0, true, false), |
| true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 2); |
| EXPECT_EQ(hook_history.front().scancode, kScanCodeControlLeft); |
| EXPECT_EQ(hook_history.front().was_down, false); |
| EXPECT_EQ(hook_history.back().scancode, kScanCodeAltLeft); |
| EXPECT_EQ(hook_history.back().was_down, false); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| hook_history.back().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeAltLeft); |
| |
| // Resolve redispatches. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYDOWN, 0, false, false), |
| false); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, |
| WM_KEYDOWN, 0, true, false), |
| false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| |
| // The key up event only causes a AltRight (extended AltLeft) up. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, |
| 0, true, true), |
| true); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.front().scancode, kScanCodeAltLeft); |
| EXPECT_EQ(hook_history.front().was_down, true); |
| |
| // A ControlLeft key up is synthesized. |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYUP, 0, false, true), |
| true); |
| |
| EXPECT_EQ(hook_history.back().scancode, kScanCodeControlLeft); |
| EXPECT_EQ(hook_history.back().was_down, true); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeAltLeft); |
| hook_history.back().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| |
| // Resolve redispatches. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYUP, 0, false, true), |
| false); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, |
| 0, true, true), |
| false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| |
| // Sequence 2: Tap CtrlLeft and AltGr. |
| // This also tests tapping AltGr twice in a row when combined with sequence |
| // 1 since "tapping CtrlLeft and AltGr" only sends an extra CtrlLeft key up |
| // than "tapping AltGr". |
| |
| // Key down ControlLeft. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYDOWN, 0, false, false), |
| true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.front().scancode, kScanCodeControlLeft); |
| EXPECT_EQ(hook_history.front().was_down, false); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| hook_history.clear(); |
| redispatch_scancode = 0; |
| |
| // Resolve redispatches. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYDOWN, 0, false, false), |
| false); |
| |
| // Key down AltRight. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, |
| WM_KEYDOWN, 0, true, false), |
| true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.front().scancode, kScanCodeAltLeft); |
| EXPECT_EQ(hook_history.front().was_down, false); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeAltLeft); |
| hook_history.clear(); |
| |
| // Resolve redispatches. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, |
| WM_KEYDOWN, 0, true, false), |
| false); |
| |
| redispatch_scancode = 0; |
| |
| // Key up AltRight. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, |
| 0, true, true), |
| true); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.front().scancode, kScanCodeAltLeft); |
| EXPECT_EQ(hook_history.front().was_down, true); |
| |
| // A ControlLeft key up is synthesized. |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYUP, 0, false, true), |
| true); |
| |
| EXPECT_EQ(hook_history.back().scancode, kScanCodeControlLeft); |
| EXPECT_EQ(hook_history.back().was_down, true); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeAltLeft); |
| hook_history.back().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| |
| // Resolve redispatches. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYUP, 0, false, true), |
| false); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, |
| 0, true, true), |
| false); |
| |
| hook_history.clear(); |
| redispatch_scancode = 0; |
| |
| // Key up ControlLeft should be dispatched to delegates, but will be properly |
| // handled by delegates' logic. |
| |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYUP, 0, false, true), |
| true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.front().scancode, kScanCodeControlLeft); |
| EXPECT_EQ(hook_history.front().was_down, true); |
| |
| hook_history.front().callback(true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| hook_history.clear(); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| |
| // Sequence 3: Hold AltGr for repeated events. |
| // Every AltGr key repeat event is also preceded by a ControlLeft down |
| // (repeat). |
| |
| // Key down AltRight. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYDOWN, 0, false, false), |
| true); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, |
| WM_KEYDOWN, 0, true, false), |
| true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 2); |
| EXPECT_EQ(hook_history.front().scancode, kScanCodeControlLeft); |
| EXPECT_EQ(hook_history.front().was_down, false); |
| EXPECT_EQ(hook_history.back().scancode, kScanCodeAltLeft); |
| EXPECT_EQ(hook_history.back().was_down, false); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| hook_history.back().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeAltLeft); |
| |
| // Resolve redispatches. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYDOWN, 0, false, false), |
| false); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, |
| WM_KEYDOWN, 0, true, false), |
| false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| |
| // Another key down AltRight. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYDOWN, 0, false, true), |
| true); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, |
| WM_KEYDOWN, 0, true, true), |
| true); |
| EXPECT_EQ(redispatch_scancode, 0); |
| EXPECT_EQ(hook_history.size(), 2); |
| EXPECT_EQ(hook_history.front().scancode, kScanCodeControlLeft); |
| EXPECT_EQ(hook_history.front().was_down, true); |
| EXPECT_EQ(hook_history.back().scancode, kScanCodeAltLeft); |
| EXPECT_EQ(hook_history.back().was_down, true); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| hook_history.back().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeAltLeft); |
| |
| // Resolve redispatches. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYDOWN, 0, false, false), |
| false); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, |
| WM_KEYDOWN, 0, true, false), |
| false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| |
| // Key up AltRight. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, |
| 0, true, true), |
| true); |
| EXPECT_EQ(hook_history.size(), 1); |
| EXPECT_EQ(hook_history.front().scancode, kScanCodeAltLeft); |
| EXPECT_EQ(hook_history.front().was_down, true); |
| |
| // A ControlLeft key up is synthesized. |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYUP, 0, false, true), |
| true); |
| |
| EXPECT_EQ(hook_history.back().scancode, kScanCodeControlLeft); |
| EXPECT_EQ(hook_history.back().was_down, true); |
| |
| hook_history.front().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeAltLeft); |
| hook_history.back().callback(false); |
| EXPECT_EQ(redispatch_scancode, kScanCodeControlLeft); |
| |
| // Resolve redispatches. |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, |
| WM_KEYUP, 0, false, true), |
| false); |
| EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, |
| 0, true, true), |
| false); |
| |
| EXPECT_EQ(handler.HasRedispatched(), false); |
| redispatch_scancode = 0; |
| hook_history.clear(); |
| } |
| |
| } // namespace testing |
| } // namespace flutter |