[idd] input: report volume keys as consumer controls

Expose a Consumer Control HID report for the system volume usages that
Windows does not handle through the Keyboard/Keypad usage page.

Translate the existing mute and volume keyboard usages at the LGInput
boundary, preserve normal keyboard state, and force a neutral consumer
report across resets and HID device reactivation.
This commit is contained in:
Geoffrey McRae
2026-08-09 01:02:33 +10:00
parent ced2fb531e
commit 726086a969
5 changed files with 90 additions and 6 deletions

View File

@@ -59,6 +59,7 @@ struct HIDDeviceContext
size_t reportCount; size_t reportCount;
uint32_t relativeButtons; uint32_t relativeButtons;
uint32_t absoluteButtons; uint32_t absoluteButtons;
uint16_t consumerUsage;
bool absoluteValid; bool absoluteValid;
uint16_t absoluteX; uint16_t absoluteX;
uint16_t absoluteY; uint16_t absoluteY;
@@ -326,9 +327,10 @@ static NTSTATUS DeactivateDevice(_Inout_ HIDDeviceContext * context)
if (context->stopping) if (context->stopping)
return STATUS_DEVICE_NOT_READY; return STATUS_DEVICE_NOT_READY;
context->active = false; context->active = false;
context->reportHead = 0; context->reportHead = 0;
context->reportCount = 0; context->reportCount = 0;
context->consumerUsage = UINT16_MAX;
} }
WdfIoQueuePurgeSynchronously(context->reportQueue); WdfIoQueuePurgeSynchronously(context->reportQueue);
return STATUS_SUCCESS; return STATUS_SUCCESS;
@@ -449,6 +451,11 @@ NTSTATUS CHIDDevice::SubmitReport(
status = STATUS_DEVICE_NOT_READY; status = STATUS_DEVICE_NOT_READY;
else else
{ {
if (reportId == HID_REPORT_ID_CONSUMER &&
context->consumerUsage ==
static_cast<const HIDConsumerReport *>(report)->usage)
return STATUS_SUCCESS;
bool pureMotion = false; bool pureMotion = false;
switch (reportId) switch (reportId)
{ {
@@ -510,6 +517,11 @@ NTSTATUS CHIDDevice::SubmitReport(
break; break;
} }
case HID_REPORT_ID_CONSUMER:
context->consumerUsage =
static_cast<const HIDConsumerReport *>(report)->usage;
break;
default: default:
break; break;
} }
@@ -546,6 +558,7 @@ NTSTATUS CHIDDevice::ResetReports()
context->mouseMode = 0; context->mouseMode = 0;
context->relativeButtons = 0; context->relativeButtons = 0;
context->absoluteButtons = 0; context->absoluteButtons = 0;
context->consumerUsage = UINT16_MAX;
} }
const HIDMouseRelativeReport relative = { const HIDMouseRelativeReport relative = {
@@ -558,6 +571,9 @@ NTSTATUS CHIDDevice::ResetReports()
const HIDKeyboardReport keyboard = { const HIDKeyboardReport keyboard = {
HID_REPORT_ID_KEYBOARD, HID_REPORT_ID_KEYBOARD,
}; };
const HIDConsumerReport consumer = {
HID_REPORT_ID_CONSUMER,
};
NTSTATUS status = SubmitReport(&relative, sizeof(relative)); NTSTATUS status = SubmitReport(&relative, sizeof(relative));
if (absoluteValid) if (absoluteValid)
@@ -578,6 +594,10 @@ NTSTATUS CHIDDevice::ResetReports()
SubmitReport(&keyboard, sizeof(keyboard)); SubmitReport(&keyboard, sizeof(keyboard));
if (NT_SUCCESS(status)) if (NT_SUCCESS(status))
status = keyboardStatus; status = keyboardStatus;
const NTSTATUS consumerStatus =
SubmitReport(&consumer, sizeof(consumer));
if (NT_SUCCESS(status))
status = consumerStatus;
return status; return status;
} }

View File

@@ -120,6 +120,20 @@ static const uint8_t REPORT_DESCRIPTOR[] =
0x95, 0x06, // Report Count (6) 0x95, 0x06, // Report Count (6)
0x81, 0x00, // Input (Data, Array, Absolute) 0x81, 0x00, // Input (Data, Array, Absolute)
0xC0, // End Collection 0xC0, // End Collection
// Consumer control, report ID 4.
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x85, HID_REPORT_ID_CONSUMER,
0x19, 0x00, // Usage Minimum (Unassigned)
0x2A, 0xFF, 0x03, // Usage Maximum (1023)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x03, // Logical Maximum (1023)
0x75, 0x10, // Report Size (16)
0x95, 0x01, // Report Count (1)
0x81, 0x00, // Input (Data, Array, Absolute)
0xC0, // End Collection
}; };
const uint8_t * HIDGetReportDescriptor() const uint8_t * HIDGetReportDescriptor()
@@ -145,6 +159,9 @@ size_t HIDGetInputReportSize(uint8_t reportId)
case HID_REPORT_ID_KEYBOARD: case HID_REPORT_ID_KEYBOARD:
return sizeof(HIDKeyboardReport); return sizeof(HIDKeyboardReport);
case HID_REPORT_ID_CONSUMER:
return sizeof(HIDConsumerReport);
default: default:
return 0; return 0;
} }

