diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc index 2d8349621d650..55e6474952bcb 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -37,6 +37,16 @@ char _GetBit(char32_t ch, size_t start, size_t end) { return (ch >> end) & ((1 << (start - end)) - 1); } +// Revert the "character" for a dead key to its normal value. +// +// When a dead key is pressed, the WM_KEYDOWN's lParam is mapped to a special +// value: the "normal character" | 0x80000000. For example, when pressing +// "dead key caret" (one that makes the following e into ê), its mapped +// character is 0x8000005E. "Reverting" it gives 0x5E, which is character '^'. +uint32_t _UndeadChar(uint32_t ch) { + return ch & ~0x80000000; +} + std::string ConvertChar32ToUtf8(char32_t ch) { std::string result; assert(0 <= ch && ch <= 0x10FFFF); @@ -171,6 +181,8 @@ void KeyboardKeyEmbedderHandler::KeyboardHook( bool next_has_record = true; char character_bytes[kCharacterCacheSize]; + character = _UndeadChar(character); + if (is_physical_down) { if (had_record) { if (was_down) { diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 7d0cd475059de..5d2a6b76fd655 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -43,13 +43,30 @@ static LPARAM CreateKeyEventLparam(USHORT scancode, (LPARAM(scancode) << 16) | LPARAM(repeat_count)); } +typedef uint32_t (*MapVkToCharHandler)(uint32_t virtual_key); + +uint32_t LayoutDefault(uint32_t virtual_key) { + return MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR); +} + +uint32_t LayoutFrench(uint32_t virtual_key) { + switch (virtual_key) { + case 0xDD: + return 0x8000005E; + default: + return MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR); + } +} + class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueue { public: typedef std::function U16StringHandler; MockFlutterWindowWin32(U16StringHandler on_text) - : FlutterWindowWin32(800, 600), on_text_(std::move(on_text)) { + : FlutterWindowWin32(800, 600), + on_text_(std::move(on_text)), + map_vk_to_char_(LayoutDefault) { ON_CALL(*this, GetDpiScale()) .WillByDefault(Return(this->FlutterWindowWin32::GetDpiScale())); } @@ -90,6 +107,12 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, MOCK_METHOD0(IsVisible, bool()); MOCK_METHOD1(UpdateCursorRect, void(const Rect&)); + void SetLayout(MapVkToCharHandler map_vk_to_char) { + map_vk_to_char_ = + map_vk_to_char == nullptr ? LayoutDefault : map_vk_to_char; + } + + protected: virtual BOOL Win32PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, @@ -99,9 +122,15 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, wMsgFilterMax, wRemoveMsg); } + virtual uint32_t Win32MapVkToChar(uint32_t virtual_key) override { + return map_vk_to_char_(virtual_key); + } + private: U16StringHandler on_text_; + MapVkToCharHandler map_vk_to_char_; + LRESULT Win32SendMessage(HWND hWnd, UINT const message, WPARAM const wparam, @@ -248,6 +277,8 @@ class KeyboardTester { void Responding(bool response) { test_response = response; } + void SetLayout(MapVkToCharHandler layout) { window_->SetLayout(layout); } + void InjectMessages(int count, Win32Message message1, ...) { Win32Message messages[count]; messages[0] = message1; @@ -636,7 +667,7 @@ TEST(KeyboardTest, Digit1OnFrenchLayout) { KeyboardTester tester; tester.Responding(false); - // French Keyboard layout + tester.SetLayout(LayoutFrench); // Press 1 tester.InjectMessages( @@ -755,7 +786,7 @@ TEST(KeyboardTest, DeadKeyThatCombines) { KeyboardTester tester; tester.Responding(false); - // French Keyboard layout + tester.SetLayout(LayoutFrench); // Press ^¨ (US: Left bracket) tester.InjectMessages( @@ -767,7 +798,7 @@ TEST(KeyboardTest, DeadKeyThatCombines) { EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, - kPhysicalBracketLeft, kLogicalBracketRight, "]", + kPhysicalBracketLeft, kLogicalBracketRight, "^", kNotSynthesized); clear_key_calls(); @@ -829,7 +860,7 @@ TEST(KeyboardTest, DeadKeyThatDoesNotCombine) { KeyboardTester tester; tester.Responding(false); - // French Keyboard layout + tester.SetLayout(LayoutFrench); // Press ^¨ (US: Left bracket) tester.InjectMessages( @@ -841,7 +872,7 @@ TEST(KeyboardTest, DeadKeyThatDoesNotCombine) { EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, - kPhysicalBracketLeft, kLogicalBracketRight, "]", + kPhysicalBracketLeft, kLogicalBracketRight, "^", kNotSynthesized); clear_key_calls(); diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index d20a166bdd157..36b16c40ce38b 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -373,7 +373,7 @@ WindowWin32::HandleMessage(UINT const message, // https://docs.microsoft.com/en-us/windows/win32/learnwin32/accelerator-tables const char32_t event_character = (message == WM_DEADCHAR || message == WM_SYSDEADCHAR) - ? MapVirtualKey(keycode_for_char_message_, MAPVK_VK_TO_CHAR) + ? Win32MapVkToChar(keycode_for_char_message_) : IsPrintable(code_point) ? code_point : 0; bool handled = OnKey(keycode_for_char_message_, scancode, WM_KEYDOWN, @@ -429,7 +429,7 @@ WindowWin32::HandleMessage(UINT const message, // character messages. This allows key combinations such as "CTRL + Digit" // to properly produce key down events even though `MapVirtualKey` returns // a valid character. See https://github.com/flutter/flutter/issues/85587. - unsigned int character = MapVirtualKey(wparam, MAPVK_VK_TO_CHAR); + unsigned int character = Win32MapVkToChar(wparam); UINT next_key_message = PeekNextMessageType(WM_KEYFIRST, WM_KEYLAST); bool has_wm_char = (next_key_message == WM_DEADCHAR || @@ -516,4 +516,8 @@ BOOL WindowWin32::Win32PeekMessage(LPMSG lpMsg, return PeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); } +uint32_t WindowWin32::Win32MapVkToChar(uint32_t virtual_key) { + return MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR); +} + } // namespace flutter diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index 5fcedeaf0d622..abeb545c12cd0 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -178,6 +178,11 @@ class WindowWin32 { UINT wMsgFilterMax, UINT wRemoveMsg); + // Win32's MapVirtualKey(*, MAPVK_VK_TO_CHAR). + // + // Used to process key messages. Exposed for dependency injection. + virtual uint32_t Win32MapVkToChar(uint32_t virtual_key); + private: // Release OS resources associated with window. void Destroy();