fix(host/windows): propagate XUSB devnode-create failure instead of latching a phantom pad

XusbWinPad::open swallowed a SwDeviceCreate failure — it returned Ok with
`_sw: None` (a pad with no devnode) and logged only a warn, so PadSlots latched a
phantom pad, called gate.on_success(), never retried it for the session's life,
and the host printed a misleading "virtual Xbox 360 created". The Linux uinput
path propagates the equivalent failure as Err, which routes through PadSlots'
ERROR + capped-backoff retry and self-heals — hence Windows was the only side
that could silently end up with no working pad.

Propagate the create failure with `?` so Windows gets the same ERROR + backoff
retry as Linux. Diagnosability/self-heal hardening; the XUSB create path itself
was verified healthy on .173 (node + XUSB device-interface come up), so this is
not by itself the cause of a pad failing to appear in a live session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-16 17:45:27 +02:00
parent 4ffa2665ac
commit 1a7e3a6e4f
@@ -159,14 +159,13 @@ impl XusbWinPad {
std::ptr::write_unaligned(base.add(OFF_PAD_INDEX) as *mut u32, index as u32); std::ptr::write_unaligned(base.add(OFF_PAD_INDEX) as *mut u32, index as u32);
std::ptr::write_unaligned(base as *mut u32, SHM_MAGIC); std::ptr::write_unaligned(base as *mut u32, SHM_MAGIC);
} }
let (hsw, instance_id) = match create_swdevice(index) { // Propagate a devnode-create failure instead of swallowing it: a swallowed failure left the
Ok((h, id)) => (Some(h), id), // pad with no devnode yet still reported success, so PadSlots latched a phantom pad (never
Err(e) => { // re-created for the session's life) and the host logged a misleading "virtual Xbox 360
tracing::warn!(error = %format!("{e:#}"), "SwDeviceCreate failed; XUSB devnode unavailable"); // created". Returning Err routes it through PadSlots' ERROR + capped-backoff retry — parity
(None, None) // with the Linux uinput path, which self-heals for exactly this reason.
} let (hsw, instance_id) = create_swdevice(index)?;
}; let _sw = Some(super::gamepad_raii::SwDevice::new(hsw));
let _sw = hsw.map(super::gamepad_raii::SwDevice::new);
// Bounded eager delivery: the driver's EvtDeviceAdd publishes its pid right away; handing it // Bounded eager delivery: the driver's EvtDeviceAdd publishes its pid right away; handing it
// the DATA handle before we return means the pad is live for the game's first XInput poll. // the DATA handle before we return means the pad is live for the game's first XInput poll.
// On a missing/old driver this waits out the window once and the service pump takes over. // On a missing/old driver this waits out the window once and the service pump takes over.