View File

@@ -28,6 +28,7 @@ enum HIDReportId : uint8_t
HID_REPORT_ID_MOUSE_ABSOLUTE = 1, HID_REPORT_ID_MOUSE_ABSOLUTE = 1,
HID_REPORT_ID_MOUSE_RELATIVE = 2, HID_REPORT_ID_MOUSE_RELATIVE = 2,
HID_REPORT_ID_KEYBOARD = 3, HID_REPORT_ID_KEYBOARD = 3,
HID_REPORT_ID_CONSUMER = 4,
}; };
#pragma pack(push, 1) #pragma pack(push, 1)
@@ -64,12 +65,19 @@ struct HIDKeyboardLedsReport
uint8_t leds; uint8_t leds;
}; };
struct HIDConsumerReport
{
uint8_t reportId;
uint16_t usage;
};
#pragma pack(pop) #pragma pack(pop)
static_assert(sizeof(HIDMouseAbsoluteReport) == 10); static_assert(sizeof(HIDMouseAbsoluteReport) == 10);
static_assert(sizeof(HIDMouseRelativeReport) == 10); static_assert(sizeof(HIDMouseRelativeReport) == 10);
static_assert(sizeof(HIDKeyboardReport) == 9); static_assert(sizeof(HIDKeyboardReport) == 9);
static_assert(sizeof(HIDKeyboardLedsReport) == 2); static_assert(sizeof(HIDKeyboardLedsReport) == 2);
static_assert(sizeof(HIDConsumerReport) == 3);
static constexpr size_t HID_MAX_INPUT_REPORT_SIZE = static constexpr size_t HID_MAX_INPUT_REPORT_SIZE =
sizeof(HIDMouseAbsoluteReport); sizeof(HIDMouseAbsoluteReport);

View File

@@ -27,6 +27,14 @@
#include <string.h> #include <string.h>
static constexpr uint8_t HID_KEYBOARD_USAGE_MUTE = 0x7f;
static constexpr uint8_t HID_KEYBOARD_USAGE_VOLUME_UP = 0x80;
static constexpr uint8_t HID_KEYBOARD_USAGE_VOLUME_DOWN = 0x81;
static constexpr uint16_t HID_CONSUMER_USAGE_MUTE = 0xe2;
static constexpr uint16_t HID_CONSUMER_USAGE_VOLUME_UP = 0xe9;
static constexpr uint16_t HID_CONSUMER_USAGE_VOLUME_DOWN = 0xea;
bool CInputPipeClient::Start() bool CInputPipeClient::Start()
{ {
m_lastSequence = 0; m_lastSequence = 0;
@@ -209,14 +217,45 @@ bool CInputPipeClient::HandleKeyboard(
HIDKeyboardReport report = {}; HIDKeyboardReport report = {};
report.reportId = HID_REPORT_ID_KEYBOARD; report.reportId = HID_REPORT_ID_KEYBOARD;
report.modifiers = input.modifiers; report.modifiers = input.modifiers;
// The Consumer Control report carries one active array usage.
uint16_t consumerUsage = 0;
size_t keyIndex = 0;
for (size_t i = 0; i < LG_INPUT_KEYBOARD_KEY_COUNT; ++i) for (size_t i = 0; i < LG_INPUT_KEYBOARD_KEY_COUNT; ++i)
{ {
if (input.keys[i] > LG_INPUT_KEYBOARD_USAGE_MAX) if (input.keys[i] > LG_INPUT_KEYBOARD_USAGE_MAX)
return false; return false;
report.keys[i] = input.keys[i];
switch (input.keys[i])
{
case HID_KEYBOARD_USAGE_MUTE:
if (!consumerUsage)
consumerUsage = HID_CONSUMER_USAGE_MUTE;
break;
case HID_KEYBOARD_USAGE_VOLUME_UP:
if (!consumerUsage)
consumerUsage = HID_CONSUMER_USAGE_VOLUME_UP;
break;
case HID_KEYBOARD_USAGE_VOLUME_DOWN:
if (!consumerUsage)
consumerUsage = HID_CONSUMER_USAGE_VOLUME_DOWN;
break;
default:
report.keys[keyIndex++] = input.keys[i];
break;
}
} }
return SubmitReport(&report, sizeof(report)); if (!SubmitReport(&report, sizeof(report)))
return false;
const HIDConsumerReport consumer = {
HID_REPORT_ID_CONSUMER,
consumerUsage,
};
return SubmitReport(&consumer, sizeof(consumer));
} }
bool CInputPipeClient::SubmitReport( bool CInputPipeClient::SubmitReport(

View File

@@ -43,5 +43,5 @@ private:
bool SubmitReport(const void * report, size_t size); bool SubmitReport(const void * report, size_t size);
CPipeEndpoint m_endpoint; CPipeEndpoint m_endpoint;
uint64_t m_lastSequence = 0; uint64_t m_lastSequence = 0;
}; };