[Win32, Keyboard] Migrate FlutterWindowWin32 keyboard tests to keyboard_win32_unittests (flutter/engine#30808)

* Until AltLeft

* Alt right

* Meta keys

* Remove tests

* Format
This commit is contained in:
Tong Mu 2022-01-12 15:10:23 -08:00 committed by GitHub
parent 4f8fbb365f
commit 400ee783e3
6 changed files with 421 additions and 703 deletions

View File

@ -328,216 +328,6 @@ TEST(FlutterWindowWin32Test, CreateDestroy) {
ASSERT_TRUE(TRUE);
}
// Tests key event propagation of non-printable, non-modifier key down events.
TEST(FlutterWindowWin32Test, NonPrintableKeyDownPropagation) {
::testing::InSequence in_sequence;
constexpr WPARAM virtual_key = VK_LEFT;
constexpr WPARAM scan_code = 10;
constexpr char32_t character = 0;
MockFlutterWindowWin32 win32window;
auto window_binding_handler =
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
TestFlutterWindowsView flutter_windows_view(
std::move(window_binding_handler), virtual_key, false /* is_printable */);
win32window.SetView(&flutter_windows_view);
LPARAM lparam = CreateKeyEventLparam(scan_code, false, false);
// Test an event not handled by the framework
{
test_response = false;
flutter_windows_view.SetEngine(std::move(GetTestEngine()));
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(virtual_key, scan_code, WM_KEYDOWN, character,
false /* extended */, _))
.Times(2)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_)).Times(0);
win32window.InjectMessages(1,
Win32Message{WM_KEYDOWN, virtual_key, lparam});
flutter_windows_view.InjectPendingEvents(&win32window);
}
// Test an event handled by the framework
{
test_response = true;
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(virtual_key, scan_code, WM_KEYDOWN, character,
false /* extended */, false /* PrevState */))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(0);
win32window.InjectMessages(1,
Win32Message{WM_KEYDOWN, virtual_key, lparam});
flutter_windows_view.InjectPendingEvents(&win32window);
}
}
// Tests key event propagation of system (WM_SYSKEYDOWN) key down events.
TEST(FlutterWindowWin32Test, SystemKeyDownPropagation) {
::testing::InSequence in_sequence;
constexpr WPARAM virtual_key = VK_LEFT;
constexpr WPARAM scan_code = 10;
constexpr char32_t character = 0;
MockFlutterWindowWin32 win32window;
auto window_binding_handler =
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
TestFlutterWindowsView flutter_windows_view(
std::move(window_binding_handler), virtual_key, false /* is_printable */);
win32window.SetView(&flutter_windows_view);
LPARAM lparam = CreateKeyEventLparam(scan_code, false, false);
// Test an event not handled by the framework
{
test_response = false;
flutter_windows_view.SetEngine(std::move(GetTestEngine()));
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(virtual_key, scan_code, WM_SYSKEYDOWN, character,
false /* extended */, _))
.Times(2)
.RetiresOnSaturation();
// Syskey events are not redispatched, so TextInputPlugin, which relies on
// them to receive events, no longer works.
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(0)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_)).Times(0);
win32window.InjectMessages(
1, Win32Message{WM_SYSKEYDOWN, virtual_key, lparam, kWmResultDefault});
flutter_windows_view.InjectPendingEvents(&win32window);
}
// Test an event handled by the framework
{
test_response = true;
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(_, _, _, _, _, _))
.Times(0);
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(0);
win32window.InjectMessages(
1, Win32Message{WM_SYSKEYDOWN, virtual_key, lparam, kWmResultDefault});
flutter_windows_view.InjectPendingEvents(&win32window);
}
}
// Tests key event propagation of printable character key down events. These
// differ from non-printable characters in that they follow a different code
// path in the WndProc (HandleMessage), producing a follow-on WM_CHAR event.
TEST(FlutterWindowWin32Test, CharKeyDownPropagation) {
::testing::InSequence in_sequence;
constexpr WPARAM virtual_key = 65; // The "A" key, which produces a character
constexpr WPARAM scan_code = 30;
constexpr char32_t character = 65;
MockFlutterWindowWin32 win32window;
auto window_binding_handler =
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
TestFlutterWindowsView flutter_windows_view(
std::move(window_binding_handler), virtual_key, true /* is_printable */);
win32window.SetView(&flutter_windows_view);
LPARAM lparam = CreateKeyEventLparam(scan_code, false, false);
flutter_windows_view.SetEngine(std::move(GetTestEngine()));
// Test an event not handled by the framework
{
test_response = false;
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(virtual_key, scan_code, WM_KEYDOWN, character,
false, false))
.Times(2)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_))
.Times(1)
.RetiresOnSaturation();
win32window.InjectMessages(2, Win32Message{WM_KEYDOWN, virtual_key, lparam},
Win32Message{WM_CHAR, virtual_key, lparam});
flutter_windows_view.InjectPendingEvents(&win32window);
}
// Test an event handled by the framework
{
test_response = true;
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(virtual_key, scan_code, WM_KEYDOWN, character,
false, false))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(0);
EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_)).Times(0);
win32window.InjectMessages(2, Win32Message{WM_KEYDOWN, virtual_key, lparam},
Win32Message{WM_CHAR, virtual_key, lparam});
flutter_windows_view.InjectPendingEvents(&win32window);
}
}
// Tests key event propagation of modifier key down events. This is different
// from non-printable events in that they call MapVirtualKey, resulting in a
// slightly different code path.
TEST(FlutterWindowWin32Test, ModifierKeyDownPropagation) {
constexpr WPARAM virtual_key = VK_LSHIFT;
constexpr WPARAM scan_code = 0x2a;
constexpr char32_t character = 0;
MockFlutterWindowWin32 win32window;
auto window_binding_handler =
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
TestFlutterWindowsView flutter_windows_view(
std::move(window_binding_handler), virtual_key, false /* is_printable */);
win32window.SetView(&flutter_windows_view);
LPARAM lparam = CreateKeyEventLparam(scan_code, false, false);
// Test an event not handled by the framework
{
test_response = false;
flutter_windows_view.SetEngine(std::move(GetTestEngine()));
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(virtual_key, scan_code, WM_KEYDOWN, character,
false /* extended */, false))
.Times(2)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_)).Times(0);
EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam),
0);
flutter_windows_view.InjectPendingEvents(&win32window);
}
// Test an event handled by the framework
{
test_response = true;
EXPECT_CALL(*flutter_windows_view.key_event_handler,
KeyboardHook(virtual_key, scan_code, WM_KEYDOWN, character,
false /* extended */, false))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*flutter_windows_view.text_input_plugin,
KeyboardHook(_, _, _, _, _, _))
.Times(0);
EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam),
0);
flutter_windows_view.InjectPendingEvents(&win32window);
}
}
// Tests that composing rect updates are transformed from Flutter logical
// coordinates to device coordinates and passed to the text input manager
// when the DPI scale is 100% (96 DPI).

