Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/include/libvirtualhid/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -729,11 +729,21 @@ namespace lvh {
*/
std::uint16_t scan_code = 0;

/**
* @brief Whether `scan_code` uses the E0/E1 extended prefix from `MapVirtualKeyW`.
*/
bool scan_code_extended = false;

/**
* @brief Whether `key_code` is normalized to the Windows US English keyboard layout.
*/
bool uses_normalized_key_code = false;

/**
* @brief Sunshine keyboard packet flags (`SS_KBE_FLAG_*`) from the streaming client.
*/
std::uint8_t stream_flags = 0;

/**
* @brief Whether the backend should prefer a native scan-code translation when `scan_code` is not provided.
*/
Expand Down
58 changes: 52 additions & 6 deletions src/platform/linux/uhid_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,8 +535,29 @@
return it->second;
}

int key_code_to_linux(KeyboardKeyCode key_code) {
static constexpr std::array<std::pair<KeyboardKeyCode, int>, 47> special_keys {{
constexpr std::uint8_t keyboard_stream_flag_lang1 = 0x02;
constexpr std::uint8_t keyboard_stream_flag_lang2 = 0x04;

int key_code_to_linux(KeyboardKeyCode key_code, bool uses_normalized_key_code, std::uint8_t stream_flags) {
if ((stream_flags & keyboard_stream_flag_lang1) != 0U) {

Check warning on line 542 in src/platform/linux/uhid_backend.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "std::byte" for byte-oriented data manipulation.

See more on https://sonarcloud.io/project/issues?id=LizardByte_libvirtualhid&issues=AaC7h_Vk4jtq7QCm5YC7&open=AaC7h_Vk4jtq7QCm5YC7&pullRequest=101
return KEY_HANGEUL;
}
if ((stream_flags & keyboard_stream_flag_lang2) != 0U) {

Check warning on line 545 in src/platform/linux/uhid_backend.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "std::byte" for byte-oriented data manipulation.

See more on https://sonarcloud.io/project/issues?id=LizardByte_libvirtualhid&issues=AaC7h_Vk4jtq7QCm5YC8&open=AaC7h_Vk4jtq7QCm5YC8&pullRequest=101
return KEY_HANJA;
}

if (!uses_normalized_key_code) {
switch (key_code) {
case 0xDC:
return KEY_YEN;
case 0xE2:
return KEY_RO;
default:
break;
}
}

static constexpr std::array<std::pair<KeyboardKeyCode, int>, 50> special_keys {{
{0x08, KEY_BACKSPACE},
{0x09, KEY_TAB},
{0x0D, KEY_ENTER},
Expand All @@ -547,7 +568,10 @@
{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},
Expand Down Expand Up @@ -1442,7 +1466,7 @@

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, event.stream_flags);
if (linux_key < 0) {
return OperationStatus::failure(ErrorCode::invalid_argument, "keyboard key code is not supported by the Linux backend");
}
Expand Down Expand Up @@ -2034,8 +2058,26 @@
};

#if defined(LIBVIRTUALHID_HAVE_XTEST)
KeySym key_code_to_keysym(KeyboardKeyCode key_code) {
static constexpr std::array<std::pair<KeyboardKeyCode, KeySym>, 45> special_keysyms {{
KeySym key_code_to_keysym(KeyboardKeyCode key_code, bool uses_normalized_key_code, std::uint8_t stream_flags) {
if ((stream_flags & keyboard_stream_flag_lang1) != 0U) {

Check warning on line 2062 in src/platform/linux/uhid_backend.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "std::byte" for byte-oriented data manipulation.

See more on https://sonarcloud.io/project/issues?id=LizardByte_libvirtualhid&issues=AaC7h_Vk4jtq7QCm5YC9&open=AaC7h_Vk4jtq7QCm5YC9&pullRequest=101
return XK_Hangul;
}
if ((stream_flags & keyboard_stream_flag_lang2) != 0U) {

Check warning on line 2065 in src/platform/linux/uhid_backend.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "std::byte" for byte-oriented data manipulation.

See more on https://sonarcloud.io/project/issues?id=LizardByte_libvirtualhid&issues=AaC7h_Vk4jtq7QCm5YC-&open=AaC7h_Vk4jtq7QCm5YC-&pullRequest=101
return XK_Hangul_Hanja;
}

if (!uses_normalized_key_code) {
switch (key_code) {
case 0xDC:
return XK_yen;
case 0xE2:
return XK_backslash;
default:
break;
}
}

static constexpr std::array<std::pair<KeyboardKeyCode, KeySym>, 49> special_keysyms {{
{0x08, XK_BackSpace},
{0x09, XK_Tab},
{0x0D, XK_Return},
Expand All @@ -2046,7 +2088,10 @@
{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},
Expand Down Expand Up @@ -2081,6 +2126,7 @@
{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()) {
Expand Down Expand Up @@ -2169,7 +2215,7 @@
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, event.stream_flags);
if (keysym == NoSymbol) {
return OperationStatus::failure(ErrorCode::invalid_argument, "keyboard key code is not supported by XTest fallback");
}
Expand Down
6 changes: 3 additions & 3 deletions src/platform/windows/keylayout.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
44 changes: 39 additions & 5 deletions src/platform/windows/windows_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,30 @@ namespace lvh::detail {
}
}

/**
* @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 `MapVirtualKeyExW`.
*/
struct MappedScanCode {
WORD scan_code = 0;
bool extended = false;
};

MappedScanCode map_virtual_key_to_scan_code(KeyboardKeyCode key_code) {
const auto mapped = ::MapVirtualKeyExW(key_code, MAPVK_VK_TO_VSC_EX, nullptr);
if (mapped == 0U) {
return {};
}

const auto prefix = static_cast<UINT>((mapped >> 8U) & 0xFFU);
return {
.scan_code = static_cast<WORD>(mapped & 0xFFU),
.extended = prefix == 0xE0U || prefix == 0xE1U,
};
}

bool can_map_virtual_key_to_scan_code(KeyboardKeyCode key_code) {
return key_code != VK_LWIN && key_code != VK_RWIN && key_code != VK_PAUSE;
}
Expand Down Expand Up @@ -1272,23 +1296,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 = event.scan_code_extended;
} 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<WORD>(::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);
} 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) {
Expand Down
7 changes: 6 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND LIBVIRTUALHID_TEST_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/fixtures/linux_backend_test_hooks.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_linux_backend.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_linux_consumers.cpp")
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_linux_consumers.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_moonlight_special_key_mappings.cpp")

if(LIBVIRTUALHID_ENABLE_XTEST)
find_package(X11 QUIET)
Expand All @@ -68,6 +69,7 @@ elseif(WIN32)
"${CMAKE_CURRENT_SOURCE_DIR}/fixtures/windows_broker_client_test_hooks.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/fixtures/windows_broker_service_test_hooks.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/fixtures/windows_backend_test_hooks.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_windows_keyboard_scancodes.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_windows_backend.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_windows_broker_client.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/unit/test_windows_broker_service.cpp"
Expand Down Expand Up @@ -103,6 +105,9 @@ if(TARGET virtualhid_control_model)
endif()

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_include_directories(${TEST_BINARY}
PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../moonlight-qt/app")
target_link_libraries(${TEST_BINARY}
PRIVATE
PkgConfig::LIBEVDEV
Expand Down
18 changes: 10 additions & 8 deletions tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,16 @@ namespace lvh::detail::test {
*/
int linux_key_code(KeyboardKeyCode key_code);

/**
* @brief Translate a portable keyboard key code with normalization and stream flags.
*/
int linux_key_code_with_options(KeyboardKeyCode key_code, bool uses_normalized_key_code, std::uint8_t stream_flags);

/**
* @brief Translate a portable keyboard key code to an X11 keysym for XTest fallback.
*/
unsigned long linux_xtest_keysym(KeyboardKeyCode key_code, bool uses_normalized_key_code, std::uint8_t stream_flags);

/**
* @brief Translate a mouse button to a Linux input button code.
*
Expand Down Expand Up @@ -1220,14 +1230,6 @@ namespace lvh::detail::test {
*/
OperationStatus linux_xtest_mouse_create_query_failure();

/**
* @brief Translate a portable key code to an XTest keysym.
*
* @param key_code Portable keyboard key code.
* @return X11 keysym, or `0` when unsupported or XTest is disabled.
*/
unsigned long linux_xtest_keysym(KeyboardKeyCode key_code);

/**
* @brief Translate a mouse button to an XTest button code.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,13 @@ namespace lvh::detail::test {
WindowsOverlappedIoResult windows_backend_overlapped_device_io();
WindowsBackendSendInputResult windows_backend_send_input_devices();

struct WindowsVirtualKeyScanMapping {
std::uint16_t scan_code = 0;
bool extended = false;
};

WindowsVirtualKeyScanMapping windows_map_active_layout_scan_code(KeyboardKeyCode key_code);

WindowsSendInputRecord windows_submit_keyboard_event(const KeyboardEvent &event);

} // namespace lvh::detail::test
12 changes: 9 additions & 3 deletions tests/fixtures/linux_backend_test_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,11 @@ 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, 0);
}

int linux_key_code_with_options(KeyboardKeyCode key_code, bool uses_normalized_key_code, std::uint8_t stream_flags) {
return key_code_to_linux(key_code, uses_normalized_key_code, stream_flags);
}

int linux_mouse_button(MouseButton button) {
Expand Down Expand Up @@ -2606,11 +2610,13 @@ namespace lvh::detail::test {
#endif
}

unsigned long linux_xtest_keysym(KeyboardKeyCode key_code) {
unsigned long linux_xtest_keysym(KeyboardKeyCode key_code, bool uses_normalized_key_code, std::uint8_t stream_flags) {
#if defined(LIBVIRTUALHID_HAVE_XTEST)
return key_code_to_keysym(key_code);
return key_code_to_keysym(key_code, uses_normalized_key_code, stream_flags);
#else
static_cast<void>(key_code);
static_cast<void>(uses_normalized_key_code);
static_cast<void>(stream_flags);
return 0;
#endif
}
Expand Down
26 changes: 26 additions & 0 deletions tests/fixtures/windows_backend_test_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,32 @@ namespace lvh::detail {
return result;
}

WindowsVirtualKeyScanMapping windows_map_active_layout_scan_code(KeyboardKeyCode key_code) {
const auto mapped = map_virtual_key_to_scan_code(key_code);
return {
.scan_code = mapped.scan_code,
.extended = mapped.extended,
};
}

WindowsSendInputRecord windows_submit_keyboard_event(const KeyboardEvent &event) {
FakeSendInputState fake_send_input;
ScopedFakeSendInput scoped_send_input {fake_send_input};
WindowsBackend backend {nullptr, nullptr};

CreateKeyboardOptions keyboard_options;
keyboard_options.profile = profiles::keyboard();
if (const auto keyboard = backend.create_keyboard(99, keyboard_options); keyboard) {
static_cast<void>(keyboard.keyboard->submit(event));
}

if (fake_send_input.sent_inputs.empty()) {
return {};
}

return fake_send_input.sent_inputs.back();
}

} // namespace test

} // namespace lvh::detail
Loading