From 04fd31219cb46c627249c324369171c18406b853 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sat, 8 Aug 2026 19:39:17 +1000 Subject: [PATCH] [idd] input: add mouse and keyboard IPC messages Replace raw HID report forwarding with typed absolute mouse, relative mouse, and keyboard messages. Expose absolute and relative modes as separate Mouse collections because Windows MouHID requires one X/Y motion mode per mouse device. --- idd/LGCommon/InputPipeProtocol.h | 64 ++++++++++++++- idd/LGIdd/ipc/CInputPipeServer.cpp | 79 ++++++++++++++++++- idd/LGIdd/ipc/CInputPipeServer.h | 24 +++++- idd/LGInput/HIDReports.cpp | 20 +++-- idd/LGInput/HIDReports.h | 15 ++-- idd/LGInput/ipc/CInputPipeClient.cpp | 111 ++++++++++++++++++++++++++- idd/LGInput/ipc/CInputPipeClient.h | 4 + 7 files changed, 288 insertions(+), 29 deletions(-) diff --git a/idd/LGCommon/InputPipeProtocol.h b/idd/LGCommon/InputPipeProtocol.h index 869dc137..1d8ce664 100644 --- a/idd/LGCommon/InputPipeProtocol.h +++ b/idd/LGCommon/InputPipeProtocol.h @@ -27,15 +27,65 @@ static constexpr wchar_t LG_INPUT_PIPE_NAME[] = L"\\\\.\\pipe\\LookingGlassIDDInput"; static constexpr uint32_t LG_INPUT_PIPE_MAGIC = 0x5049474c; -static constexpr uint16_t LG_INPUT_PIPE_VERSION = 1; -static constexpr size_t LG_INPUT_PIPE_MAX_REPORT_SIZE = 64; +static constexpr uint16_t LG_INPUT_PIPE_VERSION = 2; +static constexpr size_t LG_INPUT_PIPE_MAX_PAYLOAD_SIZE = 64; +static constexpr uint8_t LG_INPUT_MOUSE_BUTTON_MASK = 0x1f; +static constexpr uint16_t LG_INPUT_MOUSE_ABSOLUTE_MAX = 32767; +static constexpr int8_t LG_INPUT_MOUSE_WHEEL_MIN = -127; +static constexpr size_t LG_INPUT_KEYBOARD_KEY_COUNT = 6; +static constexpr uint8_t LG_INPUT_KEYBOARD_USAGE_MAX = 0xe7; + +enum LGInputMouseButton : uint8_t +{ + LG_INPUT_MOUSE_BUTTON_LEFT = 1 << 0, + LG_INPUT_MOUSE_BUTTON_RIGHT = 1 << 1, + LG_INPUT_MOUSE_BUTTON_MIDDLE = 1 << 2, + LG_INPUT_MOUSE_BUTTON_BACK = 1 << 3, + LG_INPUT_MOUSE_BUTTON_FORWARD = 1 << 4, +}; + +enum LGInputKeyboardModifier : uint8_t +{ + LG_INPUT_KEYBOARD_MODIFIER_LEFT_CONTROL = 1 << 0, + LG_INPUT_KEYBOARD_MODIFIER_LEFT_SHIFT = 1 << 1, + LG_INPUT_KEYBOARD_MODIFIER_LEFT_ALT = 1 << 2, + LG_INPUT_KEYBOARD_MODIFIER_LEFT_GUI = 1 << 3, + LG_INPUT_KEYBOARD_MODIFIER_RIGHT_CONTROL = 1 << 4, + LG_INPUT_KEYBOARD_MODIFIER_RIGHT_SHIFT = 1 << 5, + LG_INPUT_KEYBOARD_MODIFIER_RIGHT_ALT = 1 << 6, + LG_INPUT_KEYBOARD_MODIFIER_RIGHT_GUI = 1 << 7, +}; enum LGInputPipeMessageType : uint16_t { - LG_INPUT_PIPE_MESSAGE_REPORT = 1, + LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE = 1, + LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE = 2, + LG_INPUT_PIPE_MESSAGE_KEYBOARD = 3, }; #pragma pack(push, 1) +struct LGInputPipeMouseRelative +{ + uint8_t buttons; + int16_t deltaX; + int16_t deltaY; + int8_t wheel; +}; + +struct LGInputPipeMouseAbsolute +{ + uint8_t buttons; + uint16_t x; + uint16_t y; + int8_t wheel; +}; + +struct LGInputPipeKeyboard +{ + uint8_t modifiers; + uint8_t keys[LG_INPUT_KEYBOARD_KEY_COUNT]; +}; + struct LGInputPipeMessage { uint32_t magic; @@ -43,9 +93,15 @@ struct LGInputPipeMessage uint16_t type; uint32_t payloadSize; uint64_t sequence; - uint8_t payload[LG_INPUT_PIPE_MAX_REPORT_SIZE]; + uint8_t payload[LG_INPUT_PIPE_MAX_PAYLOAD_SIZE]; }; #pragma pack(pop) +static_assert(sizeof(LGInputPipeMouseRelative) == 6, + "LGInputPipeMouseRelative wire layout changed"); +static_assert(sizeof(LGInputPipeMouseAbsolute) == 6, + "LGInputPipeMouseAbsolute wire layout changed"); +static_assert(sizeof(LGInputPipeKeyboard) == 7, + "LGInputPipeKeyboard wire layout changed"); static_assert(sizeof(LGInputPipeMessage) == 84, "LGInputPipeMessage wire layout changed"); diff --git a/idd/LGIdd/ipc/CInputPipeServer.cpp b/idd/LGIdd/ipc/CInputPipeServer.cpp index bcf43fce..41651ca9 100644 --- a/idd/LGIdd/ipc/CInputPipeServer.cpp +++ b/idd/LGIdd/ipc/CInputPipeServer.cpp @@ -45,17 +45,88 @@ void CInputPipeServer::DeInit() m_endpoint.Stop(); } -bool CInputPipeServer::SendReport(const void * report, size_t size) +bool CInputPipeServer::SendMouseRelative( + int16_t deltaX, + int16_t deltaY, + int8_t wheel, + uint8_t buttons) { - if (!report || !size || size > LG_INPUT_PIPE_MAX_REPORT_SIZE) + if (wheel < LG_INPUT_MOUSE_WHEEL_MIN || + (buttons & ~LG_INPUT_MOUSE_BUTTON_MASK)) + return false; + + const LGInputPipeMouseRelative payload = { + buttons, + deltaX, + deltaY, + wheel, + }; + return SendMessage( + LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE, + &payload, + sizeof(payload)); +} + +bool CInputPipeServer::SendMouseAbsolute( + uint16_t x, + uint16_t y, + int8_t wheel, + uint8_t buttons) +{ + if (x > LG_INPUT_MOUSE_ABSOLUTE_MAX || + y > LG_INPUT_MOUSE_ABSOLUTE_MAX || + wheel < LG_INPUT_MOUSE_WHEEL_MIN || + (buttons & ~LG_INPUT_MOUSE_BUTTON_MASK)) + return false; + + const LGInputPipeMouseAbsolute payload = { + buttons, + x, + y, + wheel, + }; + return SendMessage( + LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE, + &payload, + sizeof(payload)); +} + +bool CInputPipeServer::SendKeyboard( + uint8_t modifiers, + const uint8_t * keys) +{ + if (!keys) + return false; + + LGInputPipeKeyboard payload = {}; + payload.modifiers = modifiers; + for (size_t i = 0; i < LG_INPUT_KEYBOARD_KEY_COUNT; ++i) + { + if (keys[i] > LG_INPUT_KEYBOARD_USAGE_MAX) + return false; + payload.keys[i] = keys[i]; + } + + return SendMessage( + LG_INPUT_PIPE_MESSAGE_KEYBOARD, + &payload, + sizeof(payload)); +} + +bool CInputPipeServer::SendMessage( + LGInputPipeMessageType type, + const void * payload, + size_t size) +{ + if (!payload || !size || size > LG_INPUT_PIPE_MAX_PAYLOAD_SIZE) return false; LGInputPipeMessage message = {}; message.magic = LG_INPUT_PIPE_MAGIC; message.version = LG_INPUT_PIPE_VERSION; - message.type = LG_INPUT_PIPE_MESSAGE_REPORT; + message.type = type; message.payloadSize = static_cast(size); - memcpy(message.payload, report, size); + memcpy(message.payload, payload, size); AcquireSRWLockExclusive(&m_sendLock); message.sequence = ++m_sequence; diff --git a/idd/LGIdd/ipc/CInputPipeServer.h b/idd/LGIdd/ipc/CInputPipeServer.h index 21705156..053bcda8 100644 --- a/idd/LGIdd/ipc/CInputPipeServer.h +++ b/idd/LGIdd/ipc/CInputPipeServer.h @@ -21,6 +21,7 @@ #pragma once #include "CPipeEndpoint.h" +#include "InputPipeProtocol.h" #include #include @@ -33,12 +34,29 @@ public: bool Init(); void DeInit(); - bool SendReport( - _In_reads_bytes_(size) const void * report, - _In_ size_t size); + // Mouse buttons use LGInputMouseButton bits. Wheel values are -127..127. + bool SendMouseRelative( + _In_ int16_t deltaX, + _In_ int16_t deltaY, + _In_ int8_t wheel, + _In_ uint8_t buttons); + // Absolute coordinates are normalized to 0..32767 on each axis. + bool SendMouseAbsolute( + _In_range_(0, LG_INPUT_MOUSE_ABSOLUTE_MAX) uint16_t x, + _In_range_(0, LG_INPUT_MOUSE_ABSOLUTE_MAX) uint16_t y, + _In_ int8_t wheel, + _In_ uint8_t buttons); + // Keys are USB HID Keyboard/Keypad usage IDs; zero marks an empty slot. + bool SendKeyboard( + _In_ uint8_t modifiers, + _In_reads_(LG_INPUT_KEYBOARD_KEY_COUNT) const uint8_t * keys); bool IsConnected() const { return m_endpoint.IsConnected(); } private: + bool SendMessage( + _In_ LGInputPipeMessageType type, + _In_reads_bytes_(size) const void * payload, + _In_ size_t size); bool OnPipeMessage(const void * message, size_t size) override; CPipeEndpoint m_endpoint; diff --git a/idd/LGInput/HIDReports.cpp b/idd/LGInput/HIDReports.cpp index 77507061..824eaf07 100644 --- a/idd/LGInput/HIDReports.cpp +++ b/idd/LGInput/HIDReports.cpp @@ -22,11 +22,11 @@ static const uint8_t REPORT_DESCRIPTOR[] = { - // Absolute tablet, report ID 1. + // Absolute mouse, report ID 1. 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x02, // Usage (Mouse) 0xA1, 0x01, // Collection (Application) - 0x85, HID_REPORT_ID_TABLET, + 0x85, HID_REPORT_ID_MOUSE_ABSOLUTE, 0x09, 0x01, // Usage (Pointer) 0xA1, 0x00, // Collection (Physical) 0x05, 0x09, // Usage Page (Button) @@ -48,6 +48,12 @@ static const uint8_t REPORT_DESCRIPTOR[] = 0x75, 0x10, // Report Size (16) 0x95, 0x02, // Report Count (2) 0x81, 0x02, // Input (Data, Variable, Absolute) + 0x09, 0x38, // Usage (Wheel) + 0x15, 0x81, // Logical Minimum (-127) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x06, // Input (Data, Variable, Relative) 0xC0, // End Collection 0xC0, // End Collection @@ -55,7 +61,7 @@ static const uint8_t REPORT_DESCRIPTOR[] = 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x02, // Usage (Mouse) 0xA1, 0x01, // Collection (Application) - 0x85, HID_REPORT_ID_MOUSE, + 0x85, HID_REPORT_ID_MOUSE_RELATIVE, 0x09, 0x01, // Usage (Pointer) 0xA1, 0x00, // Collection (Physical) 0x05, 0x09, // Usage Page (Button) @@ -136,11 +142,11 @@ size_t HIDGetInputReportSize(uint8_t reportId) { switch (reportId) { - case HID_REPORT_ID_TABLET: - return sizeof(HIDTabletReport); + case HID_REPORT_ID_MOUSE_ABSOLUTE: + return sizeof(HIDMouseAbsoluteReport); - case HID_REPORT_ID_MOUSE: - return sizeof(HIDMouseReport); + case HID_REPORT_ID_MOUSE_RELATIVE: + return sizeof(HIDMouseRelativeReport); case HID_REPORT_ID_KEYBOARD: return sizeof(HIDKeyboardReport); diff --git a/idd/LGInput/HIDReports.h b/idd/LGInput/HIDReports.h index 0282ade2..0ec5ae2e 100644 --- a/idd/LGInput/HIDReports.h +++ b/idd/LGInput/HIDReports.h @@ -25,22 +25,23 @@ enum HIDReportId : uint8_t { - HID_REPORT_ID_TABLET = 1, - HID_REPORT_ID_MOUSE = 2, - HID_REPORT_ID_KEYBOARD = 3, + HID_REPORT_ID_MOUSE_ABSOLUTE = 1, + HID_REPORT_ID_MOUSE_RELATIVE = 2, + HID_REPORT_ID_KEYBOARD = 3, }; #pragma pack(push, 1) -struct HIDTabletReport +struct HIDMouseAbsoluteReport { uint8_t reportId; uint8_t buttons; uint16_t x; uint16_t y; + int8_t wheel; }; -struct HIDMouseReport +struct HIDMouseRelativeReport { uint8_t reportId; uint8_t buttons; @@ -65,8 +66,8 @@ struct HIDKeyboardLedsReport #pragma pack(pop) -static_assert(sizeof(HIDTabletReport) == 6); -static_assert(sizeof(HIDMouseReport) == 7); +static_assert(sizeof(HIDMouseAbsoluteReport) == 7); +static_assert(sizeof(HIDMouseRelativeReport) == 7); static_assert(sizeof(HIDKeyboardReport) == 9); static_assert(sizeof(HIDKeyboardLedsReport) == 2); diff --git a/idd/LGInput/ipc/CInputPipeClient.cpp b/idd/LGInput/ipc/CInputPipeClient.cpp index 5af1e058..a829383a 100644 --- a/idd/LGInput/ipc/CInputPipeClient.cpp +++ b/idd/LGInput/ipc/CInputPipeClient.cpp @@ -23,6 +23,9 @@ #include "CDebug.h" #include "InputPipeProtocol.h" #include "../CHIDDevice.h" +#include "../HIDReports.h" + +#include bool CInputPipeClient::Start() { @@ -67,7 +70,6 @@ bool CInputPipeClient::OnPipeMessage( *static_cast(frame); if (message.magic != LG_INPUT_PIPE_MAGIC || message.version != LG_INPUT_PIPE_VERSION || - message.type != LG_INPUT_PIPE_MESSAGE_REPORT || !message.payloadSize || message.payloadSize > sizeof(message.payload)) return false; @@ -78,10 +80,111 @@ bool CInputPipeClient::OnPipeMessage( DEBUG_WARN("LGInput pipe report sequence changed unexpectedly"); return false; } - m_lastSequence = message.sequence; + bool handled = false; + switch (message.type) + { + case LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE: + handled = HandleMouseRelative( + message.payload, message.payloadSize); + break; + + case LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE: + handled = HandleMouseAbsolute( + message.payload, message.payloadSize); + break; + + case LG_INPUT_PIPE_MESSAGE_KEYBOARD: + handled = HandleKeyboard(message.payload, message.payloadSize); + break; + + default: + return false; + } + + if (!handled) + return false; + + m_lastSequence = message.sequence; + return true; +} + +bool CInputPipeClient::HandleMouseRelative( + const void * payload, + size_t size) +{ + if (size != sizeof(LGInputPipeMouseRelative)) + return false; + + LGInputPipeMouseRelative input = {}; + memcpy(&input, payload, sizeof(input)); + if (input.wheel < LG_INPUT_MOUSE_WHEEL_MIN || + (input.buttons & ~LG_INPUT_MOUSE_BUTTON_MASK)) + return false; + + const HIDMouseRelativeReport report = { + HID_REPORT_ID_MOUSE_RELATIVE, + input.buttons, + input.deltaX, + input.deltaY, + input.wheel, + }; + return SubmitReport(&report, sizeof(report)); +} + +bool CInputPipeClient::HandleMouseAbsolute( + const void * payload, + size_t size) +{ + if (size != sizeof(LGInputPipeMouseAbsolute)) + return false; + + LGInputPipeMouseAbsolute input = {}; + memcpy(&input, payload, sizeof(input)); + if (input.x > LG_INPUT_MOUSE_ABSOLUTE_MAX || + input.y > LG_INPUT_MOUSE_ABSOLUTE_MAX || + input.wheel < LG_INPUT_MOUSE_WHEEL_MIN || + (input.buttons & ~LG_INPUT_MOUSE_BUTTON_MASK)) + return false; + + const HIDMouseAbsoluteReport report = { + HID_REPORT_ID_MOUSE_ABSOLUTE, + input.buttons, + input.x, + input.y, + input.wheel, + }; + return SubmitReport(&report, sizeof(report)); +} + +bool CInputPipeClient::HandleKeyboard( + const void * payload, + size_t size) +{ + if (size != sizeof(LGInputPipeKeyboard)) + return false; + + LGInputPipeKeyboard input = {}; + memcpy(&input, payload, sizeof(input)); + + HIDKeyboardReport report = {}; + report.reportId = HID_REPORT_ID_KEYBOARD; + report.modifiers = input.modifiers; + for (size_t i = 0; i < LG_INPUT_KEYBOARD_KEY_COUNT; ++i) + { + if (input.keys[i] > LG_INPUT_KEYBOARD_USAGE_MAX) + return false; + report.keys[i] = input.keys[i]; + } + + return SubmitReport(&report, sizeof(report)); +} + +bool CInputPipeClient::SubmitReport( + const void * report, + size_t size) +{ + const NTSTATUS status = CHIDDevice::SubmitReport(report, size); - const NTSTATUS status = - CHIDDevice::SubmitReport(message.payload, message.payloadSize); if (status == STATUS_INVALID_PARAMETER) return false; diff --git a/idd/LGInput/ipc/CInputPipeClient.h b/idd/LGInput/ipc/CInputPipeClient.h index 0d2fe2a0..7324ac3e 100644 --- a/idd/LGInput/ipc/CInputPipeClient.h +++ b/idd/LGInput/ipc/CInputPipeClient.h @@ -38,6 +38,10 @@ private: void OnPipeConnected() override; void OnPipeDisconnected() override; bool OnPipeMessage(const void * message, size_t size) override; + bool HandleMouseRelative(const void * payload, size_t size); + bool HandleMouseAbsolute(const void * payload, size_t size); + bool HandleKeyboard(const void * payload, size_t size); + bool SubmitReport(const void * report, size_t size); CPipeEndpoint m_endpoint; uint64_t m_lastSequence = 0;