feat(keyboard): support explicitly extended keys (#166)

This commit is contained in:
Dave Lane authored and GitHub committed 2026-09-27 17:58:52 -04:00
1 parent 52d788119a
commit 2db2cc2d7f
6 files changed
+93 -13

No files matched your search

+5
View File
@@ -31,6 +31,11 @@ a per-controller XUSB software device plus a correlated VHF child; other
profiles use the root control driver and Windows Virtual HID Framework.
Keyboard text input, absolute mouse input, and the keyboard and mouse fallbacks
use Win32 APIs.
Set `KeyboardEvent::extended` when the input source positively identifies an
extended key, such as keypad Enter. The Windows HID keyboard maps it to the
corresponding HID usage, and the Win32 fallback sets the extended-key input
flag. Leave it unset when the input source does not report this detail; existing
key-code and scan-code classification still applies.
The C++ library communicates with the driver through fixed-size protocol
structures and `DeviceIoControl`, not C++ STL types. This keeps the public API
+8
View File
@@ -748,6 +748,14 @@ namespace lvh {
* @brief Whether the backend should prefer a native scan-code translation when `scan_code` is not provided.
*/
bool prefer_native_scan_code = false;
/**
* @brief Whether this is an extended key, such as keypad Enter.
*
* A false value leaves backend key classification unchanged for clients
* that do not report extended keys.
*/
bool extended = false;
};
/**
+14 -12
View File
@@ -1650,7 +1650,7 @@ namespace lvh::detail {
input.ki.dwFlags |= KEYEVENTF_SCANCODE;
}
}
if (extended_key(event.key_code)) {
if (event.extended || extended_key(event.key_code)) {
input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
}
if (!event.pressed) {
@@ -1755,25 +1755,26 @@ namespace lvh::detail {
return profile;
}
bool hid_scan_code_is_extended(std::uint16_t scan_code, KeyboardKeyCode key_code) {
bool hid_scan_code_is_extended(std::uint16_t scan_code, KeyboardKeyCode key_code, bool extended) {
const auto prefix = scan_code & 0xFF00U;
return prefix == 0xE000U || prefix == 0xE100U || extended_key(key_code);
return extended || prefix == 0xE000U || prefix == 0xE100U || extended_key(key_code);
}
std::optional<unsigned> hid_modifier_bit_from_scan_code(
std::uint16_t scan_code,
KeyboardKeyCode key_code
KeyboardKeyCode key_code,
bool extended
) {
const auto extended = hid_scan_code_is_extended(scan_code, key_code);
const auto is_extended = hid_scan_code_is_extended(scan_code, key_code, extended);
switch (scan_code & 0xFFU) {
case 0x1DU:
return extended ? 4U : 0U;
return is_extended ? 4U : 0U;
case 0x2AU:
return 1U;
case 0x36U:
return 5U;
case 0x38U:
return extended ? 6U : 2U;
return is_extended ? 6U : 2U;
case 0x5BU:
return 3U;
case 0x5CU:
@@ -1828,7 +1829,8 @@ namespace lvh::detail {
std::optional<std::uint8_t> hid_usage_from_scan_code(
std::uint16_t scan_code,
KeyboardKeyCode key_code
KeyboardKeyCode key_code,
bool extended
) {
static constexpr auto extended_usages = std::to_array<HidUsageMapping<std::uint8_t>>({
{0x1CU, 0x58U}, // Keypad Enter
@@ -1927,7 +1929,7 @@ namespace lvh::detail {
});
const auto code = static_cast<std::uint8_t>(scan_code & 0xFFU);
if (hid_scan_code_is_extended(scan_code, key_code)) {
if (hid_scan_code_is_extended(scan_code, key_code, extended)) {
return mapped_hid_usage(code, extended_usages);
}
if (code >= 0x3BU && code <= 0x44U) {
@@ -2017,7 +2019,7 @@ namespace lvh::detail {
if (event.uses_normalized_key_code) {
return windows_us_english_scan_code(event.key_code);
}
if (event.prefer_native_scan_code && can_map_virtual_key_to_scan_code(event.key_code)) {
if ((event.prefer_native_scan_code || event.extended) && can_map_virtual_key_to_scan_code(event.key_code)) {
return static_cast<std::uint16_t>(::MapVirtualKeyW(event.key_code, MAPVK_VK_TO_VSC));
}
return 0U;
@@ -2025,14 +2027,14 @@ namespace lvh::detail {
std::optional<unsigned> hid_keyboard_modifier_bit(const KeyboardEvent &event) {
if (const auto scan_code = hid_keyboard_scan_code(event); scan_code != 0U) {
return hid_modifier_bit_from_scan_code(scan_code, event.key_code);
return hid_modifier_bit_from_scan_code(scan_code, event.key_code, event.extended);
}
return hid_modifier_bit_from_virtual_key(event.key_code);
}
std::optional<std::uint8_t> hid_keyboard_usage(const KeyboardEvent &event) {
if (const auto scan_code = hid_keyboard_scan_code(event); scan_code != 0U) {
return hid_usage_from_scan_code(scan_code, event.key_code);
return hid_usage_from_scan_code(scan_code, event.key_code, event.extended);
}
return hid_usage_from_virtual_key(event.key_code);
}
@@ -93,6 +93,10 @@ namespace lvh::detail::test {
OperationStatus modifier_press_status;
OperationStatus extended_press_status;
OperationStatus letter_release_status;
OperationStatus enter_press_status;
OperationStatus enter_release_status;
OperationStatus keypad_enter_press_status;
OperationStatus keypad_enter_release_status;
OperationStatus unmapped_submit_status;
OperationStatus text_status;
OperationStatus close_status;
@@ -195,8 +199,12 @@ namespace lvh::detail::test {
OperationStatus invalid_text_status;
OperationStatus failure_status;
OperationStatus normalized_status;
OperationStatus enter_status;
OperationStatus keypad_enter_status;
OperationStatus invalid_profile_status;
WindowsSendInputRecord normalized_input;
WindowsSendInputRecord enter_input;
WindowsSendInputRecord keypad_enter_input;
};
struct WindowsMouseSendInputResult {
+41
View File
@@ -703,6 +703,26 @@ namespace lvh::detail {
.scan_code = 0x4DU,
});
result.operations.letter_release_status = created.keyboard->submit({.key_code = 'A', .pressed = false});
result.operations.enter_press_status = created.keyboard->submit({
.key_code = VK_RETURN,
.pressed = true,
.uses_normalized_key_code = true,
});
result.operations.enter_release_status = created.keyboard->submit({
.key_code = VK_RETURN,
.pressed = false,
.uses_normalized_key_code = true,
});
result.operations.keypad_enter_press_status = created.keyboard->submit({
.key_code = VK_RETURN,
.pressed = true,
.extended = true,
});
result.operations.keypad_enter_release_status = created.keyboard->submit({
.key_code = VK_RETURN,
.pressed = false,
.extended = true,
});
result.operations.unmapped_submit_status =
created.keyboard->submit({.key_code = VK_BROWSER_BACK, .pressed = true});
result.operations.text_status = created.keyboard->type_text({.text = "A"});
@@ -1129,6 +1149,27 @@ namespace lvh::detail {
result.keyboard.normalized_input = fake_send_input.sent_inputs.back();
fake_send_input.sent_inputs.resize(prior_input_count);
}
result.keyboard.enter_status = keyboard.keyboard->submit({
.key_code = VK_RETURN,
.pressed = true,
.uses_normalized_key_code = true,
});
if (fake_send_input.sent_inputs.size() > prior_input_count) {
result.keyboard.enter_input = fake_send_input.sent_inputs.back();
fake_send_input.sent_inputs.resize(prior_input_count);
}
result.keyboard.keypad_enter_status = keyboard.keyboard->submit({
.key_code = VK_RETURN,
.pressed = true,
.uses_normalized_key_code = true,
.extended = true,
});
if (fake_send_input.sent_inputs.size() > prior_input_count) {
result.keyboard.keypad_enter_input = fake_send_input.sent_inputs.back();
fake_send_input.sent_inputs.resize(prior_input_count);
}
} else {
result.keyboard.down_status = keyboard.status;
}
+17 -1
View File
@@ -220,10 +220,14 @@ TEST_F(WindowsBackendTest, HidKeyboardPreservesIdentityTracksStateAndUsesNarrowF
expect_ok(result.operations.modifier_press_status);
expect_ok(result.operations.extended_press_status);
expect_ok(result.operations.letter_release_status);
expect_ok(result.operations.enter_press_status);
expect_ok(result.operations.enter_release_status);
expect_ok(result.operations.keypad_enter_press_status);
expect_ok(result.operations.keypad_enter_release_status);
expect_ok(result.operations.unmapped_submit_status);
expect_ok(result.operations.text_status);
expect_ok(result.operations.close_status);
ASSERT_EQ(result.observations.reports.size(), 4U);
ASSERT_EQ(result.observations.reports.size(), 8U);
for (const auto &report : result.observations.reports) {
EXPECT_EQ(report.size(), LVH_WINDOWS_KEYBOARD_INPUT_REPORT_SIZE);
}
@@ -236,6 +240,12 @@ TEST_F(WindowsBackendTest, HidKeyboardPreservesIdentityTracksStateAndUsesNarrowF
EXPECT_EQ(result.observations.reports[2][3], 0x4FU);
EXPECT_EQ(result.observations.reports[3][0], 0x02U);
EXPECT_EQ(result.observations.reports[3][2], 0x4FU);
EXPECT_EQ(result.observations.reports[4][2], 0x28U);
EXPECT_EQ(result.observations.reports[4][3], 0x4FU);
EXPECT_EQ(result.observations.reports[5][2], 0x4FU);
EXPECT_EQ(result.observations.reports[6][2], 0x4FU);
EXPECT_EQ(result.observations.reports[6][3], 0x58U);
EXPECT_EQ(result.observations.reports[7][2], 0x4FU);
EXPECT_EQ(result.observations.destroy_requests, 1U);
EXPECT_EQ(result.observations.fallback_send_inputs, 3U);
@@ -334,6 +344,12 @@ TEST_F(WindowsBackendTest, SendInputDevicesTranslateKeyboardMouseFailuresAndUnsu
EXPECT_EQ(result.keyboard.normalized_input.virtual_key, 0U);
EXPECT_EQ(result.keyboard.normalized_input.scan_code, 30U);
EXPECT_EQ(result.keyboard.normalized_input.key_flags, KEYEVENTF_SCANCODE);
expect_ok(result.keyboard.enter_status);
expect_ok(result.keyboard.keypad_enter_status);
EXPECT_EQ(result.keyboard.enter_input.scan_code, 0x1CU);
EXPECT_EQ(result.keyboard.enter_input.key_flags, KEYEVENTF_SCANCODE);
EXPECT_EQ(result.keyboard.keypad_enter_input.scan_code, 0x1CU);
EXPECT_EQ(result.keyboard.keypad_enter_input.key_flags, KEYEVENTF_SCANCODE | KEYEVENTF_EXTENDEDKEY);
expect_ok(result.mouse.relative_status);
expect_ok(result.mouse.absolute_status);