mirror of
https://github.com/LizardByte/libvirtualhid.git
synced 2026-11-02 18:16:44 +09:00
feat(windows): add keyboard/mouse support and expose global UMDF control-device path (#34)
This commit is contained in:
1 parent
0d704b0c6d
commit
f06302f841
11 files changed
+629
-34
No files matched your search
@@ -99,13 +99,16 @@ be compiler-neutral: prefer a stable C ABI, named pipe, device interface IOCTL,
|
||||
or similar control channel over passing C++ STL types across that boundary.
|
||||
|
||||
The current Windows backend selects a UMDF control-channel implementation for
|
||||
`BackendKind::platform_default`. It probes `\\.\LibVirtualHid`, reports
|
||||
`requires_installed_driver = true`, and only advertises gamepad/output-report
|
||||
support when the driver package is installed and the control device can be
|
||||
opened. The client library stays buildable with MSVC and MinGW/UCRT64 because
|
||||
the backend talks to the driver through fixed-size C protocol structures and
|
||||
Win32 `DeviceIoControl` calls. The default control device path can be overridden
|
||||
for diagnostics with `LIBVIRTUALHID_WINDOWS_CONTROL_DEVICE`.
|
||||
`BackendKind::platform_default`. It always exposes keyboard and mouse through
|
||||
Win32 `SendInput`, then probes `\\.\LibVirtualHid` for descriptor-driven virtual
|
||||
gamepads. It reports `requires_installed_driver = true`, and only advertises
|
||||
gamepad/output-report support when the driver package is installed and the
|
||||
control device can be opened. Touchscreen, trackpad, and pen tablet support are
|
||||
not implemented in the Windows backend yet. The client library stays buildable
|
||||
with MSVC and MinGW/UCRT64 because the gamepad path talks to the driver through
|
||||
fixed-size C protocol structures and Win32 `DeviceIoControl` calls. The default
|
||||
control device path can be overridden for diagnostics with
|
||||
`LIBVIRTUALHID_WINDOWS_CONTROL_DEVICE`.
|
||||
|
||||
The UMDF driver uses Windows Virtual HID Framework (VHF) for OS-visible gamepad
|
||||
devices. Create requests start a VHF child device from the requested descriptor,
|
||||
|
||||
+9
-2
@@ -12,8 +12,15 @@ target_sources(${PROJECT_NAME}
|
||||
|
||||
if(LIBVIRTUALHID_USES_THREADS)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBEVDEV REQUIRED IMPORTED_TARGET libevdev)
|
||||
if(LIBEVDEV_CUSTOM_INCLUDE_DIR AND LIBEVDEV_CUSTOM_LIBRARY)
|
||||
add_library(PkgConfig::LIBEVDEV UNKNOWN IMPORTED)
|
||||
set_target_properties(PkgConfig::LIBEVDEV PROPERTIES
|
||||
IMPORTED_LOCATION "${LIBEVDEV_CUSTOM_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LIBEVDEV_CUSTOM_INCLUDE_DIR}")
|
||||
else()
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBEVDEV REQUIRED IMPORTED_TARGET libevdev)
|
||||
endif()
|
||||
if(LIBVIRTUALHID_ENABLE_XTEST)
|
||||
find_package(X11 QUIET)
|
||||
endif()
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
namespace lvh::detail::windows {
|
||||
|
||||
inline constexpr std::string_view default_control_device_path {LVH_WINDOWS_CONTROL_DEVICE_PATH};
|
||||
inline constexpr std::string_view global_control_device_path {LVH_WINDOWS_GLOBAL_CONTROL_DEVICE_PATH};
|
||||
|
||||
inline std::uint32_t gamepad_flags(const GamepadProfileCapabilities &capabilities) {
|
||||
std::uint32_t flags = 0;
|
||||
|
||||
@@ -53,6 +53,7 @@ EVT_VHF_ASYNC_OPERATION LvhEvtVhfWriteReport;
|
||||
namespace {
|
||||
|
||||
constexpr auto symbolic_link_name = L"\\DosDevices\\LibVirtualHid";
|
||||
constexpr auto global_symbolic_link_name = L"\\DosDevices\\Global\\LibVirtualHid";
|
||||
|
||||
struct DeviceRecord {
|
||||
std::mutex mutex;
|
||||
@@ -472,12 +473,15 @@ NTSTATUS LvhEvtDeviceAdd(WDFDRIVER driver, PWDFDEVICE_INIT device_init) {
|
||||
}
|
||||
|
||||
UNICODE_STRING symbolic_link;
|
||||
RtlInitUnicodeString(&symbolic_link, symbolic_link_name);
|
||||
RtlInitUnicodeString(&symbolic_link, global_symbolic_link_name);
|
||||
status = WdfDeviceCreateSymbolicLink(device, &symbolic_link);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
RtlInitUnicodeString(&symbolic_link, symbolic_link_name);
|
||||
static_cast<void>(WdfDeviceCreateSymbolicLink(device, &symbolic_link));
|
||||
|
||||
status = initialize_vhf_target(device);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
return status;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
inline constexpr uint32_t LVH_WINDOWS_CONTROL_PROTOCOL_VERSION = 1u;
|
||||
inline constexpr char LVH_WINDOWS_CONTROL_DEVICE_PATH[] = R"(\\.\LibVirtualHid)";
|
||||
inline constexpr char LVH_WINDOWS_GLOBAL_CONTROL_DEVICE_PATH[] = R"(\\.\Global\LibVirtualHid)";
|
||||
|
||||
inline constexpr uint32_t LVH_WINDOWS_MAX_REPORT_DESCRIPTOR_SIZE = 1024u;
|
||||
inline constexpr uint32_t LVH_WINDOWS_MAX_INPUT_REPORT_SIZE = 256u;
|
||||
@@ -161,6 +162,7 @@ enum {
|
||||
};
|
||||
|
||||
static const char LVH_WINDOWS_CONTROL_DEVICE_PATH[] = "\\\\.\\LibVirtualHid";
|
||||
static const char LVH_WINDOWS_GLOBAL_CONTROL_DEVICE_PATH[] = "\\\\.\\Global\\LibVirtualHid";
|
||||
|
||||
static const uint32_t LVH_WINDOWS_IOCTL_CREATE_GAMEPAD = 0x8000E000u;
|
||||
static const uint32_t LVH_WINDOWS_IOCTL_DESTROY_DEVICE = 0x8000E004u;
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -44,6 +47,20 @@ namespace lvh::detail {
|
||||
class WindowsBackendContext;
|
||||
|
||||
using UniqueHandle = std::unique_ptr<void, decltype(&::CloseHandle)>;
|
||||
using SendInputFunction = std::function<UINT(std::span<INPUT>)>;
|
||||
|
||||
UINT send_input_with_win32(std::span<INPUT> inputs) {
|
||||
return ::SendInput(
|
||||
static_cast<UINT>(inputs.size()),
|
||||
inputs.data(),
|
||||
static_cast<int>(sizeof(INPUT))
|
||||
);
|
||||
}
|
||||
|
||||
SendInputFunction &send_input_function() {
|
||||
static SendInputFunction function = send_input_with_win32;
|
||||
return function;
|
||||
}
|
||||
|
||||
UniqueHandle make_unique_handle(HANDLE handle) {
|
||||
return {handle, &::CloseHandle};
|
||||
@@ -82,17 +99,80 @@ namespace lvh::detail {
|
||||
return OperationStatus::failure(code, message.str());
|
||||
}
|
||||
|
||||
std::string resolve_control_device_path() {
|
||||
OperationStatus send_input(std::span<INPUT> inputs, std::string_view operation) {
|
||||
using enum ErrorCode;
|
||||
|
||||
if (const auto sent = send_input_function()(inputs); sent != static_cast<UINT>(inputs.size())) {
|
||||
return windows_failure(backend_failure, operation, ::GetLastError());
|
||||
}
|
||||
|
||||
return OperationStatus::success();
|
||||
}
|
||||
|
||||
OperationStatus send_input(const INPUT &input, std::string_view operation) {
|
||||
std::array inputs {input};
|
||||
return send_input(std::span<INPUT> {inputs}, operation);
|
||||
}
|
||||
|
||||
DWORD mouse_button_flags(MouseButton button, bool pressed) {
|
||||
switch (button) {
|
||||
using enum MouseButton;
|
||||
|
||||
case left:
|
||||
return pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
|
||||
case middle:
|
||||
return pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
|
||||
case right:
|
||||
return pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
|
||||
case side:
|
||||
case extra:
|
||||
return pressed ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD mouse_button_data(MouseButton button) {
|
||||
switch (button) {
|
||||
using enum MouseButton;
|
||||
|
||||
case side:
|
||||
return XBUTTON1;
|
||||
case extra:
|
||||
return XBUTTON2;
|
||||
case left:
|
||||
case middle:
|
||||
case right:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LONG scale_absolute_axis(std::int32_t value, std::int32_t dimension) {
|
||||
if (dimension <= 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto clamped = std::clamp(value, 0, dimension);
|
||||
const auto numerator = static_cast<std::int64_t>(clamped) * std::numeric_limits<std::uint16_t>::max();
|
||||
return static_cast<LONG>(numerator / dimension);
|
||||
}
|
||||
|
||||
std::vector<std::string> resolve_control_device_paths() {
|
||||
constexpr auto environment_name = "LIBVIRTUALHID_WINDOWS_CONTROL_DEVICE";
|
||||
if (const auto required_size = ::GetEnvironmentVariableA(environment_name, nullptr, 0); required_size > 1U) {
|
||||
std::string path(required_size - 1U, '\0');
|
||||
const auto copied_size = ::GetEnvironmentVariableA(environment_name, path.data(), required_size);
|
||||
if (copied_size > 0U && copied_size < required_size) {
|
||||
return path;
|
||||
return {path};
|
||||
}
|
||||
}
|
||||
|
||||
return std::string {windows::default_control_device_path};
|
||||
return {
|
||||
std::string {windows::default_control_device_path},
|
||||
std::string {windows::global_control_device_path},
|
||||
};
|
||||
}
|
||||
|
||||
OperationStatus protocol_status(std::uint32_t status, std::string_view operation) {
|
||||
@@ -371,6 +451,16 @@ namespace lvh::detail {
|
||||
UniqueHandle handle_;
|
||||
};
|
||||
|
||||
std::unique_ptr<WindowsControlChannel> open_control_channel() {
|
||||
for (const auto &path : resolve_control_device_paths()) {
|
||||
if (auto channel = Win32WindowsControlChannel::open(path)) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class WindowsGamepadState {
|
||||
public:
|
||||
WindowsGamepadState(
|
||||
@@ -593,12 +683,147 @@ namespace lvh::detail {
|
||||
return context_->close_gamepad(state_);
|
||||
}
|
||||
|
||||
class WindowsKeyboard final: public BackendKeyboard {
|
||||
public:
|
||||
OperationStatus submit(const KeyboardEvent &event) override {
|
||||
using enum ErrorCode;
|
||||
|
||||
if (!open_) {
|
||||
return OperationStatus::failure(device_closed, "Windows keyboard is closed");
|
||||
}
|
||||
|
||||
INPUT input {};
|
||||
input.type = INPUT_KEYBOARD;
|
||||
input.ki.wVk = event.key_code;
|
||||
if (!event.pressed) {
|
||||
input.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
}
|
||||
|
||||
return send_input(input, "submit Windows keyboard input");
|
||||
}
|
||||
|
||||
OperationStatus type_text(const KeyboardTextEvent &event) override {
|
||||
using enum ErrorCode;
|
||||
|
||||
if (!open_) {
|
||||
return OperationStatus::failure(device_closed, "Windows keyboard is closed");
|
||||
}
|
||||
if (event.text.empty()) {
|
||||
return OperationStatus::success();
|
||||
}
|
||||
|
||||
const auto required_size = ::MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
MB_ERR_INVALID_CHARS,
|
||||
event.text.data(),
|
||||
static_cast<int>(event.text.size()),
|
||||
nullptr,
|
||||
0
|
||||
);
|
||||
if (required_size <= 0) {
|
||||
return windows_failure(invalid_argument, "convert UTF-8 text for Windows keyboard input", ::GetLastError());
|
||||
}
|
||||
|
||||
std::vector<WCHAR> wide_text(static_cast<std::size_t>(required_size));
|
||||
const auto converted_size = ::MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
MB_ERR_INVALID_CHARS,
|
||||
event.text.data(),
|
||||
static_cast<int>(event.text.size()),
|
||||
wide_text.data(),
|
||||
required_size
|
||||
);
|
||||
if (converted_size <= 0) {
|
||||
return windows_failure(invalid_argument, "convert UTF-8 text for Windows keyboard input", ::GetLastError());
|
||||
}
|
||||
|
||||
std::vector<INPUT> inputs;
|
||||
inputs.reserve(static_cast<std::size_t>(converted_size) * 2U);
|
||||
for (const auto character : wide_text) {
|
||||
INPUT input {};
|
||||
input.type = INPUT_KEYBOARD;
|
||||
input.ki.wScan = character;
|
||||
input.ki.dwFlags = KEYEVENTF_UNICODE;
|
||||
inputs.push_back(input);
|
||||
}
|
||||
for (const auto character : wide_text) {
|
||||
INPUT input {};
|
||||
input.type = INPUT_KEYBOARD;
|
||||
input.ki.wScan = character;
|
||||
input.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
|
||||
inputs.push_back(input);
|
||||
}
|
||||
|
||||
return send_input(std::span<INPUT> {inputs}, "submit Windows keyboard text input");
|
||||
}
|
||||
|
||||
OperationStatus close() override {
|
||||
open_ = false;
|
||||
return OperationStatus::success();
|
||||
}
|
||||
|
||||
private:
|
||||
bool open_ = true;
|
||||
};
|
||||
|
||||
class WindowsMouse final: public BackendMouse {
|
||||
public:
|
||||
OperationStatus submit(const MouseEvent &event) override {
|
||||
using enum ErrorCode;
|
||||
|
||||
if (!open_) {
|
||||
return OperationStatus::failure(device_closed, "Windows mouse is closed");
|
||||
}
|
||||
|
||||
INPUT input {};
|
||||
input.type = INPUT_MOUSE;
|
||||
auto &mouse = input.mi;
|
||||
|
||||
switch (event.kind) {
|
||||
using enum MouseEventKind;
|
||||
|
||||
case relative_motion:
|
||||
mouse.dwFlags = MOUSEEVENTF_MOVE;
|
||||
mouse.dx = event.x;
|
||||
mouse.dy = event.y;
|
||||
break;
|
||||
case absolute_motion:
|
||||
mouse.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
|
||||
mouse.dx = scale_absolute_axis(event.x, event.width);
|
||||
mouse.dy = scale_absolute_axis(event.y, event.height);
|
||||
break;
|
||||
case button:
|
||||
mouse.dwFlags = mouse_button_flags(event.button, event.pressed);
|
||||
mouse.mouseData = mouse_button_data(event.button);
|
||||
break;
|
||||
case vertical_scroll:
|
||||
mouse.dwFlags = MOUSEEVENTF_WHEEL;
|
||||
mouse.mouseData = static_cast<DWORD>(event.high_resolution_scroll);
|
||||
break;
|
||||
case horizontal_scroll:
|
||||
mouse.dwFlags = MOUSEEVENTF_HWHEEL;
|
||||
mouse.mouseData = static_cast<DWORD>(event.high_resolution_scroll);
|
||||
break;
|
||||
}
|
||||
|
||||
return send_input(input, "submit Windows mouse input");
|
||||
}
|
||||
|
||||
OperationStatus close() override {
|
||||
open_ = false;
|
||||
return OperationStatus::success();
|
||||
}
|
||||
|
||||
private:
|
||||
bool open_ = true;
|
||||
};
|
||||
|
||||
class WindowsBackend final: public Backend {
|
||||
public:
|
||||
WindowsBackend():
|
||||
WindowsBackend(
|
||||
Win32WindowsControlChannel::open(resolve_control_device_path()),
|
||||
Win32WindowsControlChannel::open(resolve_control_device_path())
|
||||
open_control_channel(),
|
||||
open_control_channel()
|
||||
) {}
|
||||
|
||||
WindowsBackend(
|
||||
@@ -607,6 +832,8 @@ namespace lvh::detail {
|
||||
) {
|
||||
capabilities_.backend_name = "windows-umdf";
|
||||
capabilities_.requires_installed_driver = true;
|
||||
capabilities_.supports_keyboard = true;
|
||||
capabilities_.supports_mouse = true;
|
||||
|
||||
if (!command_channel || !event_channel) {
|
||||
return;
|
||||
@@ -656,43 +883,51 @@ namespace lvh::detail {
|
||||
|
||||
BackendKeyboardCreationResult create_keyboard(
|
||||
DeviceId /*id*/,
|
||||
const CreateKeyboardOptions & /*options*/
|
||||
const CreateKeyboardOptions &options
|
||||
) override {
|
||||
return {unsupported_device_status(), nullptr};
|
||||
if (options.profile.device_type != DeviceType::keyboard) {
|
||||
return {unsupported_device_status("Windows keyboard backend requires a keyboard profile"), nullptr};
|
||||
}
|
||||
|
||||
return {OperationStatus::success(), std::make_unique<WindowsKeyboard>()};
|
||||
}
|
||||
|
||||
BackendMouseCreationResult create_mouse(DeviceId /*id*/, const CreateMouseOptions & /*options*/) override {
|
||||
return {unsupported_device_status(), nullptr};
|
||||
BackendMouseCreationResult create_mouse(DeviceId /*id*/, const CreateMouseOptions &options) override {
|
||||
if (options.profile.device_type != DeviceType::mouse) {
|
||||
return {unsupported_device_status("Windows mouse backend requires a mouse profile"), nullptr};
|
||||
}
|
||||
|
||||
return {OperationStatus::success(), std::make_unique<WindowsMouse>()};
|
||||
}
|
||||
|
||||
BackendTouchscreenCreationResult create_touchscreen(
|
||||
DeviceId /*id*/,
|
||||
const CreateTouchscreenOptions & /*options*/
|
||||
) override {
|
||||
return {unsupported_device_status(), nullptr};
|
||||
return {unsupported_device_status("Windows backend currently supports gamepad, keyboard, and mouse devices only"), nullptr};
|
||||
}
|
||||
|
||||
BackendTrackpadCreationResult create_trackpad(
|
||||
DeviceId /*id*/,
|
||||
const CreateTrackpadOptions & /*options*/
|
||||
) override {
|
||||
return {unsupported_device_status(), nullptr};
|
||||
return {unsupported_device_status("Windows backend currently supports gamepad, keyboard, and mouse devices only"), nullptr};
|
||||
}
|
||||
|
||||
BackendPenTabletCreationResult create_pen_tablet(
|
||||
DeviceId /*id*/,
|
||||
const CreatePenTabletOptions & /*options*/
|
||||
) override {
|
||||
return {unsupported_device_status(), nullptr};
|
||||
return {unsupported_device_status("Windows backend currently supports gamepad, keyboard, and mouse devices only"), nullptr};
|
||||
}
|
||||
|
||||
private:
|
||||
static OperationStatus unsupported_device_status() {
|
||||
static OperationStatus unsupported_device_status(std::string message) {
|
||||
using enum ErrorCode;
|
||||
|
||||
return OperationStatus::failure(
|
||||
unsupported_profile,
|
||||
"Windows MVP backend currently supports gamepad devices only"
|
||||
std::move(message)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,21 +37,80 @@ namespace lvh::detail::test {
|
||||
OperationStatus backend_failure_status;
|
||||
OperationStatus transport_failure_status;
|
||||
OperationStatus unavailable_status;
|
||||
OperationStatus oversized_descriptor_status;
|
||||
OperationStatus oversized_input_report_status;
|
||||
OperationStatus oversized_output_report_status;
|
||||
OperationStatus empty_nodes_create_status;
|
||||
OperationStatus oversized_submit_status;
|
||||
std::vector<DeviceNode> empty_device_nodes;
|
||||
};
|
||||
|
||||
struct WindowsBackendUtilityResult {
|
||||
std::string default_device_path;
|
||||
std::string custom_device_path;
|
||||
std::vector<std::string> default_device_paths;
|
||||
std::vector<std::string> custom_device_paths;
|
||||
OperationStatus formatted_error_status;
|
||||
OperationStatus fallback_error_status;
|
||||
OperationStatus keyboard_create_status;
|
||||
OperationStatus keyboard_close_status;
|
||||
OperationStatus keyboard_submit_after_close_status;
|
||||
OperationStatus mouse_create_status;
|
||||
OperationStatus mouse_close_status;
|
||||
OperationStatus mouse_submit_after_close_status;
|
||||
bool timeout_result = true;
|
||||
};
|
||||
|
||||
struct WindowsSendInputRecord {
|
||||
std::uint32_t type = 0;
|
||||
std::uint16_t virtual_key = 0;
|
||||
std::uint16_t scan_code = 0;
|
||||
std::uint32_t key_flags = 0;
|
||||
std::int32_t mouse_x = 0;
|
||||
std::int32_t mouse_y = 0;
|
||||
std::uint32_t mouse_data = 0;
|
||||
std::uint32_t mouse_flags = 0;
|
||||
};
|
||||
|
||||
struct WindowsKeyboardSendInputResult {
|
||||
OperationStatus down_status;
|
||||
OperationStatus up_status;
|
||||
OperationStatus text_status;
|
||||
OperationStatus empty_text_status;
|
||||
OperationStatus invalid_text_status;
|
||||
OperationStatus failure_status;
|
||||
OperationStatus invalid_profile_status;
|
||||
};
|
||||
|
||||
struct WindowsMouseSendInputResult {
|
||||
OperationStatus relative_status;
|
||||
OperationStatus absolute_status;
|
||||
OperationStatus degenerate_absolute_status;
|
||||
OperationStatus left_button_status;
|
||||
OperationStatus middle_button_status;
|
||||
OperationStatus right_button_status;
|
||||
OperationStatus side_button_status;
|
||||
OperationStatus extra_button_status;
|
||||
OperationStatus vertical_scroll_status;
|
||||
OperationStatus horizontal_scroll_status;
|
||||
OperationStatus failure_status;
|
||||
OperationStatus invalid_profile_status;
|
||||
};
|
||||
|
||||
struct WindowsUnsupportedInputResult {
|
||||
OperationStatus touchscreen_status;
|
||||
OperationStatus trackpad_status;
|
||||
OperationStatus pen_tablet_status;
|
||||
};
|
||||
|
||||
struct WindowsBackendSendInputResult {
|
||||
WindowsKeyboardSendInputResult keyboard;
|
||||
WindowsMouseSendInputResult mouse;
|
||||
WindowsUnsupportedInputResult unsupported;
|
||||
std::vector<WindowsSendInputRecord> sent_inputs;
|
||||
};
|
||||
|
||||
WindowsBackendLifecycleResult windows_backend_fake_channel_lifecycle();
|
||||
WindowsBackendFailureResult windows_backend_fake_channel_failures();
|
||||
WindowsBackendUtilityResult windows_backend_fake_channel_utilities();
|
||||
WindowsBackendSendInputResult windows_backend_send_input_devices();
|
||||
|
||||
} // namespace lvh::detail::test
|
||||
+183
-2
@@ -230,6 +230,63 @@ namespace lvh::detail {
|
||||
return backend->create_gamepad(1, options).status;
|
||||
}
|
||||
|
||||
struct FakeSendInputState {
|
||||
std::vector<test::WindowsSendInputRecord> sent_inputs;
|
||||
std::optional<UINT> forced_sent_count;
|
||||
};
|
||||
|
||||
test::WindowsSendInputRecord make_send_input_record(const INPUT &input) {
|
||||
test::WindowsSendInputRecord record;
|
||||
record.type = input.type;
|
||||
if (input.type == INPUT_KEYBOARD) {
|
||||
record.virtual_key = input.ki.wVk;
|
||||
record.scan_code = input.ki.wScan;
|
||||
record.key_flags = input.ki.dwFlags;
|
||||
} else if (input.type == INPUT_MOUSE) {
|
||||
record.mouse_x = input.mi.dx;
|
||||
record.mouse_y = input.mi.dy;
|
||||
record.mouse_data = input.mi.mouseData;
|
||||
record.mouse_flags = input.mi.dwFlags;
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
UINT fake_send_input(FakeSendInputState &state, std::span<INPUT> inputs) {
|
||||
for (const auto &input : inputs) {
|
||||
state.sent_inputs.push_back(make_send_input_record(input));
|
||||
}
|
||||
|
||||
if (state.forced_sent_count.has_value()) {
|
||||
::SetLastError(ERROR_ACCESS_DENIED);
|
||||
return *state.forced_sent_count;
|
||||
}
|
||||
|
||||
return static_cast<UINT>(inputs.size());
|
||||
}
|
||||
|
||||
class ScopedFakeSendInput {
|
||||
public:
|
||||
explicit ScopedFakeSendInput(FakeSendInputState &state) {
|
||||
previous_function_ = send_input_function();
|
||||
send_input_function() = [&state](std::span<INPUT> inputs) {
|
||||
return fake_send_input(state, inputs);
|
||||
};
|
||||
}
|
||||
|
||||
ScopedFakeSendInput(const ScopedFakeSendInput &) = delete;
|
||||
ScopedFakeSendInput &operator=(const ScopedFakeSendInput &) = delete;
|
||||
ScopedFakeSendInput(ScopedFakeSendInput &&) noexcept = delete;
|
||||
ScopedFakeSendInput &operator=(ScopedFakeSendInput &&) noexcept = delete;
|
||||
|
||||
~ScopedFakeSendInput() {
|
||||
send_input_function() = std::move(previous_function_);
|
||||
}
|
||||
|
||||
private:
|
||||
SendInputFunction previous_function_ = send_input_with_win32;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace test {
|
||||
@@ -304,6 +361,18 @@ namespace lvh::detail {
|
||||
CreateGamepadOptions options;
|
||||
options.profile = profiles::xbox_360();
|
||||
result.unavailable_status = backend.create_gamepad(3, options).status;
|
||||
|
||||
auto oversized_descriptor_options = options;
|
||||
oversized_descriptor_options.profile.report_descriptor.assign(LVH_WINDOWS_MAX_REPORT_DESCRIPTOR_SIZE + 1U, 0x01);
|
||||
result.oversized_descriptor_status = backend.create_gamepad(17, oversized_descriptor_options).status;
|
||||
|
||||
auto oversized_input_options = options;
|
||||
oversized_input_options.profile.input_report_size = LVH_WINDOWS_MAX_INPUT_REPORT_SIZE + 1U;
|
||||
result.oversized_input_report_status = backend.create_gamepad(18, oversized_input_options).status;
|
||||
|
||||
auto oversized_output_options = options;
|
||||
oversized_output_options.profile.output_report_size = LVH_WINDOWS_MAX_OUTPUT_REPORT_SIZE + 1U;
|
||||
result.oversized_output_report_status = backend.create_gamepad(19, oversized_output_options).status;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -326,7 +395,7 @@ namespace lvh::detail {
|
||||
|
||||
WindowsBackendUtilityResult windows_backend_fake_channel_utilities() {
|
||||
WindowsBackendUtilityResult result;
|
||||
result.default_device_path = resolve_control_device_path();
|
||||
result.default_device_paths = resolve_control_device_paths();
|
||||
|
||||
constexpr auto environment_name = "LIBVIRTUALHID_WINDOWS_CONTROL_DEVICE";
|
||||
constexpr auto custom_path = R"(\\.\LibVirtualHid-Test)";
|
||||
@@ -337,7 +406,7 @@ namespace lvh::detail {
|
||||
static_cast<DWORD>(original_value.size())
|
||||
);
|
||||
static_cast<void>(::SetEnvironmentVariableA(environment_name, custom_path));
|
||||
result.custom_device_path = resolve_control_device_path();
|
||||
result.custom_device_paths = resolve_control_device_paths();
|
||||
static_cast<void>(::SetEnvironmentVariableA(
|
||||
environment_name,
|
||||
original_size > 0U && original_size < original_value.size() ? original_value.data() : nullptr
|
||||
@@ -348,6 +417,32 @@ namespace lvh::detail {
|
||||
result.fallback_error_status =
|
||||
windows_failure(ErrorCode::backend_failure, "format unknown Windows error", 0xE0000001U);
|
||||
|
||||
{
|
||||
WindowsBackend backend {nullptr, nullptr};
|
||||
|
||||
CreateKeyboardOptions keyboard_options;
|
||||
keyboard_options.profile = profiles::keyboard();
|
||||
auto keyboard = backend.create_keyboard(8, keyboard_options);
|
||||
result.keyboard_create_status = keyboard.status;
|
||||
if (keyboard) {
|
||||
result.keyboard_close_status = keyboard.keyboard->close();
|
||||
result.keyboard_submit_after_close_status =
|
||||
keyboard.keyboard->submit({.key_code = 0x41, .pressed = true});
|
||||
}
|
||||
|
||||
CreateMouseOptions mouse_options;
|
||||
mouse_options.profile = profiles::mouse();
|
||||
auto mouse = backend.create_mouse(9, mouse_options);
|
||||
result.mouse_create_status = mouse.status;
|
||||
if (mouse) {
|
||||
result.mouse_close_status = mouse.mouse->close();
|
||||
MouseEvent mouse_event;
|
||||
mouse_event.kind = MouseEventKind::relative_motion;
|
||||
mouse_event.x = 1;
|
||||
result.mouse_submit_after_close_status = mouse.mouse->submit(mouse_event);
|
||||
}
|
||||
}
|
||||
|
||||
auto invalid_context = std::make_shared<WindowsBackendContext>(
|
||||
std::unique_ptr<WindowsControlChannel> {},
|
||||
std::unique_ptr<WindowsControlChannel> {}
|
||||
@@ -369,6 +464,92 @@ namespace lvh::detail {
|
||||
return result;
|
||||
}
|
||||
|
||||
WindowsBackendSendInputResult windows_backend_send_input_devices() {
|
||||
using enum MouseEventKind;
|
||||
|
||||
WindowsBackendSendInputResult result;
|
||||
WindowsBackend backend {nullptr, nullptr};
|
||||
FakeSendInputState fake_send_input;
|
||||
ScopedFakeSendInput scoped_send_input {fake_send_input};
|
||||
|
||||
CreateKeyboardOptions keyboard_options;
|
||||
keyboard_options.profile = profiles::keyboard();
|
||||
if (auto keyboard = backend.create_keyboard(10, keyboard_options); keyboard) {
|
||||
result.keyboard.down_status = keyboard.keyboard->submit({.key_code = 0x41, .pressed = true});
|
||||
result.keyboard.up_status = keyboard.keyboard->submit({.key_code = 0x41, .pressed = false});
|
||||
result.keyboard.text_status = keyboard.keyboard->type_text({.text = "Az"});
|
||||
result.keyboard.empty_text_status = keyboard.keyboard->type_text({.text = ""});
|
||||
result.keyboard.invalid_text_status =
|
||||
keyboard.keyboard->type_text({.text = std::string(1U, static_cast<char>(0xFF))});
|
||||
|
||||
fake_send_input.forced_sent_count = 0U;
|
||||
result.keyboard.failure_status = keyboard.keyboard->submit({.key_code = 0x42, .pressed = true});
|
||||
fake_send_input.forced_sent_count.reset();
|
||||
} else {
|
||||
result.keyboard.down_status = keyboard.status;
|
||||
}
|
||||
|
||||
CreateMouseOptions mouse_options;
|
||||
mouse_options.profile = profiles::mouse();
|
||||
if (auto mouse = backend.create_mouse(11, mouse_options); mouse) {
|
||||
result.mouse.relative_status = mouse.mouse->submit({.kind = relative_motion, .x = 11, .y = -12});
|
||||
result.mouse.absolute_status =
|
||||
mouse.mouse->submit({.kind = absolute_motion, .x = 50, .y = 25, .width = 100, .height = 50});
|
||||
result.mouse.degenerate_absolute_status =
|
||||
mouse.mouse->submit({.kind = absolute_motion, .x = 99, .y = 99, .width = 1, .height = 0});
|
||||
|
||||
MouseEvent button_event;
|
||||
button_event.kind = button;
|
||||
button_event.button = MouseButton::left;
|
||||
button_event.pressed = true;
|
||||
result.mouse.left_button_status = mouse.mouse->submit(button_event);
|
||||
button_event.button = MouseButton::middle;
|
||||
button_event.pressed = false;
|
||||
result.mouse.middle_button_status = mouse.mouse->submit(button_event);
|
||||
button_event.button = MouseButton::right;
|
||||
button_event.pressed = true;
|
||||
result.mouse.right_button_status = mouse.mouse->submit(button_event);
|
||||
button_event.button = MouseButton::side;
|
||||
result.mouse.side_button_status = mouse.mouse->submit(button_event);
|
||||
button_event.button = MouseButton::extra;
|
||||
button_event.pressed = false;
|
||||
result.mouse.extra_button_status = mouse.mouse->submit(button_event);
|
||||
|
||||
result.mouse.vertical_scroll_status = mouse.mouse->submit({.kind = vertical_scroll, .high_resolution_scroll = 120});
|
||||
result.mouse.horizontal_scroll_status =
|
||||
mouse.mouse->submit({.kind = horizontal_scroll, .high_resolution_scroll = -240});
|
||||
|
||||
fake_send_input.forced_sent_count = 0U;
|
||||
result.mouse.failure_status = mouse.mouse->submit({.kind = relative_motion, .x = 1, .y = 1});
|
||||
fake_send_input.forced_sent_count.reset();
|
||||
} else {
|
||||
result.mouse.relative_status = mouse.status;
|
||||
}
|
||||
|
||||
CreateKeyboardOptions invalid_keyboard_options;
|
||||
invalid_keyboard_options.profile = profiles::mouse();
|
||||
result.keyboard.invalid_profile_status = backend.create_keyboard(12, invalid_keyboard_options).status;
|
||||
|
||||
CreateMouseOptions invalid_mouse_options;
|
||||
invalid_mouse_options.profile = profiles::keyboard();
|
||||
result.mouse.invalid_profile_status = backend.create_mouse(13, invalid_mouse_options).status;
|
||||
|
||||
CreateTouchscreenOptions touchscreen_options;
|
||||
touchscreen_options.profile = profiles::touchscreen();
|
||||
result.unsupported.touchscreen_status = backend.create_touchscreen(14, touchscreen_options).status;
|
||||
|
||||
CreateTrackpadOptions trackpad_options;
|
||||
trackpad_options.profile = profiles::trackpad();
|
||||
result.unsupported.trackpad_status = backend.create_trackpad(15, trackpad_options).status;
|
||||
|
||||
CreatePenTabletOptions pen_tablet_options;
|
||||
pen_tablet_options.profile = profiles::pen_tablet();
|
||||
result.unsupported.pen_tablet_status = backend.create_pen_tablet(16, pen_tablet_options).status;
|
||||
|
||||
result.sent_inputs = std::move(fake_send_input.sent_inputs);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
} // namespace lvh::detail
|
||||
@@ -45,14 +45,18 @@ TEST(RuntimeTest, PlatformDefaultReportsCurrentPlatformCapabilities) {
|
||||
#elif defined(_WIN32)
|
||||
EXPECT_EQ(runtime->capabilities().backend_name, "windows-umdf");
|
||||
EXPECT_TRUE(runtime->capabilities().requires_installed_driver);
|
||||
EXPECT_FALSE(runtime->capabilities().supports_keyboard);
|
||||
EXPECT_FALSE(runtime->capabilities().supports_mouse);
|
||||
EXPECT_TRUE(runtime->capabilities().supports_keyboard);
|
||||
EXPECT_TRUE(runtime->capabilities().supports_mouse);
|
||||
EXPECT_FALSE(runtime->capabilities().supports_touchscreen);
|
||||
EXPECT_FALSE(runtime->capabilities().supports_trackpad);
|
||||
EXPECT_FALSE(runtime->capabilities().supports_pen_tablet);
|
||||
|
||||
EXPECT_EQ(runtime->create_keyboard().status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
EXPECT_EQ(runtime->create_mouse().status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
auto keyboard = runtime->create_keyboard();
|
||||
ASSERT_TRUE(keyboard) << keyboard.status.message();
|
||||
EXPECT_TRUE(keyboard.keyboard->close().ok());
|
||||
auto mouse = runtime->create_mouse();
|
||||
ASSERT_TRUE(mouse) << mouse.status.message();
|
||||
EXPECT_TRUE(mouse.mouse->close().ok());
|
||||
EXPECT_EQ(runtime->create_touchscreen().status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
EXPECT_EQ(runtime->create_trackpad().status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
EXPECT_EQ(runtime->create_pen_tablet().status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
|
||||
@@ -7,10 +7,24 @@
|
||||
#include "fixtures/fixtures.hpp"
|
||||
#include "fixtures/windows_backend_test_hooks.hpp"
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
// platform includes
|
||||
#include <Windows.h>
|
||||
|
||||
namespace {
|
||||
|
||||
class WindowsBackendTest: public WindowsTest {};
|
||||
|
||||
void expect_ok(const lvh::OperationStatus &status) {
|
||||
EXPECT_TRUE(status.ok()) << status.message();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(WindowsBackendTest, FakeChannelExercisesLifecycleSubmitCloseAndOutput) {
|
||||
@@ -20,6 +34,8 @@ TEST_F(WindowsBackendTest, FakeChannelExercisesLifecycleSubmitCloseAndOutput) {
|
||||
EXPECT_TRUE(result.capabilities.requires_installed_driver);
|
||||
EXPECT_TRUE(result.capabilities.supports_virtual_hid);
|
||||
EXPECT_TRUE(result.capabilities.supports_gamepad);
|
||||
EXPECT_TRUE(result.capabilities.supports_keyboard);
|
||||
EXPECT_TRUE(result.capabilities.supports_mouse);
|
||||
EXPECT_TRUE(result.capabilities.supports_output_reports);
|
||||
|
||||
ASSERT_TRUE(result.create_status.ok()) << result.create_status.message();
|
||||
@@ -53,6 +69,9 @@ TEST_F(WindowsBackendTest, FakeChannelCoversCreateFailureBranches) {
|
||||
EXPECT_EQ(result.backend_failure_status.code(), lvh::ErrorCode::backend_failure);
|
||||
EXPECT_EQ(result.transport_failure_status.code(), lvh::ErrorCode::backend_failure);
|
||||
EXPECT_EQ(result.unavailable_status.code(), lvh::ErrorCode::backend_unavailable);
|
||||
EXPECT_EQ(result.oversized_descriptor_status.code(), lvh::ErrorCode::invalid_argument);
|
||||
EXPECT_EQ(result.oversized_input_report_status.code(), lvh::ErrorCode::invalid_argument);
|
||||
EXPECT_EQ(result.oversized_output_report_status.code(), lvh::ErrorCode::invalid_argument);
|
||||
|
||||
ASSERT_TRUE(result.empty_nodes_create_status.ok()) << result.empty_nodes_create_status.message();
|
||||
EXPECT_TRUE(result.empty_device_nodes.empty());
|
||||
@@ -62,8 +81,11 @@ TEST_F(WindowsBackendTest, FakeChannelCoversCreateFailureBranches) {
|
||||
TEST_F(WindowsBackendTest, UtilityHookCoversEnvironmentErrorAndThreadBranches) {
|
||||
const auto result = lvh::detail::test::windows_backend_fake_channel_utilities();
|
||||
|
||||
EXPECT_EQ(result.default_device_path, R"(\\.\LibVirtualHid)");
|
||||
EXPECT_EQ(result.custom_device_path, R"(\\.\LibVirtualHid-Test)");
|
||||
ASSERT_EQ(result.default_device_paths.size(), 2U);
|
||||
EXPECT_EQ(result.default_device_paths[0], R"(\\.\LibVirtualHid)");
|
||||
EXPECT_EQ(result.default_device_paths[1], R"(\\.\Global\LibVirtualHid)");
|
||||
ASSERT_EQ(result.custom_device_paths.size(), 1U);
|
||||
EXPECT_EQ(result.custom_device_paths[0], R"(\\.\LibVirtualHid-Test)");
|
||||
EXPECT_EQ(result.formatted_error_status.code(), lvh::ErrorCode::backend_failure);
|
||||
EXPECT_EQ(result.fallback_error_status.code(), lvh::ErrorCode::backend_failure);
|
||||
EXPECT_NE(result.formatted_error_status.message().find("format known Windows error:"), std::string::npos);
|
||||
@@ -71,5 +93,81 @@ TEST_F(WindowsBackendTest, UtilityHookCoversEnvironmentErrorAndThreadBranches) {
|
||||
result.fallback_error_status.message().find("format unknown Windows error: Windows error 3758096385"),
|
||||
std::string::npos
|
||||
);
|
||||
EXPECT_TRUE(result.keyboard_create_status.ok()) << result.keyboard_create_status.message();
|
||||
EXPECT_TRUE(result.keyboard_close_status.ok()) << result.keyboard_close_status.message();
|
||||
EXPECT_EQ(result.keyboard_submit_after_close_status.code(), lvh::ErrorCode::device_closed);
|
||||
EXPECT_TRUE(result.mouse_create_status.ok()) << result.mouse_create_status.message();
|
||||
EXPECT_TRUE(result.mouse_close_status.ok()) << result.mouse_close_status.message();
|
||||
EXPECT_EQ(result.mouse_submit_after_close_status.code(), lvh::ErrorCode::device_closed);
|
||||
EXPECT_FALSE(result.timeout_result);
|
||||
}
|
||||
|
||||
TEST_F(WindowsBackendTest, SendInputDevicesTranslateKeyboardMouseFailuresAndUnsupportedProfiles) {
|
||||
const auto result = lvh::detail::test::windows_backend_send_input_devices();
|
||||
|
||||
expect_ok(result.keyboard.down_status);
|
||||
expect_ok(result.keyboard.up_status);
|
||||
expect_ok(result.keyboard.text_status);
|
||||
expect_ok(result.keyboard.empty_text_status);
|
||||
EXPECT_EQ(result.keyboard.invalid_text_status.code(), lvh::ErrorCode::invalid_argument);
|
||||
EXPECT_EQ(result.keyboard.failure_status.code(), lvh::ErrorCode::backend_failure);
|
||||
|
||||
expect_ok(result.mouse.relative_status);
|
||||
expect_ok(result.mouse.absolute_status);
|
||||
expect_ok(result.mouse.degenerate_absolute_status);
|
||||
expect_ok(result.mouse.left_button_status);
|
||||
expect_ok(result.mouse.middle_button_status);
|
||||
expect_ok(result.mouse.right_button_status);
|
||||
expect_ok(result.mouse.side_button_status);
|
||||
expect_ok(result.mouse.extra_button_status);
|
||||
expect_ok(result.mouse.vertical_scroll_status);
|
||||
expect_ok(result.mouse.horizontal_scroll_status);
|
||||
EXPECT_EQ(result.mouse.failure_status.code(), lvh::ErrorCode::backend_failure);
|
||||
|
||||
EXPECT_EQ(result.keyboard.invalid_profile_status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
EXPECT_EQ(result.mouse.invalid_profile_status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
EXPECT_EQ(result.unsupported.touchscreen_status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
EXPECT_EQ(result.unsupported.trackpad_status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
EXPECT_EQ(result.unsupported.pen_tablet_status.code(), lvh::ErrorCode::unsupported_profile);
|
||||
|
||||
ASSERT_EQ(result.sent_inputs.size(), 18U);
|
||||
EXPECT_EQ(result.sent_inputs[0].type, INPUT_KEYBOARD);
|
||||
EXPECT_EQ(result.sent_inputs[0].virtual_key, 0x41U);
|
||||
EXPECT_EQ(result.sent_inputs[0].key_flags, 0U);
|
||||
EXPECT_EQ(result.sent_inputs[1].virtual_key, 0x41U);
|
||||
EXPECT_EQ(result.sent_inputs[1].key_flags, KEYEVENTF_KEYUP);
|
||||
EXPECT_EQ(result.sent_inputs[2].scan_code, static_cast<std::uint16_t>(L'A'));
|
||||
EXPECT_EQ(result.sent_inputs[2].key_flags, KEYEVENTF_UNICODE);
|
||||
EXPECT_EQ(result.sent_inputs[3].scan_code, static_cast<std::uint16_t>(L'z'));
|
||||
EXPECT_EQ(result.sent_inputs[3].key_flags, KEYEVENTF_UNICODE);
|
||||
EXPECT_EQ(result.sent_inputs[4].scan_code, static_cast<std::uint16_t>(L'A'));
|
||||
EXPECT_EQ(result.sent_inputs[4].key_flags, KEYEVENTF_UNICODE | KEYEVENTF_KEYUP);
|
||||
EXPECT_EQ(result.sent_inputs[5].scan_code, static_cast<std::uint16_t>(L'z'));
|
||||
EXPECT_EQ(result.sent_inputs[5].key_flags, KEYEVENTF_UNICODE | KEYEVENTF_KEYUP);
|
||||
EXPECT_EQ(result.sent_inputs[6].virtual_key, 0x42U);
|
||||
|
||||
EXPECT_EQ(result.sent_inputs[7].type, INPUT_MOUSE);
|
||||
EXPECT_EQ(result.sent_inputs[7].mouse_flags, MOUSEEVENTF_MOVE);
|
||||
EXPECT_EQ(result.sent_inputs[7].mouse_x, 11);
|
||||
EXPECT_EQ(result.sent_inputs[7].mouse_y, -12);
|
||||
EXPECT_EQ(result.sent_inputs[8].mouse_flags, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK);
|
||||
EXPECT_EQ(result.sent_inputs[8].mouse_x, 32767);
|
||||
EXPECT_EQ(result.sent_inputs[8].mouse_y, 32767);
|
||||
EXPECT_EQ(result.sent_inputs[9].mouse_flags, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK);
|
||||
EXPECT_EQ(result.sent_inputs[9].mouse_x, 0);
|
||||
EXPECT_EQ(result.sent_inputs[9].mouse_y, 0);
|
||||
EXPECT_EQ(result.sent_inputs[10].mouse_flags, MOUSEEVENTF_LEFTDOWN);
|
||||
EXPECT_EQ(result.sent_inputs[11].mouse_flags, MOUSEEVENTF_MIDDLEUP);
|
||||
EXPECT_EQ(result.sent_inputs[12].mouse_flags, MOUSEEVENTF_RIGHTDOWN);
|
||||
EXPECT_EQ(result.sent_inputs[13].mouse_flags, MOUSEEVENTF_XDOWN);
|
||||
EXPECT_EQ(result.sent_inputs[13].mouse_data, XBUTTON1);
|
||||
EXPECT_EQ(result.sent_inputs[14].mouse_flags, MOUSEEVENTF_XUP);
|
||||
EXPECT_EQ(result.sent_inputs[14].mouse_data, XBUTTON2);
|
||||
EXPECT_EQ(result.sent_inputs[15].mouse_flags, MOUSEEVENTF_WHEEL);
|
||||
EXPECT_EQ(result.sent_inputs[15].mouse_data, 120U);
|
||||
EXPECT_EQ(result.sent_inputs[16].mouse_flags, MOUSEEVENTF_HWHEEL);
|
||||
EXPECT_EQ(result.sent_inputs[16].mouse_data, static_cast<std::uint32_t>(static_cast<DWORD>(-240)));
|
||||
EXPECT_EQ(result.sent_inputs[17].mouse_flags, MOUSEEVENTF_MOVE);
|
||||
EXPECT_EQ(result.sent_inputs[17].mouse_x, 1);
|
||||
EXPECT_EQ(result.sent_inputs[17].mouse_y, 1);
|
||||
}
|
||||
@@ -39,6 +39,7 @@ namespace {
|
||||
|
||||
TEST(WindowsProtocolTest, ExposesStableProtocolConstants) {
|
||||
EXPECT_STREQ(lvh::detail::windows::default_control_device_path.data(), R"(\\.\LibVirtualHid)");
|
||||
EXPECT_STREQ(lvh::detail::windows::global_control_device_path.data(), R"(\\.\Global\LibVirtualHid)");
|
||||
|
||||
EXPECT_EQ(LVH_WINDOWS_IOCTL_CREATE_GAMEPAD, 0x8000E000U);
|
||||
EXPECT_EQ(LVH_WINDOWS_IOCTL_DESTROY_DEVICE, 0x8000E004U);
|
||||
|
||||
Reference in new issue
Block a user