Skip to content

feat(windows): back the virtual mouse with a real HID device - #99

Merged
ReenigneArcher merged 8 commits into
LizardByte:masterfrom
IronicDeGawd:feat/windows-hid-mouse
Aug 23, 2026
Merged

feat(windows): back the virtual mouse with a real HID device#99
ReenigneArcher merged 8 commits into
LizardByte:masterfrom
IronicDeGawd:feat/windows-hid-mouse

Conversation

@IronicDeGawd

@IronicDeGawd IronicDeGawd commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Description

The Windows backend built its virtual mouse on SendInput. That path goes through the Win32 cursor pipeline, which the Raw Input API does not observe. Applications reading WM_INPUT therefore see no mouse at all, even though the cursor moves on screen, relative-motion controls like camera look and click-drag do nothing.

This change creates the mouse as a real HID device through the UMDF control channel, the same route gamepads already take, and sends relative motion, buttons and scrolling as HID input reports.

  • Adds a mouse report descriptor: five buttons, 16-bit relative X/Y, an 8-bit wheel and an 8-bit AC Pan axis, no report ID.
  • Adds WindowsHidMouse, which creates the device and submits seven-byte input reports.
  • Accumulates sub-detent high-resolution scroll per axis when converting to a detent-based wheel.
  • Leaves absolute motion on the existing injection path, which has no relative HID equivalent.
  • Falls back to the previous SendInput mouse when the driver is unavailable.

Not verified on hardware, without a licence the driver declines device creation on my machine and the fallback path runs instead.

Superseded by the maintainer's branch feat/windows-hid-mouse, which generalises device creation across the broker and driver protocols and adds a mouse-specific queue policy. See the review discussion below.

Screenshot

N/A, not a UI change.

Issues Fixed or Closed

Roadmap Issues

Type of Change

  • feat: New feature (non-breaking change which adds functionality)
  • fix: Bug fix (non-breaking change which fixes an issue)
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semicolons, etc.)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit
  • BREAKING CHANGE: Introduces a breaking change (can be combined with any type above)

Checklist

  • Code follows the style guidelines of this project
  • Code has been self-reviewed
  • Code has been commented, particularly in hard-to-understand areas
  • Code docstring/documentation-blocks for new or existing methods/components have been added or updated
  • Unit tests have been added or updated for any new or modified functionality

AI Usage

See our AI usage policy.

  • None: No AI tools were used in creating this PR
  • Light: AI provided minor assistance (formatting, simple suggestions)
  • Moderate: AI helped with code generation or debugging specific parts
  • Heavy: AI generated most or all of the code changes

@CLAassistant

CLAassistant commented Aug 21, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

The Windows mouse backend synthesised input with SendInput, which travels
the Win32 cursor pipeline. Applications that read mouse input through the
Raw Input API observe nothing on that path, so relative-motion controls
such as camera look and click-drag are unusable while the mouse still
appears to move on screen.

Create the mouse as a HID device through the UMDF control channel, the
same way gamepads are created, and deliver relative motion, buttons and
scrolling as HID input reports. Raw Input consumers then see the device
as they would a physical mouse.

- Add a five-button mouse report descriptor with 16-bit relative axes, a
  wheel and an AC Pan axis.
- Accumulate sub-detent high-resolution scroll so precision is preserved
  across events.
- Delegate absolute motion to the existing injection path, which has no
  relative HID equivalent.
- Fall back to the previous SendInput mouse whenever the driver is
  unavailable, so mouse input keeps working without the driver package.
Address static analysis findings: test the optional with has_value() and
hold the HID button bitmask as std::byte rather than a raw integer.
@IronicDeGawd
IronicDeGawd force-pushed the feat/windows-hid-mouse branch from 90e8d3e to f64667d Compare August 21, 2026 19:45
@ReenigneArcher

Copy link
Copy Markdown
Member

Thank you for the PR submission, but it looks like you used AI to create this PR.

No problem with that, but please read and follow our Contributing guidelines and specifically our AI Usage policy.

Additionally, please update the PR to use the correct template. You can find it at https://github.com/LizardByte/.github/blob/master/.github/pull_request_template.md?plain=1

I'll review shortly and address your notes. Thanks!

Extends the Windows driver path to support Raw Input-visible mice alongside gamepads. Key changes:

