feat(backend): add HIDMaestro as a second Windows gamepad backend alongside ViGEmBus - #82
Open
emir-hasanbegovic wants to merge 3 commits into
Open
feat(backend): add HIDMaestro as a second Windows gamepad backend alongside ViGEmBus#82emir-hasanbegovic wants to merge 3 commits into
emir-hasanbegovic wants to merge 3 commits into
Conversation
Adds the HIDMaestro user-mode UMDF2 driver as a second, coexisting Windows gamepad backend. Windows now offers all four controller types: DualSense and Switch Pro (both with motion) materialize via HIDMaestro, while Xbox 360 / DualShock 4 keep preferring ViGEm's kernel path and fall back to HIDMaestro when ViGEmBus is absent. Satellite runs with either driver, both, or none; the offered set is the union. Core (pure, tested on every lane): - core/backend_registry: data-driven backend identity/vendor/per-type feature + latency-tier table; buildBackendsJson() and the catalog traits fold both derive from it. Catalog traits gain per-type dualsense/switchpro motion fields with their own requires codes. - core/gamepad_mux: composite IGamepadPort sharing one serial space across preference-ordered backends; SessionService keeps its single port reference and serial pool untouched. - core/ds4_report.h: the DS4 v2 codec hoisted from platform/macos so the Windows HIDMaestro packer shares it byte-for-byte. Windows backend (hot path fully native, zero allocations per frame): - hidmaestro_wire: seqlock input-frame writer (GIP slice, mandatory ExtendedReportSize clear) + 64-slot output-ring reader, pinned to the v1.7.0 driver layout (no protocol version field upstream, so the constants ARE the compatibility contract; see lib/VENDORED.md). - hidmaestro_report: per-profile packers (xbox-360-wired HID+GIP, dualshock-4-v2, dualsense at real hardware offsets incl. IMU/touch/ battery, switch-pro 0x30 body) and ring-packet rumble/lightbar decode. Sony IMU rescaled to the driver's neutral-calibration units. - hidmaestro_adapter: flat per-serial slots holding mapped views + duplicated handles; submit = pack + seqlock memcpy + SetEvent; one output-ring worker per plugged serial for rumble/lightbar callbacks. - helper/hidmaestro (satellite-hm-helper.exe, .NET 10 self-contained): the elevated lifecycle broker. Satellite stays asInvoker; the helper drives the HIDMaestro SDK (driver deploy, SwDevice + shared-section creation) and duplicates section/event handles into the satellite process over a private named pipe (client PID verified). One UAC prompt per session, only on the first HIDMaestro plug. API/UI (additive, protocol 1): capabilities + /api/backend/status gain a `backends` array; the singular `backend` object now reports the preferred-available backend. Windows catalog offers ids 2 and 3. Dashboard renders remediation for every unavailable backend (new driver-kernel/driver-user glyphs), hidmaestro copy in all six locales. Packaging/CI: optional-but-default "HIDMaestro driver" installer component with /HIDMAESTRO= and /REMOVEHIDMAESTRO= switches mirroring the ViGEmBus ones (deploy at setup, no reboot; uninstall prompt kept separate); fetch-redist pins the HIDMaestro release zip by SHA-256 and stages the SDK assemblies; windows-ci and release build the helper via pinned setup-dotnet; installer round-trip skips both drivers; SBOM and VENDORED.md entries added. Tests: test_backend_registry, test_gamepad_mux, test_hidmaestro_wire, test_hidmaestro_report (pure, all lanes), test_hidmaestro_adapter (fake-provisioner, Windows), plus catalog/route suites extended.
The dashboard banner's four-button actions row is flex-shrink: 0 inside a no-wrap flex row; below ~700px the actions overflowed off the banner and the body (the only shrinkable item, flex-basis 0) collapsed to zero width, wrapping the title/detail one character per line. Let the banner wrap and give the body a 200px basis so the actions drop to their own row instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds HIDMaestro (user-mode UMDF2, MIT) as a second Windows gamepad backend alongside — not replacing — ViGEmBus. Windows now offers all four controller types; the app, installer, and web UI work with 0, 1, or 2 drivers installed.
What each backend does
Design
core/backend_registrymodels backends as data (id, vendor, kernel/user mode, per-type features + latency tiers);core/gamepad_muxis a compositeIGamepadPortrouting one shared serial space across preference-ordered children.SessionServiceis untouched — same single port reference, same serial pool.memcpyinto the driver's mapped shared-memory section →SetEvent. No managed code, no syscall beyond the doorbell, no allocations per frame. Rumble/lightbar come back through a native reader of the driver's 64-slot output ring (one worker thread per plugged pad, driver-signaled doorbell), mirroring the ViGEm notification workers.Global\section creation, driver deploy) needs an elevated token, so it lives in the bundled self-contained .NET helper (helper/hidmaestro→satellite-hm-helper.exe). Satellite spawns it once per session on the first HIDMaestro plug (one UAC prompt; never from a status probe — ViGEm-only sessions never see it), and the helper duplicates the per-controller section/event handles back over a private named pipe (client PID verified against the spawned process). If the helper dies, submits keep working; unplug reports unconfirmed and quarantines the serial, matching the ViGEm zombie-target contract.hidmaestro_wire.h+hidmaestro_report.hre-state the v1.7.0 layout (section sizes are the runtime layout check) andlib/VENDORED.mddocuments the pin-bump procedure. The DS4 packer reuses the already-validated codec hoisted tocore/ds4_report.h(macOS + Windows now share it byte-for-byte); Sony IMU values are rescaled to the driver's neutral-calibration units (20 LSB/deg·s, 10000 LSB/g); the Switch body follows SDL's Nintendo mapping exactly like the Linux uinput adapter.API / UI (additive, protocol 1)
GET /api/server/capabilitiesandGET /api/backend/statusgain abackendsarray (identity, availability, per-type features, comparable latency ranks). The singularbackendobject is unchanged in shape and now reports the preferred-available backend, so existing clients keep working.hidmaestrowith error codesDRIVER_MISSING/HELPER_MISSING; new requires code"hidmaestro>=1.7". Windows catalog now offers ids 2 (dualsense) and 3 (switchpro) — their art already existed from the Kenney CC0 vendoring.driver-kernel/driver-userglyphs;backend.hidmaestro.*strings added to all six locales.Installer / packaging / CI
satellite-hm-helper.exe install-driver(idempotent, no reboot)./HIDMAESTRO=auto|bundled|skipand/REMOVEHIDMAESTRO=yes|no|automirror the ViGEmBus switches; uninstall prompts separately per driver (default No, shared-driver warning).fetch-redist.ps1pinsHIDMaestro-v1.7.0.zipby SHA-256 and stages the SDK assemblies for the helper build;redist/README.md, SBOM,SECURITY.md, andVENDORED.mdinventories updated.windows-ciandreleasebuild the helper via SHA-pinnedactions/setup-dotnet; the installer round-trip skips both drivers (/VIGEM=skip /HIDMAESTRO=skip);install-dependencies.bat/build-installer.batgrow the .NET SDK 10 step.Tests (all green locally: 28/28 ctest, clang-format 22.1.4 clean, core-purity gate OK)
test_backend_registry— registry identity, JSON builder, catalog-traits fold (union, preference order, availability independence).test_gamepad_mux— routing, plug fallback, quarantine-preserving unplug, callback fan-in, identity union, motion routing.test_hidmaestro_wire— byte-pinned section layouts, seqlock progression, capacity guards, legacy↔extended mode switch, GIP slice, ring cursor/lap-skip/torn-slot rules. Pure — runs on every CI lane.test_hidmaestro_report— byte-exact packer vectors round-tripped through the XUSB companion's documented decode math; DS4/DS5/Switch layouts incl. IMU rescale, touch, battery; rumble/lightbar decoders with flag gating. Pure — every lane.test_hidmaestro_adapter— the real adapter against a fake provisioner (in-process anonymous mappings): submit bytes, merged motion/touch/battery state, elevation-declined and provision-failure paths, output-ring workers delivering rumble + lightbar, closeBus teardown.backendsarray.Verified locally
satellite.exe(warning-free) + all 28 ctest suites.dotnet publishof the helper (warnings-as-errors) and a smoke run.iscc installer.isscompiles the full installer with both drivers bundled.fetch-redist.ps1end-to-end (download, hash verify, SDK staging).Not verified here: a live end-to-end run against the real HIDMaestro driver (needs the driver deployed + an elevated helper session); the wire/report layouts are pinned against the v1.7.0 driver sources instead, and the layout tests fail loudly on any future drift.
Follow-up commits after live testing
6db45f7).SessionServicecloses the backend bus whenever the last controller unplugs; the first cut shut the elevated helper down there too, which would have meant a fresh UAC prompt on every client reconnect. The helper now survives idle periods (still exits with satellite via its parent watch, or in the adapter destructor) — so it is one UAC prompt per satellite session, verified live: the prompt appears on the first HIDMaestro bind from the client.aff6994, drive-by). Pre-existing bug surfaced during testing of this build: the banner's four-button actions row isflex-shrink: 0inside a no-wrap flex row, so narrow windows pushed the buttons off the banner and crushed the body to one character per line. The banner now wraps and the body has a 200px flex basis. Unrelated to the backend work — happy to split it out if preferred.