[idd] input: preserve reports under backpressure

Move named pipe writes off the LGMP input worker so a stalled LGInput
endpoint cannot block queue draining or lease maintenance.

Coalesce motion only under queue pressure while preserving mode, button,
wheel, and keyboard transitions. Reset HID state after discontinuities
and carry all 32 mouse button bits through the pipe and HID reports.
This commit is contained in:
Geoffrey McRae
2026-08-08 23:31:44 +10:00
parent 83c552fb9d
commit ced2fb531e
16 changed files with 820 additions and 382 deletions

View File

@@ -26,16 +26,21 @@
#include "ipc/CInputPipeClient.h"
#include <hidport.h>
#include <limits.h>
#include <new>
#include <string.h>
static constexpr USHORT LG_INPUT_VENDOR_ID = 0x0000;
static constexpr USHORT LG_INPUT_PRODUCT_ID = 0x0000;
static constexpr USHORT LG_INPUT_VERSION = 0x0001;
static constexpr size_t REPORT_QUEUE_LENGTH = 64;
static constexpr USHORT LG_INPUT_VENDOR_ID = 0x0000;
static constexpr USHORT LG_INPUT_PRODUCT_ID = 0x0000;
static constexpr USHORT LG_INPUT_VERSION = 0x0001;
static constexpr size_t REPORT_QUEUE_LENGTH = 64;
static constexpr size_t MOTION_COALESCE_THRESHOLD =
REPORT_QUEUE_LENGTH / 2;
struct HIDQueuedReport
{
UCHAR data[sizeof(HIDKeyboardReport)];
UCHAR data[HID_MAX_INPUT_REPORT_SIZE];
bool pureMotion;
size_t size;
};
@@ -49,8 +54,14 @@ struct HIDDeviceContext
SRWLOCK reportLock;
bool active;
bool stopping;
UCHAR mouseMode;
size_t reportHead;
size_t reportCount;
uint32_t relativeButtons;
uint32_t absoluteButtons;
bool absoluteValid;
uint16_t absoluteX;
uint16_t absoluteY;
HIDQueuedReport reports[REPORT_QUEUE_LENGTH];
CInputPipeClient * inputPipe;
};
@@ -151,19 +162,114 @@ static void PopReport(
--context->reportCount;
}
static HIDQueuedReport& GetQueuedReport(
_Inout_ HIDDeviceContext * context,
_In_ size_t position)
{
return context->reports[
(context->reportHead + position) % REPORT_QUEUE_LENGTH];
}
static void RemoveQueuedReport(
_Inout_ HIDDeviceContext * context,
_In_ size_t position)
{
for (size_t i = position; i + 1 < context->reportCount; ++i)
GetQueuedReport(context, i) = GetQueuedReport(context, i + 1);
--context->reportCount;
}
static bool CompactStaleMotion(
_Inout_ HIDDeviceContext * context,
_In_ UCHAR incomingReportId)
{
for (size_t i = 0; i < context->reportCount; ++i)
{
HIDQueuedReport& report = GetQueuedReport(context, i);
if (!report.pureMotion ||
report.data[0] != HID_REPORT_ID_MOUSE_ABSOLUTE)
continue;
bool superseded = incomingReportId == HID_REPORT_ID_MOUSE_ABSOLUTE;
for (size_t j = i + 1; !superseded && j < context->reportCount; ++j)
superseded = GetQueuedReport(context, j).data[0] ==
HID_REPORT_ID_MOUSE_ABSOLUTE;
if (superseded)
{
RemoveQueuedReport(context, i);
return true;
}
}
return false;
}
static NTSTATUS QueueReport(
_Inout_ HIDDeviceContext * context,
_In_reads_bytes_(size) const void * data,
_In_ size_t size)
_In_ size_t size,
_In_ bool pureMotion)
{
const UCHAR reportId = *static_cast<const UCHAR *>(data);
if (context->reportCount)
{
HIDQueuedReport& tail =
GetQueuedReport(context, context->reportCount - 1);
if (tail.data[0] == reportId)
{
if (reportId == HID_REPORT_ID_MOUSE_RELATIVE &&
context->reportCount >= MOTION_COALESCE_THRESHOLD &&
tail.pureMotion && pureMotion)
{
HIDMouseRelativeReport * previous =
reinterpret_cast<HIDMouseRelativeReport *>(tail.data);
const HIDMouseRelativeReport * current =
static_cast<const HIDMouseRelativeReport *>(data);
const int32_t x = static_cast<int32_t>(previous->x) +
current->x;
const int32_t y = static_cast<int32_t>(previous->y) +
current->y;
if (x >= INT16_MIN && x <= INT16_MAX &&
y >= INT16_MIN && y <= INT16_MAX &&
previous->buttons == current->buttons)
{
previous->x = static_cast<int16_t>(x);
previous->y = static_cast<int16_t>(y);
return STATUS_SUCCESS;
}
}
else if (reportId == HID_REPORT_ID_MOUSE_ABSOLUTE &&
tail.pureMotion && pureMotion)
{
const HIDMouseAbsoluteReport * previous =
reinterpret_cast<const HIDMouseAbsoluteReport *>(tail.data);
const HIDMouseAbsoluteReport * current =
static_cast<const HIDMouseAbsoluteReport *>(data);
if (previous->buttons == current->buttons)
{
CopyMemory(tail.data, data, size);
tail.size = size;
return STATUS_SUCCESS;
}
}
else if (reportId == HID_REPORT_ID_KEYBOARD &&
tail.size == size && memcmp(tail.data, data, size) == 0)
return STATUS_SUCCESS;
}
}
if (context->reportCount == REPORT_QUEUE_LENGTH)
return STATUS_BUFFER_OVERFLOW;
{
if (!CompactStaleMotion(context, reportId))
return STATUS_BUFFER_OVERFLOW;
}
const size_t index =
(context->reportHead + context->reportCount) % REPORT_QUEUE_LENGTH;
HIDQueuedReport * report = &context->reports[index];
CopyMemory(report->data, data, size);
report->size = size;
report->size = size;
report->pureMotion = pureMotion;
++context->reportCount;
return STATUS_SUCCESS;
}
@@ -343,38 +449,136 @@ NTSTATUS CHIDDevice::SubmitReport(
status = STATUS_DEVICE_NOT_READY;
else
{
bool pureMotion = false;
switch (reportId)
{
case HID_REPORT_ID_MOUSE_RELATIVE:
{
const HIDMouseRelativeReport * mouse =
static_cast<const HIDMouseRelativeReport *>(report);
pureMotion = context->mouseMode == reportId &&
(mouse->x != 0 || mouse->y != 0 || mouse->buttons != 0) &&
mouse->wheel == 0 &&
mouse->buttons == context->relativeButtons;
break;
}
case HID_REPORT_ID_MOUSE_ABSOLUTE:
{
const HIDMouseAbsoluteReport * mouse =
static_cast<const HIDMouseAbsoluteReport *>(report);
pureMotion = context->mouseMode == reportId &&
(mouse->x != context->absoluteX ||
mouse->y != context->absoluteY || mouse->buttons != 0) &&
mouse->wheel == 0 &&
mouse->buttons == context->absoluteButtons;
break;
}
default:
break;
}
status = WdfIoQueueRetrieveNextRequest(context->reportQueue, &request);
if (status == STATUS_NO_MORE_ENTRIES)
{
request = nullptr;
status = QueueReport(context, report, size);
status = QueueReport(context, report, size, pureMotion);
}
else if (NT_SUCCESS(status))
status = CopyToRequest(request, report, size);
if (NT_SUCCESS(status))
{
switch (reportId)
{
case HID_REPORT_ID_MOUSE_RELATIVE:
context->mouseMode = reportId;
context->relativeButtons =
static_cast<const HIDMouseRelativeReport *>(report)->buttons;
break;
case HID_REPORT_ID_MOUSE_ABSOLUTE:
{
const HIDMouseAbsoluteReport * mouse =
static_cast<const HIDMouseAbsoluteReport *>(report);
context->mouseMode = reportId;
context->absoluteButtons = mouse->buttons;
context->absoluteValid = true;
context->absoluteX = mouse->x;
context->absoluteY = mouse->y;
break;
}
default:
break;
}
}
}
}
if (request)
{
status = CopyToRequest(request, report, size);
WdfRequestComplete(request, status);
}
return status;
}
NTSTATUS CHIDDevice::ClearReports()
NTSTATUS CHIDDevice::ResetReports()
{
CSRWSharedLock deviceLock(&s_deviceLock);
HIDDeviceContext * context = s_device;
if (!context)
return STATUS_DEVICE_NOT_READY;
uint16_t absoluteX = 0;
uint16_t absoluteY = 0;
bool absoluteValid = false;
{
CSRWSharedLock deviceLock(&s_deviceLock);
HIDDeviceContext * context = s_device;
if (!context)
return STATUS_DEVICE_NOT_READY;
CSRWExclusiveLock reportLock(&context->reportLock);
if (context->stopping)
return STATUS_DEVICE_NOT_READY;
CSRWExclusiveLock reportLock(&context->reportLock);
if (context->stopping)
return STATUS_DEVICE_NOT_READY;
context->reportHead = 0;
context->reportCount = 0;
return STATUS_SUCCESS;
absoluteValid = context->absoluteValid;
absoluteX = context->absoluteX;
absoluteY = context->absoluteY;
context->reportHead = 0;
context->reportCount = 0;
context->mouseMode = 0;
context->relativeButtons = 0;
context->absoluteButtons = 0;
}
const HIDMouseRelativeReport relative = {
HID_REPORT_ID_MOUSE_RELATIVE,
0,
0,
0,
0,
};
const HIDKeyboardReport keyboard = {
HID_REPORT_ID_KEYBOARD,
};
NTSTATUS status = SubmitReport(&relative, sizeof(relative));
if (absoluteValid)
{
const HIDMouseAbsoluteReport absolute = {
HID_REPORT_ID_MOUSE_ABSOLUTE,
0,
absoluteX,
absoluteY,
0,
};
const NTSTATUS absoluteStatus =
SubmitReport(&absolute, sizeof(absolute));
if (NT_SUCCESS(status))
status = absoluteStatus;
}
const NTSTATUS keyboardStatus =
SubmitReport(&keyboard, sizeof(keyboard));
if (NT_SUCCESS(status))
status = keyboardStatus;
return status;
}
VOID HIDEvtIoDeviceControl(

View File

@@ -30,5 +30,5 @@ public:
static NTSTATUS SubmitReport(
_In_reads_bytes_(size) const void * report,
_In_ size_t size);
static NTSTATUS ClearReports();
static NTSTATUS ResetReports();
};

