blob: 5bd3c39464a986de7134bb1c722be6970a329e65 [file] [log] [blame]
// 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