mirror of
https://github.com/LizardByte/libvirtualhid.git
synced 2026-11-09 13:34:44 +09:00
fix(windows): preserve extended media key translation (#218)
This commit is contained in:
1 parent
bf32e971b2
commit
9f4cac3fff
4 files changed
+191
-6
No files matched your search
@@ -486,6 +486,17 @@ namespace lvh::detail {
|
||||
pointer_info.ptPixelLocation = pointer_location(viewport, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Identify keys requiring the E0 prefix during Win32 and HID translation.
|
||||
*
|
||||
* Preserve the established navigation and modifier handling, and ask Windows
|
||||
* for the prefix of other keys so media, volume, and browser keys cannot be
|
||||
* mistaken for ordinary keys sharing the same scan code. The E1 prefix used
|
||||
* by Pause does not represent KEYEVENTF_EXTENDEDKEY.
|
||||
*
|
||||
* @param key_code Windows virtual-key code after any consumer remapping.
|
||||
* @return Whether the key requires E0 extended-key handling.
|
||||
*/
|
||||
bool extended_key(KeyboardKeyCode key_code) {
|
||||
switch (key_code) {
|
||||
case VK_LWIN:
|
||||
@@ -506,7 +517,7 @@ namespace lvh::detail {
|
||||
case VK_APPS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
return (::MapVirtualKeyW(key_code, MAPVK_VK_TO_VSC_EX) & 0xFF00U) == 0xE000U;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// standard includes
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -207,6 +208,16 @@ namespace lvh::detail::test {
|
||||
WindowsSendInputRecord keypad_enter_input;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Captured delivery of a sequence of Windows keyboard events.
|
||||
*/
|
||||
struct WindowsKeyboardTranslationResult {
|
||||
OperationStatus create_status; ///< Keyboard creation result.
|
||||
std::vector<OperationStatus> submit_statuses; ///< Result of each submitted event.
|
||||
std::vector<WindowsSendInputRecord> sent_inputs; ///< Intercepted Win32 input events.
|
||||
std::vector<std::vector<std::uint8_t>> reports; ///< Intercepted HID keyboard reports.
|
||||
};
|
||||
|
||||
struct WindowsMouseSendInputResult {
|
||||
OperationStatus relative_status;
|
||||
OperationStatus absolute_status;
|
||||
@@ -278,6 +289,18 @@ namespace lvh::detail::test {
|
||||
WindowsOverlappedIoResult windows_backend_overlapped_device_io();
|
||||
WindowsBackendSendInputResult windows_backend_send_input_devices();
|
||||
|
||||
/**
|
||||
* @brief Submit keyboard events while intercepting Win32 input and HID reports.
|
||||
*
|
||||
* @param events Keyboard events to translate without delivering input to the host.
|
||||
* @param use_hid Whether to use the fake HID control channel instead of the Win32 fallback.
|
||||
* @return Submission results and captured input.
|
||||
*/
|
||||
WindowsKeyboardTranslationResult windows_backend_translate_keyboard_events(
|
||||
std::span<const KeyboardEvent> events,
|
||||
bool use_hid
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Scale a source coordinate through a target viewport into a virtual desktop axis.
|
||||
*
|
||||
|
||||
+26
@@ -1135,6 +1135,32 @@ namespace lvh::detail {
|
||||
return result;
|
||||
}
|
||||
|
||||
WindowsKeyboardTranslationResult windows_backend_translate_keyboard_events(
|
||||
std::span<const KeyboardEvent> events,
|
||||
bool use_hid
|
||||
) {
|
||||
WindowsKeyboardTranslationResult result;
|
||||
FakeSendInputState send_input_state;
|
||||
ScopedFakeSendInput fake_send_input {send_input_state};
|
||||
auto command_state = std::make_shared<FakeWindowsControlChannelState>();
|
||||
auto backend = use_hid ?
|
||||
make_fake_windows_backend(command_state, std::make_shared<FakeWindowsControlChannelState>()) :
|
||||
std::make_unique<WindowsBackend>(nullptr, nullptr);
|
||||
|
||||
CreateKeyboardOptions options;
|
||||
options.profile = profiles::keyboard();
|
||||
auto created = backend->create_keyboard(1U, options);
|
||||
result.create_status = created.status;
|
||||
if (created) {
|
||||
for (const auto &event : events) {
|
||||
result.submit_statuses.push_back(created.keyboard->submit(event));
|
||||
}
|
||||
}
|
||||
result.sent_inputs = std::move(send_input_state.sent_inputs);
|
||||
result.reports = command_state->submit_reports();
|
||||
return result;
|
||||
}
|
||||
|
||||
WindowsBackendSendInputResult windows_backend_send_input_devices() {
|
||||
using enum MouseEventKind;
|
||||
|
||||
|
||||
@@ -24,17 +24,21 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// local includes
|
||||
// test includes
|
||||
#include "fixtures/fixtures.hpp"
|
||||
#include "fixtures/windows_backend_test_hooks.hpp"
|
||||
#include "lvh_windows_protocol.h"
|
||||
|
||||
// standard includes
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// lib includes
|
||||
#include <libvirtualhid/profiles.hpp>
|
||||
|
||||
// standard includes
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
// local includes
|
||||
#include "lvh_windows_protocol.h"
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
@@ -330,6 +334,127 @@ TEST_F(WindowsBackendTest, OverlappedDeviceIoUsesAnEventAndWaitsForPendingComple
|
||||
EXPECT_EQ(result.completion_failure_status.code(), lvh::ErrorCode::backend_failure);
|
||||
}
|
||||
|
||||
TEST_F(WindowsBackendTest, PreservesExtendedMediaAndBrowserKeysInSendInput) {
|
||||
// These scan codes overlap letters unless KEYEVENTF_EXTENDEDKEY accompanies them.
|
||||
const auto keys = std::to_array<std::pair<lvh::KeyboardKeyCode, std::uint16_t>>({
|
||||
{VK_BROWSER_BACK, 0x6AU},
|
||||
{VK_BROWSER_FORWARD, 0x69U},
|
||||
{VK_BROWSER_REFRESH, 0x67U},
|
||||
{VK_BROWSER_STOP, 0x68U},
|
||||
{VK_BROWSER_SEARCH, 0x65U},
|
||||
{VK_BROWSER_FAVORITES, 0x66U},
|
||||
{VK_BROWSER_HOME, 0x32U},
|
||||
{VK_VOLUME_MUTE, 0x20U},
|
||||
{VK_VOLUME_DOWN, 0x2EU},
|
||||
{VK_VOLUME_UP, 0x30U},
|
||||
{VK_MEDIA_NEXT_TRACK, 0x19U},
|
||||
{VK_MEDIA_PREV_TRACK, 0x10U},
|
||||
{VK_MEDIA_STOP, 0x24U},
|
||||
{VK_MEDIA_PLAY_PAUSE, 0x22U},
|
||||
{VK_LAUNCH_MAIL, 0x6CU},
|
||||
{VK_LAUNCH_MEDIA_SELECT, 0x6DU},
|
||||
{VK_LAUNCH_APP1, 0x6BU},
|
||||
{VK_LAUNCH_APP2, 0x21U},
|
||||
});
|
||||
for (const auto &[key, scan_code] : keys) {
|
||||
for (const bool normalized : {false, true}) {
|
||||
SCOPED_TRACE(key);
|
||||
SCOPED_TRACE(normalized);
|
||||
const std::array events {
|
||||
lvh::KeyboardEvent {.key_code = key, .pressed = true, .uses_normalized_key_code = normalized, .prefer_native_scan_code = true},
|
||||
lvh::KeyboardEvent {.key_code = key, .pressed = false, .uses_normalized_key_code = normalized, .prefer_native_scan_code = true},
|
||||
};
|
||||
const auto result = lvh::detail::test::windows_backend_translate_keyboard_events(events, false);
|
||||
expect_ok(result.create_status);
|
||||
ASSERT_EQ(result.submit_statuses.size(), 2U);
|
||||
ASSERT_EQ(result.sent_inputs.size(), 2U);
|
||||
for (std::size_t i = 0; i < events.size(); ++i) {
|
||||
expect_ok(result.submit_statuses[i]);
|
||||
const auto &input = result.sent_inputs[i];
|
||||
EXPECT_EQ(input.type, INPUT_KEYBOARD);
|
||||
EXPECT_EQ(input.virtual_key, 0U);
|
||||
EXPECT_EQ(input.scan_code, scan_code);
|
||||
EXPECT_EQ(input.key_flags, KEYEVENTF_SCANCODE | KEYEVENTF_EXTENDEDKEY | (i == 0U ? 0U : KEYEVENTF_KEYUP));
|
||||
EXPECT_EQ(::MapVirtualKeyW(0xE000U | input.scan_code, MAPVK_VSC_TO_VK_EX), key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WindowsBackendTest, KeepsOrdinaryKeysAndE1PauseDistinctFromE0Keys) {
|
||||
for (const auto key : std::to_array<lvh::KeyboardKeyCode>({'B', 'C', VK_RETURN, VK_PAUSE, VK_SNAPSHOT, VK_NUMLOCK})) {
|
||||
SCOPED_TRACE(key);
|
||||
const std::array events {
|
||||
lvh::KeyboardEvent {.key_code = static_cast<lvh::KeyboardKeyCode>(key), .pressed = true, .uses_normalized_key_code = true},
|
||||
lvh::KeyboardEvent {.key_code = static_cast<lvh::KeyboardKeyCode>(key), .pressed = false, .prefer_native_scan_code = true},
|
||||
};
|
||||
const auto result = lvh::detail::test::windows_backend_translate_keyboard_events(events, false);
|
||||
expect_ok(result.create_status);
|
||||
ASSERT_EQ(result.submit_statuses.size(), 2U);
|
||||
ASSERT_EQ(result.sent_inputs.size(), 2U);
|
||||
for (std::size_t i = 0; i < events.size(); ++i) {
|
||||
expect_ok(result.submit_statuses[i]);
|
||||
EXPECT_EQ(result.sent_inputs[i].key_flags & KEYEVENTF_EXTENDEDKEY, 0U);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WindowsBackendTest, PreservesVolumeKeysInHidKeyboardReports) {
|
||||
const auto keys = std::to_array<std::pair<lvh::KeyboardKeyCode, std::uint8_t>>({
|
||||
{VK_VOLUME_MUTE, 0x7FU},
|
||||
{VK_VOLUME_DOWN, 0x81U},
|
||||
{VK_VOLUME_UP, 0x80U},
|
||||
{'C', 0x06U},
|
||||
{'B', 0x05U},
|
||||
});
|
||||
for (const auto &[key, usage] : keys) {
|
||||
for (const bool normalized : {false, true}) {
|
||||
SCOPED_TRACE(key);
|
||||
SCOPED_TRACE(normalized);
|
||||
const std::array events {
|
||||
lvh::KeyboardEvent {.key_code = key, .pressed = true, .uses_normalized_key_code = normalized, .prefer_native_scan_code = true},
|
||||
lvh::KeyboardEvent {.key_code = key, .pressed = false, .uses_normalized_key_code = normalized, .prefer_native_scan_code = true},
|
||||
};
|
||||
const auto result = lvh::detail::test::windows_backend_translate_keyboard_events(events, true);
|
||||
expect_ok(result.create_status);
|
||||
ASSERT_EQ(result.submit_statuses.size(), 2U);
|
||||
for (const auto &status : result.submit_statuses) {
|
||||
expect_ok(status);
|
||||
}
|
||||
EXPECT_TRUE(result.sent_inputs.empty());
|
||||
std::vector<std::uint8_t> pressed_report(LVH_WINDOWS_KEYBOARD_INPUT_REPORT_SIZE, 0U);
|
||||
pressed_report[2U] = usage;
|
||||
const std::vector<std::uint8_t> released_report(LVH_WINDOWS_KEYBOARD_INPUT_REPORT_SIZE, 0U);
|
||||
EXPECT_EQ(result.reports, (std::vector<std::vector<std::uint8_t>> {pressed_report, released_report}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WindowsBackendTest, PreservesConsumerKeysWhenHidKeyboardFallsBackToSendInput) {
|
||||
for (const auto key : {VK_BROWSER_HOME, VK_MEDIA_NEXT_TRACK, VK_LAUNCH_APP2}) {
|
||||
for (const bool normalized : {false, true}) {
|
||||
SCOPED_TRACE(key);
|
||||
SCOPED_TRACE(normalized);
|
||||
const std::array events {
|
||||
lvh::KeyboardEvent {.key_code = static_cast<lvh::KeyboardKeyCode>(key), .pressed = true, .uses_normalized_key_code = normalized, .prefer_native_scan_code = true},
|
||||
lvh::KeyboardEvent {.key_code = static_cast<lvh::KeyboardKeyCode>(key), .pressed = false, .uses_normalized_key_code = normalized, .prefer_native_scan_code = true},
|
||||
};
|
||||
const auto result = lvh::detail::test::windows_backend_translate_keyboard_events(events, true);
|
||||
expect_ok(result.create_status);
|
||||
ASSERT_EQ(result.submit_statuses.size(), 2U);
|
||||
ASSERT_EQ(result.sent_inputs.size(), 2U);
|
||||
EXPECT_TRUE(result.reports.empty());
|
||||
for (std::size_t i = 0; i < events.size(); ++i) {
|
||||
expect_ok(result.submit_statuses[i]);
|
||||
const auto &input = result.sent_inputs[i];
|
||||
EXPECT_EQ(input.virtual_key, 0U);
|
||||
EXPECT_EQ(input.key_flags, KEYEVENTF_SCANCODE | KEYEVENTF_EXTENDEDKEY | (i == 0U ? 0U : KEYEVENTF_KEYUP));
|
||||
EXPECT_EQ(::MapVirtualKeyW(0xE000U | input.scan_code, MAPVK_VSC_TO_VK_EX), static_cast<UINT>(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WindowsBackendTest, SendInputDevicesTranslateKeyboardMouseFailuresAndUnsupportedProfiles) {
|
||||
const auto result = lvh::detail::test::windows_backend_send_input_devices();
|
||||
|
||||
|
||||
Reference in new issue
Block a user