diff --git a/shell/platform/glfw/key_event_handler.cc b/shell/platform/glfw/key_event_handler.cc index c193fda8a911eebc058fae33797a07498d40a86b..1d7994340aca97dd0010d3396bcf1a7b56b57d3c 100644 --- a/shell/platform/glfw/key_event_handler.cc +++ b/shell/platform/glfw/key_event_handler.cc @@ -12,14 +12,95 @@ static constexpr char kChannelName[] = "flutter/keyevent"; static constexpr char kKeyCodeKey[] = "keyCode"; static constexpr char kKeyMapKey[] = "keymap"; +static constexpr char kScanCodeKey[] = "scanCode"; +static constexpr char kModifiersKey[] = "modifiers"; static constexpr char kTypeKey[] = "type"; +static constexpr char kToolkitKey[] = "toolkit"; +static constexpr char kUnicodeScalarValues[] = "unicodeScalarValues"; + +static constexpr char kLinuxKeyMap[] = "linux"; +static constexpr char kGLFWKey[] = "glfw"; -static constexpr char kAndroidKeyMap[] = "android"; static constexpr char kKeyUp[] = "keyup"; static constexpr char kKeyDown[] = "keydown"; +// Masks used for UTF-8 to UTF-32 conversion. +static constexpr int kTwoByteMask = 0xC0; +static constexpr int kThreeByteMask = 0xE0; +static constexpr int kFourByteMask = 0xF0; + namespace flutter { +namespace { + +// Information about the UTF-8 encoded code point. +struct UTF8CodePointInfo { + // The bit-mask that determines the length of the code point. + int first_byte_mask; + // The number of bytes of the code point. + size_t length; +}; + +// Creates a [UTF8CodePointInfo] from a given byte. [first_byte] must be the +// first byte in the code point. +UTF8CodePointInfo GetUTF8CodePointInfo(int first_byte) { + UTF8CodePointInfo byte_info; + + // The order matters. Otherwise, it is possible that comparing against i.e. + // kThreeByteMask and kFourByteMask could be both true. + if ((first_byte & kFourByteMask) == kFourByteMask) { + byte_info.first_byte_mask = 0x07; + byte_info.length = 4; + } else if ((first_byte & kThreeByteMask) == kThreeByteMask) { + byte_info.first_byte_mask = 0x0F; + byte_info.length = 3; + } else if ((first_byte & kTwoByteMask) == kTwoByteMask) { + byte_info.first_byte_mask = 0x1F; + byte_info.length = 2; + } else { + byte_info.first_byte_mask = 0xFF; + byte_info.length = 1; + } + return byte_info; +} + +// Queries GLFW for the printable key name given a [key] and [scan_code] and +// converts it to UTF-32. The Flutter framework accepts only one code point, +// therefore, only the first code point will be used. There is unlikely to be +// more than one, but there is no guarantee that it won't happen. +bool GetUTF32CodePointFromGLFWKey(int key, + int scan_code, + uint32_t* code_point) { + // Get the name of the printable key, encoded as UTF-8. + // There's a known issue with glfwGetKeyName, where users with multiple + // layouts configured on their machines, will not always return the right + // value. See: https://github.com/glfw/glfw/issues/1462 + const char* utf8 = glfwGetKeyName(key, scan_code); + if (utf8 == nullptr) { + return false; + } + // The first byte determines the length of the whole code point. + const auto byte_info = GetUTF8CodePointInfo(utf8[0]); + // Tracks how many bits the current byte should shift to the left. + int shift = byte_info.length - 1; + + const int complement_mask = 0x3F; + uint32_t result = 0; + + size_t current_byte_index = 0; + while (current_byte_index < byte_info.length) { + const int current_byte = utf8[current_byte_index]; + const int mask = + current_byte_index == 0 ? byte_info.first_byte_mask : complement_mask; + current_byte_index++; + const int bits_to_shift = 6 * shift--; + result += (current_byte & mask) << bits_to_shift; + } + *code_point = result; + return true; +} +} // namespace + KeyEventHandler::KeyEventHandler(flutter::BinaryMessenger* messenger) : channel_( std::make_unique>( @@ -41,10 +122,20 @@ void KeyEventHandler::KeyboardHook(GLFWwindow* window, rapidjson::Document event(rapidjson::kObjectType); auto& allocator = event.GetAllocator(); event.AddMember(kKeyCodeKey, key, allocator); - event.AddMember(kKeyMapKey, kAndroidKeyMap, allocator); + event.AddMember(kKeyMapKey, kLinuxKeyMap, allocator); + event.AddMember(kScanCodeKey, scancode, allocator); + event.AddMember(kModifiersKey, mods, allocator); + event.AddMember(kToolkitKey, kGLFWKey, allocator); + + uint32_t unicodeInt; + bool result = GetUTF32CodePointFromGLFWKey(key, scancode, &unicodeInt); + if (result) { + event.AddMember(kUnicodeScalarValues, unicodeInt, allocator); + } switch (action) { case GLFW_PRESS: + case GLFW_REPEAT: event.AddMember(kTypeKey, kKeyDown, allocator); break; case GLFW_RELEASE: