diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index d3c68f191..1c99a6c2d 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -318,12 +318,15 @@ + + + @@ -432,6 +435,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 9f536e981..79ed8305f 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -323,6 +323,9 @@ + + + @@ -478,6 +481,7 @@ + diff --git a/configure b/configure index beda25d0d..d81855a0a 100755 --- a/configure +++ b/configure @@ -24336,6 +24336,8 @@ fi $as_echo "#define SDL_JOYSTICK_HIDAPI 1" >>confdefs.h +$as_echo "#define SDL_JOYSTICK_RAWINPUT 1" >>confdefs.h + EXTRA_CFLAGS="$EXTRA_CFLAGS -I$srcdir/src/hidapi/hidapi" SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c" SOURCES="$SOURCES $srcdir/src/hidapi/SDL_hidapi.c" diff --git a/configure.ac b/configure.ac index 2a3654da3..4596adc6b 100644 --- a/configure.ac +++ b/configure.ac @@ -3316,6 +3316,7 @@ AS_HELP_STRING([--enable-hidapi], [use HIDAPI for low level joystick drivers [[d if test x$hidapi_support = xyes; then AC_DEFINE(SDL_JOYSTICK_HIDAPI, 1, [ ]) + AC_DEFINE(SDL_JOYSTICK_RAWINPUT, 1, [ ]) EXTRA_CFLAGS="$EXTRA_CFLAGS -I$srcdir/src/hidapi/hidapi" SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c" SOURCES="$SOURCES $srcdir/src/hidapi/SDL_hidapi.c" diff --git a/include/SDL_config.h.in b/include/SDL_config.h.in index b47da68c7..42488cc9a 100644 --- a/include/SDL_config.h.in +++ b/include/SDL_config.h.in @@ -289,6 +289,7 @@ #undef SDL_JOYSTICK_USBHID #undef SDL_JOYSTICK_USBHID_MACHINE_JOYSTICK_H #undef SDL_JOYSTICK_HIDAPI +#undef SDL_JOYSTICK_RAWINPUT #undef SDL_JOYSTICK_EMSCRIPTEN #undef SDL_JOYSTICK_VIRTUAL #undef SDL_HAPTIC_DUMMY diff --git a/include/SDL_config_windows.h b/include/SDL_config_windows.h index f269bfc04..16fa06f72 100644 --- a/include/SDL_config_windows.h +++ b/include/SDL_config_windows.h @@ -197,6 +197,7 @@ typedef unsigned int uintptr_t; #define SDL_JOYSTICK_DINPUT 1 #define SDL_JOYSTICK_XINPUT 1 #define SDL_JOYSTICK_HIDAPI 1 +#define SDL_JOYSTICK_RAWINPUT 1 #define SDL_HAPTIC_DINPUT 1 #define SDL_HAPTIC_XINPUT 1 diff --git a/include/SDL_hints.h b/include/SDL_hints.h index a3a537383..c07c7f9e3 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -634,10 +634,23 @@ extern "C" { * "0" - HIDAPI driver is not used * "1" - HIDAPI driver is used * - * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + * The default is "0" on Windows, otherwise the value of SDL_HINT_JOYSTICK_HIDAPI */ #define SDL_HINT_JOYSTICK_HIDAPI_XBOX "SDL_JOYSTICK_HIDAPI_XBOX" + /** + * \brief A variable controlling whether the HIDAPI driver for XBox controllers on Windows should pull correlated + * data from XInput. + * + * This variable can be set to the following values: + * "0" - HIDAPI Xbox driver will only use HIDAPI data + * "1" - HIDAPI Xbox driver will also pull data from XInput, providing better trigger axes, guide button + * presses, and rumble support + * + * The default is "1". This hint applies to any joysticks opened after setting the hint. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_CORRELATE_XINPUT "SDL_JOYSTICK_HIDAPI_CORRELATE_XINPUT" + /** * \brief A variable controlling whether the HIDAPI driver for Nintendo GameCube controllers should be used. * @@ -660,6 +673,15 @@ extern "C" { */ #define SDL_HINT_ENABLE_STEAM_CONTROLLERS "SDL_ENABLE_STEAM_CONTROLLERS" + /** + * \brief A variable controlling whether the RAWINPUT joystick drivers should be used for better handling XInput-capable devices. + * + * This variable can be set to the following values: + * "0" - RAWINPUT drivers are not used + * "1" - RAWINPUT drivers are used (the default) + * + */ +#define SDL_HINT_JOYSTICK_RAWINPUT "SDL_JOYSTICK_RAWINPUT" /** * \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it. diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c index 3775ad11c..9d92aa5cf 100644 --- a/src/joystick/SDL_gamecontroller.c +++ b/src/joystick/SDL_gamecontroller.c @@ -439,6 +439,12 @@ static ControllerMapping_t *SDL_PrivateGetControllerMappingForGUID(SDL_JoystickG /* This is a HIDAPI device */ return s_pHIDAPIMapping; } +#if SDL_JOYSTICK_RAWINPUT + if (SDL_IsJoystickRAWINPUT(*guid)) { + /* This is a RAWINPUT device - same data as HIDAPI */ + return s_pHIDAPIMapping; + } +#endif #if SDL_JOYSTICK_XINPUT if (SDL_IsJoystickXInput(*guid)) { /* This is an XInput device */ diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index 709d199d1..aeedd296d 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -51,6 +51,13 @@ #endif static SDL_JoystickDriver *SDL_joystick_drivers[] = { +#ifdef SDL_JOYSTICK_RAWINPUT /* Before WINDOWS_ driver, as WINDOWS wants to check if this driver is handling things */ + /* Also before HIDAPI, as HIDAPI wants to check if this driver is handling things */ + &SDL_RAWINPUT_JoystickDriver, +#endif +#ifdef SDL_JOYSTICK_HIDAPI /* Before WINDOWS_ driver, as WINDOWS wants to check if this driver is handling things */ + &SDL_HIDAPI_JoystickDriver, +#endif #if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT) &SDL_WINDOWS_JoystickDriver, #endif @@ -75,9 +82,6 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = { #ifdef SDL_JOYSTICK_USBHID /* !!! FIXME: "USBHID" is a generic name, and doubly-confusing with HIDAPI next to it. This is the *BSD interface, rename this. */ &SDL_BSD_JoystickDriver, #endif -#ifdef SDL_JOYSTICK_HIDAPI - &SDL_HIDAPI_JoystickDriver, -#endif #ifdef SDL_JOYSTICK_VIRTUAL &SDL_VIRTUAL_JoystickDriver, #endif @@ -1729,6 +1733,12 @@ SDL_IsJoystickHIDAPI(SDL_JoystickGUID guid) return (guid.data[14] == 'h') ? SDL_TRUE : SDL_FALSE; } +SDL_bool +SDL_IsJoystickRAWINPUT(SDL_JoystickGUID guid) +{ + return (guid.data[14] == 'r') ? SDL_TRUE : SDL_FALSE; +} + SDL_bool SDL_IsJoystickVirtual(SDL_JoystickGUID guid) { diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index e1329738d..6a56cc4eb 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/src/joystick/SDL_joystick_c.h @@ -73,6 +73,9 @@ extern SDL_bool SDL_IsJoystickXInput(SDL_JoystickGUID guid); /* Function to return whether a joystick guid comes from the HIDAPI driver */ extern SDL_bool SDL_IsJoystickHIDAPI(SDL_JoystickGUID guid); +/* Function to return whether a joystick guid comes from the RAWINPUT driver */ +extern SDL_bool SDL_IsJoystickRAWINPUT(SDL_JoystickGUID guid); + /* Function to return whether a joystick guid comes from the Virtual driver */ extern SDL_bool SDL_IsJoystickVirtual(SDL_JoystickGUID guid); diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index a6b2858d9..5aa7577df 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/src/joystick/SDL_sysjoystick.h @@ -150,6 +150,7 @@ extern SDL_JoystickDriver SDL_DUMMY_JoystickDriver; extern SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver; extern SDL_JoystickDriver SDL_HAIKU_JoystickDriver; extern SDL_JoystickDriver SDL_HIDAPI_JoystickDriver; +extern SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver; extern SDL_JoystickDriver SDL_IOS_JoystickDriver; extern SDL_JoystickDriver SDL_LINUX_JoystickDriver; extern SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver; diff --git a/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c index 7ef22b673..35b25bd93 100644 --- a/src/joystick/hidapi/SDL_hidapi_gamecube.c +++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c @@ -176,11 +176,11 @@ HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */ if (ctx->joysticks[i] == -1) { ResetAxisRange(ctx, i); - HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); + HIDAPI_JoystickConnected(device, &ctx->joysticks[i], SDL_FALSE); } } else { if (ctx->joysticks[i] != -1) { - HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]); + HIDAPI_JoystickDisconnected(device, ctx->joysticks[i], SDL_FALSE); ctx->joysticks[i] = -1; } continue; @@ -252,7 +252,7 @@ HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */ if (ctx->joysticks[i] == -1) { ResetAxisRange(ctx, i); - HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); + HIDAPI_JoystickConnected(device, &ctx->joysticks[i], SDL_FALSE); } joystick = SDL_JoystickFromInstanceID(ctx->joysticks[i]); @@ -262,7 +262,7 @@ HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) } } else { if (ctx->joysticks[i] != -1) { - HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]); + HIDAPI_JoystickDisconnected(device, ctx->joysticks[i], SDL_FALSE); ctx->joysticks[i] = -1; } continue; diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c index 8288d1fb1..f56fdc326 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -213,7 +213,7 @@ SetLedsForPlayerIndex(DS4EffectsState_t *effects, int player_index) static SDL_bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) { - return HIDAPI_JoystickConnected(device, NULL); + return HIDAPI_JoystickConnected(device, NULL, SDL_FALSE); } static int @@ -496,7 +496,7 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) if (size < 0) { /* Read error, device is disconnected */ - HIDAPI_JoystickDisconnected(device, joystick->instance_id); + HIDAPI_JoystickDisconnected(device, joystick->instance_id, SDL_FALSE); } return (size >= 0); } diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c index 26f72271e..a6747357e 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -671,7 +671,7 @@ static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button) static SDL_bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) { - return HIDAPI_JoystickConnected(device, NULL); + return HIDAPI_JoystickConnected(device, NULL, SDL_FALSE); } static int @@ -1121,7 +1121,7 @@ HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device) if (size < 0) { /* Read error, device is disconnected */ - HIDAPI_JoystickDisconnected(device, joystick->instance_id); + HIDAPI_JoystickDisconnected(device, joystick->instance_id, SDL_FALSE); } return (size >= 0); } diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c index 1be44a0f8..d22c9544d 100644 --- a/src/joystick/hidapi/SDL_hidapi_xbox360.c +++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c @@ -47,34 +47,190 @@ #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT #include "../../core/windows/SDL_windows.h" +typedef struct WindowsGamingInputGamepadState WindowsGamingInputGamepadState; +#define GamepadButtons_GUIDE 0x40000000 #define COBJMACROS #include "windows.gaming.input.h" #endif +#if defined(SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT) || defined(SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT) +#define SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING +#endif typedef struct { Uint8 last_state[USB_PACKET_LENGTH]; +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING + Uint32 match_state; /* Low 16 bits for button states, high 16 for 4 4bit axes */ + Uint32 last_state_packet; +#endif #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT SDL_bool xinput_enabled; + SDL_bool xinput_correlated; + Uint8 xinput_correlation_id; + Uint8 xinput_correlation_count; + Uint8 xinput_uncorrelate_count; Uint8 xinput_slot; #endif #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT - SDL_bool coinitialized; - __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics; - __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; - struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; + SDL_bool wgi_correlated; + Uint8 wgi_correlation_id; + Uint8 wgi_correlation_count; + Uint8 wgi_uncorrelate_count; + WindowsGamingInputGamepadState *wgi_slot; #endif } SDL_DriverXbox360_Context; +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING +static struct { + Uint32 last_state_packet; + SDL_Joystick *joystick; + SDL_Joystick *last_joystick; +} guide_button_candidate; + +typedef struct WindowsMatchState { + SHORT match_axes[4]; +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + WORD xinput_buttons; +#endif +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + Uint32 wgi_buttons; +#endif + SDL_bool any_data; +} WindowsMatchState; + +static void HIDAPI_DriverXbox360_FillMatchState(WindowsMatchState *state, Uint32 match_state) +{ + int ii; + state->any_data = SDL_FALSE; + /* SHORT state->match_axes[4] = { + (match_state & 0x000F0000) >> 4, + (match_state & 0x00F00000) >> 8, + (match_state & 0x0F000000) >> 12, + (match_state & 0xF0000000) >> 16, + }; */ + for (ii = 0; ii < 4; ii++) { + state->match_axes[ii] = (match_state & (0x000F0000 << (ii * 4))) >> (4 + ii * 4); + if ((Uint32)(state->match_axes[ii] + 0x1000) > 0x2000) { /* match_state bit is not 0xF, 0x1, or 0x2 */ + state->any_data = SDL_TRUE; + } + } #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT -static Uint8 xinput_slots; + /* Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less */ +#define XInputAxesMatch(gamepad) (\ + (Uint32)(gamepad.sThumbLX - state->match_axes[0] + 0x1000) <= 0x2fff && \ + (Uint32)(~gamepad.sThumbLY - state->match_axes[1] + 0x1000) <= 0x2fff && \ + (Uint32)(gamepad.sThumbRX - state->match_axes[2] + 0x1000) <= 0x2fff && \ + (Uint32)(~gamepad.sThumbRY - state->match_axes[3] + 0x1000) <= 0x2fff) + /* Explicit +#define XInputAxesMatch(gamepad) (\ + SDL_abs((Sint8)((gamepad.sThumbLX & 0xF000) >> 8) - ((match_state & 0x000F0000) >> 12)) <= 0x10 && \ + SDL_abs((Sint8)((~gamepad.sThumbLY & 0xF000) >> 8) - ((match_state & 0x00F00000) >> 16)) <= 0x10 && \ + SDL_abs((Sint8)((gamepad.sThumbRX & 0xF000) >> 8) - ((match_state & 0x0F000000) >> 20)) <= 0x10 && \ + SDL_abs((Sint8)((~gamepad.sThumbRY & 0xF000) >> 8) - ((match_state & 0xF0000000) >> 24)) <= 0x10) */ + + + state->xinput_buttons = + /* Bitwise map .RLDUWVQTS.KYXBA -> YXBA..WVQTKSRLDU */ + match_state << 12 | (match_state & 0x0780) >> 1 | (match_state & 0x0010) << 1 | (match_state & 0x0040) >> 2 | (match_state & 0x7800) >> 11; + /* Explicit + ((match_state & (1<xinput_buttons) + state->any_data = SDL_TRUE; +#endif + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + /* Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less */ +#define WindowsGamingInputAxesMatch(gamepad) (\ + (Uint16)(((Sint16)(gamepad.LeftThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[0] + 0x1000) <= 0x2fff && \ + (Uint16)((~(Sint16)(gamepad.LeftThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[1] + 0x1000) <= 0x2fff && \ + (Uint16)(((Sint16)(gamepad.RightThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[2] + 0x1000) <= 0x2fff && \ + (Uint16)((~(Sint16)(gamepad.RightThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[3] + 0x1000) <= 0x2fff) + + + state->wgi_buttons = + /* Bitwise map .RLD UWVQ TS.K YXBA -> ..QT WVRL DUYX BAKS */ + /* RStick/LStick (QT) RShould/LShould (WV) DPad R/L/D/U YXBA bac(K) (S)tart */ + (match_state & 0x0180) << 5 | (match_state & 0x0600) << 1 | (match_state & 0x7800) >> 5 | (match_state & 0x000F) << 2 | (match_state & 0x0010) >> 3 | (match_state & 0x0040) >> 6; + /* Explicit + ((match_state & (1<wgi_buttons) + state->any_data = SDL_TRUE; +#endif + +} + + +#endif + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT +static struct { + XINPUT_STATE_EX state; + SDL_bool connected; /* Currently has an active XInput device */ + SDL_bool used; /* Is currently mapped to an SDL device */ + Uint8 correlation_id; +} xinput_state[XUSER_MAX_COUNT]; +static SDL_bool xinput_device_change = SDL_TRUE; +static SDL_bool xinput_state_dirty = SDL_TRUE; + +static void +HIDAPI_DriverXbox360_UpdateXInput() +{ + DWORD user_index; + if (xinput_device_change) { + for (user_index = 0; user_index < XUSER_MAX_COUNT; user_index++) { + XINPUT_CAPABILITIES capabilities; + xinput_state[user_index].connected = XINPUTGETCAPABILITIES(user_index, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS; + } + xinput_device_change = SDL_FALSE; + xinput_state_dirty = SDL_TRUE; + } + if (xinput_state_dirty) { + xinput_state_dirty = SDL_FALSE; + for (user_index = 0; user_index < SDL_arraysize(xinput_state); ++user_index) { + if (xinput_state[user_index].connected) { + if (!XINPUTGETSTATE(user_index, &xinput_state[user_index].state) == ERROR_SUCCESS) { + xinput_state[user_index].connected = SDL_FALSE; + } + } + } + } +} static void HIDAPI_DriverXbox360_MarkXInputSlotUsed(Uint8 xinput_slot) { if (xinput_slot != XUSER_INDEX_ANY) { - xinput_slots |= (0x01 << xinput_slot); + xinput_state[xinput_slot].used = SDL_TRUE; } } @@ -82,58 +238,209 @@ static void HIDAPI_DriverXbox360_MarkXInputSlotFree(Uint8 xinput_slot) { if (xinput_slot != XUSER_INDEX_ANY) { - xinput_slots &= ~(0x01 << xinput_slot); + xinput_state[xinput_slot].used = SDL_FALSE; } } - static SDL_bool HIDAPI_DriverXbox360_MissingXInputSlot() { - return xinput_slots != 0x0F; + int ii; + for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) { + if (xinput_state[ii].connected && !xinput_state[ii].used) { + return SDL_TRUE; + } + } + return SDL_FALSE; } -static Uint8 -HIDAPI_DriverXbox360_GuessXInputSlot(WORD wButtons) +static SDL_bool +HIDAPI_DriverXbox360_XInputSlotMatches(const WindowsMatchState *state, Uint8 slot_idx) { - DWORD user_index; - int match_count; - Uint8 match_slot; - - if (!XINPUTGETSTATE) { - return XUSER_INDEX_ANY; + if (xinput_state[slot_idx].connected) { + WORD xinput_buttons = xinput_state[slot_idx].state.Gamepad.wButtons; + if ((xinput_buttons & ~XINPUT_GAMEPAD_GUIDE) == state->xinput_buttons && XInputAxesMatch(xinput_state[slot_idx].state.Gamepad)) { + return SDL_TRUE; + } } + return SDL_FALSE; +} + + +static SDL_bool +HIDAPI_DriverXbox360_GuessXInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, Uint8 *slot_idx) +{ + int user_index; + int match_count; match_count = 0; for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) { - XINPUT_STATE_EX xinput_state; - - if (XINPUTGETSTATE(user_index, &xinput_state) == ERROR_SUCCESS) { - if (xinput_state.Gamepad.wButtons == wButtons) { - ++match_count; - match_slot = (Uint8)user_index; - } + if (!xinput_state[user_index].used && HIDAPI_DriverXbox360_XInputSlotMatches(state, user_index)) { + ++match_count; + *slot_idx = (Uint8)user_index; + /* Incrementing correlation_id for any match, as negative evidence for others being correlated */ + *correlation_id = ++xinput_state[user_index].correlation_id; } } - if (match_count == 1) { - return match_slot; + /* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched. + Note that we're still invalidating *other* potential correlations if we have more than one match or we have no + data. */ + if (match_count == 1 && state->any_data) { + return SDL_TRUE; } - return XUSER_INDEX_ANY; + return SDL_FALSE; } #endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT */ #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT +typedef struct WindowsGamingInputGamepadState { + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state; + SDL_DriverXbox360_Context *correlated_context; + SDL_bool used; /* Is currently mapped to an SDL device */ + SDL_bool connected; /* Just used during update to track disconnected */ + Uint8 correlation_id; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; +} WindowsGamingInputGamepadState; + +static struct { + WindowsGamingInputGamepadState **per_gamepad; + int per_gamepad_count; + SDL_bool initialized; + SDL_bool dirty; + SDL_bool need_device_list_update; + int ref_count; + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics; +} wgi_state; + +static void +HIDAPI_DriverXbox360_MarkWindowsGamingInputSlotUsed(WindowsGamingInputGamepadState *wgi_slot, SDL_DriverXbox360_Context *ctx) +{ + wgi_slot->used = SDL_TRUE; + wgi_slot->correlated_context = ctx; +} + +static void +HIDAPI_DriverXbox360_MarkWindowsGamingInputSlotFree(WindowsGamingInputGamepadState *wgi_slot) +{ + wgi_slot->used = SDL_FALSE; + wgi_slot->correlated_context = NULL; +} + +static SDL_bool +HIDAPI_DriverXbox360_MissingWindowsGamingInputSlot() +{ + int ii; + for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + if (!wgi_state.per_gamepad[ii]->used) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +static void +HIDAPI_DriverXbox360_UpdateWindowsGamingInput() +{ + int ii; + if (!wgi_state.gamepad_statics) + return; + + if (!wgi_state.dirty) + return; + wgi_state.dirty = SDL_FALSE; + + if (wgi_state.need_device_list_update) { + wgi_state.need_device_list_update = SDL_FALSE; + for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + wgi_state.per_gamepad[ii]->connected = SDL_FALSE; + } + HRESULT hr; + __FIVectorView_1_Windows__CGaming__CInput__CGamepad *gamepads; + + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_get_Gamepads(wgi_state.gamepad_statics, &gamepads); + if (SUCCEEDED(hr)) { + unsigned int num_gamepads; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_get_Size(gamepads, &num_gamepads); + if (SUCCEEDED(hr)) { + unsigned int i; + for (i = 0; i < num_gamepads; ++i) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_GetAt(gamepads, i, &gamepad); + if (SUCCEEDED(hr)) { + SDL_bool found = SDL_FALSE; + int jj; + for (jj = 0; jj < wgi_state.per_gamepad_count ; jj++) { + if (wgi_state.per_gamepad[jj]->gamepad == gamepad) { + found = SDL_TRUE; + wgi_state.per_gamepad[jj]->connected = SDL_TRUE; + break; + } + } + if (!found) { + /* New device, add it */ + wgi_state.per_gamepad_count++; + wgi_state.per_gamepad = SDL_realloc(wgi_state.per_gamepad, sizeof(wgi_state.per_gamepad[0]) * wgi_state.per_gamepad_count); + if (!wgi_state.per_gamepad) { + SDL_OutOfMemory(); + return; + } + WindowsGamingInputGamepadState *gamepad_state = SDL_calloc(1, sizeof(*gamepad_state)); + if (!gamepad_state) { + SDL_OutOfMemory(); + return; + } + wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1] = gamepad_state; + gamepad_state->gamepad = gamepad; + gamepad_state->connected = SDL_TRUE; + } else { + /* Already tracked */ + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad); + } + } + } + for (ii = wgi_state.per_gamepad_count - 1; ii >= 0; ii--) { + WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii]; + if (!gamepad_state->connected) { + /* Device missing, must be disconnected */ + if (gamepad_state->correlated_context) { + gamepad_state->correlated_context->wgi_correlated = SDL_FALSE; + gamepad_state->correlated_context->wgi_slot = NULL; + } + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad_state->gamepad); + SDL_free(gamepad_state); + wgi_state.per_gamepad[ii] = wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1]; + --wgi_state.per_gamepad_count; + } + } + } + __FIVectorView_1_Windows__CGaming__CInput__CGamepad_Release(gamepads); + } + } /* need_device_list_update */ + + for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(wgi_state.per_gamepad[ii]->gamepad, &wgi_state.per_gamepad[ii]->state); + if (!SUCCEEDED(hr)) { + wgi_state.per_gamepad[ii]->connected = SDL_FALSE; /* Not used by anything, currently */ + } + } +} static void HIDAPI_DriverXbox360_InitWindowsGamingInput(SDL_DriverXbox360_Context *ctx) { - /* I think this takes care of RoInitialize() in a way that is compatible with the rest of SDL */ - if (FAILED(WIN_CoInitialize())) { - return; - } - ctx->coinitialized = SDL_TRUE; + wgi_state.need_device_list_update = SDL_TRUE; + wgi_state.ref_count++; + if (!wgi_state.initialized) { + /* I think this takes care of RoInitialize() in a way that is compatible with the rest of SDL */ + if (FAILED(WIN_CoInitialize())) { + return; + } + wgi_state.initialized = SDL_TRUE; + wgi_state.dirty = SDL_TRUE; - { static const IID SDL_IID_IGamepadStatics = { 0x8BBCE529, 0xD49C, 0x39E9, { 0x95, 0x60, 0xE4, 0x7D, 0xDE, 0x96, 0xB7, 0xC8 } }; HRESULT hr; HMODULE hModule = LoadLibraryA("combase.dll"); @@ -151,7 +458,7 @@ HIDAPI_DriverXbox360_InitWindowsGamingInput(SDL_DriverXbox360_Context *ctx) hr = WindowsCreateStringFunc(pNamespace, SDL_wcslen(pNamespace), &hNamespaceString); if (SUCCEEDED(hr)) { - RoGetActivationFactoryFunc(hNamespaceString, &SDL_IID_IGamepadStatics, &ctx->gamepad_statics); + RoGetActivationFactoryFunc(hNamespaceString, &SDL_IID_IGamepadStatics, &wgi_state.gamepad_statics); WindowsDeleteStringFunc(hNamespaceString); } } @@ -160,90 +467,112 @@ HIDAPI_DriverXbox360_InitWindowsGamingInput(SDL_DriverXbox360_Context *ctx) } } -static Uint8 -HIDAPI_DriverXbox360_GetGamepadButtonsForMatch(__x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad) +static SDL_bool +HIDAPI_DriverXbox360_WindowsGamingInputSlotMatches(const WindowsMatchState *state, WindowsGamingInputGamepadState *slot) { - HRESULT hr; - struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state; - Uint8 buttons = 0; - - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(gamepad, &state); - if (SUCCEEDED(hr)) { - if (state.Buttons & GamepadButtons_A) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_A); - } - if (state.Buttons & GamepadButtons_B) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_B); - } - if (state.Buttons & GamepadButtons_X) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_X); - } - if (state.Buttons & GamepadButtons_Y) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_Y); - } + Uint32 wgi_buttons = slot->state.Buttons; + if ((wgi_buttons & 0x3FFF) == state->wgi_buttons && WindowsGamingInputAxesMatch(slot->state)) { + return SDL_TRUE; } - return buttons; + return SDL_FALSE; } -static void -HIDAPI_DriverXbox360_GuessGamepad(SDL_DriverXbox360_Context *ctx, Uint8 buttons) +static SDL_bool +HIDAPI_DriverXbox360_GuessWindowsGamingInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, WindowsGamingInputGamepadState **slot) { - HRESULT hr; - __FIVectorView_1_Windows__CGaming__CInput__CGamepad *gamepads; + int match_count; - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_get_Gamepads(ctx->gamepad_statics, &gamepads); - if (SUCCEEDED(hr)) { - unsigned int i, num_gamepads; - - hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_get_Size(gamepads, &num_gamepads); - if (SUCCEEDED(hr)) { - int match_count; - unsigned int match_slot; - - match_count = 0; - for (i = 0; i < num_gamepads; ++i) { - __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; - - hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_GetAt(gamepads, i, &gamepad); - if (SUCCEEDED(hr)) { - Uint8 gamepad_buttons = HIDAPI_DriverXbox360_GetGamepadButtonsForMatch(gamepad); - if (buttons == gamepad_buttons) { - ++match_count; - match_slot = i; - } - __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad); - } - } - if (match_count == 1) { - hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_GetAt(gamepads, match_slot, &ctx->gamepad); - if (SUCCEEDED(hr)) { - } - } + match_count = 0; + for (int user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) { + WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[user_index]; + if (HIDAPI_DriverXbox360_WindowsGamingInputSlotMatches(state, gamepad_state)) { + ++match_count; + *slot = gamepad_state; + /* Incrementing correlation_id for any match, as negative evidence for others being correlated */ + *correlation_id = ++gamepad_state->correlation_id; } - __FIVectorView_1_Windows__CGaming__CInput__CGamepad_Release(gamepads); } + /* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched. + Note that we're still invalidating *other* potential correlations if we have more than one match or we have no + data. */ + if (match_count == 1 && state->any_data) { + return SDL_TRUE; + } + return SDL_FALSE; } static void HIDAPI_DriverXbox360_QuitWindowsGamingInput(SDL_DriverXbox360_Context *ctx) { - if (ctx->gamepad_statics) { - __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(ctx->gamepad_statics); - ctx->gamepad_statics = NULL; - } - if (ctx->gamepad) { - __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(ctx->gamepad); - ctx->gamepad = NULL; - } - - if (ctx->coinitialized) { + wgi_state.need_device_list_update = SDL_TRUE; + --wgi_state.ref_count; + if (!wgi_state.ref_count && wgi_state.initialized) { + for (int ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(wgi_state.per_gamepad[ii]->gamepad); + } + if (wgi_state.per_gamepad) { + SDL_free(wgi_state.per_gamepad); + wgi_state.per_gamepad = NULL; + } + wgi_state.per_gamepad_count = 0; + if (wgi_state.gamepad_statics) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi_state.gamepad_statics); + wgi_state.gamepad_statics = NULL; + } WIN_CoUninitialize(); - ctx->coinitialized = SDL_FALSE; + wgi_state.initialized = SDL_FALSE; } } #endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT */ +static void +HIDAPI_DriverXbox360_PostUpdate(void) +{ +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING + SDL_bool unmapped_guide_pressed = SDL_FALSE; + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + if (!wgi_state.dirty) { + int ii; + for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { + WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii]; + if (!gamepad_state->used && (gamepad_state->state.Buttons & GamepadButtons_GUIDE)) { + unmapped_guide_pressed = SDL_TRUE; + break; + } + } + } + wgi_state.dirty = SDL_TRUE; +#endif + + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + if (!xinput_state_dirty) { + int ii; + for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) { + if (xinput_state[ii].connected && !xinput_state[ii].used && (xinput_state[ii].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE)) { + unmapped_guide_pressed = SDL_TRUE; + break; + } + } + } + xinput_state_dirty = SDL_TRUE; +#endif + + if (unmapped_guide_pressed) { + if (guide_button_candidate.joystick && !guide_button_candidate.last_joystick) { + SDL_PrivateJoystickButton(guide_button_candidate.joystick, SDL_CONTROLLER_BUTTON_GUIDE, SDL_PRESSED); + guide_button_candidate.last_joystick = guide_button_candidate.joystick; + } + } else if (guide_button_candidate.last_joystick) { + SDL_PrivateJoystickButton(guide_button_candidate.last_joystick, SDL_CONTROLLER_BUTTON_GUIDE, SDL_RELEASED); + guide_button_candidate.last_joystick = NULL; + } + guide_button_candidate.joystick = NULL; +#endif +} + #if defined(__MACOSX__) static SDL_bool IsBluetoothXboxOneController(Uint16 vendor_id, Uint16 product_id) @@ -320,7 +649,7 @@ static SDL_bool SetSlotLED(hid_device *dev, Uint8 slot) static SDL_bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device) { - return HIDAPI_JoystickConnected(device, NULL); + return HIDAPI_JoystickConnected(device, NULL, SDL_FALSE); } static int @@ -349,17 +678,20 @@ HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joyst return SDL_FALSE; } - device->dev = hid_open_path(device->path, 0); - if (!device->dev) { - SDL_free(ctx); - SDL_SetError("Couldn't open %s", device->path); - return SDL_FALSE; + if (device->path) { /* else opened for RAWINPUT driver */ + device->dev = hid_open_path(device->path, 0); + if (!device->dev) { + SDL_SetError("Couldn't open %s", device->path); + SDL_free(ctx); + return SDL_FALSE; + } } device->context = ctx; #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT - ctx->xinput_enabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, SDL_TRUE); - if (ctx->xinput_enabled && WIN_LoadXInputDLL() < 0) { + xinput_device_change = SDL_TRUE; + ctx->xinput_enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_CORRELATE_XINPUT, SDL_TRUE); + if (ctx->xinput_enabled && (WIN_LoadXInputDLL() < 0 || !XINPUTGETSTATE)) { ctx->xinput_enabled = SDL_FALSE; } ctx->xinput_slot = XUSER_INDEX_ANY; @@ -370,7 +702,7 @@ HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joyst /* Set the controller LED */ player_index = SDL_JoystickGetPlayerIndex(joystick); - if (player_index >= 0) { + if (player_index >= 0 && device->dev) { SetSlotLED(device->dev, (player_index % 4)); } @@ -393,12 +725,12 @@ HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joy SDL_bool rumbled = SDL_FALSE; #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT - if (!rumbled && ctx->gamepad) { + if (!rumbled && ctx->wgi_correlated) { + WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; HRESULT hr; - - ctx->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; - ctx->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(ctx->gamepad, ctx->vibration); + gamepad_state->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; + gamepad_state->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); if (SUCCEEDED(hr)) { rumbled = SDL_TRUE; } @@ -406,7 +738,7 @@ HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joy #endif #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT - if (!rumbled && ctx->xinput_slot != XUSER_INDEX_ANY) { + if (!rumbled && ctx->xinput_correlated) { XINPUT_VIBRATION XVibration; if (!XINPUTSETSTATE) { @@ -473,6 +805,13 @@ HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joy static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size) { +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING + Uint32 match_state = ctx->match_state; + /* Update match_state with button bit, then fall through */ +# define SDL_PrivateJoystickButton(joystick, button, state) if (state) match_state |= 1 << (button); else match_state &=~(1<<(button)); SDL_PrivateJoystickButton(joystick, button, state) + /* Grab high 4 bits of value, then fall through */ +# define SDL_PrivateJoystickAxis(joystick, axis, value) if (axis < 4) match_state = (match_state & ~(0xF << (4 * axis + 16))) | ((value) & 0xF000) << (4 * axis + 4); SDL_PrivateJoystickAxis(joystick, axis, value) +#endif Sint16 axis; SDL_bool has_trigger_data = SDL_FALSE; @@ -543,96 +882,56 @@ HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, axis = (int)*(Uint16*)(&data[6]) - 0x8000; SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); -#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT - if (ctx->gamepad_statics && !ctx->gamepad) { - Uint8 buttons = 0; - - if (data[10] & 0x01) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_A); - } - if (data[10] & 0x02) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_B); - } - if (data[10] & 0x04) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_X); - } - if (data[10] & 0x08) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_Y); - } - if (buttons != 0) { - HIDAPI_DriverXbox360_GuessGamepad(ctx, buttons); - } - } - - if (ctx->gamepad) { - HRESULT hr; - struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state; - - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(ctx->gamepad, &state); - if (SUCCEEDED(hr)) { - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (state.Buttons & 0x40000000) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, ((int)(state.LeftTrigger * SDL_MAX_UINT16)) - 32768); - SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, ((int)(state.RightTrigger * SDL_MAX_UINT16)) - 32768); - has_trigger_data = SDL_TRUE; - } - } -#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT */ +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING +#undef SDL_PrivateJoystickAxis +#endif #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT - if (ctx->xinput_enabled) { - if (ctx->xinput_slot == XUSER_INDEX_ANY && HIDAPI_DriverXbox360_MissingXInputSlot()) { - WORD wButtons = 0; - - if (data[10] & 0x01) { - wButtons |= XINPUT_GAMEPAD_A; - } - if (data[10] & 0x02) { - wButtons |= XINPUT_GAMEPAD_B; - } - if (data[10] & 0x04) { - wButtons |= XINPUT_GAMEPAD_X; - } - if (data[10] & 0x08) { - wButtons |= XINPUT_GAMEPAD_Y; - } - if (wButtons != 0) { - Uint8 xinput_slot = HIDAPI_DriverXbox360_GuessXInputSlot(wButtons); - if (xinput_slot != XUSER_INDEX_ANY) { - HIDAPI_DriverXbox360_MarkXInputSlotUsed(xinput_slot); - ctx->xinput_slot = xinput_slot; - } - } - } - - if (!has_trigger_data && ctx->xinput_slot != XUSER_INDEX_ANY) { - XINPUT_STATE_EX xinput_state; - - if (XINPUTGETSTATE(ctx->xinput_slot, &xinput_state) == ERROR_SUCCESS) { - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, ((int)xinput_state.Gamepad.bLeftTrigger * 257) - 32768); - SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, ((int)xinput_state.Gamepad.bRightTrigger * 257) - 32768); - has_trigger_data = SDL_TRUE; - } - } + /* Prefer XInput over WindowsGamingInput, it continues to provide data in the background */ + if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) { + has_trigger_data = SDL_TRUE; } #endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT */ +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + if (!has_trigger_data && ctx->wgi_correlated) { + has_trigger_data = SDL_TRUE; + } +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT */ + if (!has_trigger_data) { axis = (data[9] * 257) - 32768; if (data[9] < 0x80) { axis = -axis * 2 - 32769; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_MIN_SINT16); SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); } else if (data[9] > 0x80) { axis = axis * 2 - 32767; SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, SDL_MIN_SINT16); } else { SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_MIN_SINT16); SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, SDL_MIN_SINT16); } } +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING + ctx->match_state = match_state; + ctx->last_state_packet = SDL_GetTicks(); +#undef SDL_PrivateJoystickButton +#endif SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); } + +#ifdef SDL_JOYSTICK_RAWINPUT +static void +HIDAPI_DriverXbox360_HandleStatePacketFromRAWINPUT(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 *data, int size) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; + HIDAPI_DriverXbox360_HandleStatePacket(joystick, NULL, ctx, data, size); +} +#endif + #else static void @@ -689,13 +988,243 @@ HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, } #endif /* __WIN32__ */ +static void +HIDAPI_DriverXbox360_UpdateOtherAPIs(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; + SDL_bool has_trigger_data = SDL_FALSE; + SDL_bool correlated = SDL_FALSE; +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING + WindowsMatchState match_state_xinput; +#endif + + /* Poll for trigger data once (not per-state-packet) */ +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + /* Prefer XInput over WindowsGamingInput, it continues to provide data in the background */ + if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) { + HIDAPI_DriverXbox360_UpdateXInput(); + if (xinput_state[ctx->xinput_slot].connected) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (xinput_state[ctx->xinput_slot].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bLeftTrigger * 257) - 32768); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bRightTrigger * 257) - 32768); + has_trigger_data = SDL_TRUE; + } + } +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT */ + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + if (!has_trigger_data && ctx->wgi_correlated) { + HIDAPI_DriverXbox360_UpdateWindowsGamingInput(); /* May detect disconnect / cause uncorrelation */ + if (ctx->wgi_correlated) { /* Still connected */ + struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading *state = &ctx->wgi_slot->state; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (state->Buttons & GamepadButtons_GUIDE) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, ((int)(state->LeftTrigger * SDL_MAX_UINT16)) - 32768); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, ((int)(state->RightTrigger * SDL_MAX_UINT16)) - 32768); + has_trigger_data = SDL_TRUE; + } + } +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT */ + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING + HIDAPI_DriverXbox360_FillMatchState(&match_state_xinput, ctx->match_state); + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + /* Parallel logic to WINDOWS_XINPUT below */ + HIDAPI_DriverXbox360_UpdateWindowsGamingInput(); + if (ctx->wgi_correlated) { + /* We have been previously correlated, ensure we are still matching, see comments in XINPUT section */ + if (HIDAPI_DriverXbox360_WindowsGamingInputSlotMatches(&match_state_xinput, ctx->wgi_slot)) { + ctx->wgi_uncorrelate_count = 0; + } else { + ++ctx->wgi_uncorrelate_count; + /* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event + pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but + let's set it to 3 to be safe. An incorrect un-correlation will simply result in lower precision + triggers for a frame. */ + if (ctx->wgi_uncorrelate_count >= 3) { +#ifdef DEBUG_JOYSTICK + SDL_Log("UN-Correlated joystick %d to WindowsGamingInput device #%d\n", joystick->instance_id, ctx->wgi_slot); +#endif + HIDAPI_DriverXbox360_MarkWindowsGamingInputSlotFree(ctx->wgi_slot); + ctx->wgi_correlated = SDL_FALSE; + ctx->wgi_correlation_count = 0; + /* Force immediate update of triggers */ + HIDAPI_DriverXbox360_HandleStatePacket(joystick, NULL, ctx, ctx->last_state, sizeof(ctx->last_state)); + /* Force release of Guide button, it can't possibly be down on this device now. */ + /* It gets left down if we were actually correlated incorrectly and it was released on the WindowsGamingInput + device but we didn't get a state packet. */ + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, SDL_RELEASED); + } + } + } + if (!ctx->wgi_correlated) { + SDL_bool new_correlation_count = 0; + if (HIDAPI_DriverXbox360_MissingWindowsGamingInputSlot()) { + Uint8 correlation_id; + WindowsGamingInputGamepadState *slot_idx; + if (HIDAPI_DriverXbox360_GuessWindowsGamingInputSlot(&match_state_xinput, &correlation_id, &slot_idx)) { + /* we match exactly one WindowsGamingInput device */ + /* Probably can do without wgi_correlation_count, just check and clear wgi_slot to NULL, unless we need + even more frames to be sure. */ + if (ctx->wgi_correlation_count && ctx->wgi_slot == slot_idx) { + /* was correlated previously, and still the same device */ + if (ctx->wgi_correlation_id + 1 == correlation_id) { + /* no one else was correlated in the meantime */ + new_correlation_count = ctx->wgi_correlation_count + 1; + if (new_correlation_count == 2) { + /* correlation stayed steady and uncontested across multiple frames, guaranteed match */ + ctx->wgi_correlated = SDL_TRUE; +#ifdef DEBUG_JOYSTICK + SDL_Log("Correlated joystick %d to WindowsGamingInput device #%d\n", joystick->instance_id, slot_idx); +#endif + correlated = SDL_TRUE; + HIDAPI_DriverXbox360_MarkWindowsGamingInputSlotUsed(ctx->wgi_slot, ctx); + /* If the generalized Guide button was using us, it doesn't need to anymore */ + if (guide_button_candidate.joystick == joystick) + guide_button_candidate.joystick = NULL; + if (guide_button_candidate.last_joystick == joystick) + guide_button_candidate.last_joystick = NULL; + /* Force immediate update of guide button / triggers */ + HIDAPI_DriverXbox360_HandleStatePacket(joystick, NULL, ctx, ctx->last_state, sizeof(ctx->last_state)); + } + } else { + /* someone else also possibly correlated to this device, start over */ + new_correlation_count = 1; + } + } else { + /* new possible correlation */ + new_correlation_count = 1; + ctx->wgi_slot = slot_idx; + } + ctx->wgi_correlation_id = correlation_id; + } else { + /* Match multiple WindowsGamingInput devices, or none (possibly due to no buttons pressed) */ + } + } + ctx->wgi_correlation_count = new_correlation_count; + } else { + correlated = SDL_TRUE; + } +#endif + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + /* Parallel logic to WINDOWS_GAMING_INPUT above */ + if (ctx->xinput_enabled) { + HIDAPI_DriverXbox360_UpdateXInput(); + if (ctx->xinput_correlated) { + /* We have been previously correlated, ensure we are still matching */ + /* This is required to deal with two (mostly) un-preventable mis-correlation situations: + A) Since the HID data stream does not provide an initial state (but polling XInput does), if we open + 5 controllers (#1-4 XInput mapped, #5 is not), and controller 1 had the A button down (and we don't + know), and the user presses A on controller #5, we'll see exactly 1 controller with A down (#5) and + exactly 1 XInput device with A down (#1), and incorrectly correlate. This code will then un-correlate + when A is released from either controller #1 or #5. + B) Since the app may not open all controllers, we could have a similar situation where only controller #5 + is opened, and the user holds A on controllers #1 and #5 simultaneously - again we see only 1 controller + with A down and 1 XInput device with A down, and incorrectly correlate. This should be very unusual + (only when apps do not open all controllers, yet are listening to Guide button presses, yet + for some reason want to ignore guide button presses on the un-opened controllers, yet users are + pressing buttons on the unopened controllers), and will resolve itself when either button is released + and we un-correlate. We could prevent this by processing the state packets for *all* controllers, + even un-opened ones, as that would allow more precise correlation. + */ + if (HIDAPI_DriverXbox360_XInputSlotMatches(&match_state_xinput, ctx->xinput_slot)) { + ctx->xinput_uncorrelate_count = 0; + } else { + ++ctx->xinput_uncorrelate_count; + /* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event + pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but + let's set it to 3 to be safe. An incorrect un-correlation will simply result in lower precision + triggers for a frame. */ + if (ctx->xinput_uncorrelate_count >= 3) { +#ifdef DEBUG_JOYSTICK + SDL_Log("UN-Correlated joystick %d to XInput device #%d\n", joystick->instance_id, ctx->xinput_slot); +#endif + HIDAPI_DriverXbox360_MarkXInputSlotFree(ctx->xinput_slot); + ctx->xinput_correlated = SDL_FALSE; + ctx->xinput_correlation_count = 0; + /* Force immediate update of triggers */ + HIDAPI_DriverXbox360_HandleStatePacket(joystick, NULL, ctx, ctx->last_state, sizeof(ctx->last_state)); + /* Force release of Guide button, it can't possibly be down on this device now. */ + /* It gets left down if we were actually correlated incorrectly and it was released on the XInput + device but we didn't get a state packet. */ + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, SDL_RELEASED); + } + } + } + if (!ctx->xinput_correlated) { + SDL_bool new_correlation_count = 0; + if (HIDAPI_DriverXbox360_MissingXInputSlot()) { + Uint8 correlation_id; + Uint8 slot_idx; + if (HIDAPI_DriverXbox360_GuessXInputSlot(&match_state_xinput, &correlation_id, &slot_idx)) { + /* we match exactly one XInput device */ + /* Probably can do without xinput_correlation_count, just check and clear xinput_slot to ANY, unless + we need even more frames to be sure */ + if (ctx->xinput_correlation_count && ctx->xinput_slot == slot_idx) { + /* was correlated previously, and still the same device */ + if (ctx->xinput_correlation_id + 1 == correlation_id) { + /* no one else was correlated in the meantime */ + new_correlation_count = ctx->xinput_correlation_count + 1; + if (new_correlation_count == 2) { + /* correlation stayed steady and uncontested across multiple frames, guaranteed match */ + ctx->xinput_correlated = SDL_TRUE; +#ifdef DEBUG_JOYSTICK + SDL_Log("Correlated joystick %d to XInput device #%d\n", joystick->instance_id, slot_idx); +#endif + correlated = SDL_TRUE; + HIDAPI_DriverXbox360_MarkXInputSlotUsed(ctx->xinput_slot); + /* If the generalized Guide button was using us, it doesn't need to anymore */ + if (guide_button_candidate.joystick == joystick) + guide_button_candidate.joystick = NULL; + if (guide_button_candidate.last_joystick == joystick) + guide_button_candidate.last_joystick = NULL; + /* Force immediate update of guide button / triggers */ + HIDAPI_DriverXbox360_HandleStatePacket(joystick, NULL, ctx, ctx->last_state, sizeof(ctx->last_state)); + } + } else { + /* someone else also possibly correlated to this device, start over */ + new_correlation_count = 1; + } + } else { + /* new possible correlation */ + new_correlation_count = 1; + ctx->xinput_slot = slot_idx; + } + ctx->xinput_correlation_id = correlation_id; + } else { + /* Match multiple XInput devices, or none (possibly due to no buttons pressed) */ + } + } + ctx->xinput_correlation_count = new_correlation_count; + } else { + correlated = SDL_TRUE; + } + } +#endif + + if (!correlated) { + if (!guide_button_candidate.joystick || + (ctx->last_state_packet && ( + !guide_button_candidate.last_state_packet || + SDL_TICKS_PASSED(ctx->last_state_packet, guide_button_candidate.last_state_packet) + )) + ) { + guide_button_candidate.joystick = joystick; + guide_button_candidate.last_state_packet = ctx->last_state_packet; + } + } +#endif + +} + static SDL_bool HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device) { SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; SDL_Joystick *joystick = NULL; Uint8 data[USB_PACKET_LENGTH]; - int size; + int size = 0; if (device->num_joysticks > 0) { joystick = SDL_JoystickFromInstanceID(device->joysticks[0]); @@ -704,14 +1233,17 @@ HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device) return SDL_FALSE; } - while ((size = hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { + while (device->dev && (size = hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { HIDAPI_DriverXbox360_HandleStatePacket(joystick, device->dev, ctx, data, size); } if (size < 0) { /* Read error, device is disconnected */ - HIDAPI_JoystickDisconnected(device, joystick->instance_id); + HIDAPI_JoystickDisconnected(device, joystick->instance_id, SDL_FALSE); + } else { + HIDAPI_DriverXbox360_UpdateOtherAPIs(device, joystick); } + return (size >= 0); } @@ -721,10 +1253,20 @@ HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joys #if defined(SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT) || defined(SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT) SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context; #endif + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING + if (guide_button_candidate.joystick == joystick) + guide_button_candidate.joystick = NULL; + if (guide_button_candidate.last_joystick == joystick) + guide_button_candidate.last_joystick = NULL; +#endif #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + xinput_device_change = SDL_TRUE; if (ctx->xinput_enabled) { - HIDAPI_DriverXbox360_MarkXInputSlotFree(ctx->xinput_slot); + if (ctx->xinput_correlated) { + HIDAPI_DriverXbox360_MarkXInputSlotFree(ctx->xinput_slot); + } WIN_UnloadXInputDLL(); } #endif @@ -732,8 +1274,10 @@ HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joys HIDAPI_DriverXbox360_QuitWindowsGamingInput(ctx); #endif - hid_close(device->dev); - device->dev = NULL; + if (device->dev) { + hid_close(device->dev); + device->dev = NULL; + } SDL_free(device->context); device->context = NULL; @@ -757,7 +1301,11 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = HIDAPI_DriverXbox360_OpenJoystick, HIDAPI_DriverXbox360_RumbleJoystick, HIDAPI_DriverXbox360_CloseJoystick, - HIDAPI_DriverXbox360_FreeDevice + HIDAPI_DriverXbox360_FreeDevice, + HIDAPI_DriverXbox360_PostUpdate, +#ifdef SDL_JOYSTICK_RAWINPUT + HIDAPI_DriverXbox360_HandleStatePacketFromRAWINPUT, +#endif }; #endif /* SDL_JOYSTICK_HIDAPI_XBOX360 */ diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/src/joystick/hidapi/SDL_hidapi_xbox360w.c index 13dca5e56..9834870a5 100644 --- a/src/joystick/hidapi/SDL_hidapi_xbox360w.c +++ b/src/joystick/hidapi/SDL_hidapi_xbox360w.c @@ -231,10 +231,10 @@ HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device) if (connected) { SDL_JoystickID joystickID; - HIDAPI_JoystickConnected(device, &joystickID); + HIDAPI_JoystickConnected(device, &joystickID, SDL_FALSE); } else if (device->num_joysticks > 0) { - HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + HIDAPI_JoystickDisconnected(device, device->joysticks[0], SDL_FALSE); } } } else if (size == 29 && data[0] == 0x00 && data[1] == 0x0f && data[2] == 0x00 && data[3] == 0xf0) { @@ -262,7 +262,7 @@ HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device) if (joystick) { if (size < 0) { /* Read error, device is disconnected */ - HIDAPI_JoystickDisconnected(device, joystick->instance_id); + HIDAPI_JoystickDisconnected(device, joystick->instance_id, SDL_FALSE); } } return (size >= 0); diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c index 991b81f84..48568d460 100644 --- a/src/joystick/hidapi/SDL_hidapi_xboxone.c +++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -279,7 +279,7 @@ HIDAPI_DriverXboxOne_GetDeviceName(Uint16 vendor_id, Uint16 product_id) static SDL_bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device) { - return HIDAPI_JoystickConnected(device, NULL); + return HIDAPI_JoystickConnected(device, NULL, SDL_FALSE); } static int @@ -694,7 +694,7 @@ HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) !ControllerSendsWaitingForInit(device->vendor_id, device->product_id)) { if (SDL_TICKS_PASSED(SDL_GetTicks(), ctx->start_time + CONTROLLER_INIT_DELAY_MS)) { if (!SendControllerInit(device, ctx)) { - HIDAPI_JoystickDisconnected(device, joystick->instance_id); + HIDAPI_JoystickDisconnected(device, joystick->instance_id, SDL_FALSE); return SDL_FALSE; } ctx->initialized = SDL_TRUE; @@ -737,7 +737,7 @@ HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device) SDL_Log("Delay after init: %ums\n", SDL_GetTicks() - ctx->start_time); #endif if (!SendControllerInit(device, ctx)) { - HIDAPI_JoystickDisconnected(device, joystick->instance_id); + HIDAPI_JoystickDisconnected(device, joystick->instance_id, SDL_FALSE); return SDL_FALSE; } ctx->initialized = SDL_TRUE; diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 9eeaf0872..ad7446161 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -37,6 +37,7 @@ #if defined(__WIN32__) #include "../../core/windows/SDL_windows.h" +#include "../windows/SDL_rawinputjoystick_c.h" #endif #if defined(__MACOSX__) @@ -419,6 +420,13 @@ HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device) return NULL; } +#ifdef SDL_JOYSTICK_RAWINPUT + if (RAWINPUT_IsDevicePresent(device->vendor_id, device->product_id, device->version)) { + /* The RAWINPUT driver is taking care of this device */ + return NULL; + } +#endif + if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) { return NULL; } @@ -502,7 +510,7 @@ HIDAPI_CleanupDeviceDriver(SDL_HIDAPI_Device *device) /* Disconnect any joysticks */ while (device->num_joysticks) { - HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + HIDAPI_JoystickDisconnected(device, device->joysticks[0], SDL_FALSE); } device->driver->FreeDevice(device); @@ -575,6 +583,11 @@ HIDAPI_JoystickInit(void) return -1; } +#ifdef __WINDOWS__ + /* On Windows, turns out HIDAPI for Xbox controllers doesn't allow background input, so off by default */ + SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0", SDL_HINT_DEFAULT); +#endif + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; SDL_AddHintCallback(driver->hint, SDL_HIDAPIDriverHintChanged, NULL); @@ -591,7 +604,7 @@ HIDAPI_JoystickInit(void) } SDL_bool -HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID) +HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID, SDL_bool is_external) { SDL_JoystickID joystickID; SDL_JoystickID *joysticks = (SDL_JoystickID *)SDL_realloc(device->joysticks, (device->num_joysticks + 1)*sizeof(*device->joysticks)); @@ -602,7 +615,9 @@ HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID) joystickID = SDL_GetNextJoystickInstanceID(); device->joysticks = joysticks; device->joysticks[device->num_joysticks++] = joystickID; - ++SDL_HIDAPI_numjoysticks; + if (!is_external) { + ++SDL_HIDAPI_numjoysticks; + } SDL_PrivateJoystickAdded(joystickID); @@ -613,20 +628,22 @@ HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID) } void -HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) +HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID, SDL_bool is_external) { int i; for (i = 0; i < device->num_joysticks; ++i) { if (device->joysticks[i] == joystickID) { SDL_Joystick *joystick = SDL_JoystickFromInstanceID(joystickID); - if (joystick) { + if (joystick && !is_external) { HIDAPI_JoystickClose(joystick); } SDL_memmove(&device->joysticks[i], &device->joysticks[i+1], device->num_joysticks - i - 1); --device->num_joysticks; - --SDL_HIDAPI_numjoysticks; + if (!is_external) { + --SDL_HIDAPI_numjoysticks; + } if (device->num_joysticks == 0) { SDL_free(device->joysticks); device->joysticks = NULL; @@ -882,6 +899,7 @@ HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, cons static void HIDAPI_JoystickDetect(void) { + int i; if (SDL_AtomicTryLock(&SDL_HIDAPI_spinlock)) { HIDAPI_UpdateDiscovery(); if (SDL_HIDAPI_discovery.m_bHaveDevicesChanged) { @@ -891,6 +909,12 @@ HIDAPI_JoystickDetect(void) } SDL_AtomicUnlock(&SDL_HIDAPI_spinlock); } + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + if (driver->enabled && driver->PostUpdate) { + driver->PostUpdate(); + } + } } void diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index 562e8fa65..363ed3273 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -40,8 +40,6 @@ #ifdef __WINDOWS__ /* On Windows, Xbox One controllers are handled by the Xbox 360 driver */ #undef SDL_JOYSTICK_HIDAPI_XBOXONE -/* It turns out HIDAPI for Xbox controllers doesn't allow background input */ -#undef SDL_JOYSTICK_HIDAPI_XBOX360 #endif #ifdef __MACOSX__ @@ -103,6 +101,10 @@ typedef struct _SDL_HIDAPI_DeviceDriver int (*RumbleJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); void (*CloseJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick); void (*FreeDevice)(SDL_HIDAPI_Device *device); + void (*PostUpdate)(void); +#ifdef SDL_JOYSTICK_RAWINPUT + void (*HandleStatePacketFromRAWINPUT)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 *data, int size); +#endif } SDL_HIDAPI_DeviceDriver; @@ -120,8 +122,8 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube; extern SDL_bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); extern void HIDAPI_UpdateDevices(void); -extern SDL_bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID); -extern void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID); +extern SDL_bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID, SDL_bool is_external); +extern void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID, SDL_bool is_external); #endif /* SDL_JOYSTICK_HIDAPI_H */ diff --git a/src/joystick/windows/SDL_dinputjoystick.c b/src/joystick/windows/SDL_dinputjoystick.c index 578a63604..13e549e08 100644 --- a/src/joystick/windows/SDL_dinputjoystick.c +++ b/src/joystick/windows/SDL_dinputjoystick.c @@ -26,6 +26,7 @@ #include "SDL_windowsjoystick_c.h" #include "SDL_dinputjoystick_c.h" +#include "SDL_rawinputjoystick_c.h" #include "SDL_xinputjoystick_c.h" #include "../hidapi/SDL_hidapijoystick_c.h" @@ -672,6 +673,14 @@ EnumJoysticksCallback(const DIDEVICEINSTANCE * pdidInstance, VOID * pContext) } #endif +#ifdef SDL_JOYSTICK_RAWINPUT + if (RAWINPUT_IsDevicePresent(vendor, product, 0)) { + /* The RAWINPUT driver is taking care of this device */ + SDL_free(pNewJoystick); + return DIENUM_CONTINUE; + } +#endif + WINDOWS_AddJoystickDevice(pNewJoystick); return DIENUM_CONTINUE; /* get next device, please */ diff --git a/src/joystick/windows/SDL_xinputjoystick.c b/src/joystick/windows/SDL_xinputjoystick.c index 85ebb5c5b..a70fa173f 100644 --- a/src/joystick/windows/SDL_xinputjoystick.c +++ b/src/joystick/windows/SDL_xinputjoystick.c @@ -30,6 +30,7 @@ #include "SDL_timer.h" #include "SDL_windowsjoystick_c.h" #include "SDL_xinputjoystick_c.h" +#include "SDL_rawinputjoystick_c.h" #include "../hidapi/SDL_hidapijoystick_c.h" /* @@ -292,6 +293,14 @@ AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext) } #endif +#ifdef SDL_JOYSTICK_RAWINPUT + if (RAWINPUT_IsDevicePresent(vendor, product, version)) { + /* The RAWINPUT driver is taking care of this device */ + SDL_free(pNewJoystick); + return; + } +#endif + WINDOWS_AddJoystickDevice(pNewJoystick); } @@ -318,6 +327,18 @@ SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) const Uint8 userid = (Uint8)iuserid; XINPUT_CAPABILITIES capabilities; if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) { + /* Adding a new device, must handle all removes first, or GuessXInputDevice goes terribly wrong (returns + a product/vendor ID that is not even attached to the system) when we get a remove and add on the same tick + (e.g. when disconnecting a device and the OS reassigns which userid an already-attached controller is) + */ + int iuserid2; + for (iuserid2 = iuserid - 1; iuserid2 >= 0; iuserid2--) { + const Uint8 userid2 = (Uint8)iuserid2; + XINPUT_CAPABILITIES capabilities2; + if (XINPUTGETCAPABILITIES(userid2, XINPUT_FLAG_GAMEPAD, &capabilities2) != ERROR_SUCCESS) { + DelXInputDevice(userid2); + } + } AddXInputDevice(userid, capabilities.SubType, pContext); } else { DelXInputDevice(userid); diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index 237d38082..c1162937d 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -30,6 +30,7 @@ #include "../../events/SDL_keyboard_c.h" #include "../../events/SDL_mouse_c.h" +#include "../../joystick/windows/SDL_rawinputjoystick_c.h" #include "SDL_windowsvideo.h" #include "SDL_windowswindow.h" #include "SDL_hints.h" @@ -808,9 +809,18 @@ WIN_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info) } } +static LRESULT CALLBACK SDL_HelperWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ +#if SDL_JOYSTICK_RAWINPUT + if (RAWINPUT_WindowProc(hWnd, msg, wParam, lParam) == 0) { + return 0; + } +#endif + return DefWindowProc(hWnd, msg, wParam, lParam); +} /* - * Creates a HelperWindow used for DirectInput events. + * Creates a HelperWindow used for DirectInput and RawInput events. */ int SDL_HelperWindowCreate(void) @@ -825,7 +835,7 @@ SDL_HelperWindowCreate(void) /* Create the class. */ SDL_zero(wce); - wce.lpfnWndProc = DefWindowProc; + wce.lpfnWndProc = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, SDL_TRUE) ? SDL_HelperWindowProc : DefWindowProc; wce.lpszClassName = (LPCWSTR) SDL_HelperWindowClassName; wce.hInstance = hInstance;