[Win32, keyboard] Fix dead key events that don't have the dead key mask (flutter/engine#30004)

This PR fixes flutter/flutter#92654, a rare case where dead key events are not properly handled.
This commit is contained in:
Tong Mu
2021-12-01 00:49:44 -08:00
committed by GitHub
parent 2626d453ff
commit fdc78e4146
8 changed files with 155 additions and 33 deletions

View File

@@ -1759,6 +1759,7 @@ 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/keyboard_unittests.cc
FILE: ../../../flutter/shell/platform/windows/keyboard_win32_common.h
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

View File

@@ -72,6 +72,7 @@ source_set("flutter_windows_source") {
"keyboard_key_embedder_handler.h",
"keyboard_key_handler.cc",
"keyboard_key_handler.h",
"keyboard_win32_common.h",
"platform_handler.cc",
"platform_handler.h",
"sequential_id_generator.cc",

View File

@@ -14,6 +14,7 @@
#include <iostream>
#include "flutter/shell/platform/common/json_message_codec.h"
#include "flutter/shell/platform/windows/keyboard_win32_common.h"
namespace flutter {
@@ -146,17 +147,6 @@ int GetModsForKeyState() {
#endif
}
// Revert the "character" for a dead key to its normal value, or the argument
// unchanged otherwise.
//
// 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;
}
} // namespace
KeyboardKeyChannelHandler::KeyboardKeyChannelHandler(
@@ -184,7 +174,7 @@ void KeyboardKeyChannelHandler::KeyboardHook(
event.AddMember(kKeyCodeKey, key, allocator);
event.AddMember(kScanCodeKey, scancode | (extended ? kScancodeExtended : 0),
allocator);
event.AddMember(kCharacterCodePointKey, _UndeadChar(character), allocator);
event.AddMember(kCharacterCodePointKey, UndeadChar(character), allocator);
event.AddMember(kKeyMapKey, kWindowsKeyMap, allocator);
event.AddMember(kModifiersKey, GetModsForKeyState(), allocator);

View File

@@ -12,6 +12,7 @@
#include <iostream>
#include <string>
#include "flutter/shell/platform/windows/keyboard_win32_common.h"
#include "flutter/shell/platform/windows/string_conversion.h"
namespace flutter {
@@ -28,17 +29,6 @@ constexpr SHORT kStateMaskPressed = 0x80;
const char* empty_character = "";
// Revert the "character" for a dead key to its normal value, or the argument
// unchanged otherwise.
//
// 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;
}
// Get some bits of the char, from the start'th bit from the right (excluded)
// to the end'th bit from the right (included).
//
@@ -185,7 +175,7 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl(
uint64_t eventual_logical_record;
char character_bytes[kCharacterCacheSize];
character = _UndeadChar(character);
character = UndeadChar(character);
if (is_physical_down) {
if (had_record) {

View File

@@ -9,6 +9,7 @@
#include <iostream>
#include "flutter/shell/platform/common/json_message_codec.h"
#include "flutter/shell/platform/windows/keyboard_win32_common.h"
namespace flutter {
@@ -20,7 +21,7 @@ static constexpr int kMaxPendingEvents = 1000;
// Returns if a character sent by Win32 is a dead key.
bool _IsDeadKey(uint32_t ch) {
return (ch & 0x80000000) != 0;
return (ch & kDeadKeyCharMask) != 0;
}
// Returns true if this key is a key down event of ShiftRight.

View File

@@ -349,6 +349,7 @@ constexpr uint64_t kScanCodeKeyE = 0x12;
constexpr uint64_t kScanCodeKeyQ = 0x10;
constexpr uint64_t kScanCodeKeyW = 0x11;
constexpr uint64_t kScanCodeDigit1 = 0x02;
constexpr uint64_t kScanCodeDigit6 = 0x07;
// constexpr uint64_t kScanCodeNumpad1 = 0x4f;
// constexpr uint64_t kScanCodeNumLock = 0x45;
constexpr uint64_t kScanCodeControl = 0x1d;
@@ -860,8 +861,112 @@ TEST(KeyboardTest, DeadKeyThatCombines) {
EXPECT_EQ(key_calls.size(), 0);
}
// This tests dead key ^ then E on a US INTL keyboard, which should be combined
// into ê.
//
// It is different from French AZERTY because the character that the ^ key is
// mapped to does not contain the dead key character somehow.
TEST(KeyboardTest, DeadKeyWithoutDeadMaskThatCombines) {
KeyboardTester tester;
tester.Responding(false);
// Press ShiftLeft
tester.SetKeyState(VK_LSHIFT, true, true);
tester.InjectMessages(
1,
WmKeyDownInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
kPhysicalShiftLeft, kLogicalShiftLeft, "",
kNotSynthesized);
clear_key_calls();
tester.InjectPendingEvents();
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Press 6^
tester.InjectMessages(
2,
WmKeyDownInfo{'6', kScanCodeDigit6, kNotExtended, kWasUp}.Build(
kWmResultZero),
WmDeadCharInfo{'^', kScanCodeDigit6, kNotExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit6,
kLogicalDigit6, "6", kNotSynthesized);
clear_key_calls();
EXPECT_EQ(tester.InjectPendingEvents(), 0);
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Release 6^
tester.InjectMessages(
1, WmKeyUpInfo{'6', kScanCodeDigit6, kNotExtended}.Build(kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalDigit6,
kLogicalDigit6, "", kNotSynthesized);
clear_key_calls();
tester.InjectPendingEvents();
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Release ShiftLeft
tester.SetKeyState(VK_LSHIFT, false, true);
tester.InjectMessages(
1, WmKeyUpInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalShiftLeft,
kLogicalShiftLeft, "", kNotSynthesized);
clear_key_calls();
tester.InjectPendingEvents();
EXPECT_EQ(key_calls.size(), 0);
clear_key_calls();
// Press E
tester.InjectMessages(
2,
WmKeyDownInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended, kWasUp}.Build(
kWmResultZero),
WmCharInfo{0xEA, kScanCodeKeyE, kNotExtended, kWasUp}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyE,
kLogicalKeyE, "ê", kNotSynthesized);
clear_key_calls();
tester.InjectPendingEvents(
0xEA); // The redispatched event uses unmodified 'e'
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_TEXT(key_calls[0], u"ê");
clear_key_calls();
// Release E
tester.InjectMessages(
1, WmKeyUpInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended}.Build(
kWmResultZero));
EXPECT_EQ(key_calls.size(), 1);
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyE,
kLogicalKeyE, "", kNotSynthesized);
clear_key_calls();
tester.InjectPendingEvents();
EXPECT_EQ(key_calls.size(), 0);
}
// This tests dead key ^ then & (US: 1) on a French keyboard, which do not
// combine and should output "^$".
// combine and should output "^&".
TEST(KeyboardTest, DeadKeyThatDoesNotCombine) {
KeyboardTester tester;
tester.Responding(false);

View File

@@ -0,0 +1,29 @@
// 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_WIN32_COMMON_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_WIN32_COMMON_H_
#include <stdint.h>
namespace flutter {
// The bit of a mapped character in a WM_KEYDOWN message that indicates the
// character is a dead key.
//
// 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 '^'.
constexpr int kDeadKeyCharMask = 0x80000000;
// Revert the "character" for a dead key to its normal value, or the argument
// unchanged otherwise.
inline uint32_t UndeadChar(uint32_t ch) {
return ch & ~kDeadKeyCharMask;
}
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_WIN32_COMMON_H_

View File

@@ -15,6 +15,7 @@
#include <cstring>
#include "dpi_utils_win32.h"
#include "keyboard_win32_common.h"
namespace flutter {
@@ -514,14 +515,18 @@ WindowWin32::HandleMessage(UINT const message,
const bool extended = ((lparam >> 24) & 0x01) == 0x01;
const bool was_down = lparam & 0x40000000;
// Certain key combinations yield control characters as WM_CHAR's
// lParam. For example, 0x01 for Ctrl-A. Filter these characters.
// See
// lParam. For example, 0x01 for Ctrl-A. Filter these characters. See
// https://docs.microsoft.com/en-us/windows/win32/learnwin32/accelerator-tables
const char32_t event_character =
(message == WM_DEADCHAR || message == WM_SYSDEADCHAR)
? Win32MapVkToChar(keycode_for_char_message_)
: IsPrintable(code_point) ? code_point
: 0;
char32_t event_character;
if (message == WM_DEADCHAR || message == WM_SYSDEADCHAR) {
// Mask the resulting char with kDeadKeyCharMask anyway, because in
// rare cases the bit is *not* set (US INTL Shift-6 circumflex, see
// https://github.com/flutter/flutter/issues/92654 .)
event_character =
Win32MapVkToChar(keycode_for_char_message_) | kDeadKeyCharMask;
} else {
event_character = IsPrintable(code_point) ? code_point : 0;
}
bool handled = OnKey(keycode_for_char_message_, scancode,
message == WM_SYSCHAR ? WM_SYSKEYDOWN : WM_KEYDOWN,
event_character, extended, was_down);