View File

@@ -31,15 +31,12 @@ static const uint8_t REPORT_DESCRIPTOR[] =
0xA1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (Button 1)
0x29, 0x05, // Usage Maximum (Button 5)
0x29, 0x20, // Usage Maximum (Button 32)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x05, // Report Count (5)
0x95, 0x20, // Report Count (32)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x75, 0x03, // Report Size (3)
0x95, 0x01, // Report Count (1)
0x81, 0x03, // Input (Constant)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
@@ -66,15 +63,12 @@ static const uint8_t REPORT_DESCRIPTOR[] =
0xA1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (Button 1)
0x29, 0x05, // Usage Maximum (Button 5)
0x29, 0x20, // Usage Maximum (Button 32)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x05, // Report Count (5)
0x95, 0x20, // Report Count (32)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x75, 0x03, // Report Size (3)
0x95, 0x01, // Report Count (1)
0x81, 0x03, // Input (Constant)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)

View File

@@ -35,7 +35,7 @@ enum HIDReportId : uint8_t
struct HIDMouseAbsoluteReport
{
uint8_t reportId;
uint8_t buttons;
uint32_t buttons;
uint16_t x;
uint16_t y;
int8_t wheel;
@@ -44,7 +44,7 @@ struct HIDMouseAbsoluteReport
struct HIDMouseRelativeReport
{
uint8_t reportId;
uint8_t buttons;
uint32_t buttons;
int16_t x;
int16_t y;
int8_t wheel;
@@ -66,11 +66,14 @@ struct HIDKeyboardLedsReport
#pragma pack(pop)
static_assert(sizeof(HIDMouseAbsoluteReport) == 7);
static_assert(sizeof(HIDMouseRelativeReport) == 7);
static_assert(sizeof(HIDMouseAbsoluteReport) == 10);
static_assert(sizeof(HIDMouseRelativeReport) == 10);
static_assert(sizeof(HIDKeyboardReport) == 9);
static_assert(sizeof(HIDKeyboardLedsReport) == 2);
static constexpr size_t HID_MAX_INPUT_REPORT_SIZE =
sizeof(HIDMouseAbsoluteReport);
const uint8_t * HIDGetReportDescriptor();
size_t HIDGetReportDescriptorSize();
size_t HIDGetInputReportSize(uint8_t reportId);

View File

@@ -80,7 +80,7 @@
<WppRecorderEnabled>true</WppRecorderEnabled>
<WppScanConfigurationData Condition="'%(ClCompile.ScanConfigurationData)' == ''">Trace.h</WppScanConfigurationData>
<AdditionalOptions>/EHsc /D_ATL_NO_WIN_SUPPORT %(AdditionalOptions)</AdditionalOptions>
<AdditionalIncludeDirectories>$(ProjectDir)..\LGCommon;$(DDK_INC_PATH);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)..\LGCommon;$(ProjectDir)..\..\common\include;$(DDK_INC_PATH);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);OneCoreUAP.lib</AdditionalDependencies>

