diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter old mode 100644 new mode 100755 index 1207450a51884..ae5d437b0c642 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1487,6 +1487,7 @@ FILE: ../../../flutter/shell/platform/windows/cursor_handler.cc FILE: ../../../flutter/shell/platform/windows/cursor_handler.h FILE: ../../../flutter/shell/platform/windows/external_texture_gl.cc FILE: ../../../flutter/shell/platform/windows/external_texture_gl.h +FILE: ../../../flutter/shell/platform/windows/flutter_key_map.cc FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle.cc FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle.h FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle_unittests.cc @@ -1503,10 +1504,16 @@ FILE: ../../../flutter/shell/platform/windows/flutter_windows_view.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_view.h FILE: ../../../flutter/shell/platform/windows/flutter_windows_win32.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_winuwp.cc -FILE: ../../../flutter/shell/platform/windows/key_event_handler.cc -FILE: ../../../flutter/shell/platform/windows/key_event_handler.h -FILE: ../../../flutter/shell/platform/windows/key_event_handler_unittests.cc -FILE: ../../../flutter/shell/platform/windows/keyboard_hook_handler.h +FILE: ../../../flutter/shell/platform/windows/keyboard_handler_base.h +FILE: ../../../flutter/shell/platform/windows/keyboard_key_channel_handler.cc +FILE: ../../../flutter/shell/platform/windows/keyboard_key_channel_handler.h +FILE: ../../../flutter/shell/platform/windows/keyboard_key_channel_handler_unittests.cc +FILE: ../../../flutter/shell/platform/windows/keyboard_key_embedder_handler.cc +FILE: ../../../flutter/shell/platform/windows/keyboard_key_embedder_handler.h +FILE: ../../../flutter/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc +FILE: ../../../flutter/shell/platform/windows/keyboard_key_handler.cc +FILE: ../../../flutter/shell/platform/windows/keyboard_key_handler.h +FILE: ../../../flutter/shell/platform/windows/keyboard_key_handler_unittests.cc FILE: ../../../flutter/shell/platform/windows/platform_handler.cc FILE: ../../../flutter/shell/platform/windows/platform_handler.h FILE: ../../../flutter/shell/platform/windows/platform_handler_unittests.cc diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 0468c04fc2bdc..55c024879bb12 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -49,6 +49,7 @@ source_set("flutter_windows_source") { "cursor_handler.h", "external_texture_gl.cc", "external_texture_gl.h", + "flutter_key_map.cc", "flutter_project_bundle.cc", "flutter_project_bundle.h", "flutter_windows.cc", @@ -58,9 +59,13 @@ source_set("flutter_windows_source") { "flutter_windows_texture_registrar.h", "flutter_windows_view.cc", "flutter_windows_view.h", - "key_event_handler.cc", - "key_event_handler.h", - "keyboard_hook_handler.h", + "keyboard_handler_base.h", + "keyboard_key_channel_handler.cc", + "keyboard_key_channel_handler.h", + "keyboard_key_embedder_handler.cc", + "keyboard_key_embedder_handler.h", + "keyboard_key_handler.cc", + "keyboard_key_handler.h", "platform_handler.cc", "platform_handler.h", "string_conversion.cc", @@ -195,7 +200,9 @@ executable("flutter_windows_unittests") { "flutter_project_bundle_unittests.cc", "flutter_windows_engine_unittests.cc", "flutter_windows_texture_registrar_unittests.cc", - "key_event_handler_unittests.cc", + "keyboard_key_channel_handler_unittests.cc", + "keyboard_key_embedder_handler_unittests.cc", + "keyboard_key_handler_unittests.cc", "testing/mock_win32_window.cc", "testing/mock_win32_window.h", "testing/mock_window_binding_handler.cc", diff --git a/shell/platform/windows/client_wrapper/BUILD.gn b/shell/platform/windows/client_wrapper/BUILD.gn index 232f8951c1263..18cf24d75abd3 100644 --- a/shell/platform/windows/client_wrapper/BUILD.gn +++ b/shell/platform/windows/client_wrapper/BUILD.gn @@ -18,7 +18,7 @@ _wrapper_sources = [ "flutter_view_controller.cc", ] -# This code will be merged into .../common/cpp/client_wrapper for client use, +# This code will be merged into .../common/client_wrapper for client use, # so uses header paths that assume the merged state. Include the header # directories of the core wrapper files so these includes will work. config("relative_core_wrapper_headers") { diff --git a/shell/platform/windows/flutter_key_map.cc b/shell/platform/windows/flutter_key_map.cc new file mode 100644 index 0000000000000..69256a78f2606 --- /dev/null +++ b/shell/platform/windows/flutter_key_map.cc @@ -0,0 +1,329 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_KEY_MAP_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_KEY_MAP_H_ + +#include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h" + +#include + +// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT +// This file is generated by +// flutter/flutter@dev/tools/gen_keycodes/bin/gen_keycodes.dart and should not +// be edited directly. +// +// Edit the template dev/tools/gen_keycodes/data/windows_flutter_key_map_cc.tmpl +// instead. See dev/tools/gen_keycodes/README.md for more information. + +namespace flutter { + +std::map KeyboardKeyEmbedderHandler::windowsToPhysicalMap_ = + { + {0x0000e05f, 0x00010082}, // sleep + {0x0000e063, 0x00010083}, // wakeUp + {0x000000ff, 0x00070001}, // usbErrorRollOver + {0x000000fc, 0x00070002}, // usbPostFail + {0x0000001e, 0x00070004}, // keyA + {0x00000030, 0x00070005}, // keyB + {0x0000002e, 0x00070006}, // keyC + {0x00000020, 0x00070007}, // keyD + {0x00000012, 0x00070008}, // keyE + {0x00000021, 0x00070009}, // keyF + {0x00000022, 0x0007000a}, // keyG + {0x00000023, 0x0007000b}, // keyH + {0x00000017, 0x0007000c}, // keyI + {0x00000024, 0x0007000d}, // keyJ + {0x00000025, 0x0007000e}, // keyK + {0x00000026, 0x0007000f}, // keyL + {0x00000032, 0x00070010}, // keyM + {0x00000031, 0x00070011}, // keyN + {0x00000018, 0x00070012}, // keyO + {0x00000019, 0x00070013}, // keyP + {0x00000010, 0x00070014}, // keyQ + {0x00000013, 0x00070015}, // keyR + {0x0000001f, 0x00070016}, // keyS + {0x00000014, 0x00070017}, // keyT + {0x00000016, 0x00070018}, // keyU + {0x0000002f, 0x00070019}, // keyV + {0x00000011, 0x0007001a}, // keyW + {0x0000002d, 0x0007001b}, // keyX + {0x00000015, 0x0007001c}, // keyY + {0x0000002c, 0x0007001d}, // keyZ + {0x00000002, 0x0007001e}, // digit1 + {0x00000003, 0x0007001f}, // digit2 + {0x00000004, 0x00070020}, // digit3 + {0x00000005, 0x00070021}, // digit4 + {0x00000006, 0x00070022}, // digit5 + {0x00000007, 0x00070023}, // digit6 + {0x00000008, 0x00070024}, // digit7 + {0x00000009, 0x00070025}, // digit8 + {0x0000000a, 0x00070026}, // digit9 + {0x0000000b, 0x00070027}, // digit0 + {0x0000001c, 0x00070028}, // enter + {0x00000001, 0x00070029}, // escape + {0x0000000e, 0x0007002a}, // backspace + {0x0000000f, 0x0007002b}, // tab + {0x00000039, 0x0007002c}, // space + {0x0000000c, 0x0007002d}, // minus + {0x0000000d, 0x0007002e}, // equal + {0x0000001a, 0x0007002f}, // bracketLeft + {0x0000001b, 0x00070030}, // bracketRight + {0x0000002b, 0x00070031}, // backslash + {0x00000027, 0x00070033}, // semicolon + {0x00000028, 0x00070034}, // quote + {0x00000029, 0x00070035}, // backquote + {0x00000033, 0x00070036}, // comma + {0x00000034, 0x00070037}, // period + {0x00000035, 0x00070038}, // slash + {0x0000003a, 0x00070039}, // capsLock + {0x0000003b, 0x0007003a}, // f1 + {0x0000003c, 0x0007003b}, // f2 + {0x0000003d, 0x0007003c}, // f3 + {0x0000003e, 0x0007003d}, // f4 + {0x0000003f, 0x0007003e}, // f5 + {0x00000040, 0x0007003f}, // f6 + {0x00000041, 0x00070040}, // f7 + {0x00000042, 0x00070041}, // f8 + {0x00000043, 0x00070042}, // f9 + {0x00000044, 0x00070043}, // f10 + {0x00000057, 0x00070044}, // f11 + {0x00000058, 0x00070045}, // f12 + {0x0000e037, 0x00070046}, // printScreen + {0x00000046, 0x00070047}, // scrollLock + {0x00000045, 0x00070048}, // pause + {0x0000e052, 0x00070049}, // insert + {0x0000e047, 0x0007004a}, // home + {0x0000e049, 0x0007004b}, // pageUp + {0x0000e053, 0x0007004c}, // delete + {0x0000e04f, 0x0007004d}, // end + {0x0000e051, 0x0007004e}, // pageDown + {0x0000e04d, 0x0007004f}, // arrowRight + {0x0000e04b, 0x00070050}, // arrowLeft + {0x0000e050, 0x00070051}, // arrowDown + {0x0000e048, 0x00070052}, // arrowUp + {0x0000e045, 0x00070053}, // numLock + {0x0000e035, 0x00070054}, // numpadDivide + {0x00000037, 0x00070055}, // numpadMultiply + {0x0000004a, 0x00070056}, // numpadSubtract + {0x0000004e, 0x00070057}, // numpadAdd + {0x0000e01c, 0x00070058}, // numpadEnter + {0x0000004f, 0x00070059}, // numpad1 + {0x00000050, 0x0007005a}, // numpad2 + {0x00000051, 0x0007005b}, // numpad3 + {0x0000004b, 0x0007005c}, // numpad4 + {0x0000004c, 0x0007005d}, // numpad5 + {0x0000004d, 0x0007005e}, // numpad6 + {0x00000047, 0x0007005f}, // numpad7 + {0x00000048, 0x00070060}, // numpad8 + {0x00000049, 0x00070061}, // numpad9 + {0x00000052, 0x00070062}, // numpad0 + {0x00000053, 0x00070063}, // numpadDecimal + {0x00000056, 0x00070064}, // intlBackslash + {0x0000e05d, 0x00070065}, // contextMenu + {0x0000e05e, 0x00070066}, // power + {0x00000059, 0x00070067}, // numpadEqual + {0x00000064, 0x00070068}, // f13 + {0x00000065, 0x00070069}, // f14 + {0x00000066, 0x0007006a}, // f15 + {0x00000067, 0x0007006b}, // f16 + {0x00000068, 0x0007006c}, // f17 + {0x00000069, 0x0007006d}, // f18 + {0x0000006a, 0x0007006e}, // f19 + {0x0000006b, 0x0007006f}, // f20 + {0x0000006c, 0x00070070}, // f21 + {0x0000006d, 0x00070071}, // f22 + {0x0000006e, 0x00070072}, // f23 + {0x00000076, 0x00070073}, // f24 + {0x0000e03b, 0x00070075}, // help + {0x0000e008, 0x0007007a}, // undo + {0x0000e017, 0x0007007b}, // cut + {0x0000e018, 0x0007007c}, // copy + {0x0000e00a, 0x0007007d}, // paste + {0x0000e020, 0x0007007f}, // audioVolumeMute + {0x0000e030, 0x00070080}, // audioVolumeUp + {0x0000e02e, 0x00070081}, // audioVolumeDown + {0x0000007e, 0x00070085}, // numpadComma + {0x00000073, 0x00070087}, // intlRo + {0x00000070, 0x00070088}, // kanaMode + {0x0000007d, 0x00070089}, // intlYen + {0x00000079, 0x0007008a}, // convert + {0x0000007b, 0x0007008b}, // nonConvert + {0x00000072, 0x00070090}, // lang1 + {0x00000071, 0x00070091}, // lang2 + {0x00000078, 0x00070092}, // lang3 + {0x00000077, 0x00070093}, // lang4 + {0x0000001d, 0x000700e0}, // controlLeft + {0x0000002a, 0x000700e1}, // shiftLeft + {0x00000038, 0x000700e2}, // altLeft + {0x0000e05b, 0x000700e3}, // metaLeft + {0x0000e01d, 0x000700e4}, // controlRight + {0x00000036, 0x000700e5}, // shiftRight + {0x0000e038, 0x000700e6}, // altRight + {0x0000e05c, 0x000700e7}, // metaRight + {0x0000e019, 0x000c00b5}, // mediaTrackNext + {0x0000e010, 0x000c00b6}, // mediaTrackPrevious + {0x0000e024, 0x000c00b7}, // mediaStop + {0x0000e02c, 0x000c00b8}, // eject + {0x0000e022, 0x000c00cd}, // mediaPlayPause + {0x0000e06d, 0x000c0183}, // mediaSelect + {0x0000e06c, 0x000c018a}, // launchMail + {0x0000e021, 0x000c0192}, // launchApp2 + {0x0000e06b, 0x000c0194}, // launchApp1 + {0x0000e065, 0x000c0221}, // browserSearch + {0x0000e032, 0x000c0223}, // browserHome + {0x0000e06a, 0x000c0224}, // browserBack + {0x0000e069, 0x000c0225}, // browserForward + {0x0000e068, 0x000c0226}, // browserStop + {0x0000e067, 0x000c0227}, // browserRefresh + {0x0000e066, 0x000c022a}, // browserFavorites +}; + +std::map KeyboardKeyEmbedderHandler::windowsToLogicalMap_ = + { + {0x00000008, 0x00000000008}, // BACK + {0x00000009, 0x00000000009}, // TAB + {0x0000000d, 0x0000000000d}, // RETURN + {0x0000001b, 0x0000000001b}, // ESCAPE + {0x00000020, 0x00000000020}, // SPACE + {0x000000de, 0x00000000022}, // OEM_7 + {0x000000bc, 0x0000000002c}, // OEM_COMMA + {0x000000bd, 0x0000000002d}, // OEM_MINUS + {0x000000be, 0x0000000002e}, // OEM_PERIOD + {0x000000bf, 0x0000000002f}, // OEM_2 + {0x000000ba, 0x0000000003b}, // OEM_1 + {0x000000bb, 0x0000000003d}, // OEM_PLUS + {0x000000db, 0x0000000005b}, // OEM_4 + {0x000000dc, 0x0000000005c}, // OEM_5 + {0x000000dd, 0x0000000005d}, // OEM_6 + {0x000000c0, 0x00000000060}, // OEM_3 + {0x0000002e, 0x0000000007f}, // DELETE + {0x00000014, 0x00000000104}, // CAPITAL + {0x00000090, 0x0000000010a}, // NUMLOCK + {0x00000091, 0x0000000010c}, // SCROLL + {0x00000028, 0x00000000301}, // DOWN + {0x00000025, 0x00000000302}, // LEFT + {0x00000027, 0x00000000303}, // RIGHT + {0x00000026, 0x00000000304}, // UP + {0x00000023, 0x00000000305}, // END + {0x00000024, 0x00000000306}, // HOME + {0x00000022, 0x00000000307}, // NEXT + {0x00000021, 0x00000000308}, // PRIOR + {0x0000000c, 0x00000000401}, // CLEAR + {0x0000002d, 0x00000000407}, // INSERT + {0x0000001e, 0x00000000501}, // ACCEPT + {0x000000f6, 0x00000000503}, // ATTN + {0x00000003, 0x00000000504}, // CANCEL + {0x0000005d, 0x00000000505}, // APPS + {0x0000002b, 0x00000000506}, // EXECUTE + {0x0000002f, 0x00000000508}, // HELP + {0x00000013, 0x00000000509}, // PAUSE + {0x000000fa, 0x0000000050a}, // PLAY + {0x00000029, 0x0000000050c}, // SELECT + {0x0000001c, 0x00000000705}, // CONVERT + {0x0000001f, 0x0000000070b}, // MODECHANGE + {0x00000070, 0x00000000801}, // F1 + {0x00000071, 0x00000000802}, // F2 + {0x00000072, 0x00000000803}, // F3 + {0x00000073, 0x00000000804}, // F4 + {0x00000074, 0x00000000805}, // F5 + {0x00000075, 0x00000000806}, // F6 + {0x00000076, 0x00000000807}, // F7 + {0x00000077, 0x00000000808}, // F8 + {0x00000078, 0x00000000809}, // F9 + {0x00000079, 0x0000000080a}, // F10 + {0x0000007a, 0x0000000080b}, // F11 + {0x0000007b, 0x0000000080c}, // F12 + {0x0000007c, 0x0000000080d}, // F13 + {0x0000007d, 0x0000000080e}, // F14 + {0x0000007e, 0x0000000080f}, // F15 + {0x0000007f, 0x00000000810}, // F16 + {0x00000080, 0x00000000811}, // F17 + {0x00000081, 0x00000000812}, // F18 + {0x00000082, 0x00000000813}, // F19 + {0x00000083, 0x00000000814}, // F20 + {0x00000084, 0x00000000815}, // F21 + {0x00000085, 0x00000000816}, // F22 + {0x00000086, 0x00000000817}, // F23 + {0x00000087, 0x00000000818}, // F24 + {0x000000b3, 0x00000000a05}, // MEDIA_PLAY_PAUSE + {0x000000b2, 0x00000000a07}, // MEDIA_STOP + {0x0000002a, 0x00000000a0c}, // PRINT + {0x000000ae, 0x00000000a0f}, // VOLUME_DOWN + {0x000000af, 0x00000000a10}, // VOLUME_UP + {0x000000ad, 0x00000000a11}, // VOLUME_MUTE + {0x000000b4, 0x00000000b03}, // LAUNCH_MAIL + {0x000000a6, 0x00000000c01}, // BROWSER_BACK + {0x000000ab, 0x00000000c02}, // BROWSER_FAVORITES + {0x000000a7, 0x00000000c03}, // BROWSER_FORWARD + {0x000000ac, 0x00000000c04}, // BROWSER_HOME + {0x000000a8, 0x00000000c05}, // BROWSER_REFRESH + {0x000000aa, 0x00000000c06}, // BROWSER_SEARCH + {0x000000a9, 0x00000000c07}, // BROWSER_STOP + {0x000000c3, 0x0000005ff08}, // GAMEPAD_A + {0x000000c4, 0x0000005ff09}, // GAMEPAD_B + {0x000000c5, 0x0000005ff0a}, // GAMEPAD_X + {0x000000c6, 0x0000005ff0b}, // GAMEPAD_Y + {0x000000c7, 0x0000005ff0c}, // GAMEPAD_RIGHT_SHOULDER + {0x000000c8, 0x0000005ff0d}, // GAMEPAD_LEFT_SHOULDER + {0x000000c9, 0x0000005ff0e}, // GAMEPAD_LEFT_TRIGGER + {0x000000ca, 0x0000005ff0f}, // GAMEPAD_RIGHT_TRIGGER + {0x000000cb, 0x0000005ff10}, // GAMEPAD_DPAD_UP + {0x0000005f, 0x00100010082}, // SLEEP + {0x00000015, 0x00100070090}, // KANA + {0x00000015, 0x00100070090}, // HANGEUL + {0x00000015, 0x00100070090}, // HANGUL + {0x0000006a, 0x0020000002a}, // MULTIPLY + {0x0000006b, 0x0020000002b}, // ADD + {0x0000006d, 0x0020000002d}, // SUBTRACT + {0x0000006e, 0x0020000002e}, // DECIMAL + {0x0000006f, 0x0020000002f}, // DIVIDE + {0x00000060, 0x00200000030}, // NUMPAD0 + {0x00000061, 0x00200000031}, // NUMPAD1 + {0x00000062, 0x00200000032}, // NUMPAD2 + {0x00000063, 0x00200000033}, // NUMPAD3 + {0x00000064, 0x00200000034}, // NUMPAD4 + {0x00000065, 0x00200000035}, // NUMPAD5 + {0x00000066, 0x00200000036}, // NUMPAD6 + {0x00000067, 0x00200000037}, // NUMPAD7 + {0x00000068, 0x00200000038}, // NUMPAD8 + {0x00000069, 0x00200000039}, // NUMPAD9 + {0x00000092, 0x0020000003d}, // OEM_NEC_EQUAL + {0x000000a4, 0x00300000102}, // LMENU + {0x00000011, 0x00300000105}, // CONTROL + {0x000000a2, 0x00300000105}, // LCONTROL + {0x0000005b, 0x00300000109}, // LWIN + {0x00000010, 0x0030000010d}, // SHIFT + {0x000000a0, 0x0030000010d}, // LSHIFT + {0x000000a5, 0x00400000102}, // RMENU + {0x000000a3, 0x00400000105}, // RCONTROL + {0x0000005c, 0x00400000109}, // RWIN + {0x000000a1, 0x0040000010d}, // RSHIFT +}; + +std::map KeyboardKeyEmbedderHandler::scanCodeToLogicalMap_ = + { + {0x0000e01d, 0x0400000105}, // ControlRight + {0x0000e038, 0x0400000102}, // AltRight + {0x0000004f, 0x0200000031}, // Numpad1 + {0x00000050, 0x0200000032}, // Numpad2 + {0x00000051, 0x0200000033}, // Numpad3 + {0x0000004b, 0x0200000034}, // Numpad4 + {0x0000004c, 0x0200000035}, // Numpad5 + {0x0000004d, 0x0200000036}, // Numpad6 + {0x00000047, 0x0200000037}, // Numpad7 + {0x00000048, 0x0200000038}, // Numpad8 + {0x00000049, 0x0200000039}, // Numpad9 + {0x00000052, 0x0200000030}, // Numpad0 + {0x0000004e, 0x020000002b}, // NumpadAdd + {0x00000053, 0x020000002e}, // NumpadDecimal + {0x0000e035, 0x020000002f}, // NumpadDivide + {0x00000059, 0x020000003d}, // NumpadEqual + {0x00000037, 0x020000002a}, // NumpadMultiply + {0x0000004a, 0x020000002d}, // NumpadSubtract +}; + +} // namespace flutter + +#endif diff --git a/shell/platform/windows/flutter_window_winuwp.cc b/shell/platform/windows/flutter_window_winuwp.cc index 7c68cfd9b05dc..2f12763143d23 100644 --- a/shell/platform/windows/flutter_window_winuwp.cc +++ b/shell/platform/windows/flutter_window_winuwp.cc @@ -270,7 +270,8 @@ void FlutterWindowWinUWP::OnKeyUp( int key = static_cast(args.VirtualKey()); char32_t chararacter = static_cast(key | 32); int action = 0x0101; - binding_handler_delegate_->OnKey(key, scancode, action, chararacter, false); + binding_handler_delegate_->OnKey(key, scancode, action, chararacter, + false /* extended */, true /* was_down */); } void FlutterWindowWinUWP::OnKeyDown( @@ -285,7 +286,8 @@ void FlutterWindowWinUWP::OnKeyDown( int key = static_cast(args.VirtualKey()); char32_t chararacter = static_cast(key | 32); int action = 0x0100; - binding_handler_delegate_->OnKey(key, scancode, action, chararacter, false); + binding_handler_delegate_->OnKey(key, scancode, action, chararacter, + false /* extended */, false /* was_down */); } void FlutterWindowWinUWP::OnCharacterReceived( diff --git a/shell/platform/windows/flutter_windows.cc b/shell/platform/windows/flutter_windows.cc index 4e1ef6ecc5af1..625b5e3465385 100644 --- a/shell/platform/windows/flutter_windows.cc +++ b/shell/platform/windows/flutter_windows.cc @@ -148,7 +148,7 @@ void FlutterDesktopResyncOutputStreams() { std::ios::sync_with_stdio(); } -// Implementations of common/cpp/ API methods. +// Implementations of common/ API methods. FlutterDesktopMessengerRef FlutterDesktopPluginRegistrarGetMessenger( FlutterDesktopPluginRegistrarRef registrar) { diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index c2be3492b1974..f7fca8b4b5e72 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -290,6 +290,14 @@ void FlutterWindowsEngine::SendPointerEvent(const FlutterPointerEvent& event) { } } +void FlutterWindowsEngine::SendKeyEvent(const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, + void* user_data) { + if (engine_) { + embedder_api_.SendKeyEvent(engine_, &event, callback, user_data); + } +} + bool FlutterWindowsEngine::SendPlatformMessage( const char* channel, const uint8_t* message, diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index a605d5a50c3e1..c346b14282804 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -97,6 +97,11 @@ class FlutterWindowsEngine { // Informs the engine of an incoming pointer event. void SendPointerEvent(const FlutterPointerEvent& event); + // Informs the engine of an incoming key event. + void SendKeyEvent(const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, + void* user_data); + // Sends the given message to the engine, calling |reply| with |user_data| // when a reponse is received from the engine if they are non-null. bool SendPlatformMessage(const char* channel, diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 1eb03b30efb91..93d2aae6346d8 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -6,6 +6,10 @@ #include +#include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" +#include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h" +#include "flutter/shell/platform/windows/text_input_plugin.h" + namespace flutter { /// Returns true if the surface will be updated as part of the resize process. @@ -53,7 +57,7 @@ void FlutterWindowsView::SetEngine( // Set up the system channel handlers. auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); - RegisterKeyboardHookHandlers(internal_plugin_messenger); + RegisterKeyboardHandlers(internal_plugin_messenger); platform_handler_ = PlatformHandler::Create(internal_plugin_messenger, this); cursor_handler_ = std::make_unique( internal_plugin_messenger, binding_handler_.get()); @@ -64,16 +68,33 @@ void FlutterWindowsView::SetEngine( binding_handler_->GetDpiScale()); } -void FlutterWindowsView::RegisterKeyboardHookHandlers( +void FlutterWindowsView::RegisterKeyboardHandlers( flutter::BinaryMessenger* messenger) { - AddKeyboardHookHandler(std::make_unique(messenger)); - AddKeyboardHookHandler( + // There must be only one handler that receives |SendInput|, i.e. only one + // handler that might redispatch events. (See the documentation of + // |KeyboardKeyHandler| to learn about redispatching.) + // + // Whether an event is a redispatched event is decided by calculating the hash + // of the event. In order to allow the same real event in the future, the + // handler is "toggled" when events pass through, therefore the redispatching + // algorithm does not allow more than 1 handler that takes |SendInput|. + auto key_handler = std::make_unique(SendInput); + key_handler->AddDelegate( + std::make_unique(messenger)); + key_handler->AddDelegate(std::make_unique( + [this](const FlutterKeyEvent& event, FlutterKeyEventCallback callback, + void* user_data) { + return engine_->SendKeyEvent(event, callback, user_data); + }, + GetKeyState)); + AddKeyboardHandler(std::move(key_handler)); + AddKeyboardHandler( std::make_unique(messenger, this)); } -void FlutterWindowsView::AddKeyboardHookHandler( - std::unique_ptr handler) { - keyboard_hook_handlers_.push_back(std::move(handler)); +void FlutterWindowsView::AddKeyboardHandler( + std::unique_ptr handler) { + keyboard_handlers_.push_back(std::move(handler)); } uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) { @@ -170,8 +191,9 @@ bool FlutterWindowsView::OnKey(int key, int scancode, int action, char32_t character, - bool extended) { - return SendKey(key, scancode, action, character, extended); + bool extended, + bool was_down) { + return SendKey(key, scancode, action, character, extended, was_down); } void FlutterWindowsView::OnComposeBegin() { @@ -270,7 +292,7 @@ void FlutterWindowsView::SendPointerLeave() { } void FlutterWindowsView::SendText(const std::u16string& text) { - for (const auto& handler : keyboard_hook_handlers_) { + for (const auto& handler : keyboard_handlers_) { handler->TextHook(this, text); } } @@ -279,10 +301,11 @@ bool FlutterWindowsView::SendKey(int key, int scancode, int action, char32_t character, - bool extended) { - for (const auto& handler : keyboard_hook_handlers_) { - if (handler->KeyboardHook(this, key, scancode, action, character, - extended)) { + bool extended, + bool was_down) { + for (const auto& handler : keyboard_handlers_) { + if (handler->KeyboardHook(this, key, scancode, action, character, extended, + was_down)) { // key event was handled, so don't send to other handlers. return true; } @@ -291,20 +314,20 @@ bool FlutterWindowsView::SendKey(int key, } void FlutterWindowsView::SendComposeBegin() { - for (const auto& handler : keyboard_hook_handlers_) { + for (const auto& handler : keyboard_handlers_) { handler->ComposeBeginHook(); } } void FlutterWindowsView::SendComposeEnd() { - for (const auto& handler : keyboard_hook_handlers_) { + for (const auto& handler : keyboard_handlers_) { handler->ComposeEndHook(); } } void FlutterWindowsView::SendComposeChange(const std::u16string& text, int cursor_pos) { - for (const auto& handler : keyboard_hook_handlers_) { + for (const auto& handler : keyboard_handlers_) { handler->ComposeChangeHook(text, cursor_pos); } } diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index d65acb7adc9a4..865b0b9e09fc1 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -18,11 +18,9 @@ #include "flutter/shell/platform/windows/angle_surface_manager.h" #include "flutter/shell/platform/windows/cursor_handler.h" #include "flutter/shell/platform/windows/flutter_windows_engine.h" -#include "flutter/shell/platform/windows/key_event_handler.h" -#include "flutter/shell/platform/windows/keyboard_hook_handler.h" +#include "flutter/shell/platform/windows/keyboard_handler_base.h" #include "flutter/shell/platform/windows/platform_handler.h" #include "flutter/shell/platform/windows/public/flutter_windows.h" -#include "flutter/shell/platform/windows/text_input_plugin.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" #include "flutter/shell/platform/windows/window_binding_handler.h" #include "flutter/shell/platform/windows/window_binding_handler_delegate.h" @@ -107,7 +105,8 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, int scancode, int action, char32_t character, - bool extended) override; + bool extended, + bool was_down) override; // |WindowBindingHandlerDelegate| void OnComposeBegin() override; @@ -130,12 +129,11 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, protected: // Called to create the keyboard hook handlers. - virtual void RegisterKeyboardHookHandlers( - flutter::BinaryMessenger* messenger); + virtual void RegisterKeyboardHandlers(flutter::BinaryMessenger* messenger); - // Used by RegisterKeyboardHookHandlers to add a new keyboard hook handler. - void AddKeyboardHookHandler( - std::unique_ptr handler); + // Used by RegisterKeyboardHandlers to add a new keyboard hook handler. + void AddKeyboardHandler( + std::unique_ptr handler); private: // Struct holding the mouse state. The engine doesn't keep track of which @@ -195,7 +193,8 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, int scancode, int action, char32_t character, - bool extended); + bool extended, + bool was_down); // Reports an IME compose begin event. // @@ -269,8 +268,7 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, std::unique_ptr internal_plugin_registrar_; // Handlers for keyboard events from Windows. - std::vector> - keyboard_hook_handlers_; + std::vector> keyboard_handlers_; // Handler for the flutter/platform channel. std::unique_ptr platform_handler_; diff --git a/shell/platform/windows/key_event_handler.cc b/shell/platform/windows/key_event_handler.cc deleted file mode 100644 index de607b917d21e..0000000000000 --- a/shell/platform/windows/key_event_handler.cc +++ /dev/null @@ -1,284 +0,0 @@ -// 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/key_event_handler.h" - -#include - -#include - -#include "flutter/shell/platform/common/json_message_codec.h" - -namespace flutter { - -namespace { - -static constexpr char kChannelName[] = "flutter/keyevent"; - -static constexpr char kKeyCodeKey[] = "keyCode"; -static constexpr char kScanCodeKey[] = "scanCode"; -static constexpr char kCharacterCodePointKey[] = "characterCodePoint"; -static constexpr char kModifiersKey[] = "modifiers"; -static constexpr char kKeyMapKey[] = "keymap"; -static constexpr char kTypeKey[] = "type"; -static constexpr char kHandledKey[] = "handled"; - -static constexpr char kWindowsKeyMap[] = "windows"; -static constexpr char kKeyUp[] = "keyup"; -static constexpr char kKeyDown[] = "keydown"; - -// The maximum number of pending events to keep before -// emitting a warning on the console about unhandled events. -static constexpr int kMaxPendingEvents = 1000; - -// Re-definition of the modifiers for compatibility with the Flutter framework. -// These have to be in sync with the framework's RawKeyEventDataWindows -// modifiers definition. -// https://github.com/flutter/flutter/blob/19ff596979e407c484a32f4071420fca4f4c885f/packages/flutter/lib/src/services/raw_keyboard_windows.dart#L203 -static constexpr int kShift = 1 << 0; -static constexpr int kShiftLeft = 1 << 1; -static constexpr int kShiftRight = 1 << 2; -static constexpr int kControl = 1 << 3; -static constexpr int kControlLeft = 1 << 4; -static constexpr int kControlRight = 1 << 5; -static constexpr int kAlt = 1 << 6; -static constexpr int kAltLeft = 1 << 7; -static constexpr int kAltRight = 1 << 8; -static constexpr int kWinLeft = 1 << 9; -static constexpr int kWinRight = 1 << 10; -static constexpr int kCapsLock = 1 << 11; -static constexpr int kNumLock = 1 << 12; -static constexpr int kScrollLock = 1 << 13; - -// TODO(clarkezone) need to add support for get keystate -// https://github.com/flutter/flutter/issues/70202 -#ifndef WINUWP -/// Calls GetKeyState() an all modifier keys and packs the result in an int, -/// with the re-defined values declared above for compatibility with the Flutter -/// framework. -int GetModsForKeyState() { - int mods = 0; - - if (GetKeyState(VK_SHIFT) < 0) - mods |= kShift; - if (GetKeyState(VK_LSHIFT) < 0) - mods |= kShiftLeft; - if (GetKeyState(VK_RSHIFT) < 0) - mods |= kShiftRight; - if (GetKeyState(VK_CONTROL) < 0) - mods |= kControl; - if (GetKeyState(VK_LCONTROL) < 0) - mods |= kControlLeft; - if (GetKeyState(VK_RCONTROL) < 0) - mods |= kControlRight; - if (GetKeyState(VK_MENU) < 0) - mods |= kAlt; - if (GetKeyState(VK_LMENU) < 0) - mods |= kAltLeft; - if (GetKeyState(VK_RMENU) < 0) - mods |= kAltRight; - if (GetKeyState(VK_LWIN) < 0) - mods |= kWinLeft; - if (GetKeyState(VK_RWIN) < 0) - mods |= kWinRight; - if (GetKeyState(VK_CAPITAL) < 0) - mods |= kCapsLock; - if (GetKeyState(VK_NUMLOCK) < 0) - mods |= kNumLock; - if (GetKeyState(VK_SCROLL) < 0) - mods |= kScrollLock; - return mods; -} -#endif - -// This uses event data instead of generating a serial number because -// information can't be attached to the redispatched events, so it has to be -// possible to compute an ID from the identifying data in the event when it is -// received again in order to differentiate between events that are new, and -// events that have been redispatched. -// -// Another alternative would be to compute a checksum from all the data in the -// event (just compute it over the bytes in the struct, probably skipping -// timestamps), but the fields used below are enough to differentiate them, and -// since Windows does some processing on the events (coming up with virtual key -// codes, setting timestamps, etc.), it's not clear that the redispatched -// events would have the same checksums. -uint64_t CalculateEventId(int scancode, int action, bool extended) { - // Calculate a key event ID based on the scan code of the key pressed, - // and the flags we care about. - return scancode | (((action == WM_KEYUP ? KEYEVENTF_KEYUP : 0x0) | - (extended ? KEYEVENTF_EXTENDEDKEY : 0x0)) - << 16); -} - -} // namespace - -KeyEventHandler::KeyEventHandler(flutter::BinaryMessenger* messenger, - KeyEventHandler::SendInputDelegate send_input) - : channel_( - std::make_unique>( - messenger, - kChannelName, - &flutter::JsonMessageCodec::GetInstance())), - send_input_(send_input) { -// As described in the header, UWP doesn't support the SendInput API hence we -// only need to assert that the delegate is null in the non-UWP case since it is -// expected to be null in the UWP case. -#ifndef WINUWP - assert(send_input != nullptr); -#endif -} - -KeyEventHandler::~KeyEventHandler() = default; - -void KeyEventHandler::TextHook(FlutterWindowsView* view, - const std::u16string& code_point) {} - -KEYBDINPUT* KeyEventHandler::FindPendingEvent(uint64_t id) { - if (pending_events_.empty()) { - return nullptr; - } - for (auto iter = pending_events_.begin(); iter != pending_events_.end(); - ++iter) { - if (iter->first == id) { - return &iter->second; - } - } - return nullptr; -} - -void KeyEventHandler::RemovePendingEvent(uint64_t id) { - for (auto iter = pending_events_.begin(); iter != pending_events_.end(); - ++iter) { - if (iter->first == id) { - pending_events_.erase(iter); - return; - } - } - std::cerr << "Tried to remove pending event with id " << id - << ", but the event was not found." << std::endl; -} - -void KeyEventHandler::AddPendingEvent(uint64_t id, - int scancode, - int action, - bool extended) { - if (pending_events_.size() > kMaxPendingEvents) { - std::cerr - << "There are " << pending_events_.size() - << " keyboard events that have not yet received a response from the " - << "framework. Are responses being sent?" << std::endl; - } - KEYBDINPUT key_event = KEYBDINPUT{0}; - key_event.wScan = scancode; - key_event.dwFlags = KEYEVENTF_SCANCODE | - (extended ? KEYEVENTF_EXTENDEDKEY : 0x0) | - (action == WM_KEYUP ? KEYEVENTF_KEYUP : 0x0); - pending_events_.push_back(std::make_pair(id, key_event)); -} - -void KeyEventHandler::HandleResponse(bool handled, - uint64_t id, - int action, - bool extended, - int scancode, - int character) { - if (handled) { - this->RemovePendingEvent(id); - } else { - // Since the framework didn't handle the event, we inject a newly - // synthesized one. We let Windows figure out the virtual key and - // character for the given scancode, as well as a new timestamp. - const KEYBDINPUT* key_event = this->FindPendingEvent(id); - if (key_event == nullptr) { - std::cerr << "Unable to find event " << id << " in pending events queue."; - return; - } - -// As described in the header, the user32 SendInput function is not supported in -// UWP appcontainer and there is no WinRT equivalent hence we pass null for -// SendInputDelegate param. Since this handler is one of last resort, it is -// only applicable for platformview scenarios where the host view can handle -// input events in the event the Flutter view does not choose to handle them. -// Since platformview is currently not support for desktop, there is no -// functional gap caused by this currently. -#ifndef WINUWP - INPUT input_event; - input_event.type = INPUT_KEYBOARD; - input_event.ki = *key_event; - UINT accepted = send_input_(1, &input_event, sizeof(input_event)); - if (accepted != 1) { - std::cerr << "Unable to synthesize event for unhandled keyboard event " - "with scancode " - << scancode << " (character " << character << ")" << std::endl; - } -#endif - } -} - -bool KeyEventHandler::KeyboardHook(FlutterWindowsView* view, - int key, - int scancode, - int action, - char32_t character, - bool extended) { - const uint64_t id = CalculateEventId(scancode, action, extended); - if (FindPendingEvent(id) != nullptr) { - // Don't pass messages that we synthesized to the framework again. - RemovePendingEvent(id); - return false; - } - - // TODO: Translate to a cross-platform key code system rather than passing - // the native key code. - rapidjson::Document event(rapidjson::kObjectType); - auto& allocator = event.GetAllocator(); - event.AddMember(kKeyCodeKey, key, allocator); - event.AddMember(kScanCodeKey, scancode, allocator); - event.AddMember(kCharacterCodePointKey, character, allocator); - event.AddMember(kKeyMapKey, kWindowsKeyMap, allocator); -#ifndef WINUWP - event.AddMember(kModifiersKey, GetModsForKeyState(), allocator); -#else - // TODO: Implement modifiers in UWP codepath - // TODO: https://github.com/flutter/flutter/issues/70202 - event.AddMember(kModifiersKey, 0, allocator); -#endif - - switch (action) { - case WM_KEYDOWN: - event.AddMember(kTypeKey, kKeyDown, allocator); - break; - case WM_KEYUP: - event.AddMember(kTypeKey, kKeyUp, allocator); - break; - default: - std::cerr << "Unknown key event action: " << action << std::endl; - return false; - } - AddPendingEvent(id, scancode, action, extended); - channel_->Send(event, [this, id, action, extended, scancode, character]( - const uint8_t* reply, size_t reply_size) { - auto decoded = flutter::JsonMessageCodec::GetInstance().DecodeMessage( - reply, reply_size); - bool handled = (*decoded)[kHandledKey].GetBool(); - this->HandleResponse(handled, id, action, extended, scancode, character); - }); - return true; -} - -void KeyEventHandler::ComposeBeginHook() { - // Ignore. -} - -void KeyEventHandler::ComposeEndHook() { - // Ignore. -} - -void KeyEventHandler::ComposeChangeHook(const std::u16string& text, - int cursor_pos) { - // Ignore. -} - -} // namespace flutter diff --git a/shell/platform/windows/key_event_handler.h b/shell/platform/windows/key_event_handler.h deleted file mode 100644 index 7c4df0f038fc6..0000000000000 --- a/shell/platform/windows/key_event_handler.h +++ /dev/null @@ -1,93 +0,0 @@ -// 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. - -#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_KEY_EVENT_HANDLER_H_ -#define FLUTTER_SHELL_PLATFORM_WINDOWS_KEY_EVENT_HANDLER_H_ - -#include -#include -#include - -#include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h" -#include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" -#include "flutter/shell/platform/windows/keyboard_hook_handler.h" -#include "flutter/shell/platform/windows/public/flutter_windows.h" -#include "rapidjson/document.h" - -namespace flutter { - -class FlutterWindowsView; - -// Implements a KeyboardHookHandler -// -// Handles key events and forwards them to the Flutter engine. -class KeyEventHandler : public KeyboardHookHandler { - public: - using SendInputDelegate = - std::function; - -// the user32 SendInput function is not supported in UWP appcontainer and there -// is no WinRT equivalent hence we pass null for SendInputDelegate param. Since -// this handler is one of last resort, it is only applicable for platformview -// scenarios where the host view can handle input events in the event the -// Flutter view does not choose to handle them. Since platformview is currently -// not support for desktop, there is no functional gap caused by this currently. -#ifdef WINUWP - explicit KeyEventHandler(flutter::BinaryMessenger* messenger, - SendInputDelegate delegate = nullptr); -#else - explicit KeyEventHandler(flutter::BinaryMessenger* messenger, - SendInputDelegate delegate = SendInput); -#endif - - virtual ~KeyEventHandler(); - - // |KeyboardHookHandler| - bool KeyboardHook(FlutterWindowsView* window, - int key, - int scancode, - int action, - char32_t character, - bool extended) override; - - // |KeyboardHookHandler| - void TextHook(FlutterWindowsView* window, - const std::u16string& text) override; - - // |KeyboardHookHandler| - void ComposeBeginHook() override; - - // |KeyboardHookHandler| - void ComposeEndHook() override; - - // |KeyboardHookHandler| - void ComposeChangeHook(const std::u16string& text, int cursor_pos) override; - - private: - KEYBDINPUT* FindPendingEvent(uint64_t id); - void RemovePendingEvent(uint64_t id); - void AddPendingEvent(uint64_t id, int scancode, int action, bool extended); - void HandleResponse(bool handled, - uint64_t id, - int action, - bool extended, - int scancode, - int character); - - // The Flutter system channel for key event messages. - std::unique_ptr> channel_; - - // The queue of key events that have been sent to the framework but have not - // yet received a response. - std::deque> pending_events_; - - // A function used to dispatch synthesized events. Used in testing to inject a - // test function to collect events. Defaults to the Windows function - // SendInput. - SendInputDelegate send_input_; -}; - -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_KEY_EVENT_HANDLER_H_ diff --git a/shell/platform/windows/keyboard_hook_handler.h b/shell/platform/windows/keyboard_handler_base.h similarity index 79% rename from shell/platform/windows/keyboard_hook_handler.h rename to shell/platform/windows/keyboard_handler_base.h index 527a83687f6dc..44e91bc29c492 100644 --- a/shell/platform/windows/keyboard_hook_handler.h +++ b/shell/platform/windows/keyboard_handler_base.h @@ -13,10 +13,15 @@ namespace flutter { class FlutterWindowsView; -// Abstract class for handling keyboard input events. -class KeyboardHookHandler { +// Interface for classes that handles keyboard input events. +// +// Keyboard handlers are added to |FlutterWindowsView| in a chain. +// When a key event arrives, |KeyboardHook| is called on each handler +// until the first one that returns true. Then the proper text hooks +// are called on each handler. +class KeyboardHandlerBase { public: - virtual ~KeyboardHookHandler() = default; + virtual ~KeyboardHandlerBase() = default; // A function for hooking into keyboard input. // @@ -27,7 +32,8 @@ class KeyboardHookHandler { int scancode, int action, char32_t character, - bool extended) = 0; + bool extended, + bool was_down) = 0; // A function for hooking into Unicode text input. virtual void TextHook(FlutterWindowsView* view, diff --git a/shell/platform/windows/keyboard_key_channel_handler.cc b/shell/platform/windows/keyboard_key_channel_handler.cc new file mode 100644 index 0000000000000..a5b0790b98ad4 --- /dev/null +++ b/shell/platform/windows/keyboard_key_channel_handler.cc @@ -0,0 +1,142 @@ +// 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_channel_handler.h" + +#include + +#include + +#include "flutter/shell/platform/common/json_message_codec.h" + +namespace flutter { + +namespace { + +static constexpr char kChannelName[] = "flutter/keyevent"; + +static constexpr char kKeyCodeKey[] = "keyCode"; +static constexpr char kScanCodeKey[] = "scanCode"; +static constexpr char kCharacterCodePointKey[] = "characterCodePoint"; +static constexpr char kModifiersKey[] = "modifiers"; +static constexpr char kKeyMapKey[] = "keymap"; +static constexpr char kTypeKey[] = "type"; +static constexpr char kHandledKey[] = "handled"; + +static constexpr char kWindowsKeyMap[] = "windows"; +static constexpr char kKeyUp[] = "keyup"; +static constexpr char kKeyDown[] = "keydown"; + +// The maximum number of pending events to keep before +// emitting a warning on the console about unhandled events. +static constexpr int kMaxPendingEvents = 1000; + +// Re-definition of the modifiers for compatibility with the Flutter framework. +// These have to be in sync with the framework's RawKeyEventDataWindows +// modifiers definition. +// https://github.com/flutter/flutter/blob/19ff596979e407c484a32f4071420fca4f4c885f/packages/flutter/lib/src/services/raw_keyboard_windows.dart#L203 +static constexpr int kShift = 1 << 0; +static constexpr int kShiftLeft = 1 << 1; +static constexpr int kShiftRight = 1 << 2; +static constexpr int kControl = 1 << 3; +static constexpr int kControlLeft = 1 << 4; +static constexpr int kControlRight = 1 << 5; +static constexpr int kAlt = 1 << 6; +static constexpr int kAltLeft = 1 << 7; +static constexpr int kAltRight = 1 << 8; +static constexpr int kWinLeft = 1 << 9; +static constexpr int kWinRight = 1 << 10; +static constexpr int kCapsLock = 1 << 11; +static constexpr int kNumLock = 1 << 12; +static constexpr int kScrollLock = 1 << 13; + +/// Calls GetKeyState() an all modifier keys and packs the result in an int, +/// with the re-defined values declared above for compatibility with the Flutter +/// framework. +int GetModsForKeyState() { + int mods = 0; + + if (GetKeyState(VK_SHIFT) < 0) + mods |= kShift; + if (GetKeyState(VK_LSHIFT) < 0) + mods |= kShiftLeft; + if (GetKeyState(VK_RSHIFT) < 0) + mods |= kShiftRight; + if (GetKeyState(VK_CONTROL) < 0) + mods |= kControl; + if (GetKeyState(VK_LCONTROL) < 0) + mods |= kControlLeft; + if (GetKeyState(VK_RCONTROL) < 0) + mods |= kControlRight; + if (GetKeyState(VK_MENU) < 0) + mods |= kAlt; + if (GetKeyState(VK_LMENU) < 0) + mods |= kAltLeft; + if (GetKeyState(VK_RMENU) < 0) + mods |= kAltRight; + if (GetKeyState(VK_LWIN) < 0) + mods |= kWinLeft; + if (GetKeyState(VK_RWIN) < 0) + mods |= kWinRight; + if (GetKeyState(VK_CAPITAL) < 0) + mods |= kCapsLock; + if (GetKeyState(VK_NUMLOCK) < 0) + mods |= kNumLock; + if (GetKeyState(VK_SCROLL) < 0) + mods |= kScrollLock; + return mods; +} + +} // namespace + +KeyboardKeyChannelHandler::KeyboardKeyChannelHandler( + flutter::BinaryMessenger* messenger) + : channel_( + std::make_unique>( + messenger, + kChannelName, + &flutter::JsonMessageCodec::GetInstance())) {} + +KeyboardKeyChannelHandler::~KeyboardKeyChannelHandler() = default; + +void KeyboardKeyChannelHandler::KeyboardHook( + int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down, + std::function callback) { + // TODO: Translate to a cross-platform key code system rather than passing + // the native key code. + rapidjson::Document event(rapidjson::kObjectType); + auto& allocator = event.GetAllocator(); + event.AddMember(kKeyCodeKey, key, allocator); + event.AddMember(kScanCodeKey, scancode, allocator); + event.AddMember(kCharacterCodePointKey, character, allocator); + event.AddMember(kKeyMapKey, kWindowsKeyMap, allocator); + event.AddMember(kModifiersKey, GetModsForKeyState(), allocator); + + switch (action) { + case WM_KEYDOWN: + event.AddMember(kTypeKey, kKeyDown, allocator); + break; + case WM_KEYUP: + event.AddMember(kTypeKey, kKeyUp, allocator); + break; + default: + std::cerr << "Unknown key event action: " << action << std::endl; + callback(false); + return; + } + channel_->Send(event, [callback = std::move(callback)](const uint8_t* reply, + size_t reply_size) { + auto decoded = flutter::JsonMessageCodec::GetInstance().DecodeMessage( + reply, reply_size); + bool handled = (*decoded)[kHandledKey].GetBool(); + callback(handled); + }); +} + +} // namespace flutter diff --git a/shell/platform/windows/keyboard_key_channel_handler.h b/shell/platform/windows/keyboard_key_channel_handler.h new file mode 100644 index 0000000000000..ff9aa1a8cf4b6 --- /dev/null +++ b/shell/platform/windows/keyboard_key_channel_handler.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_CHANNEL_HANDLER_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_CHANNEL_HANDLER_H_ + +#include +#include +#include + +#include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/windows/keyboard_key_handler.h" +#include "rapidjson/document.h" + +namespace flutter { + +// A delegate of |KeyboardKeyHandler| that handles events by sending the +// raw information through the method channel. +// +// This class corresponds to the RawKeyboard API in the framework. +class KeyboardKeyChannelHandler + : public KeyboardKeyHandler::KeyboardKeyHandlerDelegate { + public: + // Create a |KeyboardKeyChannelHandler| by specifying the messenger + // through which the events are sent. + explicit KeyboardKeyChannelHandler(flutter::BinaryMessenger* messenger); + + ~KeyboardKeyChannelHandler(); + + // |KeyboardKeyHandler::KeyboardKeyHandlerDelegate| + void KeyboardHook(int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down, + std::function callback); + + private: + // The Flutter system channel for key event messages. + std::unique_ptr> channel_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_CHANNEL_HANDLER_H_ diff --git a/shell/platform/windows/key_event_handler_unittests.cc b/shell/platform/windows/keyboard_key_channel_handler_unittests.cc similarity index 62% rename from shell/platform/windows/key_event_handler_unittests.cc rename to shell/platform/windows/keyboard_key_channel_handler_unittests.cc index 8698e20f2d6b7..c967596322b62 100644 --- a/shell/platform/windows/key_event_handler_unittests.cc +++ b/shell/platform/windows/keyboard_key_channel_handler_unittests.cc @@ -1,121 +1,111 @@ -// 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/key_event_handler.h" - -#include -#include - -#include "flutter/shell/platform/common/json_message_codec.h" -#include "flutter/shell/platform/windows/flutter_windows_view.h" -#include "flutter/shell/platform/windows/testing/test_binary_messenger.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace flutter { -namespace testing { - -namespace { -static constexpr char kScanCodeKey[] = "scanCode"; -static constexpr int kHandledScanCode = 20; -static constexpr int kUnhandledScanCode = 21; - -static std::unique_ptr> CreateResponse(bool handled) { - auto response_doc = - std::make_unique(rapidjson::kObjectType); - auto& allocator = response_doc->GetAllocator(); - response_doc->AddMember("handled", handled, allocator); - return JsonMessageCodec::GetInstance().EncodeMessage(*response_doc); -} - -} // namespace - -TEST(KeyEventHandlerTest, KeyboardHookHandling) { - auto handled_message = CreateResponse(true); - auto unhandled_message = CreateResponse(false); - int received_scancode = 0; - - TestBinaryMessenger messenger( - [&received_scancode, &handled_message, &unhandled_message]( - const std::string& channel, const uint8_t* message, - size_t message_size, BinaryReply reply) { - if (channel == "flutter/keyevent") { - auto message_doc = JsonMessageCodec::GetInstance().DecodeMessage( - message, message_size); - received_scancode = (*message_doc)[kScanCodeKey].GetInt(); - if (received_scancode == kHandledScanCode) { - reply(handled_message->data(), handled_message->size()); - } else { - reply(unhandled_message->data(), unhandled_message->size()); - } - } - }); - - int redispatch_scancode = 0; - KeyEventHandler handler(&messenger, - [&redispatch_scancode](UINT cInputs, LPINPUT pInputs, - int cbSize) -> UINT { - EXPECT_TRUE(cbSize > 0); - redispatch_scancode = pInputs->ki.wScan; - return 1; - }); - - handler.KeyboardHook(nullptr, 64, kHandledScanCode, WM_KEYDOWN, L'a', false); - EXPECT_EQ(received_scancode, kHandledScanCode); - EXPECT_EQ(redispatch_scancode, 0); - received_scancode = 0; - handler.KeyboardHook(nullptr, 64, kUnhandledScanCode, WM_KEYDOWN, L'b', - false); - EXPECT_EQ(received_scancode, kUnhandledScanCode); - EXPECT_EQ(redispatch_scancode, kUnhandledScanCode); -} - -TEST(KeyEventHandlerTest, ExtendedKeysAreSentToRedispatch) { - auto handled_message = CreateResponse(true); - auto unhandled_message = CreateResponse(false); - int received_scancode = 0; - bool is_extended_key = false; - - TestBinaryMessenger messenger( - [&received_scancode, &handled_message, &unhandled_message]( - const std::string& channel, const uint8_t* message, - size_t message_size, BinaryReply reply) { - if (channel == "flutter/keyevent") { - auto message_doc = JsonMessageCodec::GetInstance().DecodeMessage( - message, message_size); - received_scancode = (*message_doc)[kScanCodeKey].GetInt(); - if (received_scancode == kHandledScanCode) { - reply(handled_message->data(), handled_message->size()); - } else { - reply(unhandled_message->data(), unhandled_message->size()); - } - } - }); - - int redispatch_scancode = 0; - KeyEventHandler handler( - &messenger, - [&redispatch_scancode, &is_extended_key](UINT cInputs, LPINPUT pInputs, - int cbSize) -> UINT { - EXPECT_TRUE(cbSize > 0); - redispatch_scancode = pInputs->ki.wScan; - is_extended_key = (pInputs->ki.dwFlags & KEYEVENTF_EXTENDEDKEY) != 0; - return 1; - }); - - // Extended key flag is passed to redispatched events if set. - handler.KeyboardHook(nullptr, 64, kUnhandledScanCode, WM_KEYDOWN, L'b', true); - EXPECT_EQ(received_scancode, kUnhandledScanCode); - EXPECT_EQ(redispatch_scancode, kUnhandledScanCode); - EXPECT_EQ(is_extended_key, true); - - // Extended key flag is not passed to redispatched events if not set. - handler.KeyboardHook(nullptr, 64, kUnhandledScanCode, WM_KEYDOWN, L'b', - false); - EXPECT_EQ(received_scancode, kUnhandledScanCode); - EXPECT_EQ(redispatch_scancode, kUnhandledScanCode); - EXPECT_EQ(is_extended_key, false); -} - -} // namespace testing -} // namespace flutter +// 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_channel_handler.h" + +#include + +#include "flutter/shell/platform/common/json_message_codec.h" +#include "flutter/shell/platform/windows/flutter_windows_view.h" +#include "flutter/shell/platform/windows/testing/test_binary_messenger.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +namespace { +static constexpr char kScanCodeKey[] = "scanCode"; +static constexpr int kHandledScanCode = 20; +static constexpr int kUnhandledScanCode = 21; + +static std::unique_ptr> CreateResponse(bool handled) { + auto response_doc = + std::make_unique(rapidjson::kObjectType); + auto& allocator = response_doc->GetAllocator(); + response_doc->AddMember("handled", handled, allocator); + return JsonMessageCodec::GetInstance().EncodeMessage(*response_doc); +} +} // namespace + +TEST(KeyboardKeyChannelHandlerTest, KeyboardHookHandling) { + auto handled_message = CreateResponse(true); + auto unhandled_message = CreateResponse(false); + int received_scancode = 0; + + TestBinaryMessenger messenger( + [&received_scancode, &handled_message, &unhandled_message]( + const std::string& channel, const uint8_t* message, + size_t message_size, BinaryReply reply) { + if (channel == "flutter/keyevent") { + auto message_doc = JsonMessageCodec::GetInstance().DecodeMessage( + message, message_size); + received_scancode = (*message_doc)[kScanCodeKey].GetInt(); + if (received_scancode == kHandledScanCode) { + reply(handled_message->data(), handled_message->size()); + } else { + reply(unhandled_message->data(), unhandled_message->size()); + } + } + }); + + KeyboardKeyChannelHandler handler(&messenger); + bool last_handled = false; + + handler.KeyboardHook( + 64, kHandledScanCode, WM_KEYDOWN, L'a', false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(received_scancode, kHandledScanCode); + EXPECT_EQ(last_handled, true); + + received_scancode = 0; + + handler.KeyboardHook( + 64, kUnhandledScanCode, WM_KEYDOWN, L'b', false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(received_scancode, kUnhandledScanCode); + EXPECT_EQ(last_handled, false); +} + +TEST(KeyboardKeyChannelHandlerTest, ExtendedKeysAreSentToRedispatch) { + auto handled_message = CreateResponse(true); + auto unhandled_message = CreateResponse(false); + int received_scancode = 0; + + TestBinaryMessenger messenger( + [&received_scancode, &handled_message, &unhandled_message]( + const std::string& channel, const uint8_t* message, + size_t message_size, BinaryReply reply) { + if (channel == "flutter/keyevent") { + auto message_doc = JsonMessageCodec::GetInstance().DecodeMessage( + message, message_size); + received_scancode = (*message_doc)[kScanCodeKey].GetInt(); + if (received_scancode == kHandledScanCode) { + reply(handled_message->data(), handled_message->size()); + } else { + reply(unhandled_message->data(), unhandled_message->size()); + } + } + }); + + KeyboardKeyChannelHandler handler(&messenger); + bool last_handled = true; + + // Extended key flag is passed to redispatched events if set. + handler.KeyboardHook( + 64, kUnhandledScanCode, WM_KEYDOWN, L'b', true, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(received_scancode, kUnhandledScanCode); + + last_handled = true; + // Extended key flag is not passed to redispatched events if not set. + handler.KeyboardHook( + 64, kUnhandledScanCode, WM_KEYDOWN, L'b', false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(received_scancode, kUnhandledScanCode); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc new file mode 100644 index 0000000000000..ad34793ca2b40 --- /dev/null +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -0,0 +1,408 @@ +// 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_embedder_handler.h" + +#include +#include + +#include +#include +#include +#include + +#include "flutter/shell/platform/windows/string_conversion.h" + +namespace flutter { + +namespace { +// An arbitrary size for the character cache in bytes. +// +// It should hold a UTF-32 character encoded in UTF-8 as well as the trailing +// '\0'. +constexpr size_t kCharacterCacheSize = 8; + +/** + * The code prefix for keys which do not have a Unicode representation. + * + * This is used by platform-specific code to generate Flutter key codes using + * HID Usage codes. + */ +constexpr uint64_t kHidPlane = 0x00100000000; + +/** + * The code prefix for keys which have a Unicode representation. + * + * This is used by platform-specific code to generate Flutter key codes. + */ +constexpr uint64_t kUnicodePlane = 0x00000000000; + +/** + */ +constexpr uint64_t kWindowsKeyIdPlane = 0x00700000000; + +/** + * Mask for the auto-generated bit portion of the key code. + * + * This is used by platform-specific code to generate new Flutter key codes + * for keys which are not recognized. + */ +constexpr uint64_t kAutogeneratedMask = 0x10000000000; + +constexpr SHORT kStateMaskToggled = 0x01; +constexpr SHORT kStateMaskPressed = 0x80; + +} // namespace + +KeyboardKeyEmbedderHandler::KeyboardKeyEmbedderHandler( + std::function send_event, + GetKeyStateHandler get_key_state) + : sendEvent_(send_event), get_key_state_(get_key_state), response_id_(1) { + InitCriticalKeys(); +} + +KeyboardKeyEmbedderHandler::~KeyboardKeyEmbedderHandler() = default; + +static bool isAsciiPrintable(int codeUnit) { + return codeUnit <= 0x7f && codeUnit >= 0x20; +} + +static bool isControlCharacter(int codeUnit) { + return (codeUnit <= 0x1f && codeUnit >= 0x00) || + (codeUnit >= 0x7f && codeUnit <= 0x9f); +} + +// Transform scancodes sent by windows to scancodes written in Chromium spec. +static uint16_t normalizeScancode(int windowsScanCode, bool extended) { + // In Chromium spec the extended bit is shown as 0xe000 bit, + // e.g. PageUp is represented as 0xe049. + return (windowsScanCode & 0xff) | (extended ? 0xe000 : 0); +} + +uint64_t KeyboardKeyEmbedderHandler::getPhysicalKey(int scancode, + bool extended) { + int chromiumScancode = normalizeScancode(scancode, extended); + auto resultIt = windowsToPhysicalMap_.find(chromiumScancode); + if (resultIt != windowsToPhysicalMap_.end()) + return resultIt->second; + return scancode | kHidPlane; +} + +uint64_t KeyboardKeyEmbedderHandler::getLogicalKey(int key, + bool extended, + int scancode) { + // Normally logical keys should only be derived from key codes, but since some + // key codes are either 0 or ambiguous (multiple keys using the same key + // code), these keys are resolved by scan codes. + auto numpadIter = + scanCodeToLogicalMap_.find(normalizeScancode(scancode, extended)); + if (numpadIter != scanCodeToLogicalMap_.cend()) + return numpadIter->second; + + // Check if the keyCode is one we know about and have a mapping for. + auto logicalIt = windowsToLogicalMap_.find(key); + if (logicalIt != windowsToLogicalMap_.cend()) + return logicalIt->second; + + // Upper case letters should be normalized into lower case letters. + if (isAsciiPrintable(key)) { + if (isupper(key)) { + return tolower(key); + } + return key; + } + + // For keys that do not exist in the map, if it has a non-control-character + // label, then construct a new Unicode-based key from it. Don't mark it as + // autogenerated, since the label uniquely identifies an ID from the Unicode + // plane. + if (!isControlCharacter(key)) { + return key | kUnicodePlane; + } else { + // This is a non-printable key that we don't know about, so we mint a new + // code with the autogenerated bit set. + return key | kWindowsKeyIdPlane | kAutogeneratedMask; + } +} + +void KeyboardKeyEmbedderHandler::KeyboardHook( + int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down, + std::function callback) { + const uint64_t physical_key = getPhysicalKey(scancode, extended); + const uint64_t logical_key = getLogicalKey(key, extended, scancode); + assert(action == WM_KEYDOWN || action == WM_KEYUP); + const bool is_physical_down = action == WM_KEYDOWN; + + auto last_logical_record_iter = pressingRecords_.find(physical_key); + const bool had_record = last_logical_record_iter != pressingRecords_.end(); + const uint64_t last_logical_record = + had_record ? last_logical_record_iter->second : 0; + + // The resulting event's `type`. + FlutterKeyEventType type; + // The resulting event's `logical_key`. + uint64_t result_logical_key; + // The next value of pressingRecords_[physical_key] (or to remove it). + uint64_t next_logical_record; + bool next_has_record = true; + char character_bytes[kCharacterCacheSize]; + + if (is_physical_down) { + if (had_record) { + if (was_down) { + // A normal repeated key. + type = kFlutterKeyEventTypeRepeat; + assert(had_record); + ConvertUtf32ToUtf8_(character_bytes, character); + next_logical_record = last_logical_record; + result_logical_key = last_logical_record; + } else { + // A non-repeated key has been pressed that has the exact physical key + // as a currently pressed one, usually indicating multiple keyboards are + // pressing keys with the same physical key, or the up event was lost + // during a loss of focus. The down event is ignored. + callback(true); + return; + } + } else { + // A normal down event (whether the system event is a repeat or not). + type = kFlutterKeyEventTypeDown; + assert(!had_record); + ConvertUtf32ToUtf8_(character_bytes, character); + next_logical_record = logical_key; + result_logical_key = logical_key; + } + } else { // isPhysicalDown is false + if (last_logical_record == 0) { + // The physical key has been released before. It might indicate a missed + // event due to loss of focus, or multiple keyboards pressed keys with the + // same physical key. Ignore the up event. + callback(true); + return; + } else { + // A normal up event. + type = kFlutterKeyEventTypeUp; + assert(had_record); + // Up events never have character. + character_bytes[0] = '\0'; + next_has_record = false; + result_logical_key = last_logical_record; + } + } + + UpdateLastSeenCritialKey(key, physical_key, result_logical_key); + SynchronizeCritialToggledStates(type == kFlutterKeyEventTypeDown ? key : 0); + + if (next_has_record) { + pressingRecords_[physical_key] = next_logical_record; + } else { + pressingRecords_.erase(last_logical_record_iter); + } + + SynchronizeCritialPressedStates(); + + if (result_logical_key == VK_PROCESSKEY) { + // VK_PROCESSKEY means that the key press is used by an IME. These key + // presses are considered handled and not sent to Flutter. These events must + // be filtered by result_logical_key because the key up event of such + // presses uses the "original" logical key. + callback(true); + return; + } + + FlutterKeyEvent key_data{ + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = static_cast( + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count()), + .type = type, + .physical = physical_key, + .logical = result_logical_key, + .character = character_bytes, + .synthesized = false, + }; + + response_id_ += 1; + uint64_t response_id = response_id_; + PendingResponse pending{ + .callback = + [this, callback = std::move(callback)](bool handled, + uint64_t reponse_id) { + auto found = pending_responses_.find(reponse_id); + if (found != pending_responses_.end()) { + pending_responses_.erase(found); + } + callback(handled); + }, + .response_id = response_id, + }; + auto pending_ptr = std::make_unique(std::move(pending)); + pending_responses_[response_id] = std::move(pending_ptr); + sendEvent_(key_data, KeyboardKeyEmbedderHandler::HandleResponse, + reinterpret_cast(pending_responses_[response_id].get())); +} + +void KeyboardKeyEmbedderHandler::UpdateLastSeenCritialKey( + int virtual_key, + uint64_t physical_key, + uint64_t logical_key) { + auto found = critical_keys_.find(virtual_key); + if (found != critical_keys_.end()) { + found->second.physical_key = physical_key; + found->second.logical_key = logical_key; + } +} + +void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( + int toggle_virtual_key) { + for (auto& kv : critical_keys_) { + UINT virtual_key = kv.first; + CriticalKey& key_info = kv.second; + if (key_info.physical_key == 0) { + // Never seen this key. + continue; + } + assert(key_info.logical_key != 0); + SHORT state = get_key_state_(virtual_key); + + // Check toggling state first, because it might alter pressing state. + if (key_info.check_toggled) { + bool should_toggled = state & kStateMaskToggled; + if (virtual_key == toggle_virtual_key) { + key_info.toggled_on = !key_info.toggled_on; + } + if (key_info.toggled_on != should_toggled) { + const char* empty_character = ""; + // If the key is pressed, release it first. + if (pressingRecords_.find(key_info.physical_key) != + pressingRecords_.end()) { + sendEvent_(SynthesizeSimpleEvent( + kFlutterKeyEventTypeUp, key_info.physical_key, + key_info.logical_key, empty_character), + nullptr, nullptr); + } else { + // This key will always be pressed in the following synthesized event. + pressingRecords_[key_info.physical_key] = key_info.logical_key; + } + sendEvent_(SynthesizeSimpleEvent(kFlutterKeyEventTypeDown, + key_info.physical_key, + key_info.logical_key, empty_character), + nullptr, nullptr); + } + key_info.toggled_on = should_toggled; + } + } +} + +void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates() { + for (auto& kv : critical_keys_) { + UINT virtual_key = kv.first; + CriticalKey& key_info = kv.second; + if (key_info.physical_key == 0) { + // Never seen this key. + continue; + } + assert(key_info.logical_key != 0); + SHORT state = get_key_state_(virtual_key); + if (key_info.check_pressed) { + auto recorded_pressed_iter = pressingRecords_.find(key_info.physical_key); + bool recorded_pressed = recorded_pressed_iter != pressingRecords_.end(); + bool should_pressed = state & kStateMaskPressed; + if (recorded_pressed != should_pressed) { + if (should_pressed) { + pressingRecords_[key_info.physical_key] = key_info.logical_key; + } else { + pressingRecords_.erase(recorded_pressed_iter); + } + const char* empty_character = ""; + sendEvent_( + SynthesizeSimpleEvent(should_pressed ? kFlutterKeyEventTypeDown + : kFlutterKeyEventTypeUp, + key_info.physical_key, key_info.logical_key, + empty_character), + nullptr, nullptr); + } + } + } +} + +void KeyboardKeyEmbedderHandler::HandleResponse(bool handled, void* user_data) { + PendingResponse* pending = reinterpret_cast(user_data); + auto callback = std::move(pending->callback); + callback(handled, pending->response_id); +} + +void KeyboardKeyEmbedderHandler::InitCriticalKeys() { + auto createCheckedKey = [this](UINT virtual_key, bool extended, + bool check_pressed, + bool check_toggled) -> CriticalKey { + UINT scan_code = MapVirtualKey(virtual_key, MAPVK_VK_TO_VSC); + return CriticalKey{ + .physical_key = getPhysicalKey(scan_code, extended), + .logical_key = getLogicalKey(virtual_key, extended, scan_code), + .check_pressed = check_pressed || check_toggled, + .check_toggled = check_toggled, + .toggled_on = check_toggled + ? !!(get_key_state_(virtual_key) & kStateMaskToggled) + : false, + }; + }; + + // TODO(dkwingsmt): Consider adding more critical keys here. + // https://github.com/flutter/flutter/issues/76736 + critical_keys_.emplace(VK_LSHIFT, + createCheckedKey(VK_LSHIFT, false, true, false)); + critical_keys_.emplace(VK_RSHIFT, + createCheckedKey(VK_RSHIFT, false, true, false)); + critical_keys_.emplace(VK_LCONTROL, + createCheckedKey(VK_LCONTROL, false, true, false)); + critical_keys_.emplace(VK_RCONTROL, + createCheckedKey(VK_RCONTROL, true, true, false)); + + critical_keys_.emplace(VK_CAPITAL, + createCheckedKey(VK_CAPITAL, false, true, true)); + critical_keys_.emplace(VK_SCROLL, + createCheckedKey(VK_SCROLL, false, true, true)); + critical_keys_.emplace(VK_NUMLOCK, + createCheckedKey(VK_NUMLOCK, true, true, true)); +} + +void KeyboardKeyEmbedderHandler::ConvertUtf32ToUtf8_(char* out, char32_t ch) { + if (ch == 0) { + out[0] = '\0'; + return; + } + // TODO: Correctly handle UTF-32 + std::wstring text({static_cast(ch)}); + strcpy_s(out, kCharacterCacheSize, Utf8FromUtf16(text).c_str()); +} + +FlutterKeyEvent KeyboardKeyEmbedderHandler::SynthesizeSimpleEvent( + FlutterKeyEventType type, + uint64_t physical, + uint64_t logical, + const char* character) { + return FlutterKeyEvent{ + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = static_cast( + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count()), + .type = type, + .physical = physical, + .logical = logical, + .character = character, + .synthesized = true, + }; +} + +} // namespace flutter diff --git a/shell/platform/windows/keyboard_key_embedder_handler.h b/shell/platform/windows/keyboard_key_embedder_handler.h new file mode 100644 index 0000000000000..3dc875349856c --- /dev/null +++ b/shell/platform/windows/keyboard_key_embedder_handler.h @@ -0,0 +1,131 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_EMBEDDER_HANDLER_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_EMBEDDER_HANDLER_H_ + +#include +#include +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/keyboard_key_handler.h" + +namespace flutter { + +namespace {} // namespace + +// A delegate of |KeyboardKeyHandler| that handles events by sending +// converted |FlutterKeyEvent|s through the embedder API. +// +// This class corresponds to the HardwareKeyboard API in the framework. +class KeyboardKeyEmbedderHandler + : public KeyboardKeyHandler::KeyboardKeyHandlerDelegate { + public: + using SendEvent = std::function; + using GetKeyStateHandler = std::function; + + // Build a KeyboardKeyEmbedderHandler. + // + // Use `send_event` to define how the class should dispatch converted + // flutter events, as well as how to receive the response, to the engine. It's + // typically FlutterWindowsEngine::SendKeyEvent. The 2nd and 3rd parameter + // of the SendEvent call might be nullptr. + // + // Use `get_key_state` to define how the class should get a reliable result of + // the state for a virtual key. It's typically Win32's GetKeyState. + explicit KeyboardKeyEmbedderHandler(SendEvent send_event, + GetKeyStateHandler get_key_state); + + virtual ~KeyboardKeyEmbedderHandler(); + + // |KeyboardHandlerBase| + void KeyboardHook(int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down, + std::function callback) override; + + private: + struct PendingResponse { + std::function callback; + uint64_t response_id; + }; + + // The information for a virtual key that's important enough that its + // state is checked after every event. + struct CriticalKey { + // Last seen value of physical key and logical key for the virtual key. + // + // Used to synthesize down events. + uint64_t physical_key; + uint64_t logical_key; + + // Whether to ensure the pressing state of the key (usually for modifier + // keys). + bool check_pressed; + // Whether to ensure the toggled state of the key (usually for lock keys). + // + // If this is true, `check_pressed` must be true. + bool check_toggled; + // Whether the lock key is currently toggled on. + bool toggled_on; + }; + + // Assign |critical_keys_| with basic information. + void InitCriticalKeys(); + // Update |critical_keys_| with last seen logical and physical key. + void UpdateLastSeenCritialKey(int virtual_key, + uint64_t physical_key, + uint64_t logical_key); + // Check each key's state from |get_key_state_| and synthesize events + // if their toggling states have been desynchronized. + void SynchronizeCritialToggledStates(int this_virtual_key); + // Check each key's state from |get_key_state_| and synthesize events + // if their pressing states have been desynchronized. + void SynchronizeCritialPressedStates(); + + std::function + sendEvent_; + GetKeyStateHandler get_key_state_; + + // A map from physical keys to logical keys, each entry indicating a pressed + // key. + std::map pressingRecords_; + // Information for key events that have been sent to the framework but yet + // to receive the response. Indexed by response IDs. + std::map> pending_responses_; + // A self-incrementing integer, used as the ID for the next entry for + // |pending_responses_|. + uint64_t response_id_; + + // Important keys whose states are checked and guaranteed synchronized + // on every key event. + // + // The following maps map Win32 virtual key to the physical key and logical + // key they're last seen. + std::map critical_keys_; + + static uint64_t getPhysicalKey(int scancode, bool extended); + static uint64_t getLogicalKey(int key, bool extended, int scancode); + static void HandleResponse(bool handled, void* user_data); + static void ConvertUtf32ToUtf8_(char* out, char32_t ch); + static FlutterKeyEvent SynthesizeSimpleEvent(FlutterKeyEventType type, + uint64_t physical, + uint64_t logical, + const char* character); + + static std::map windowsToPhysicalMap_; + static std::map windowsToLogicalMap_; + static std::map scanCodeToLogicalMap_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_EMBEDDER_HANDLER_H_ diff --git a/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc b/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc new file mode 100644 index 0000000000000..57a11e48e9792 --- /dev/null +++ b/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc @@ -0,0 +1,841 @@ +// 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_embedder_handler.h" + +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" +#include "flutter/shell/platform/windows/testing/engine_embedder_api_modifier.h" +#include "gtest/gtest.h" + +namespace flutter { + +namespace { + +constexpr SHORT kStateMaskToggled = 0x01; +constexpr SHORT kStateMaskPressed = 0x80; + +class TestFlutterKeyEvent : public FlutterKeyEvent { + public: + TestFlutterKeyEvent(const FlutterKeyEvent& src, + FlutterKeyEventCallback callback, + void* user_data) + : character_str(src.character), callback(callback), user_data(user_data) { + struct_size = src.struct_size; + timestamp = src.timestamp; + type = src.type; + physical = src.physical; + logical = src.logical; + character = character_str.c_str(); + synthesized = src.synthesized; + } + + TestFlutterKeyEvent(TestFlutterKeyEvent&& source) + : FlutterKeyEvent(source), + callback(std::move(source.callback)), + user_data(source.user_data) { + character = character_str.c_str(); + } + + FlutterKeyEventCallback callback; + void* user_data; + + private: + const std::string character_str; +}; + +class TestKeystate { + public: + void Set(int virtual_key, bool pressed, bool toggled_on = false) { + state_[virtual_key] = (pressed ? kStateMaskPressed : 0) | + (toggled_on ? kStateMaskToggled : 0); + } + + SHORT Get(int virtual_key) { return state_[virtual_key]; } + + KeyboardKeyEmbedderHandler::GetKeyStateHandler Getter() { + return [this](int virtual_key) { return Get(virtual_key); }; + } + + private: + std::map state_; +}; + +} // namespace + +namespace testing { + +namespace { +constexpr uint64_t kScanCodeKeyA = 0x1e; +constexpr uint64_t kScanCodeNumpad1 = 0x4f; +constexpr uint64_t kScanCodeNumLock = 0x45; +constexpr uint64_t kScanCodeControl = 0x1d; +constexpr uint64_t kScanCodeShiftLeft = 0x2a; +constexpr uint64_t kScanCodeShiftRight = 0x36; + +constexpr uint64_t kVirtualKeyA = 0x41; + +constexpr uint64_t kPhysicalKeyA = 0x00070004; +constexpr uint64_t kPhysicalControlLeft = 0x000700e0; +constexpr uint64_t kPhysicalControlRight = 0x000700e4; +constexpr uint64_t kPhysicalShiftLeft = 0x000700e1; +constexpr uint64_t kPhysicalShiftRight = 0x000700e5; +constexpr uint64_t kPhysicalKeyNumLock = 0x00070053; + +constexpr uint64_t kLogicalKeyA = 0x00000061; +constexpr uint64_t kLogicalControlLeft = 0x00300000105; +constexpr uint64_t kLogicalControlRight = 0x00400000105; +constexpr uint64_t kLogicalShiftLeft = 0x0030000010d; +constexpr uint64_t kLogicalShiftRight = 0x0040000010d; +constexpr uint64_t kLogicalKeyNumLock = 0x0000000010a; +} // namespace + +// Test the most basic key events. +// +// Press, hold, and release key A on an US keyboard. +TEST(KeyboardKeyEmbedderHandlerTest, BasicKeyPressingAndHolding) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + + // Press KeyA. + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + key_state.Set(kVirtualKeyA, true); + + // Hold KeyA. + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, true); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeRepeat); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + event->callback(false, event->user_data); + EXPECT_EQ(last_handled, false); + results.clear(); + + // Release KeyA. + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYUP, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + event->callback(false, event->user_data); +} + +// Press numpad 1, toggle NumLock, and release numpad 1 on an US +// keyboard. +// +// This is special because the virtual key for numpad 1 will +// change in this process. +TEST(KeyboardKeyEmbedderHandlerTest, ToggleNumLockDuringNumpadPress) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + + // Press NumPad1. + key_state.Set(VK_NUMPAD1, true); + handler->KeyboardHook( + VK_NUMPAD1, kScanCodeNumpad1, WM_KEYDOWN, 0, false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, 0x00070059); + EXPECT_EQ(event->logical, 0x00200000031); + // EXPECT_STREQ(event->character, "1"); // TODO + EXPECT_EQ(event->synthesized, false); + results.clear(); + + // Press NumLock. + key_state.Set(VK_NUMLOCK, true, true); + handler->KeyboardHook( + VK_NUMLOCK, kScanCodeNumLock, WM_KEYDOWN, 0, true, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, 0x00070053); + EXPECT_EQ(event->logical, 0x0000010a); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + results.clear(); + + // Release NumLock. + key_state.Set(VK_NUMLOCK, false, true); + handler->KeyboardHook( + VK_NUMLOCK, kScanCodeNumLock, WM_KEYUP, 0, true, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, 0x00070053); + EXPECT_EQ(event->logical, 0x0000010a); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + results.clear(); + + // Release NumPad1. (The logical key is now NumpadEnd) + handler->KeyboardHook( + VK_END, kScanCodeNumpad1, WM_KEYUP, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, 0x00070059); + EXPECT_EQ(event->logical, 0x00200000031); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + results.clear(); +} + +// Key presses that trigger IME should be ignored by this API (and handled by +// compose API). +TEST(KeyboardKeyEmbedderHandlerTest, ImeEventsAreIgnored) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + + // Press A in an IME + last_handled = false; + handler->KeyboardHook( + VK_PROCESSKEY, kScanCodeKeyA, WM_KEYDOWN, 0, true, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, true); + + last_handled = false; + handler->KeyboardHook( + // The up event for an IME press has a normal virtual key. + kVirtualKeyA, kScanCodeKeyA, WM_KEYUP, 0, true, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, true); + + // The entire A press does not yield events. + EXPECT_EQ(results.size(), 0); + + // Press A out of an IME + key_state.Set(kVirtualKeyA, true); + last_handled = false; + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 0, true, false, + [&last_handled](bool handled) { last_handled = handled; }); + // Not decided yet + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + + last_handled = false; + key_state.Set(kVirtualKeyA, false); + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYUP, 0, true, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); +} + +// Test if modifier keys that are told apart by the extended bit +// can be identified. +TEST(KeyboardKeyEmbedderHandlerTest, ModifierKeysByExtendedBit) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + + // Press Ctrl left. + last_handled = false; + key_state.Set(VK_LCONTROL, true); + handler->KeyboardHook( + VK_CONTROL, kScanCodeControl, WM_KEYDOWN, 0, false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalControlLeft); + EXPECT_EQ(event->logical, kLogicalControlLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + + // Press Ctrl right. + last_handled = false; + key_state.Set(VK_RCONTROL, true); + handler->KeyboardHook( + VK_CONTROL, kScanCodeControl, WM_KEYDOWN, 0, true, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalControlRight); + EXPECT_EQ(event->logical, kLogicalControlRight); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + + // Release Ctrl left. + last_handled = false; + key_state.Set(VK_LCONTROL, false); + handler->KeyboardHook( + VK_CONTROL, kScanCodeControl, WM_KEYUP, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalControlLeft); + EXPECT_EQ(event->logical, kLogicalControlLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + + // Release Ctrl right. + last_handled = false; + key_state.Set(VK_RCONTROL, false); + handler->KeyboardHook( + VK_CONTROL, kScanCodeControl, WM_KEYUP, 0, true, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalControlRight); + EXPECT_EQ(event->logical, kLogicalControlRight); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); +} + +// Test if modifier keys that are told apart by the virtual key +// can be identified. +TEST(KeyboardKeyEmbedderHandlerTest, ModifierKeysByVirtualKey) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + + // Press Shift left. + last_handled = false; + key_state.Set(VK_LSHIFT, true); + handler->KeyboardHook( + VK_LSHIFT, kScanCodeShiftLeft, WM_KEYDOWN, 0, false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + + // Press Shift right. + last_handled = false; + key_state.Set(VK_RSHIFT, true); + handler->KeyboardHook( + VK_RSHIFT, kScanCodeShiftRight, WM_KEYDOWN, 0, false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftRight); + EXPECT_EQ(event->logical, kLogicalShiftRight); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + + // Release Shift left. + last_handled = false; + key_state.Set(VK_LSHIFT, false); + handler->KeyboardHook( + VK_LSHIFT, kScanCodeShiftLeft, WM_KEYUP, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + + // Release Shift right. + last_handled = false; + key_state.Set(VK_RSHIFT, false); + handler->KeyboardHook( + VK_RSHIFT, kScanCodeShiftRight, WM_KEYUP, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftRight); + EXPECT_EQ(event->logical, kLogicalShiftRight); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); +} + +TEST(KeyboardKeyEmbedderHandlerTest, AbruptRepeatIsConvertedtoDown) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + last_handled = false; + + key_state.Set(kVirtualKeyA, true); + + // Press A (with was_down true) + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + + // Release A + last_handled = false; + key_state.Set(kVirtualKeyA, false); + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYUP, 'a', false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); +} + +TEST(KeyboardKeyEmbedderHandlerTest, SynthesizeForDesyncPressingState) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + + // A key down of control left is missed. + key_state.Set(VK_LCONTROL, true); + + // Send a normal event + key_state.Set(kVirtualKeyA, true); + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 2); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalControlLeft); + EXPECT_EQ(event->logical, kLogicalControlLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + EXPECT_EQ(event->callback, nullptr); + + event = &results[1]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + last_handled = true; + event->callback(false, event->user_data); + EXPECT_EQ(last_handled, false); + results.clear(); + + // A key down of control right is missed. + key_state.Set(VK_LCONTROL, false); + + // Hold KeyA. + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 2); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalControlLeft); + EXPECT_EQ(event->logical, kLogicalControlLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + EXPECT_EQ(event->callback, nullptr); + + event = &results[1]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeRepeat); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + last_handled = true; + event->callback(false, event->user_data); + EXPECT_EQ(last_handled, false); + results.clear(); + + // Release KeyA. + key_state.Set(kVirtualKeyA, false); + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYUP, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + event->callback(false, event->user_data); +} + +TEST(KeyboardKeyEmbedderHandlerTest, SynthesizeForDesyncToggledState) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + + // The NumLock is desynchronized by toggled on + key_state.Set(VK_NUMLOCK, false, true); + + // Send a normal event + key_state.Set(kVirtualKeyA, true); + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 3); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + + event = &results[1]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + + event = &results[2]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); + + // Test if the NumLock is mis-toggled while it should also be pressed + key_state.Set(VK_NUMLOCK, true, true); + + // Send a normal event + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, true); + EXPECT_EQ(results.size(), 2); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + + event = &results[1]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeRepeat); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + event->callback(false, event->user_data); + EXPECT_EQ(last_handled, false); + results.clear(); + + // Numlock is pressed at this moment. + + // Test if the NumLock is mis-toggled while it should also be released + key_state.Set(VK_NUMLOCK, false, false); + + // Send a normal event + key_state.Set(kVirtualKeyA, false); + handler->KeyboardHook( + kVirtualKeyA, kScanCodeKeyA, WM_KEYUP, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(results.size(), 4); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + EXPECT_EQ(event->callback, nullptr); + + event = &results[1]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + EXPECT_EQ(event->callback, nullptr); + + event = &results[2]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + EXPECT_EQ(event->callback, nullptr); + + event = &results[3]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + event->callback(false, event->user_data); +} + +TEST(KeyboardKeyEmbedderHandlerTest, SynthesizeForDesyncToggledStateByItself) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + + // When NumLock is down + key_state.Set(VK_NUMLOCK, true, true); + handler->KeyboardHook( + VK_NUMLOCK, kScanCodeNumLock, WM_KEYDOWN, 0, true, false, + [&last_handled](bool handled) { last_handled = handled; }); + event = &results.back(); + event->callback(false, event->user_data); + results.clear(); + + // Numlock is desynchronized by being off and released + key_state.Set(VK_NUMLOCK, false, false); + // Send a NumLock key up + handler->KeyboardHook( + VK_NUMLOCK, kScanCodeNumLock, WM_KEYUP, 0, true, true, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 3); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + EXPECT_EQ(event->callback, nullptr); + + event = &results[1]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + EXPECT_EQ(event->callback, nullptr); + + event = &results[2]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + last_handled = false; + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); +} + +TEST(KeyboardKeyEmbedderHandlerTest, SynthesizeWithInitialTogglingState) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + // The app starts with NumLock toggled on + key_state.Set(VK_NUMLOCK, false, true); + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter()); + + // NumLock key down + key_state.Set(VK_NUMLOCK, true, false); + handler->KeyboardHook( + VK_NUMLOCK, kScanCodeNumLock, WM_KEYDOWN, 0, true, false, + [&last_handled](bool handled) { last_handled = handled; }); + EXPECT_EQ(last_handled, false); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyNumLock); + EXPECT_EQ(event->logical, kLogicalKeyNumLock); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, false); + + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); + results.clear(); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc new file mode 100644 index 0000000000000..d21a9f9577324 --- /dev/null +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -0,0 +1,196 @@ +// 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 + +#include + +#include "flutter/shell/platform/common/json_message_codec.h" + +namespace flutter { + +namespace { + +static constexpr char kChannelName[] = "flutter/keyevent"; + +static constexpr char kKeyCodeKey[] = "keyCode"; +static constexpr char kScanCodeKey[] = "scanCode"; +static constexpr char kCharacterCodePointKey[] = "characterCodePoint"; +static constexpr char kModifiersKey[] = "modifiers"; +static constexpr char kKeyMapKey[] = "keymap"; +static constexpr char kTypeKey[] = "type"; +static constexpr char kHandledKey[] = "handled"; + +static constexpr char kWindowsKeyMap[] = "windows"; +static constexpr char kKeyUp[] = "keyup"; +static constexpr char kKeyDown[] = "keydown"; + +// The maximum number of pending events to keep before +// emitting a warning on the console about unhandled events. +static constexpr int kMaxPendingEvents = 1000; + +} // namespace + +KeyboardKeyHandler::KeyboardKeyHandlerDelegate::~KeyboardKeyHandlerDelegate() = + default; + +KeyboardKeyHandler::KeyboardKeyHandler(EventRedispatcher redispatch_event) + : redispatch_event_(redispatch_event), last_sequence_id_(1) { + assert(redispatch_event_ != nullptr); +} + +KeyboardKeyHandler::~KeyboardKeyHandler() = default; + +void KeyboardKeyHandler::TextHook(FlutterWindowsView* view, + const std::u16string& code_point) {} + +void KeyboardKeyHandler::AddDelegate( + std::unique_ptr delegate) { + delegates_.push_back(std::move(delegate)); +} + +size_t KeyboardKeyHandler::RedispatchedCount() { + return pending_redispatches_.size(); +} + +void KeyboardKeyHandler::RedispatchEvent(std::unique_ptr event) { + uint8_t scancode = event->scancode; + char32_t character = event->character; + + INPUT input_event{ + .type = INPUT_KEYBOARD, + .ki = + KEYBDINPUT{ + .wVk = 0, + .wScan = static_cast(event->scancode), + .dwFlags = static_cast( + KEYEVENTF_SCANCODE | + (event->extended ? KEYEVENTF_EXTENDEDKEY : 0x0) | + (event->action == WM_KEYUP ? KEYEVENTF_KEYUP : 0x0)), + }, + }; + + pending_redispatches_.push_back(std::move(event)); + + UINT accepted = redispatch_event_(1, &input_event, sizeof(input_event)); + if (accepted != 1) { + std::cerr << "Unable to synthesize event for unhandled keyboard event " + "with scancode " + << scancode << " (character " << character << ")" << std::endl; + } +} + +bool KeyboardKeyHandler::KeyboardHook(FlutterWindowsView* view, + int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down) { + std::unique_ptr incoming = + std::make_unique(PendingEvent{ + .key = static_cast(key), + .scancode = static_cast(scancode), + .action = static_cast(action), + .character = character, + .extended = extended, + .was_down = was_down, + }); + incoming->hash = ComputeEventHash(*incoming); + + if (RemoveRedispatchedEvent(*incoming)) { + return false; + } + + uint64_t sequence_id = ++last_sequence_id_; + incoming->sequence_id = sequence_id; + incoming->unreplied = delegates_.size(); + incoming->any_handled = false; + + if (pending_responds_.size() > kMaxPendingEvents) { + std::cerr + << "There are " << pending_responds_.size() + << " keyboard events that have not yet received a response from the " + << "framework. Are responses being sent?" << std::endl; + } + pending_responds_.push_back(std::move(incoming)); + + for (const auto& delegate : delegates_) { + delegate->KeyboardHook(key, scancode, action, character, extended, was_down, + [sequence_id, this](bool handled) { + ResolvePendingEvent(sequence_id, handled); + }); + } + + // |ResolvePendingEvent| might trigger redispatching synchronously, + // which might occur before |KeyboardHook| is returned. This won't + // make events out of order though, because |KeyboardHook| will always + // return true at this time, preventing this event from affecting + // others. + + return true; +} + +bool KeyboardKeyHandler::RemoveRedispatchedEvent(const PendingEvent& incoming) { + for (auto iter = pending_redispatches_.begin(); + iter != pending_redispatches_.end(); ++iter) { + if ((*iter)->hash == incoming.hash) { + pending_redispatches_.erase(iter); + return true; + } + } + return false; + ; +} + +void KeyboardKeyHandler::ResolvePendingEvent(uint64_t sequence_id, + bool handled) { + // Find the pending event + for (auto iter = pending_responds_.begin(); iter != pending_responds_.end(); + ++iter) { + if ((*iter)->sequence_id == sequence_id) { + PendingEvent& event = **iter; + event.any_handled = event.any_handled || handled; + event.unreplied -= 1; + assert(event.unreplied >= 0); + // If all delegates have replied, redispatch if no one handled. + if (event.unreplied == 0) { + auto event_ptr = std::move(*iter); + pending_responds_.erase(iter); + if (!event_ptr->any_handled) { + RedispatchEvent(std::move(event_ptr)); + } + } + // Return here; |iter| can't do ++ after erase. + return; + } + } + // The pending event should always be found. + assert(false); +} + +void KeyboardKeyHandler::ComposeBeginHook() { + // Ignore. +} + +void KeyboardKeyHandler::ComposeEndHook() { + // Ignore. +} + +void KeyboardKeyHandler::ComposeChangeHook(const std::u16string& text, + int cursor_pos) { + // Ignore. +} + +uint64_t KeyboardKeyHandler::ComputeEventHash(const PendingEvent& event) { + // Calculate a key event ID based on the scan code of the key pressed, + // and the flags we care about. + return event.scancode | (((event.action == WM_KEYUP ? KEYEVENTF_KEYUP : 0x0) | + (event.extended ? KEYEVENTF_EXTENDEDKEY : 0x0)) + << 16); +} + +} // namespace flutter diff --git a/shell/platform/windows/keyboard_key_handler.h b/shell/platform/windows/keyboard_key_handler.h new file mode 100644 index 0000000000000..b7856b55e0571 --- /dev/null +++ b/shell/platform/windows/keyboard_key_handler.h @@ -0,0 +1,183 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_HANDLER_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_HANDLER_H_ + +#include +#include +#include + +#include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/windows/keyboard_handler_base.h" +#include "flutter/shell/platform/windows/public/flutter_windows.h" +#include "rapidjson/document.h" + +namespace flutter { + +class FlutterWindowsView; + +// Handles key events. +// +// This class detects whether an incoming event is a redispatched one, +// dispatches native events to delegates and collect their responses, +// and redispatches events unhandled by Flutter back to the system. +// See |KeyboardHook| for more information about dispatching. +// +// This class owns multiple |KeyboardKeyHandlerDelegate|s, which +// implements the exact behavior to asynchronously handle events. In +// reality, this design is only to support sending events through +// "channel" (RawKeyEvent) and "embedder" (KeyEvent) simultaneously, +// the former of which shall be removed after the deprecation window +// of the RawKeyEvent system. +class KeyboardKeyHandler : public KeyboardHandlerBase { + public: + // An interface for concrete definition of how to asynchronously handle key + // events. + class KeyboardKeyHandlerDelegate { + public: + // Defines how to how to asynchronously handle key events. + // + // |KeyboardHook| should invoke |callback| with the response (whether the + // event is handled) later for exactly once. + virtual void KeyboardHook(int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down, + std::function callback) = 0; + + virtual ~KeyboardKeyHandlerDelegate(); + }; + + using EventRedispatcher = + std::function; + + // Create a KeyboardKeyHandler and specify where to redispatch events. + // + // The |redispatch_event| is typically |SendInput|. + explicit KeyboardKeyHandler(EventRedispatcher redispatch_event); + + ~KeyboardKeyHandler(); + + // Add a delegate that handles events received by |KeyboardHook|. + void AddDelegate(std::unique_ptr delegate); + + // Handles a key event. + // + // Returns whether this handler claims to handle the event, which is true if + // and only if the event is a non-synthesized event. + // + // Windows requires a synchronous response of whether a key event should be + // handled, while the query to Flutter is always asynchronous. This is + // resolved by the "redispatching" algorithm: by default, the response to a + // fresh event is always always true. The event is then sent to the framework. + // If the framework later decides not to handle the event, this class will + // create an identical event and dispatch it to the system, and remember all + // synthesized events. The fist time an exact event (by |ComputeEventHash|) is + // received in the future, the new event is considered a synthesized one, + // causing |KeyboardHook| to return false to fall back to other keyboard + // handlers. + // + // Whether a non-synthesized event is considered handled by the framework is + // decided by dispatching the event to all delegates, simultaneously, + // unconditionally, in insertion order, and collecting their responses later. + // It's not supported to prevent any delegates to process the events, because + // in reality this will only support 2 hardcoded delegates, and only to + // continut supporting the legacy API (channel) during the deprecation window, + // after which the channel delegate should be removed. + // + // Inherited from |KeyboardHandlerBase|. + bool KeyboardHook(FlutterWindowsView* window, + int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down) override; + + // |KeyboardHandlerBase| + void TextHook(FlutterWindowsView* window, + const std::u16string& text) override; + + // |KeyboardHandlerBase| + void ComposeBeginHook() override; + + // |KeyboardHandlerBase| + void ComposeEndHook() override; + + // |KeyboardHandlerBase| + void ComposeChangeHook(const std::u16string& text, int cursor_pos) override; + + protected: + size_t RedispatchedCount(); + + private: + struct PendingEvent { + uint32_t key; + uint8_t scancode; + uint32_t action; + char32_t character; + bool extended; + bool was_down; + + // Self-incrementing ID attached to an event sent to the framework. + uint64_t sequence_id; + // The number of delegates that haven't replied. + size_t unreplied; + // Whether any replied delegates reported true (handled). + bool any_handled; + + // A value calculated out of critical event information that can be used + // to identify redispatched events. + uint64_t hash; + }; + + // Find an event in the redispatch list that matches the given one. + // + // If an matching event is found, removes the matching event from the + // redispatch list, and returns true. Otherwise, returns false; + bool RemoveRedispatchedEvent(const PendingEvent& incoming); + void RedispatchEvent(std::unique_ptr event); + void ResolvePendingEvent(uint64_t sequence_id, bool handled); + + std::vector> delegates_; + + // The queue of key events that have been sent to the framework but have not + // yet received a response. + std::deque> pending_responds_; + + // The queue of key events that have been redispatched to the system but have + // not yet been received for a second time. + std::deque> pending_redispatches_; + + // The sequence_id attached to the last event sent to the framework. + uint64_t last_sequence_id_; + + // The callback used to redispatch synthesized events. + EventRedispatcher redispatch_event_; + + // Calculate a hash based on event data for fast comparison for a redispatched + // event. + // + // This uses event data instead of generating a serial number because + // information can't be attached to the redispatched events, so it has to be + // possible to compute an ID from the identifying data in the event when it is + // received again in order to differentiate between events that are new, and + // events that have been redispatched. + // + // Another alternative would be to compute a checksum from all the data in the + // event (just compute it over the bytes in the struct, probably skipping + // timestamps), but the fields used are enough to differentiate them, and + // since Windows does some processing on the events (coming up with virtual + // key codes, setting timestamps, etc.), it's not clear that the redispatched + // events would have the same checksums. + static uint64_t ComputeEventHash(const PendingEvent& event); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_HANDLER_H_ diff --git a/shell/platform/windows/keyboard_key_handler_unittests.cc b/shell/platform/windows/keyboard_key_handler_unittests.cc new file mode 100644 index 0000000000000..22e6244b3f0fb --- /dev/null +++ b/shell/platform/windows/keyboard_key_handler_unittests.cc @@ -0,0 +1,411 @@ +// 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 +#include + +#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; + +typedef std::function Callback; +typedef std::function 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 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* 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 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* hook_history; +}; + +class TestKeyboardKeyHandler : public KeyboardKeyHandler { + public: + explicit TestKeyboardKeyHandler(EventRedispatcher redispatch_event) + : KeyboardKeyHandler(redispatch_event) {} + + bool HasRedispatched() { return RedispatchedCount() > 0; } +}; + +} // namespace + +TEST(KeyboardKeyHandlerTest, SingleDelegateWithAsyncResponds) { + std::list 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(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 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(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 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(1, &hook_history); + CallbackHandler& delegate1_handler = delegate1->callback_handler; + handler.AddDelegate(std::move(delegate1)); + + auto delegate2 = std::make_unique(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 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(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(); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/testing/mock_win32_window.h b/shell/platform/windows/testing/mock_win32_window.h index 08417e144990f..0ae73465148c7 100644 --- a/shell/platform/windows/testing/mock_win32_window.h +++ b/shell/platform/windows/testing/mock_win32_window.h @@ -39,11 +39,12 @@ class MockWin32Window : public Win32Window { MOCK_METHOD0(OnPointerLeave, void()); MOCK_METHOD0(OnSetCursor, void()); MOCK_METHOD1(OnText, void(const std::u16string&)); - MOCK_METHOD5(OnKey, bool(int, int, int, char32_t, bool)); + MOCK_METHOD6(OnKey, bool(int, int, int, char32_t, bool, bool)); MOCK_METHOD2(OnScroll, void(double, double)); MOCK_METHOD0(OnComposeBegin, void()); MOCK_METHOD0(OnComposeEnd, void()); MOCK_METHOD2(OnComposeChange, void(const std::u16string&, int)); + MOCK_METHOD4(DefaultWindowProc, LRESULT(HWND, UINT, WPARAM, LPARAM)); }; } // namespace testing diff --git a/shell/platform/windows/text_input_plugin.cc b/shell/platform/windows/text_input_plugin.cc index 96d846e81160f..49d5f7f0e46dc 100644 --- a/shell/platform/windows/text_input_plugin.cc +++ b/shell/platform/windows/text_input_plugin.cc @@ -66,7 +66,8 @@ bool TextInputPlugin::KeyboardHook(FlutterWindowsView* view, int scancode, int action, char32_t character, - bool extended) { + bool extended, + bool was_down) { if (active_model_ == nullptr) { return false; } diff --git a/shell/platform/windows/text_input_plugin.h b/shell/platform/windows/text_input_plugin.h index 41ca4b96ab7c7..b091d12d1bba5 100644 --- a/shell/platform/windows/text_input_plugin.h +++ b/shell/platform/windows/text_input_plugin.h @@ -14,7 +14,7 @@ #include "flutter/shell/platform/common/geometry.h" #include "flutter/shell/platform/common/json_method_codec.h" #include "flutter/shell/platform/common/text_input_model.h" -#include "flutter/shell/platform/windows/keyboard_hook_handler.h" +#include "flutter/shell/platform/windows/keyboard_handler_base.h" #include "flutter/shell/platform/windows/public/flutter_windows.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" @@ -25,31 +25,32 @@ class FlutterWindowsView; // Implements a text input plugin. // // Specifically handles window events within windows. -class TextInputPlugin : public KeyboardHookHandler { +class TextInputPlugin : public KeyboardHandlerBase { public: explicit TextInputPlugin(flutter::BinaryMessenger* messenger, TextInputPluginDelegate* delegate); virtual ~TextInputPlugin(); - // |KeyboardHookHandler| + // |KeyboardHandlerBase| bool KeyboardHook(FlutterWindowsView* view, int key, int scancode, int action, char32_t character, - bool extended) override; + bool extended, + bool was_down) override; - // |KeyboardHookHandler| + // |KeyboardHandlerBase| void TextHook(FlutterWindowsView* view, const std::u16string& text) override; - // |KeyboardHookHandler| + // |KeyboardHandlerBase| void ComposeBeginHook() override; - // |KeyboardHookHandler| + // |KeyboardHandlerBase| void ComposeEndHook() override; - // |KeyboardHookHandler| + // |KeyboardHandlerBase| void ComposeChangeHook(const std::u16string& text, int cursor_pos) override; private: diff --git a/shell/platform/windows/text_input_plugin_unittest.cc b/shell/platform/windows/text_input_plugin_unittest.cc index fe3c356b8fef0..aac581e50b4f3 100644 --- a/shell/platform/windows/text_input_plugin_unittest.cc +++ b/shell/platform/windows/text_input_plugin_unittest.cc @@ -49,7 +49,7 @@ TEST(TextInputPluginTest, TextMethodsWorksWithEmptyModel) { int redispatch_scancode = 0; TextInputPlugin handler(&messenger, &delegate); - handler.KeyboardHook(nullptr, VK_RETURN, 100, WM_KEYDOWN, '\n', false); + handler.KeyboardHook(nullptr, VK_RETURN, 100, WM_KEYDOWN, '\n', false, false); handler.ComposeBeginHook(); std::u16string text; text.push_back('\n'); diff --git a/shell/platform/windows/win32_flutter_window.cc b/shell/platform/windows/win32_flutter_window.cc index ba4e3880dad1d..727b2dd154ae7 100644 --- a/shell/platform/windows/win32_flutter_window.cc +++ b/shell/platform/windows/win32_flutter_window.cc @@ -166,9 +166,10 @@ bool Win32FlutterWindow::OnKey(int key, int scancode, int action, char32_t character, - bool extended) { + bool extended, + bool was_down) { return binding_handler_delegate_->OnKey(key, scancode, action, character, - extended); + extended, was_down); } void Win32FlutterWindow::OnComposeBegin() { diff --git a/shell/platform/windows/win32_flutter_window.h b/shell/platform/windows/win32_flutter_window.h index 25096279d24f5..b6456fe6ca9c4 100644 --- a/shell/platform/windows/win32_flutter_window.h +++ b/shell/platform/windows/win32_flutter_window.h @@ -59,7 +59,8 @@ class Win32FlutterWindow : public Win32Window, public WindowBindingHandler { int scancode, int action, char32_t character, - bool extended) override; + bool extended, + bool was_down) override; // |Win32Window| void OnComposeBegin() override; diff --git a/shell/platform/windows/win32_flutter_window_unittests.cc b/shell/platform/windows/win32_flutter_window_unittests.cc index 0131d5f3d0cc2..39ed090049c25 100644 --- a/shell/platform/windows/win32_flutter_window_unittests.cc +++ b/shell/platform/windows/win32_flutter_window_unittests.cc @@ -6,9 +6,12 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/windows/flutter_windows_engine.h" +#include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" +#include "flutter/shell/platform/windows/keyboard_key_handler.h" #include "flutter/shell/platform/windows/testing/engine_embedder_api_modifier.h" #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" #include "flutter/shell/platform/windows/testing/win32_flutter_window_test.h" +#include "flutter/shell/platform/windows/text_input_plugin.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" #include "gmock/gmock.h" @@ -25,15 +28,15 @@ namespace testing { namespace { // Creates a valid Windows LPARAM for WM_KEYDOWN and WM_CHAR from parameters // given. -static LPARAM CreateKeyEventLparam(USHORT ScanCode, +static LPARAM CreateKeyEventLparam(USHORT scancode, bool extended = false, - USHORT RepeatCount = 1, - bool ContextCode = 0, - bool PreviousKeyState = 1, - bool TransitionState = 1) { - return ((LPARAM(TransitionState) << 31) | (LPARAM(PreviousKeyState) << 30) | - (LPARAM(ContextCode) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | - (LPARAM(ScanCode) << 16) | LPARAM(RepeatCount)); + bool was_down = 1, + USHORT repeat_count = 1, + bool context_code = 0, + bool transition_state = 1) { + return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) | + (LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | + (LPARAM(scancode) << 16) | LPARAM(repeat_count)); } // A struc to hold simulated events that will be delivered after the framework @@ -46,27 +49,31 @@ struct SimulatedEvent { // A key event handler that can be spied on while it forwards calls to the real // key event handler. -class SpyKeyEventHandler : public KeyboardHookHandler { +class SpyKeyboardKeyHandler : public KeyboardHandlerBase { public: - SpyKeyEventHandler(flutter::BinaryMessenger* messenger, - KeyEventHandler::SendInputDelegate delegate) { + SpyKeyboardKeyHandler( + flutter::BinaryMessenger* messenger, + KeyboardKeyHandler::EventRedispatcher redispatch_event) { real_implementation_ = - std::make_unique(messenger, delegate); - ON_CALL(*this, KeyboardHook(_, _, _, _, _, _)) - .WillByDefault( - Invoke(real_implementation_.get(), &KeyEventHandler::KeyboardHook)); + std::make_unique(redispatch_event); + real_implementation_->AddDelegate( + std::make_unique(messenger)); + ON_CALL(*this, KeyboardHook(_, _, _, _, _, _, _)) + .WillByDefault(Invoke(real_implementation_.get(), + &KeyboardKeyHandler::KeyboardHook)); ON_CALL(*this, TextHook(_, _)) .WillByDefault( - Invoke(real_implementation_.get(), &KeyEventHandler::TextHook)); + Invoke(real_implementation_.get(), &KeyboardKeyHandler::TextHook)); } - MOCK_METHOD6(KeyboardHook, + MOCK_METHOD7(KeyboardHook, bool(FlutterWindowsView* window, int key, int scancode, int action, char32_t character, - bool extended)); + bool extended, + bool was_down)); MOCK_METHOD2(TextHook, void(FlutterWindowsView* window, const std::u16string& text)); MOCK_METHOD0(ComposeBeginHook, void()); @@ -75,17 +82,17 @@ class SpyKeyEventHandler : public KeyboardHookHandler { void(const std::u16string& text, int cursor_pos)); private: - std::unique_ptr real_implementation_; + std::unique_ptr real_implementation_; }; // A text input plugin that can be spied on while it forwards calls to the real // text input plugin. -class SpyTextInputPlugin : public KeyboardHookHandler, +class SpyTextInputPlugin : public KeyboardHandlerBase, public TextInputPluginDelegate { public: SpyTextInputPlugin(flutter::BinaryMessenger* messenger) { real_implementation_ = std::make_unique(messenger, this); - ON_CALL(*this, KeyboardHook(_, _, _, _, _, _)) + ON_CALL(*this, KeyboardHook(_, _, _, _, _, _, _)) .WillByDefault( Invoke(real_implementation_.get(), &TextInputPlugin::KeyboardHook)); ON_CALL(*this, TextHook(_, _)) @@ -93,13 +100,14 @@ class SpyTextInputPlugin : public KeyboardHookHandler, Invoke(real_implementation_.get(), &TextInputPlugin::TextHook)); } - MOCK_METHOD6(KeyboardHook, + MOCK_METHOD7(KeyboardHook, bool(FlutterWindowsView* window, int key, int scancode, int action, char32_t character, - bool extended)); + bool extended, + bool was_down)); MOCK_METHOD2(TextHook, void(FlutterWindowsView* window, const std::u16string& text)); MOCK_METHOD0(ComposeBeginHook, void()); @@ -140,9 +148,10 @@ class MockWin32FlutterWindow : public Win32FlutterWindow { MOCK_METHOD0(OnPointerLeave, void()); MOCK_METHOD0(OnSetCursor, void()); MOCK_METHOD2(OnScroll, void(double, double)); + MOCK_METHOD4(DefaultWindowProc, LRESULT(HWND, UINT, WPARAM, LPARAM)); }; -// A FlutterWindowsView that overrides the RegisterKeyboardHookHandlers function +// A FlutterWindowsView that overrides the RegisterKeyboardHandlers function // to register the keyboard hook handlers that can be spied upon. class TestFlutterWindowsView : public FlutterWindowsView { public: @@ -153,22 +162,21 @@ class TestFlutterWindowsView : public FlutterWindowsView { virtual_key_(virtual_key), is_printable_(is_printable) {} - SpyKeyEventHandler* key_event_handler; + SpyKeyboardKeyHandler* key_event_handler; SpyTextInputPlugin* text_input_plugin; void InjectPendingEvents(MockWin32FlutterWindow* win32window) { - while (pending_events_.size() > 0) { - SimulatedEvent event = pending_events_.front(); + while (pending_responds_.size() > 0) { + SimulatedEvent event = pending_responds_.front(); win32window->InjectWindowMessage(event.message, event.wparam, event.lparam); - pending_events_.pop_front(); + pending_responds_.pop_front(); } } protected: - void RegisterKeyboardHookHandlers( - flutter::BinaryMessenger* messenger) override { - auto spy_key_event_handler = std::make_unique( + void RegisterKeyboardHandlers(flutter::BinaryMessenger* messenger) override { + auto spy_key_event_handler = std::make_unique( messenger, [this](UINT cInputs, LPINPUT pInputs, int cbSize) -> UINT { return this->SendInput(cInputs, pInputs, cbSize); }); @@ -176,8 +184,8 @@ class TestFlutterWindowsView : public FlutterWindowsView { std::make_unique(messenger); key_event_handler = spy_key_event_handler.get(); text_input_plugin = spy_text_input_plugin.get(); - AddKeyboardHookHandler(std::move(spy_key_event_handler)); - AddKeyboardHookHandler(std::move(spy_text_input_plugin)); + AddKeyboardHandler(std::move(spy_key_event_handler)); + AddKeyboardHandler(std::move(spy_text_input_plugin)); } private: @@ -193,14 +201,15 @@ class TestFlutterWindowsView : public FlutterWindowsView { // simulate it for the test with the key we know is in the test. The // KBDINPUT we're passed doesn't have it filled in (on purpose, so that // Windows will fill it in). - pending_events_.push_back(SimulatedEvent{message, virtual_key_, lparam}); + pending_responds_.push_back(SimulatedEvent{message, virtual_key_, lparam}); if (is_printable_ && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { - pending_events_.push_back(SimulatedEvent{WM_CHAR, virtual_key_, lparam}); + pending_responds_.push_back( + SimulatedEvent{WM_CHAR, virtual_key_, lparam}); } return 1; } - std::deque pending_events_; + std::deque pending_responds_; WPARAM virtual_key_; bool is_printable_; }; @@ -284,6 +293,8 @@ TEST(Win32FlutterWindowTest, CreateDestroy) { // Tests key event propagation of non-printable, non-modifier key down events. TEST(Win32FlutterWindowTest, NonPrintableKeyDownPropagation) { + ::testing::InSequence in_sequence; + constexpr WPARAM virtual_key = VK_LEFT; constexpr WPARAM scan_code = 10; constexpr char32_t character = 0; @@ -294,7 +305,8 @@ TEST(Win32FlutterWindowTest, NonPrintableKeyDownPropagation) { TestFlutterWindowsView flutter_windows_view( std::move(window_binding_handler), virtual_key, false /* is_printable */); win32window.SetView(&flutter_windows_view); - LPARAM lparam = CreateKeyEventLparam(scan_code); + LPARAM lparam = CreateKeyEventLparam(scan_code, false /* extended */, + false /* PrevState */); // Test an event not handled by the framework { @@ -302,13 +314,16 @@ TEST(Win32FlutterWindowTest, NonPrintableKeyDownPropagation) { 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 /* extended */, _)) .Times(2) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, - KeyboardHook(_, _, _, _, _, _)) + KeyboardHook(_, _, _, _, _, _, _)) .Times(1) .RetiresOnSaturation(); + EXPECT_CALL(win32window, DefaultWindowProc(_, _, _, _)) + .Times(0) + .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _)) .Times(0); EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _)) @@ -323,11 +338,11 @@ TEST(Win32FlutterWindowTest, NonPrintableKeyDownPropagation) { test_response = true; EXPECT_CALL(*flutter_windows_view.key_event_handler, KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character, - false /* extended */)) + false /* extended */, false /* PrevState */)) .Times(1) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, - KeyboardHook(_, _, _, _, _, _)) + KeyboardHook(_, _, _, _, _, _, _)) .Times(0); EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam), 0); @@ -339,6 +354,8 @@ TEST(Win32FlutterWindowTest, NonPrintableKeyDownPropagation) { // 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(Win32FlutterWindowTest, 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; @@ -349,19 +366,20 @@ TEST(Win32FlutterWindowTest, CharKeyDownPropagation) { TestFlutterWindowsView flutter_windows_view( std::move(window_binding_handler), virtual_key, true /* is_printable */); win32window.SetView(&flutter_windows_view); - LPARAM lparam = CreateKeyEventLparam(scan_code); + LPARAM lparam = CreateKeyEventLparam(scan_code, false /* extended */, + true /* PrevState */); // 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)) + EXPECT_CALL(*flutter_windows_view.key_event_handler, + KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character, + false, true)) .Times(2) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, - KeyboardHook(_, _, _, _, _, _)) + KeyboardHook(_, _, _, _, _, _, _)) .Times(1) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _)) @@ -375,17 +393,18 @@ TEST(Win32FlutterWindowTest, CharKeyDownPropagation) { EXPECT_EQ(win32window.InjectWindowMessage(WM_CHAR, virtual_key, lparam), 0); flutter_windows_view.InjectPendingEvents(&win32window); } + return; // 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 /* is_printable */)) + false /* is_printable */, true)) .Times(1) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, - KeyboardHook(_, _, _, _, _, _)) + KeyboardHook(_, _, _, _, _, _, _)) .Times(0); EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _)) .Times(0); @@ -420,11 +439,11 @@ TEST(Win32FlutterWindowTest, ModifierKeyDownPropagation) { 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 /* extended */, true)) .Times(2) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, - KeyboardHook(_, _, _, _, _, _)) + KeyboardHook(_, _, _, _, _, _, _)) .Times(1) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _)) @@ -441,11 +460,11 @@ TEST(Win32FlutterWindowTest, ModifierKeyDownPropagation) { test_response = true; EXPECT_CALL(*flutter_windows_view.key_event_handler, KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character, - false /* extended */)) + false /* extended */, true)) .Times(1) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, - KeyboardHook(_, _, _, _, _, _)) + KeyboardHook(_, _, _, _, _, _, _)) .Times(0); EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam), 0); diff --git a/shell/platform/windows/win32_window.cc b/shell/platform/windows/win32_window.cc index 92be90f4dda3a..a8957b9819776 100644 --- a/shell/platform/windows/win32_window.cc +++ b/shell/platform/windows/win32_window.cc @@ -10,11 +10,6 @@ #include "win32_dpi_utils.h" -// KeyCode used to indicate key events to be handled by the IME. These include -// the kana key, fullwidth/halfwidth (zenkaku/hankaku) key, and keypresses when -// the IME is in composing mode. -static constexpr int kImeComposingKeyCode = 229; - namespace flutter { namespace { @@ -341,9 +336,9 @@ Win32Window::HandleMessage(UINT const message, if (keycode_for_char_message_ != 0) { const unsigned int scancode = (lparam >> 16) & 0xff; const bool extended = ((lparam >> 24) & 0x01) == 0x01; - + const bool was_down = lparam & 0x40000000; bool handled = OnKey(keycode_for_char_message_, scancode, WM_KEYDOWN, - code_point, extended); + code_point, extended, was_down); keycode_for_char_message_ = 0; if (handled) { // If the OnKey handler handles the message, then return so we don't @@ -383,12 +378,6 @@ Win32Window::HandleMessage(UINT const message, break; } unsigned int keyCode(wparam); - if (keyCode == kImeComposingKeyCode) { - // This is an IME composing mode keypress that will be handled via - // WM_IME_* messages, which update the framework via updates to the text - // and composing range in text editing update messages. - break; - } const unsigned int scancode = (lparam >> 16) & 0xff; const bool extended = ((lparam >> 24) & 0x01) == 0x01; // If the key is a modifier, get its side. @@ -396,7 +385,8 @@ Win32Window::HandleMessage(UINT const message, keyCode = MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX); } const int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP; - if (OnKey(keyCode, scancode, action, 0, extended)) { + const bool was_down = lparam & 0x40000000; + if (OnKey(keyCode, scancode, action, 0, extended, was_down)) { return 0; } break; @@ -441,4 +431,11 @@ Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { GetWindowLongPtr(window, GWLP_USERDATA)); } +LRESULT Win32Window::DefaultWindowProc(HWND hWnd, + UINT Msg, + WPARAM wParam, + LPARAM lParam) { + return DefWindowProc(hWnd, Msg, wParam, lParam); +} + } // namespace flutter diff --git a/shell/platform/windows/win32_window.h b/shell/platform/windows/win32_window.h index bd51d386fcec7..69d9137ded1de 100644 --- a/shell/platform/windows/win32_window.h +++ b/shell/platform/windows/win32_window.h @@ -101,7 +101,8 @@ class Win32Window { int scancode, int action, char32_t character, - bool extended) = 0; + bool extended, + bool was_down) = 0; // Called when IME composing begins. virtual void OnComposeBegin() = 0; @@ -149,6 +150,9 @@ class Win32Window { UINT GetCurrentHeight(); + protected: + LRESULT DefaultWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + private: // Release OS resources asociated with window. void Destroy(); diff --git a/shell/platform/windows/win32_window_unittests.cc b/shell/platform/windows/win32_window_unittests.cc index 325d0ca81315e..b9297528096f7 100644 --- a/shell/platform/windows/win32_window_unittests.cc +++ b/shell/platform/windows/win32_window_unittests.cc @@ -13,15 +13,15 @@ namespace { // Creates a valid Windows LPARAM for WM_KEYDOWN and WM_KEYUP from parameters // given. -static LPARAM CreateKeyEventLparam(USHORT ScanCode, +static LPARAM CreateKeyEventLparam(USHORT scancode, bool extended = false, - USHORT RepeatCount = 1, - bool ContextCode = 0, - bool PreviousKeyState = 1, - bool TransitionState = 1) { - return ((LPARAM(TransitionState) << 31) | (LPARAM(PreviousKeyState) << 30) | - (LPARAM(ContextCode) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | - (LPARAM(ScanCode) << 16) | LPARAM(RepeatCount)); + bool was_down = 1, + USHORT repeat_count = 1, + bool context_code = 0, + bool transition_state = 1) { + return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) | + (LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | + (LPARAM(scancode) << 16) | LPARAM(repeat_count)); } } // namespace @@ -57,7 +57,7 @@ TEST(MockWin32Window, HorizontalScroll) { TEST(MockWin32Window, KeyDown) { MockWin32Window window; - EXPECT_CALL(window, OnKey(_, _, _, _, _)).Times(1); + EXPECT_CALL(window, OnKey(_, _, _, _, _, _)).Times(1); LPARAM lparam = CreateKeyEventLparam(42); // send a "Shift" key down event. window.InjectWindowMessage(WM_KEYDOWN, 16, lparam); @@ -65,7 +65,7 @@ TEST(MockWin32Window, KeyDown) { TEST(MockWin32Window, KeyUp) { MockWin32Window window; - EXPECT_CALL(window, OnKey(_, _, _, _, _)).Times(1); + EXPECT_CALL(window, OnKey(_, _, _, _, _, _)).Times(1); LPARAM lparam = CreateKeyEventLparam(42); // send a "Shift" key up event. window.InjectWindowMessage(WM_KEYUP, 16, lparam); @@ -75,11 +75,11 @@ TEST(MockWin32Window, KeyDownPrintable) { MockWin32Window window; LPARAM lparam = CreateKeyEventLparam(30); // OnKey shouldn't be called until the WM_CHAR message. - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false)).Times(0); + EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, true)).Times(0); // send a "A" key down event. window.InjectWindowMessage(WM_KEYDOWN, 65, lparam); - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false)).Times(1); + EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, true)).Times(1); EXPECT_CALL(window, OnText(_)).Times(1); window.InjectWindowMessage(WM_CHAR, 65, lparam); } diff --git a/shell/platform/windows/window_binding_handler_delegate.h b/shell/platform/windows/window_binding_handler_delegate.h index 40bd8f513dacc..df3499545c91f 100644 --- a/shell/platform/windows/window_binding_handler_delegate.h +++ b/shell/platform/windows/window_binding_handler_delegate.h @@ -50,7 +50,8 @@ class WindowBindingHandlerDelegate { int scancode, int action, char32_t character, - bool extended) = 0; + bool extended, + bool was_down) = 0; // Notifies the delegate that IME composing mode has begun. //