- Bumps control protocol to v3 and broker protocol to v4, renaming gamepad-specific structs/IOCTLs to generic device equivalents (e.g. `LvhWindowsCreateDeviceRequest`, `LVH_WINDOWS_IOCTL_CREATE_DEVICE`)
- Adds `LVH_WINDOWS_DEVICE_GAMEPAD`/`LVH_WINDOWS_DEVICE_MOUSE` device type field to the create request
- Introduces `mouse_protocol.hpp` with a canonical 7-byte five-button relative descriptor shared across backend, broker, and driver
- `VhfInputReportQueue` gains a mouse accumulation path that batches relative motion/scroll by button state and emits descriptor-sized chunks, preventing VHF backpressure from discarding relative movement
- `WindowsHidMouse` preserves caller-supplied bus type, VID, PID, version, name, manufacturer, and stable ID; falls back to `SendInput` only for license/availability errors, not protocol failures
- Broker and driver validation enforce mouse-specific field constraints (fixed descriptor, no report ID, no output reports, no gamepad flags)
- Renames internal gamepad-scoped state and methods to device-scoped equivalents (`WindowsVhfDeviceState`, `devices_` map, etc.)
@ReenigneArcher

Copy link
Copy Markdown
Member

Review generated by GPT-5.6 Sol. I fed this into my agent, since it has all the memory of decisions throughout the whole development of the project.


Required changes

  1. Add an explicit device type to the Windows control path.
    The mouse currently uses GamepadProfileKind::generic, which causes VhfInputReportQueue to apply gamepad-state coalescing. Mouse reports contain relative deltas, so replacing pending reports loses movement and scrolling. For example, consecutive vertical-motion or wheel reports can overwrite each other instead of accumulating.
    Please generalize the create operation from create_gamepad to something like create_device and carry an explicit device type through the broker and driver protocols. The driver then needs a mouse-specific queue policy that preserves or aggregates relative X/Y, wheel, and pan values while preserving button transitions. This can remain entirely internal to the Windows backend.
  2. Restrict the SendInput fallback to expected outcomes.
    create_mouse() currently falls back whenever driver-backed creation fails, including malformed requests, protocol mismatches, and unexpected driver failures. Those errors should remain visible rather than silently disabling the Raw Input behavior.
    Falling back is appropriate when the driver/broker is unavailable or when driver-backed creation is rejected for licensing reasons. Other failures should be returned to the caller.
  3. Do not discard large motion or scroll values.
    Relative motion is currently clamped to 16 bits, which loses the remainder of a larger delta. Scroll conversion subtracts all calculated detents before clamping the emitted value to ±127, so any excess is permanently discarded. The integer remainder may also overflow.
    Please split large values across multiple HID reports or retain the unsubmitted remainder, using a wider accumulator.
  4. Preserve the supplied mouse profile identity.
    CreateMouseOptions::profile is currently replaced by a hard-coded profile, with only stable_id retained. Please preserve the caller’s name, manufacturer, VID, PID, and version, while overriding only the Windows-owned descriptor, report framing, and other required transport fields.
  5. Add tests and documentation.
    Please add focused tests covering:
    • The descriptor and seven-byte report layout.
    • Relative X/Y encoding and preservation through the driver queue.
    • Button ordering and held-button state.
    • Vertical and horizontal scrolling, including sub-detent accumulation and overflow.
    • Absolute-motion delegation.
    • Licensed creation and SendInput fallback.
    • Expected versus unexpected creation failures.
    • Device destruction and submission after close.
      This also changes the documented Windows driver and consumer behavior, so docs/windows-driver.md should describe driver-backed mouse support, licensing, fallback behavior, and validation.

Answers to the questions in the PR body

  1. Request type: Please generalize the request to a virtual-HID device creation request rather than adding an otherwise duplicated mouse request. It must carry an explicit device type because the driver’s buffering and report handling differ between state-based gamepads and relative mice. The broker and control protocol versions should be updated as required.
  2. Report ID: A descriptor without a Report ID and a seven-byte report without a prefix is correct. The driver should submit it to VHF with reportId = 0; callers should not prepend a zero byte.
  3. Licensing: Creating a real HID mouse through the installed driver should require the existing machine license, just like other driver-created devices. It does not require or consume another Polar machine activation; it is simply another active device authorized by the same license. If authorization is unavailable, falling back to SendInput preserves the existing unlicensed behavior, but the Raw Input-capable driver path remains a licensed feature. The broker’s gamepad-specific authorization/count/revocation terminology should therefore be generalized to driver-created virtual HID devices.

