(xinput) Clear stale pad state when XInputGetState fails

Companion to fda77fa, which fixed the equivalent issue in dinput.

Both xinput_joypad.c and xinput_hybrid_joypad.c poll into the cached
state directly and only inspect the result to detect connect/disconnect
transitions:

   status        = g_XInputGetStateEx(i, &state->xstate);
   success       = status == ERROR_SUCCESS;
   new_connected = status != ERROR_DEVICE_NOT_CONNECTED;

   if (new_connected != state->connected)
   {
      ...
   }

'success' is computed but only ever read inside that branch, so when a
call fails for any reason other than ERROR_DEVICE_NOT_CONNECTED the pad
remains marked connected and 'state->xstate' keeps whatever was read
last. XInput can return ERROR_ACCESS_DENIED or ERROR_BUSY, and
transient failures occur during device arbitration - for instance when
another process takes exclusive access, or across a fast user switch.

xinput_joypad_button() gates only on state->connected, then reads
state->xstate.Gamepad.wButtons, which carries the XINPUT_GAMEPAD_DPAD_*
bits. A direction held at the time of the last good read therefore
continues to be reported for as long as the failure persists.

Read into a temporary and copy it into the cached state only on
success, zeroing the cache otherwise. Reading into a temporary also
avoids leaving the cached state indeterminate should the call fail
after partially writing the output buffer.

Unlike the dinput POV case, a plain memset is correct here:
XINPUT_GAMEPAD.wButtons uses zero to mean no buttons pressed, whereas
DirectInput's rgdwPOV[] uses zero to mean north.

winraw_joypad.c was checked and does not share this defect: it is
event-driven via WM_INPUT with RIDEV_INPUTSINK, so it continues to
receive reports while unfocused, and its cached hats[] hold converted
bitmasks where zero already means centred.
This commit is contained in:
libretroadmin committed 2026-07-20 21:17:43 +02:00
1 parent b37da8ce41
commit 8b85429fcc
2 files changed
+36 -2

No files matched your search

+15 -1
View File
@@ -1149,6 +1149,7 @@ static void xinput_joypad_poll(void)
for (i = 0; i < 4; ++i)
{
DWORD status;
XINPUT_STATE tmp_xstate;
bool success, new_connected;
xinput_joypad_state *state;
/* On UWP, controllers may become available after initialization.
@@ -1163,9 +1164,22 @@ static void xinput_joypad_poll(void)
#endif
state = &g_xinput_states[i];
status = g_XInputGetStateEx(i, &state->xstate);
/* Read into a temporary: XInputGetStateEx() writing directly
* into the cached state would leave it indeterminate if the
* call fails partway. */
status = g_XInputGetStateEx(i, &tmp_xstate);
success = (status == ERROR_SUCCESS);
new_connected = (status != ERROR_DEVICE_NOT_CONNECTED);
if (success)
state->xstate = tmp_xstate;
else
/* Any status other than ERROR_DEVICE_NOT_CONNECTED leaves
* the pad marked connected, so the cached state would keep
* reporting the buttons held at the last good read - see
* the matching comment in xinput_joypad.c */
memset(&state->xstate, 0, sizeof(state->xstate));
if (new_connected != state->connected)
{
state->connected = new_connected;
+21 -1
View File
@@ -575,6 +575,7 @@ static void xinput_joypad_poll(void)
for (i = 0; i < 4; ++i)
{
DWORD status;
XINPUT_STATE tmp_xstate;
bool success, new_connected;
xinput_joypad_state *state;
/* On UWP, controllers may become available after initialization.
@@ -589,10 +590,29 @@ static void xinput_joypad_poll(void)
#endif
state = &g_xinput_states[i];
status = g_XInputGetStateEx(i, &state->xstate);
/* Read into a temporary: XInputGetStateEx() writing directly
* into the cached state would leave it indeterminate if the
* call fails partway. */
status = g_XInputGetStateEx(i, &tmp_xstate);
success = status == ERROR_SUCCESS;
new_connected = status != ERROR_DEVICE_NOT_CONNECTED;
if (success)
state->xstate = tmp_xstate;
else
{
/* The pad is still considered connected for any status
* other than ERROR_DEVICE_NOT_CONNECTED (access denied,
* busy, transient failures during device arbitration),
* so the cached state would otherwise keep reporting
* whatever was held at the time of the last good read -
* including the Gamepad.wButtons D-pad bits, which
* xinput_joypad_button() consumes with no staleness
* check. Treat an unreadable pad as having nothing
* pressed. */
memset(&state->xstate, 0, sizeof(state->xstate));
}
if (new_connected != state->connected)
{
/* Normally, dinput handles device insertion/removal for us, but