View File

@@ -43,7 +43,7 @@ void CInputPipeClient::Stop()
m_endpoint.Stop();
m_lastSequence = 0;
if (wasRunning)
CHIDDevice::ClearReports();
CHIDDevice::ResetReports();
}
void CInputPipeClient::OnPipeConnected()
@@ -55,7 +55,7 @@ void CInputPipeClient::OnPipeConnected()
void CInputPipeClient::OnPipeDisconnected()
{
m_lastSequence = 0;
CHIDDevice::ClearReports();
CHIDDevice::ResetReports();
DEBUG_INFO("Disconnected from the LGIdd input transport; reconnecting");
}
@@ -78,6 +78,7 @@ bool CInputPipeClient::OnPipeMessage(
(m_lastSequence && message.sequence != m_lastSequence + 1))
{
DEBUG_WARN("LGInput pipe report sequence changed unexpectedly");
CHIDDevice::ResetReports();
return false;
}
bool handled = false;
@@ -117,18 +118,41 @@ bool CInputPipeClient::HandleMouseRelative(
LGInputPipeMouseRelative input = {};
memcpy(&input, payload, sizeof(input));
if (input.wheel < LG_INPUT_MOUSE_WHEEL_MIN ||
(input.buttons & ~LG_INPUT_MOUSE_BUTTON_MASK))
if (input.deltaX < LG_INPUT_MOUSE_DELTA_MIN ||
input.deltaX > LG_INPUT_MOUSE_DELTA_MAX ||
input.deltaY < LG_INPUT_MOUSE_DELTA_MIN ||
input.deltaY > LG_INPUT_MOUSE_DELTA_MAX ||
input.wheel < LG_INPUT_MOUSE_WHEEL_MIN_TOTAL ||
input.wheel > LG_INPUT_MOUSE_WHEEL_MAX)
return false;
const HIDMouseRelativeReport report = {
HID_REPORT_ID_MOUSE_RELATIVE,
input.buttons,
input.deltaX,
input.deltaY,
input.wheel,
};
return SubmitReport(&report, sizeof(report));
int32_t x = input.deltaX;
int32_t y = input.deltaY;
int32_t wheel = input.wheel;
do
{
const int16_t reportX = x > INT16_MAX ? INT16_MAX :
x < INT16_MIN ? INT16_MIN : static_cast<int16_t>(x);
const int16_t reportY = y > INT16_MAX ? INT16_MAX :
y < INT16_MIN ? INT16_MIN : static_cast<int16_t>(y);
const int8_t reportWheel = wheel > INT8_MAX ? INT8_MAX :
wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN :
static_cast<int8_t>(wheel);
const HIDMouseRelativeReport report = {
HID_REPORT_ID_MOUSE_RELATIVE,
input.buttons,
reportX,
reportY,
reportWheel,
};
if (!SubmitReport(&report, sizeof(report)))
return false;
x -= reportX;
y -= reportY;
wheel -= reportWheel;
}
while (x || y || wheel);
return true;
}
bool CInputPipeClient::HandleMouseAbsolute(
@@ -142,18 +166,30 @@ bool CInputPipeClient::HandleMouseAbsolute(
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))
input.wheel < LG_INPUT_MOUSE_WHEEL_MIN_TOTAL ||
input.wheel > LG_INPUT_MOUSE_WHEEL_MAX ||
input.reserved)
return false;
const HIDMouseAbsoluteReport report = {
HID_REPORT_ID_MOUSE_ABSOLUTE,
input.buttons,
input.x,
input.y,
input.wheel,
};
return SubmitReport(&report, sizeof(report));
int32_t wheel = input.wheel;
do
{
const int8_t reportWheel = wheel > INT8_MAX ? INT8_MAX :
wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN :
static_cast<int8_t>(wheel);
const HIDMouseAbsoluteReport report = {
HID_REPORT_ID_MOUSE_ABSOLUTE,
input.buttons,
input.x,
input.y,
reportWheel,
};
if (!SubmitReport(&report, sizeof(report)))
return false;
wheel -= reportWheel;
}
while (wheel);
return true;
}
bool CInputPipeClient::HandleKeyboard(
@@ -166,6 +202,10 @@ bool CInputPipeClient::HandleKeyboard(
LGInputPipeKeyboard input = {};
memcpy(&input, payload, sizeof(input));
for (size_t i = 0; i < sizeof(input.reserved); ++i)
if (input.reserved[i])
return false;
HIDKeyboardReport report = {};
report.reportId = HID_REPORT_ID_KEYBOARD;
report.modifiers = input.modifiers;
@@ -189,8 +229,16 @@ bool CInputPipeClient::SubmitReport(
return false;
if (status == STATUS_BUFFER_OVERFLOW)
DEBUG_WARN("LGInput HID report queue is full; dropping a report");
else if (!NT_SUCCESS(status) && status != STATUS_DEVICE_NOT_READY)
DEBUG_WARN_HR(status, "Failed to submit an LGInput HID report");
{
DEBUG_WARN("LGInput HID report queue overflowed; resetting input state");
CHIDDevice::ResetReports();
return false;
}
if (!NT_SUCCESS(status))
{
if (status != STATUS_DEVICE_NOT_READY)
DEBUG_WARN_HR(status, "Failed to submit an LGInput HID report");
return false;
}
return true;
}

View File

@@ -32,7 +32,6 @@ public:
bool Start();
void Stop();
bool IsConnected() const { return m_endpoint.IsConnected(); }
private:
void OnPipeConnected() override;