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

View File

@@ -120,6 +120,20 @@ static const uint8_t REPORT_DESCRIPTOR[] =
0x95, 0x06, // Report Count (6)
0x81, 0x00, // Input (Data, Array, Absolute)
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()
@@ -145,6 +159,9 @@ size_t HIDGetInputReportSize(uint8_t reportId)
case HID_REPORT_ID_KEYBOARD:
return sizeof(HIDKeyboardReport);
case HID_REPORT_ID_CONSUMER:
return sizeof(HIDConsumerReport);
default:
return 0;
}

View File

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

View File

@@ -27,6 +27,14 @@
#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()
{
m_lastSequence = 0;
@@ -209,14 +217,45 @@ bool CInputPipeClient::HandleKeyboard(
HIDKeyboardReport report = {};
report.reportId = HID_REPORT_ID_KEYBOARD;
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)
{
if (input.keys[i] > LG_INPUT_KEYBOARD_USAGE_MAX)
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(