diff --git a/src/platform/linux/uhid_backend.cpp b/src/platform/linux/uhid_backend.cpp index 699ed72..e518b23 100644 --- a/src/platform/linux/uhid_backend.cpp +++ b/src/platform/linux/uhid_backend.cpp @@ -630,7 +630,7 @@ namespace lvh::detail { } int key_code_to_linux(KeyboardKeyCode key_code) { - static constexpr std::array, 47> special_keys {{ + static constexpr std::array, 48> special_keys {{ {0x08, KEY_BACKSPACE}, {0x09, KEY_TAB}, {0x0D, KEY_ENTER}, @@ -656,6 +656,7 @@ namespace lvh::detail { {0x2E, KEY_DELETE}, {0x5B, KEY_LEFTMETA}, {0x5C, KEY_RIGHTMETA}, + {0x5D, KEY_COMPOSE}, {0x6A, KEY_KPASTERISK}, {0x6B, KEY_KPPLUS}, {0x6D, KEY_KPMINUS}, @@ -2283,7 +2284,7 @@ namespace lvh::detail { #if defined(LIBVIRTUALHID_HAVE_XTEST) KeySym key_code_to_keysym(KeyboardKeyCode key_code) { - static constexpr std::array, 45> special_keysyms {{ + static constexpr std::array, 46> special_keysyms {{ {0x08, XK_BackSpace}, {0x09, XK_Tab}, {0x0D, XK_Return}, @@ -2308,6 +2309,7 @@ namespace lvh::detail { {0x2E, XK_Delete}, {0x5B, XK_Super_L}, {0x5C, XK_Super_R}, + {0x5D, XK_Menu}, {0x6A, XK_KP_Multiply}, {0x6B, XK_KP_Add}, {0x6D, XK_KP_Subtract}, @@ -2422,7 +2424,10 @@ namespace lvh::detail { return OperationStatus::failure(ErrorCode::invalid_argument, "keyboard key code is not supported by XTest fallback"); } - const auto keycode = XKeysymToKeycode(display_, keysym); + auto keycode = XKeysymToKeycode(display_, keysym); + if (keycode == 0 && event.key_code == 0x5D) { + keycode = XKeysymToKeycode(display_, XK_Multi_key); + } if (keycode == 0) { return OperationStatus::failure(ErrorCode::invalid_argument, "keyboard key code has no X11 keycode"); } diff --git a/tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp b/tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp index 84c2cee..6015bae 100644 --- a/tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp +++ b/tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp @@ -1401,6 +1401,13 @@ namespace lvh::detail::test { */ OperationStatus linux_xtest_keyboard_submit_success(); + /** + * @brief Submit the Menu key when XTest can resolve only its Compose keysym. + * + * @return Submit status. + */ + OperationStatus linux_xtest_keyboard_submit_compose_fallback(); + /** * @brief Submit unsupported keyboard input through the XTest fallback. * diff --git a/tests/fixtures/linux_backend_test_hooks.cpp b/tests/fixtures/linux_backend_test_hooks.cpp index 49b37da..a402239 100644 --- a/tests/fixtures/linux_backend_test_hooks.cpp +++ b/tests/fixtures/linux_backend_test_hooks.cpp @@ -2812,6 +2812,23 @@ namespace lvh::detail::test { #endif } + OperationStatus linux_xtest_keyboard_submit_compose_fallback() { + #if defined(LIBVIRTUALHID_HAVE_XTEST) + LinuxTestSyscalls syscalls; + syscalls.override_x_keycode = true; + syscalls.fail_x_keycode_call = 1; + ScopedLinuxTestSyscalls scoped_syscalls {syscalls}; + + XTestKeyboard keyboard; + if (const auto status = keyboard.create(); !status.ok()) { + return status; + } + return keyboard.submit({.key_code = 0x5D, .pressed = true}); + #else + return OperationStatus::failure(ErrorCode::backend_unavailable, "XTest fallback is not enabled"); + #endif + } + OperationStatus linux_xtest_keyboard_submit_invalid() { #if defined(LIBVIRTUALHID_HAVE_XTEST) XTestKeyboard keyboard; diff --git a/tests/unit/test_linux_backend.cpp b/tests/unit/test_linux_backend.cpp index 5f39285..76f7a6c 100644 --- a/tests/unit/test_linux_backend.cpp +++ b/tests/unit/test_linux_backend.cpp @@ -70,6 +70,7 @@ TEST_F(LinuxBackendTest, TranslatesKeyboardKeys) { EXPECT_EQ(lvh::detail::test::linux_key_code(0x2E), KEY_DELETE); EXPECT_EQ(lvh::detail::test::linux_key_code(0x5B), KEY_LEFTMETA); EXPECT_EQ(lvh::detail::test::linux_key_code(0x5C), KEY_RIGHTMETA); + EXPECT_EQ(lvh::detail::test::linux_key_code(0x5D), KEY_COMPOSE); EXPECT_EQ(lvh::detail::test::linux_key_code(0x90), KEY_NUMLOCK); EXPECT_EQ(lvh::detail::test::linux_key_code(0x91), KEY_SCROLLLOCK); EXPECT_EQ(lvh::detail::test::linux_key_code(0xA0), KEY_LEFTSHIFT); @@ -359,6 +360,19 @@ TEST_F(LinuxBackendTest, PipeBackedUinputKeyboardEmitsEvents) { EXPECT_EQ(lvh::detail::test::linux_uinput_user_device_pipe().code(), lvh::ErrorCode::backend_failure); } +TEST_F(LinuxBackendTest, PipeBackedUinputKeyboardEmitsComposeKey) { + for (const auto pressed : {true, false}) { + const auto result = lvh::detail::test::linux_uinput_keyboard_submit_pipe({.key_code = 0x5D, .pressed = pressed}); + ASSERT_TRUE(result.status.ok()) << result.status.message(); + ASSERT_EQ(result.events.size(), 2U); + EXPECT_EQ(result.events[0].type, EV_KEY); + EXPECT_EQ(result.events[0].code, KEY_COMPOSE); + EXPECT_EQ(result.events[0].value, pressed ? 1 : 0); + EXPECT_EQ(result.events[1].type, EV_SYN); + EXPECT_EQ(result.events[1].code, SYN_REPORT); + } +} + TEST_F(LinuxBackendTest, PipeBackedUinputGamepadsUseCanonicalLinuxEvents) { using enum lvh::GamepadButton; using enum lvh::GamepadProfileKind; @@ -1357,6 +1371,13 @@ TEST_F(LinuxBackendTest, XTestFallbackCoversKeyboardAndMousePaths) { const auto keyboard_status = lvh::detail::test::linux_xtest_keyboard_submit_success(); EXPECT_TRUE(keyboard_status.ok() || keyboard_status.code() == lvh::ErrorCode::backend_unavailable); + const auto compose_status = lvh::detail::test::linux_xtest_keyboard_submit_compose_fallback(); +#if defined(LIBVIRTUALHID_HAVE_XTEST) + EXPECT_TRUE(compose_status.ok()) << compose_status.message(); +#else + EXPECT_EQ(compose_status.code(), lvh::ErrorCode::backend_unavailable); +#endif + const auto keyboard_invalid_status = lvh::detail::test::linux_xtest_keyboard_submit_invalid(); EXPECT_TRUE(keyboard_invalid_status.code() == lvh::ErrorCode::invalid_argument || keyboard_invalid_status.code() == lvh::ErrorCode::backend_unavailable); @@ -1405,6 +1426,7 @@ TEST_F(LinuxBackendTest, XTestFallbackCoversKeyboardAndMousePaths) { EXPECT_EQ(lvh::detail::test::linux_xtest_keysym(0x2E), XK_Delete); EXPECT_EQ(lvh::detail::test::linux_xtest_keysym(0x5B), XK_Super_L); EXPECT_EQ(lvh::detail::test::linux_xtest_keysym(0x5C), XK_Super_R); + EXPECT_EQ(lvh::detail::test::linux_xtest_keysym(0x5D), XK_Menu); EXPECT_EQ(lvh::detail::test::linux_xtest_keysym(0x90), XK_Num_Lock); EXPECT_EQ(lvh::detail::test::linux_xtest_keysym(0x91), XK_Scroll_Lock); EXPECT_EQ(lvh::detail::test::linux_xtest_keysym(0xA1), XK_Shift_R);