[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.
This commit is contained in:
Geoffrey McRae
2026-08-08 19:39:17 +10:00
parent 241ab3fad2
commit 04fd31219c
7 changed files with 288 additions and 29 deletions

View File

@@ -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");

View File

@@ -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<uint32_t>(size);
memcpy(message.payload, report, size);
memcpy(message.payload, payload, size);
AcquireSRWLockExclusive(&m_sendLock);
message.sequence = ++m_sequence;

View File

@@ -21,6 +21,7 @@
#pragma once
#include "CPipeEndpoint.h"
#include "InputPipeProtocol.h"
#include <stddef.h>
#include <stdint.h>
@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -23,6 +23,9 @@
#include "CDebug.h"
#include "InputPipeProtocol.h"
#include "../CHIDDevice.h"
#include "../HIDReports.h"
#include <string.h>
bool CInputPipeClient::Start()
{
@@ -67,7 +70,6 @@ bool CInputPipeClient::OnPipeMessage(
*static_cast<const LGInputPipeMessage *>(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;

View File

@@ -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;