executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
hid-input-filterWindows Human Interface Device (HID) stack and filter drivers. Covers HID class architecture, transport drivers (HIDUSB / HIDBTH / HIDI2C), HIDClass, mouse/keyboard filter (Mouclass / Kbdclass) drivers, internal IOCTLs (IOCTL_HID_*), filter placement (upper/lower), event interception, injection, and the Virtual HID Framework (VHF). USE WHEN: user mentions …
Independent GitHub snapshot · Automated classification · Original documentation governs
name: hid-input-filter description: | Windows Human Interface Device (HID) stack and filter drivers. Covers HID class architecture, transport drivers (HIDUSB / HIDBTH / HIDI2C), HIDClass, mouse/keyboard filter (Mouclass / Kbdclass) drivers, internal IOCTLs (IOCTL_HID_*), filter placement (upper/lower), event interception, injection, and the Virtual HID Framework (VHF).
USE WHEN: user mentions "HID", "HID filter", "mouse filter", "keyboard filter", "touch filter", "pen filter", "HIDClass", "mouclass", "kbdclass", "moufiltr", "kbfiltr", "IOCTL_HID_READ_REPORT", "IOCTL_HID_GET_FEATURE", "VHF", "Vhf.sys", "input intercept", "input injection"
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:hid-input-filter. Authoritative source:learn.microsoft.com/en-us/windows-hardware/drivers/hid/.
Application (RIM, RawInput, GetMessage WM_INPUT, DirectInput, XInput)
|
User mode
───────────────────────────────────────────────────────────
Kernel mode
|
Mouclass.sys / Kbdclass.sys / Hidclass.sys filter clients
|
┌──────────────────────────┐
│ Upper filter(s) │ <- intercept here for class-wide effect
│ HIDClass.sys │
│ Lower filter(s) │ <- intercept here for transport-specific effect
│ Hidusb / Hidbth / Hidi2c │ <- transport (mini)driver
│ USB / Bluetooth / I2C │
└──────────────────────────┘
For mouse and keyboard, inputs flow as:
| Where to filter | Effect |
|---|---|
Upper filter on HID class (above HIDClass.sys) | See parsed HID reports for all HID devices in that instance — clean, structured data |
Lower filter on HID class | See raw transport interactions before HIDClass parses |
Upper filter on Mouse class (above Mouclass.sys) | See mouse-specific events (movement deltas, button bitmasks) for that mouse |
Upper filter on Keyboard class (above Kbdclass.sys) | See keyboard scan codes for that keyboard |
Decision rule: filter as high as you can while still seeing the data you need. For "intercept mouse + touchscreen events and route to specific apps", an upper HID class filter sees everything (mouse, touch, pen) in one place.
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
WDF_DRIVER_CONFIG config;
WDF_DRIVER_CONFIG_INIT(&config, MyHidFilterEvtDeviceAdd);
return WdfDriverCreate(DriverObject, RegistryPath,
WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE);
}
NTSTATUS MyHidFilterEvtDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit) {
UNREFERENCED_PARAMETER(Driver);
WdfFdoInitSetFilter(DeviceInit);
WDFDEVICE device;
NTSTATUS status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &device);
if (!NT_SUCCESS(status)) return status;
// HID uses *internal* IOCTLs between class drivers — not regular IOCTLs.
WDF_IO_QUEUE_CONFIG q;
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&q, WdfIoQueueDispatchParallel);
q.EvtIoInternalDeviceControl = MyHidFilterEvtIoInternalDeviceControl;
return WdfIoQueueCreate(device, &q, WDF_NO_OBJECT_ATTRIBUTES, NULL);
}
Defined in <hidclass.h> / <hidport.h>:
| IOCTL | Direction | Purpose |
|---|---|---|
IOCTL_HID_GET_DEVICE_DESCRIPTOR | down → up | Gets the HID descriptor |
IOCTL_HID_GET_REPORT_DESCRIPTOR | down → up | Gets the Report Descriptor (the structure of every report) |
IOCTL_HID_GET_DEVICE_ATTRIBUTES | down → up | VID/PID/version |
IOCTL_HID_READ_REPORT | up → down → up | Input reports — this is the request that streams events |
IOCTL_HID_WRITE_REPORT | up → down | Output reports (e.g. LEDs) |
IOCTL_HID_GET_FEATURE / IOCTL_HID_SET_FEATURE | up ↔ down | Feature reports (configuration) |
For a filter, the most common pattern is to forward most IOCTLs untouched and intercept IOCTL_HID_READ_REPORT completions to inspect/modify the report.
// Forward + completion routine (modify on the way back up)
VOID MyHidFilterEvtIoInternalDeviceControl(
WDFQUEUE Queue, WDFREQUEST Request,
size_t OutputBufferLength, size_t InputBufferLength,
ULONG IoControlCode)
{
WDFDEVICE device = WdfIoQueueGetDevice(Queue);
WDFIOTARGET target = WdfDeviceGetIoTarget(device);
if (IoControlCode == IOCTL_HID_READ_REPORT) {
// Hand it off and inspect the result on completion
WdfRequestFormatRequestUsingCurrentType(Request);
WdfRequestSetCompletionRoutine(Request, MyReadReportCompleted, device);
if (!WdfRequestSend(Request, target, WDF_NO_SEND_OPTIONS)) {
WdfRequestComplete(Request, WdfRequestGetStatus(Request));
}
return;
}
// Default: forward without monitoring
WDF_REQUEST_SEND_OPTIONS opts;
WDF_REQUEST_SEND_OPTIONS_INIT(&opts, WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET);
if (!WdfRequestSend(Request, target, &opts)) {
WdfRequestComplete(Request, WdfRequestGetStatus(Request));
}
}
VOID MyReadReportCompleted(WDFREQUEST Request, WDFIOTARGET Target,
PWDF_REQUEST_COMPLETION_PARAMS Params, WDFCONTEXT Context)
{
// Params->Parameters.Ioctl.Output.Buffer points at the HID report
// - Inspect it
// - Optionally rewrite/zero it (suppress event from system)
// - Optionally route a copy to user-mode (inverted call / shared memory)
WdfRequestComplete(Request, Params->IoStatus.Status);
}
To make events "invisible" to the rest of the system:
Be very careful with security: hiding events from the OS while exposing them to one app is a sensitive design. Document the threat model and limit the filter to specific devices via INF (HardwareId / CompatibleId). Don't catch arbitrary HID devices.
To synthesize input events:
SendInput (mouse/keyboard) — works for most cases, no driver neededVhf.sys) lets you create a virtual HID source and submit reports as if they came from a real device. The system sees them like any other HID device's reports.VHF skeleton:
VHF_CONFIG cfg;
VHF_CONFIG_INIT(&cfg, WdfDeviceWdmGetDeviceObject(device),
sizeof(MyReportDescriptor), MyReportDescriptor);
cfg.OperationContextSize = sizeof(MY_OP_CTX);
NTSTATUS s = VhfCreate(&cfg, &g_VhfHandle);
s = VhfStart(g_VhfHandle);
// Later, to deliver an input report
HID_XFER_PACKET packet = { .reportBuffer = buf, .reportBufferLen = sizeof(buf), .reportId = 1 };
VhfReadReportSubmit(g_VhfHandle, &packet);
[ClassInstall32]
AddReg = MyHidFilter.AddReg
[MyHidFilter.AddReg]
HKR,,UpperFilters,0x00010000,"MyHidFilter"
[Manufacturer]
%MfgName% = Standard,NT$ARCH$.10.0...19041
[Standard.NT$ARCH$.10.0...19041]
%Filter.DeviceDesc% = MyHidFilter_Inst, {745A17A0-74D3-11D0-B6FE-00A0C90F57DA} ; HIDClass GUID
For a per-device filter, add UpperFilters to the device's hardware key via the device's INF — narrower scope, easier to reason about.
| Anti-Pattern | Why It's Bad | Correct Approach |
|---|---|---|
| Class-wide filter when one device is the target | Affects every keyboard/mouse on the box | Per-device filter via the device's INF + HardwareId |
| Modifying the report buffer at DISPATCH_LEVEL with paged work | Bugcheck | Dispatch parsing/forwarding to PASSIVE workitem |
| Forgetting to complete or pass through unhandled IOCTLs | Stack hangs, devices disappear | Default branch always forwards or completes |
| Hard-coded report layouts | Devices vary; descriptors change between firmware revs | Parse the Report Descriptor at attach time |
| Polling for input from user mode | Wastes battery, adds latency | Inverted-call IOCTL or shared event |
Catching SendInput events | They go in via Mouclass, not via HID — different path | Filter on Mouclass/Kbdclass for user-injected input too |