View File

@ -105,7 +105,8 @@ KeyboardKeyHandler::KeyboardKeyHandlerDelegate::~KeyboardKeyHandlerDelegate() =
KeyboardKeyHandler::KeyboardKeyHandler(EventDispatcher dispatch_event)
: dispatch_event_(dispatch_event),
last_sequence_id_(1),
last_key_is_ctrl_left_down(false) {}
last_key_is_ctrl_left_down(false),
should_synthesize_ctrl_left_up(false) {}
KeyboardKeyHandler::~KeyboardKeyHandler() = default;

View File

@ -19,7 +19,7 @@ static constexpr int kHandledScanCode2 = 22;
static constexpr int kUnhandledScanCode = 21;
constexpr uint64_t kScanCodeShiftRight = 0x36;
constexpr uint64_t kScanCodeControlLeft = 0x1D;
constexpr uint64_t kScanCodeControl = 0x1D;
constexpr uint64_t kScanCodeAltLeft = 0x38;
typedef std::function<void(bool)> Callback;
@ -237,496 +237,5 @@ TEST(KeyboardKeyHandlerTest, SingleDelegateWithSyncResponds) {
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(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(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(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(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(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(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(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(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(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN,
0, false, false),
true);
EXPECT_EQ(handler.KeyboardHook(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN,
0, false, false),
false);
EXPECT_EQ(handler.KeyboardHook(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(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(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYUP, 0,
false, true),
false);
EXPECT_EQ(
handler.KeyboardHook(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(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN,
0, false, false),
false);
// Key down AltRight.
EXPECT_EQ(handler.KeyboardHook(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(VK_RMENU, kScanCodeAltLeft, WM_KEYDOWN, 0,
true, false),
false);
redispatch_scancode = 0;
// Key up AltRight.
EXPECT_EQ(
handler.KeyboardHook(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(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYUP, 0,
false, true),
false);
EXPECT_EQ(
handler.KeyboardHook(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(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN,
0, false, false),
true);
EXPECT_EQ(handler.KeyboardHook(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN,
0, false, false),
false);
EXPECT_EQ(handler.KeyboardHook(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN,
0, false, true),
true);
EXPECT_EQ(handler.KeyboardHook(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN,
0, false, false),
false);
EXPECT_EQ(handler.KeyboardHook(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(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(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(VK_LCONTROL, kScanCodeControlLeft, WM_KEYUP, 0,
false, true),
false);
EXPECT_EQ(
handler.KeyboardHook(VK_RMENU, kScanCodeAltLeft, WM_KEYUP, 0, true, true),
false);
EXPECT_EQ(handler.HasRedispatched(), false);
redispatch_scancode = 0;
hook_history.clear();
}
} // namespace testing
} // namespace flutter

View File

@ -352,10 +352,13 @@ constexpr uint64_t kScanCodeDigit6 = 0x07;
// constexpr uint64_t kScanCodeNumpad1 = 0x4f;
// constexpr uint64_t kScanCodeNumLock = 0x45;
constexpr uint64_t kScanCodeControl = 0x1d;
constexpr uint64_t kScanCodeMetaLeft = 0x5b;
constexpr uint64_t kScanCodeMetaRight = 0x5c;
constexpr uint64_t kScanCodeAlt = 0x38;
constexpr uint64_t kScanCodeShiftLeft = 0x2a;
constexpr uint64_t kScanCodeShiftRight = 0x36;
constexpr uint64_t kScanCodeBracketLeft = 0x1a;
constexpr uint64_t kScanCodeArrowLeft = 0x4b;
constexpr uint64_t kVirtualDigit1 = 0x31;
constexpr uint64_t kVirtualKeyA = 0x41;
@ -454,6 +457,386 @@ TEST(KeyboardTest, LowerCaseAUnhandled) {
EXPECT_EQ(key_calls.size(), 0);
}
TEST(KeyboardTest, ArrowLeftHandled) {
KeyboardTester tester;
tester.Responding(true);
// US Keyboard layout
// Press ArrowLeft
tester.InjectMessages(
1, WmKeyDownInfo{VK_LEFT, kScanCodeArrowLeft, kExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalArrowLeft, kLogicalArrowLeft, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 0);
EXPECT_EQ(key_calls.size(), 0);
// Release ArrowLeft
tester.InjectMessages(
1,
WmKeyUpInfo{VK_LEFT, kScanCodeArrowLeft, kExtended}.Build(kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalArrowLeft,
kLogicalArrowLeft, "", kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 0);
EXPECT_EQ(key_calls.size(), 0);
}
TEST(KeyboardTest, ArrowLeftUnhandled) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
// Press ArrowLeft
tester.InjectMessages(
1, WmKeyDownInfo{VK_LEFT, kScanCodeArrowLeft, kExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalArrowLeft, kLogicalArrowLeft, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
// Release ArrowLeft
tester.InjectMessages(
1,
WmKeyUpInfo{VK_LEFT, kScanCodeArrowLeft, kExtended}.Build(kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalArrowLeft,
kLogicalArrowLeft, "", kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
}
TEST(KeyboardTest, ShiftLeftUnhandled) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
// Press ShiftLeft
tester.SetKeyState(VK_LSHIFT, true, false);
tester.InjectMessages(
1,
WmKeyDownInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalShiftLeft, kLogicalShiftLeft, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Release ShiftLeft
tester.SetKeyState(VK_LSHIFT, false, true);
tester.InjectMessages(
1, WmKeyUpInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalShiftLeft,
kLogicalShiftLeft, "", kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
}
TEST(KeyboardTest, ShiftRightUnhandled) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
// Press ShiftRight
tester.SetKeyState(VK_RSHIFT, true, false);
tester.InjectMessages(
1,
WmKeyDownInfo{VK_SHIFT, kScanCodeShiftRight, kNotExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalShiftRight, kLogicalShiftRight, "",
kNotSynthesized);
clear_key_calls();
// Never redispatch ShiftRight.
EXPECT_EQ(tester.InjectPendingEvents(), 0);
EXPECT_EQ(key_calls.size(), 0);
// Release ShiftRight
tester.SetKeyState(VK_RSHIFT, false, true);
tester.InjectMessages(
1, WmKeyUpInfo{VK_SHIFT, kScanCodeShiftRight, kNotExtended}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp,
kPhysicalShiftRight, kLogicalShiftRight, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
}
TEST(KeyboardTest, CtrlLeftUnhandled) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
// Press CtrlLeft
tester.SetKeyState(VK_LCONTROL, true, false);
tester.InjectMessages(
1,
WmKeyDownInfo{VK_CONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalControlLeft, kLogicalControlLeft, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Release CtrlLeft
tester.SetKeyState(VK_LCONTROL, false, true);
tester.InjectMessages(
1, WmKeyUpInfo{VK_SHIFT, kScanCodeControl, kNotExtended}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp,
kPhysicalControlLeft, kLogicalControlLeft, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
}
TEST(KeyboardTest, CtrlRightUnhandled) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
// Press CtrlRight
tester.SetKeyState(VK_RCONTROL, true, false);
tester.InjectMessages(
1, WmKeyDownInfo{VK_CONTROL, kScanCodeControl, kExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalControlRight, kLogicalControlRight, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Release CtrlRight
tester.SetKeyState(VK_RCONTROL, false, true);
tester.InjectMessages(
1, WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kExtended}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp,
kPhysicalControlRight, kLogicalControlRight, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
}
TEST(KeyboardTest, AltLeftUnhandled) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
// Press AltLeft. AltLeft is a SysKeyDown event.
tester.SetKeyState(VK_LMENU, true, false);
tester.InjectMessages(
1, WmSysKeyDownInfo{VK_MENU, kScanCodeAlt, kNotExtended, kWasUp}.Build(
kWmResultDefault)); // Always pass to the default WndProc.
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalAltLeft,
kLogicalAltLeft, "", kNotSynthesized);
clear_key_calls();
// Sys events are not redispatched.
EXPECT_EQ(tester.InjectPendingEvents(), 0);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Release AltLeft. AltLeft is a SysKeyUp event.
tester.SetKeyState(VK_LMENU, false, true);
tester.InjectMessages(
1, WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kNotExtended}.Build(
kWmResultDefault)); // Always pass to the default WndProc.
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalAltLeft,
kLogicalAltLeft, "", kNotSynthesized);
clear_key_calls();
// Sys events are not redispatched.
EXPECT_EQ(tester.InjectPendingEvents(), 0);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
}
TEST(KeyboardTest, AltRightUnhandled) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
// Press AltRight. AltRight is a SysKeyDown event.
tester.SetKeyState(VK_RMENU, true, false);
tester.InjectMessages(
1, WmSysKeyDownInfo{VK_MENU, kScanCodeAlt, kExtended, kWasUp}.Build(
kWmResultDefault)); // Always pass to the default WndProc.
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalAltRight, kLogicalAltRight, "",
kNotSynthesized);
clear_key_calls();
// Sys events are not redispatched.
EXPECT_EQ(tester.InjectPendingEvents(), 0);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Release AltRight. AltRight is a SysKeyUp event.
tester.SetKeyState(VK_RMENU, false, true);
tester.InjectMessages(
1, WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kExtended}.Build(
kWmResultDefault)); // Always pass to the default WndProc.
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalAltRight,
kLogicalAltRight, "", kNotSynthesized);
clear_key_calls();
// Sys events are not redispatched.
EXPECT_EQ(tester.InjectPendingEvents(), 0);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
}
TEST(KeyboardTest, MetaLeftUnhandled) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
// Press MetaLeft
tester.SetKeyState(VK_LWIN, true, false);
tester.InjectMessages(
1, WmKeyDownInfo{VK_LWIN, kScanCodeMetaLeft, kExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalMetaLeft, kLogicalMetaLeft, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Release MetaLeft
tester.SetKeyState(VK_LWIN, false, true);
tester.InjectMessages(
1,
WmKeyUpInfo{VK_LWIN, kScanCodeMetaLeft, kExtended}.Build(kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalMetaLeft,
kLogicalMetaLeft, "", kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
}
TEST(KeyboardTest, MetaRightUnhandled) {
KeyboardTester tester;
tester.Responding(false);
// US Keyboard layout
// Press MetaRight
tester.SetKeyState(VK_RWIN, true, false);
tester.InjectMessages(
1, WmKeyDownInfo{VK_RWIN, kScanCodeMetaRight, kExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalMetaRight, kLogicalMetaRight, "",
kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Release MetaRight
tester.SetKeyState(VK_RWIN, false, true);
tester.InjectMessages(
1,
WmKeyUpInfo{VK_RWIN, kScanCodeMetaRight, kExtended}.Build(kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalMetaRight,
kLogicalMetaRight, "", kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 1);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
}
// Press Shift-A. This is special because Win32 gives 'A' as character for the
// KeyA press.
TEST(KeyboardTest, ShiftLeftKeyA) {

View File

@ -44,6 +44,19 @@ Win32Message WmCharInfo::Build(LRESULT expected_result, HWND hWnd) {
};
}
Win32Message WmSysKeyDownInfo::Build(LRESULT expected_result, HWND hWnd) {
uint32_t lParam = (repeat_count << 0) | (scan_code << 16) | (extended << 24) |
(context << 29) | (prev_state << 30) |
(0 /* transition */ << 31);
return Win32Message{
.message = WM_SYSKEYDOWN,
.wParam = key,
.lParam = lParam,
.expected_result = expected_result,
.hWnd = hWnd,
};
}
Win32Message WmSysKeyUpInfo::Build(LRESULT expected_result, HWND hWnd) {
uint32_t lParam = (1 /* repeat_count */ << 0) | (scan_code << 16) |
(extended << 24) | (context << 29) |

View File

@ -115,6 +115,28 @@ typedef struct WmCharInfo {
HWND hWnd = NULL);
} WmCharInfo;
// WM_SYSKEYDOWN messages.
//
// See https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-syskeydown.
typedef struct WmSysKeyDownInfo {
uint32_t key;
uint8_t scan_code;
WmFieldExtended extended;
WmFieldPrevState prev_state;
// WmFieldTransitionState transition; // Always 0.
WmFieldContext context;
uint16_t repeat_count;
Win32Message Build(LRESULT expected_result = kWmResultDontCheck,
HWND hWnd = NULL);
} WmSysKeyDownInfo;
// WM_SYSKEYUP messages.
//
// See https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-syskeyup.