5d7091bf87fd1cb9c605d1ac1f265feaf719fe2b
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
94c2f62490 |
test(tools): a GameInput probe — and it cannot see our promoted Xbox pad
`win-input-matrix` covered four of the five rows and said so; GameInput was the gap, because it has no binding in the `windows` crate and needs hand-written COM. This adds it: `--gameinput` reports whether GameInput has a reading, and `--gi-rumble l,h,lt,rt [--gi-pid PID]` drives `SetRumbleState`. Every vtable slot is taken from the SDK header, not guessed — a COM vtable is positional, so a wrong slot calls a different method with the wrong signature. WHY RUMBLE AND NOT JUST ENUMERATION. `XINPUT_VIBRATION` has two members, so classic XInput can never exercise an Xbox pad's two IMPULSE-TRIGGER motors. `GameInputRumbleParams` has four (`lowFrequency`, `highFrequency`, `leftTrigger`, `rightTrigger`), which makes GameInput the only API that can settle `design/trigger-rumble-plane.md` §2.1's open question — the `enable`-mask bit assignment for the two trigger actuators, where bits 2/3 (the handles) are measured and bits 0/1 (the triggers) are inferred from field order and nothing else. TWO THINGS MEASURED ON .173, 2026-08-09: 1. ⭐ GameInput's device enumeration is ASYNCHRONOUS, and the first `GetCurrentReading` reliably returns nothing even with pads actively reporting. This is the GameInput analogue of `wake_wgi`: the API looks like a query and is really a cache someone else fills. A bounded poll fixes it. ⚠️ Focus is NOT the cause, and the header rules it out rather than my guessing: `GameInputDefaultFocusPolicy` is 0 and every `GameInputFocusPolicy` flag is a RESTRICTION, so the default already admits background input. Do not "fix" this with `SetFocusPolicy`. 2. 🛑 **GameInput never sees our pad.** Hunting by product id for six seconds with the pad live and sweeping, it enumerated `054C:0CE6` (DualSense) and `3434:D031` (8BitDo) — both plain HID pads — and never `045E:02FD`, ours, while classic XInput was reading ours live in the same moment. ⇒ THE TRIGGER ENABLE BITS REMAIN CONJECTURE, but for a better reason than before: it is not that nobody has tried, it is that on this box NOTHING CAN DELIVER a four-motor rumble to our pad. XInput structurally cannot; GameInput can but does not see it. ⚠️ The obvious suspicion is that `xinputhid` claiming the HID collection exclusively is what hides the pad from GameInput — which would mean promotion costs us the API most Game-Pass-era titles use, a trade we have shipped by default. **That is NOT established here.** The decisive control is cheap and has not been run: power on the REAL Xbox Elite, which Microsoft's own driver promotes the same way, and see whether GameInput enumerates it. If a real promoted Xbox pad is also absent, this is a property of GameInput in a non-interactive session and not our defect — the same shape as the WGI `ts=0` row, which a real Elite reproduced. VERIFIED * `cargo fmt --check` clean; `cargo clippy --target x86_64-pc-windows-msvc --all-targets -- -D warnings` clean (cross-checked from macOS). * Builds and runs on .173; `GameInputCreate` succeeds, readings arrive after the poll, and `SetRumbleState` is accepted. * The runtime is loaded by name, so a box without GameInput reports "unavailable" rather than failing to link or crashing. NOT VERIFIED * That `SetRumbleState` reaches ANY pad's motors — it was accepted for the DualSense but nothing observable was checked on that device, and it never reached ours. * `GameInputDeviceInfo` is read only for `vendorId`/`productId` (offsets 4 and 6). The rest of the struct has variable-size members whose layout would have to be mirrored exactly; nothing here needs them. `supportedRumbleMotors` is in there and would answer "does GameInput think this pad has trigger motors" — worth adding if this line of enquiry continues. |
||
|
|
f9fe496dbc |
feat(drivers/pf-gamepad): declare the rumble output report, and the Xbox pad gets rumble at all
`XBOX_RDESC` declared no OUTPUT item — zero `0x91` bytes. hidclass routes an output report only if
the descriptor declares one, so `on_output_report` never fired, `publish_output` never wrote the
out-ring, and `parse_xbox_output` in `inject/windows/xbox_windows.rs` was unreachable code. The
entire host-side rumble plane was already built, wired and tested, and was simply never fed. The
HID Xbox pad therefore had NO rumble whatsoever, not merely no trigger rumble.
This appends the PID-page `Set Effect Report` collection, report id `0x03`, 8 payload bytes, sized
to exactly the layout `parse_xbox_output` and `design/trigger-rumble-plane.md` §2.1 already
specify. It is declared AFTER the final Input item and re-states every global it uses, so the
16-byte input layout `xbox_proto`'s tests pin is untouched.
⚠️ PROVENANCE: hand-written, and it could not be otherwise. The Elite capture taken for WP-A
reports `OUTPUT items: 0` — Windows exposes no literal descriptor bytes and the reconstruction
carries no output collection for that pad — so there was nothing to copy. The comment says so and
asks for a Linux hidraw capture to replace it.
Also adds a compile-time assert pairing every descriptor with its HID-descriptor `wReportLength`.
Those are two copies of one length, edited in different places, and a mismatch fails SILENTLY:
hidclass asks for `wReportLength` bytes, parses whatever it got, and the pad either enumerates
truncated or not at all with nothing naming the cause. It now cannot build out of step. This
caught nothing today because I updated both by hand, but it is exactly the trap this descriptor
has already sprung twice in other forms.
MEASURED ON .173 (Win11 26200), with the pad promoted via the WP-B0 xinputhid bus-filter config:
* `XInputSetState(0xFFFF, 0x8000)` produced, on the host side,
`rumble from game: pad=0 low=65535 high=32767`
`rumble from game: pad=0 low=0 high=0`
i.e. XInputSetState -> xinputhid -> HID output report 0x03 -> on_output_report -> out-ring ->
parse_xbox_output -> PadFeedback. First rumble this backend has ever delivered.
* The round-trip values confirm the descriptor's `Logical Maximum (100)` percent domain is
right: 0x8000 -> 50% -> 32767. A 0..255 domain would have produced different numbers.
* This also answers `trigger-rumble-plane.md`'s WP0 gate — YES, Windows writes output reports
to a synthesized 045E:0B13 — which was blocking the whole trigger plane.
* classic XInput reads the pad fully: packets advancing, `buttons=0x1000` (the devtest's A), and
`LX [-32768..31744]`, the complete sweep. LY/RX/RY frozen is correct; the devtest drives only
LS-X and A.
VERIFIED
* `cargo test -p pf-inject --lib xbox` 11/11 — the input layout is byte-identical, as intended.
* `hid-descriptor-dump --rust-source ... --symbol XBOX_RDESC` decodes it clean: input report
0x01 unchanged at 16 bytes and the same offsets, new output report 0x03 at 9 bytes on the
wire, feature 0x85 unchanged, `structure: OK`.
* Driver builds and signs on .173 with the WDK; the new const asserts compile, so all five
descriptor/wReportLength pairs agree.
* fmt clean on both tools; .173 fully reverted afterwards.
NOT VERIFIED
* The enable-mask bit assignments for the two TRIGGER actuators. `XINPUT_VIBRATION` has only two
members, so XInput can never drive them and this run could not exercise them. Still open, as
trigger-rumble-plane.md WP0 says.
* That this equals the real pad's output collection, byte for byte. Needs Linux hidraw.
* Nothing about the INF is changed: `pf_gamepad.inx` still has no AddReg, so none of the
promotion config ships. The rumble descriptor is inert until something drives it.
|
||
|
|
13438b1287 |
test(tools): ask Windows which input APIs can see the pad, and find what promotes it
The Xbox-pad-on-Windows programme is a five-row matrix — classic XInput, WGI `Gamepad`, WGI
`RawGameController`, GameInput, and the HID/DirectInput/Steam family — and nothing in this tree
measured any of it. Every reading in the handoff came from ad-hoc off-tree tools, which is why
several could not be reproduced later and why one was a false positive. `win-input-matrix` makes
the matrix a command you can run twice and diff.
Two traps are baked into it because both have already cost this programme a wrong conclusion.
`--watch` samples repeatedly and reports LIVE vs MUTE per device, because an API listing a pad that
never reports is the exact failure mode here — worse than not listing it, since a title that binds
the first gamepad latches a dead one. And the doc comment insists on a baseline with the virtual pad
STOPPED: a real Xbox pad owns XInput slot 0, which is how `rc=0 LX=-885` was once read as success
with our pad already killed.
⭐ `wake_wgi()` is not optional and is commented as such. `Gamepad::Gamepads()` and
`RawGameController::RawGameControllers()` return a cache filled by WGI's device-watcher, which a
GUI app has already started and a console app has not. Without subscribing to the Added events
first, BOTH collections come back empty with real controllers attached — measured here: a DualSense
sitting in the HID interface class, `RawGameControllers` count=0. A probe missing this reports "WGI
cannot see the pad" when WGI could not see anything.
WHAT IT FOUND (full record in measurements/2026-08-09-xbox-hid-xinputhid-busfilter.md): with
`UpperFilters=xinputhid` on the pad's PARENT devnode AND `DevicePropertyFlags=1` in that parent's
SOFTWARE key, the HID Xbox pad is promoted for the first time — the child gains the `IG_00` token,
an XUSB interface appears, classic XInput admits it, and WGI `Gamepad` lists it. All four had never
happened on this backend. A one-value A/B proves `DevicePropertyFlags` is the decisive half:
removing it alone reverts all four. That retro-explains the earlier "the filter installs fine and
produces nothing" result — the filter was loading without ever being put in bus-filter mode, which
is what `BusDevice = 0x1` means in Microsoft's own comment in `xinputhid.inf`.
Not a workspace member, for the same reason as `hid-descriptor-dump`: it is a Windows-only
bring-your-own-hardware tool with no business on a CI leg.
VERIFIED
* `cargo fmt --check` clean; `cargo clippy --target x86_64-pc-windows-msvc --all-targets
-- -D warnings` clean (cross-checked from macOS; the target is installed).
* Builds and runs on .173 (Win11 26200).
* Self-checked against known-good hardware before any conclusion was drawn from it: baseline
reads the USB DualSense as LIVE in both WGI collections and the resting 8BitDo as MUTE.
* The A/B was run in both directions on the same box in one session.
* `cargo metadata` on the root workspace resolves and does NOT list this crate.
* .173 fully reverted: registry values removed, devnodes removed, oem100.inf deleted, both certs
delstored, 6 pre-existing pf_gamepad packages and the production service untouched.
NOT VERIFIED
* GameInput — no binding in the `windows` crate, needs hand-written COM vtables. Not covered;
the doc comment says so.
* That the promotion survives a reboot or a devnode re-create from a shipped INF `AddReg` rather
than a hand-written registry value. Nothing is shipped: `pf_gamepad.inx` is UNCHANGED and still
contains no AddReg of any kind.
* WHY the promoted pad still translates no data. Enumeration is fixed; translation is not. The
evidence points at the report descriptor, which is gated on the §3.3 decision.
|