I also had the agent make the changes, and put them into a separate branch (https://github.com/LizardByte/libvirtualhid/tree/feat/windows-hid-mouse). These changes end up much bigger than what you originally had. Feel free to review/push back on whatever. I did not really test any of this yet other than via unit tests locally.

This is definitely something needed though, and was planned to be added here all along. I know I've greatly increased the complexity of the change, so if you'd rather I usher this to the finish line I'm fine with that (you'll still get credit for contributing and all).

Also, I don't know if you're aware but I am granting some contributors free yearly licenses. Details are in https://app.lizardbyte.dev/2026-08-16-introducing-libvirtualhid-and-virtual-hid-driver/ ... feel free to apply after the PR is merged.

@IronicDeGawd

IronicDeGawd commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review, please do take it to the finish line. The remaining work is mostly inside the driver and broker, and I can't build or test that side properly from here, so you'll get there faster than I would.

The queue coalescing point is the one I'm glad you caught. I reused the gamepad create path because it already carried an arbitrary descriptor, and I completely missed that replacing a pending report is fine for a gamepad reporting full state but destroys relative deltas from a mouse. That would have shown up as dropped movement under load and I'd have had no idea why.

The other four all make sense to me, narrowing the fallback to unavailable/unlicensed, keeping the caller's profile identity, and carrying the remainder instead of clamping it away.

On AI usage: it was moderate, and I should have said so up front. I've updated the PR to your template with that declared.

I'll apply for a licence once this merges. Thanks again.

@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 35.96059% with 520 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.85%. Comparing base (15a37d3) to head (2b88b72).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
tools/virtualhid_control.cpp 0.00% 458 Missing and 19 partials ⚠️
src/platform/windows/windows_backend.cpp 77.14% 39 Missing and 1 partial ⚠️
...platform/windows/shared/vhf_input_report_queue.hpp 97.43% 1 Missing and 1 partial ⚠️
tools/virtualhid_control_model.cpp 97.50% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #99      +/-   ##
==========================================
- Coverage   75.47%   72.85%   -2.62%     
==========================================
  Files          34       34              
  Lines        8133     8752     +619     
  Branches     3044     3251     +207     
==========================================
+ Hits         6138     6376     +238     
- Misses       1454     2081     +627     
+ Partials      541      295     -246     
Flag Coverage Δ
FreeBSD 52.30% <80.80%> (+0.57%) ⬆️
Linux-Clang 54.43% <14.87%> (-3.06%) ⬇️
Linux-GCC 64.99% <21.50%> (-3.21%) ⬇️
Windows-MSVC 88.27% <87.15%> (-0.15%) ⬇️
Windows-MinGW-UCRT64 62.21% <32.04%> (-2.96%) ⬇️
macOS 48.17% <16.88%> (-3.25%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/platform/windows/control_protocol.hpp 98.85% <100.00%> (+0.14%) ⬆️
...rm/windows/shared/playstation_feature_protocol.hpp 95.09% <100.00%> (ø)
...latform/windows/shared/windows_device_identity.hpp 100.00% <100.00%> (ø)
tools/virtualhid_control_model.cpp 97.99% <97.50%> (-0.10%) ⬇️
...platform/windows/shared/vhf_input_report_queue.hpp 96.98% <97.43%> (-0.87%) ⬇️
src/platform/windows/windows_backend.cpp 83.14% <77.14%> (-1.00%) ⬇️
tools/virtualhid_control.cpp 0.00% <0.00%> (ø)

... and 9 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 15a37d3...2b88b72. Read the comment docs.

Extend the native control tool to create, inspect, reset, and remove virtual mice alongside gamepads. This adds keyboard-navigable mouse motion, button, and wheel actions, plus delayed browser-test scheduling for focus-sensitive mouse validation. Update the shared control model tests and refresh README and Windows/platform/store-review docs to reflect driver-backed mouse support and its licensing requirements.
@sonarqubecloud

Copy link
Copy Markdown

@ReenigneArcher
ReenigneArcher merged commit acd2e33 into LizardByte:master Aug 23, 2026
31 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants