From e03487b3056fad30e21247e2bf95547e241906d1 Mon Sep 17 00:00:00 2001 From: FoxRefire <155989196+FoxRefire@users.noreply.github.com> Date: Sat, 22 Aug 2026 20:30:05 +0900 Subject: [PATCH] Fix Japanese keyboard mapping --- src/platform/linux/uhid_backend.cpp | 41 ++++++-- src/platform/windows/keylayout.hpp | 6 +- src/platform/windows/windows_backend.cpp | 102 +++++++++++++++++++- tests/fixtures/linux_backend_test_hooks.cpp | 2 +- 4 files changed, 136 insertions(+), 15 deletions(-) diff --git a/src/platform/linux/uhid_backend.cpp b/src/platform/linux/uhid_backend.cpp index eaffb3e..6b14581 100644 --- a/src/platform/linux/uhid_backend.cpp +++ b/src/platform/linux/uhid_backend.cpp @@ -535,8 +535,19 @@ namespace lvh::detail { return it->second; } - int key_code_to_linux(KeyboardKeyCode key_code) { - static constexpr std::array, 47> special_keys {{ + int key_code_to_linux(KeyboardKeyCode key_code, bool uses_normalized_key_code) { + if (!uses_normalized_key_code) { + switch (key_code) { + case 0xDC: + return KEY_YEN; + case 0xE2: + return KEY_RO; + default: + break; + } + } + + static constexpr std::array, 50> special_keys {{ {0x08, KEY_BACKSPACE}, {0x09, KEY_TAB}, {0x0D, KEY_ENTER}, @@ -547,7 +558,10 @@ namespace lvh::detail { {0x12, KEY_LEFTALT}, {0xA4, KEY_LEFTALT}, {0x14, KEY_CAPSLOCK}, + {0x15, KEY_KATAKANAHIRAGANA}, {0x1B, KEY_ESC}, + {0x1C, KEY_HENKAN}, + {0x1D, KEY_MUHENKAN}, {0x20, KEY_SPACE}, {0x21, KEY_PAGEUP}, {0x22, KEY_PAGEDOWN}, @@ -1442,7 +1456,7 @@ namespace lvh::detail { private: OperationStatus emit_keyboard_event(const KeyboardEvent &event) { - const auto linux_key = key_code_to_linux(event.key_code); + const auto linux_key = key_code_to_linux(event.key_code, event.uses_normalized_key_code); if (linux_key < 0) { return OperationStatus::failure(ErrorCode::invalid_argument, "keyboard key code is not supported by the Linux backend"); } @@ -2034,8 +2048,19 @@ namespace lvh::detail { }; #if defined(LIBVIRTUALHID_HAVE_XTEST) - KeySym key_code_to_keysym(KeyboardKeyCode key_code) { - static constexpr std::array, 45> special_keysyms {{ + KeySym key_code_to_keysym(KeyboardKeyCode key_code, bool uses_normalized_key_code) { + if (!uses_normalized_key_code) { + switch (key_code) { + case 0xDC: + return XK_yen; + case 0xE2: + return XK_backslash; + default: + break; + } + } + + static constexpr std::array, 49> special_keysyms {{ {0x08, XK_BackSpace}, {0x09, XK_Tab}, {0x0D, XK_Return}, @@ -2046,7 +2071,10 @@ namespace lvh::detail { {0x12, XK_Alt_L}, {0xA4, XK_Alt_L}, {0x14, XK_Caps_Lock}, + {0x15, XK_Hiragana_Katakana}, {0x1B, XK_Escape}, + {0x1C, XK_Henkan}, + {0x1D, XK_Muhenkan}, {0x20, XK_space}, {0x21, XK_Page_Up}, {0x22, XK_Page_Down}, @@ -2081,6 +2109,7 @@ namespace lvh::detail { {0xDC, XK_backslash}, {0xDD, XK_bracketright}, {0xDE, XK_apostrophe}, + {0xE2, XK_backslash}, }}; if (const auto keysym = mapped_keyboard_code(key_code, special_keysyms); keysym.has_value()) { @@ -2169,7 +2198,7 @@ namespace lvh::detail { return OperationStatus::failure(ErrorCode::device_closed, "XTest keyboard is closed"); } - const auto keysym = key_code_to_keysym(event.key_code); + const auto keysym = key_code_to_keysym(event.key_code, event.uses_normalized_key_code); if (keysym == NoSymbol) { return OperationStatus::failure(ErrorCode::invalid_argument, "keyboard key code is not supported by XTest fallback"); } diff --git a/src/platform/windows/keylayout.hpp b/src/platform/windows/keylayout.hpp index 279d46a..6eaa34f 100644 --- a/src/platform/windows/keylayout.hpp +++ b/src/platform/windows/keylayout.hpp @@ -37,15 +37,15 @@ namespace lvh::detail { 56, /* 0x12 */ 0, /* 0x13 */ 58, /* 0x14 */ - 0, /* 0x15 */ + 0x70, /* 0x15 VK_KANA */ 0, /* 0x16 */ 0, /* 0x17 */ 0, /* 0x18 */ 0, /* 0x19 */ 0, /* 0x1a */ 1, /* 0x1b */ - 0, /* 0x1c */ - 0, /* 0x1d */ + 0x79, /* 0x1c VK_CONVERT (Henkan) */ + 0x7B, /* 0x1d VK_NONCONVERT (Muhenkan) */ 0, /* 0x1e */ 0, /* 0x1f */ 57, /* 0x20 */ diff --git a/src/platform/windows/windows_backend.cpp b/src/platform/windows/windows_backend.cpp index a9c2a92..0f096f5 100644 --- a/src/platform/windows/windows_backend.cpp +++ b/src/platform/windows/windows_backend.cpp @@ -466,6 +466,88 @@ namespace lvh::detail { } } + /** + * @brief Whether a scan code is sent with the E0 extended prefix. + * + * @param scan_code Low byte of the scan code. + * @return True when the scan code requires `KEYEVENTF_EXTENDEDKEY`. + */ + bool extended_scan_code(WORD scan_code) { + switch (scan_code) { + case 0x1C: + case 0x35: + case 0x37: + case 0x38: + case 0x47: + case 0x48: + case 0x49: + case 0x4B: + case 0x4D: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5F: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return true; + default: + return false; + } + } + + /** + * @brief Map a virtual key to a scan code using the active keyboard layout. + * + * @param key_code Windows virtual key code. + * @return Scan code and extended-prefix flag from `MapVirtualKeyW`. + */ + struct MappedScanCode { + WORD scan_code = 0; + bool extended = false; + }; + + MappedScanCode map_virtual_key_to_scan_code(KeyboardKeyCode key_code) { + const auto mapped = ::MapVirtualKeyW(key_code, MAPVK_VK_TO_VSC); + if (mapped == 0U) { + return {}; + } + + return { + .scan_code = static_cast(mapped & 0xFFU), + .extended = (mapped & 0xFF00U) != 0U, + }; + } + bool can_map_virtual_key_to_scan_code(KeyboardKeyCode key_code) { return key_code != VK_LWIN && key_code != VK_RWIN && key_code != VK_PAUSE; } @@ -1272,23 +1354,33 @@ namespace lvh::detail { INPUT input {}; input.type = INPUT_KEYBOARD; input.ki.wVk = event.key_code; + auto use_extended_key = false; + if (event.scan_code != 0U) { input.ki.wVk = 0; input.ki.wScan = event.scan_code; input.ki.dwFlags |= KEYEVENTF_SCANCODE; + use_extended_key = extended_scan_code(event.scan_code); } else { + WORD scan_code = 0; if (event.uses_normalized_key_code) { - input.ki.wScan = windows_us_english_scan_code(event.key_code); - } else if (event.prefer_native_scan_code && can_map_virtual_key_to_scan_code(event.key_code)) { - input.ki.wScan = static_cast(::MapVirtualKeyW(event.key_code, MAPVK_VK_TO_VSC)); + scan_code = windows_us_english_scan_code(event.key_code); + use_extended_key = extended_key(event.key_code) || extended_scan_code(scan_code); + } else if (can_map_virtual_key_to_scan_code(event.key_code)) { + // Non-normalized keys use the host's active keyboard layout. + const auto mapped = map_virtual_key_to_scan_code(event.key_code); + scan_code = mapped.scan_code; + use_extended_key = mapped.extended; } - if (input.ki.wScan != 0U) { + if (scan_code != 0U) { input.ki.wVk = 0; + input.ki.wScan = scan_code; input.ki.dwFlags |= KEYEVENTF_SCANCODE; } } - if (extended_key(event.key_code)) { + + if (use_extended_key) { input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; } if (!event.pressed) { diff --git a/tests/fixtures/linux_backend_test_hooks.cpp b/tests/fixtures/linux_backend_test_hooks.cpp index 91345e3..02d9b41 100644 --- a/tests/fixtures/linux_backend_test_hooks.cpp +++ b/tests/fixtures/linux_backend_test_hooks.cpp @@ -962,7 +962,7 @@ namespace lvh::detail::test { } int linux_key_code(KeyboardKeyCode key_code) { - return key_code_to_linux(key_code); + return key_code_to_linux(key_code, true); } int linux_mouse_button(MouseButton button) {