From 514850fc0618f91e5aba78d34763ba4287bf93e4 Mon Sep 17 00:00:00 2001 From: Jimb Esser Date: Thu, 21 Mar 2019 14:48:37 -0700 Subject: [PATCH] Add new RawInput controller API, and improved correlation with XInput/WGI Reorder joystick init so drivers can ask the others if they handle a device reliably Do not poll disconnected XInput devices (major perf issue) Fix various cases where incorrect correlation could happen Simple mechanism for propagating unhandled Guide button presses even before guaranteed correlation Correlate by axis motion as well as button presses Fix failing to zero other trigger Fix SDL_HINT_JOYSTICK_HIDAPI not working if set before calling SDL_Init() Add missing device to device names Disable RawInput if we have a mismatch of XInput-capable but not RawInput-capable devices --- VisualC/SDL/SDL.vcxproj | 4 + VisualC/SDL/SDL.vcxproj.filters | 4 + configure | 3 + configure.in | 1 + include/SDL_config.h.in | 1 + include/SDL_config_windows.h | 1 + include/SDL_hints.h | 24 +- src/joystick/SDL_gamecontroller.c | 6 + src/joystick/SDL_joystick.c | 16 +- src/joystick/SDL_joystick_c.h | 3 + src/joystick/SDL_sysjoystick.h | 1 + src/joystick/hidapi/SDL_hidapi_xbox360.c | 889 ++++++++++++++---- src/joystick/hidapi/SDL_hidapijoystick.c | 21 + src/joystick/hidapi/SDL_hidapijoystick_c.h | 7 +- src/joystick/windows/SDL_dinputjoystick.c | 9 + src/joystick/windows/SDL_rawinputjoystick.c | 673 +++++++++++++ src/joystick/windows/SDL_rawinputjoystick_c.h | 30 + src/joystick/windows/SDL_xinputjoystick.c | 9 + src/video/windows/SDL_windowswindow.c | 15 +- 19 files changed, 1533 insertions(+), 184 deletions(-) create mode 100644 src/joystick/windows/SDL_rawinputjoystick.c create mode 100644 src/joystick/windows/SDL_rawinputjoystick_c.h diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 082070cc2f..b7dac9683d 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -320,11 +320,14 @@ + + + @@ -430,6 +433,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index b911d1bf58..1e18e7bfac 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -322,6 +322,9 @@ + + + @@ -473,6 +476,7 @@ + diff --git a/configure b/configure index 7f402c0a82..e2e7eeee4b 100755 --- a/configure +++ b/configure @@ -24666,6 +24666,9 @@ $as_echo "#define SDL_JOYSTICK_WINMM 1" >>confdefs.h $as_echo "#define SDL_JOYSTICK_HIDAPI 1" >>confdefs.h + +$as_echo "#define SDL_JOYSTICK_RAWINPUT 1" >>confdefs.h + SOURCES="$SOURCES $srcdir/src/joystick/windows/*.c" SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c" SOURCES="$SOURCES $srcdir/src/hidapi/windows/hid.c" diff --git a/configure.in b/configure.in index 3fad091864..c399d07b90 100644 --- a/configure.in +++ b/configure.in @@ -3548,6 +3548,7 @@ AS_HELP_STRING([--enable-render-d3d], [enable the Direct3D render driver [[defau AC_DEFINE(SDL_JOYSTICK_WINMM, 1, [ ]) fi AC_DEFINE(SDL_JOYSTICK_HIDAPI, 1, [ ]) + AC_DEFINE(SDL_JOYSTICK_RAWINPUT, 1, [ ]) SOURCES="$SOURCES $srcdir/src/joystick/windows/*.c" SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c" SOURCES="$SOURCES $srcdir/src/hidapi/windows/hid.c" diff --git a/include/SDL_config.h.in b/include/SDL_config.h.in index ef70652452..5ba1e756a0 100644 --- a/include/SDL_config.h.in +++ b/include/SDL_config.h.in @@ -284,6 +284,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_HAPTIC_DUMMY #undef SDL_HAPTIC_ANDROID diff --git a/include/SDL_config_windows.h b/include/SDL_config_windows.h index 395ad7c266..8361dbf86b 100644 --- a/include/SDL_config_windows.h +++ b/include/SDL_config_windows.h @@ -194,6 +194,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 ac605db095..f7cc35a103 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -551,10 +551,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. * @@ -577,6 +590,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 d417a44ba7..2e427b6c00 100644 --- a/src/joystick/SDL_gamecontroller.c +++ b/src/joystick/SDL_gamecontroller.c @@ -436,6 +436,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 a5b3e63b48..156742a352 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -46,6 +46,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 @@ -70,9 +77,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 #if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED) &SDL_DUMMY_JoystickDriver #endif @@ -1210,6 +1214,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; +} + static SDL_bool SDL_IsJoystickProductWheel(Uint32 vidpid) { static Uint32 wheel_joysticks[] = { diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index f02d30b667..50d5b97e65 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/src/joystick/SDL_joystick_c.h @@ -75,6 +75,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 should be ignored */ extern SDL_bool SDL_ShouldIgnoreJoystick(const char *name, SDL_JoystickGUID guid); diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index ef7f082e47..3ac5073075 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/src/joystick/SDL_sysjoystick.h @@ -148,6 +148,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_WINDOWS_JoystickDriver; diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c index ad0a1922db..b4864597c4 100644 --- a/src/joystick/hidapi/SDL_hidapi_xbox360.c +++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c @@ -46,38 +46,194 @@ #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 #define USB_PACKET_LENGTH 64 +#if defined(SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT) || defined(SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT) +#define SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING +#endif typedef struct { SDL_JoystickID joystickID; Uint8 last_state[USB_PACKET_LENGTH]; Uint32 rumble_expiration; +#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 + /* 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 Uint8 xinput_slots; +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; } } @@ -85,58 +241,210 @@ 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_InitWindowsGamingInput(SDL_DriverXbox360_Context *ctx) +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) { - /* I think this takes care of RoInitialize() in a way that is compatible with the rest of SDL */ - if (FAILED(WIN_CoInitialize())) { + 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 */ + } } - ctx->coinitialized = SDL_TRUE; +} + +static void +HIDAPI_DriverXbox360_InitWindowsGamingInput(SDL_DriverXbox360_Context *ctx) +{ + 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"); @@ -154,7 +462,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); } } @@ -163,90 +471,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; + Uint32 wgi_buttons = slot->state.Buttons; + if ((wgi_buttons & 0x3FFF) == state->wgi_buttons && WindowsGamingInputAxesMatch(slot->state)) { + return SDL_TRUE; + } + return SDL_FALSE; +} - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(gamepad, &state); - if (SUCCEEDED(hr)) { - if (state.Buttons & GamepadButtons_A) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_A); +static SDL_bool +HIDAPI_DriverXbox360_GuessWindowsGamingInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, WindowsGamingInputGamepadState **slot) +{ + int match_count; + + 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; } - if (state.Buttons & GamepadButtons_B) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_B); + } + /* 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) +{ + 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 (state.Buttons & GamepadButtons_X) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_X); + if (wgi_state.per_gamepad) { + SDL_free(wgi_state.per_gamepad); + wgi_state.per_gamepad = NULL; } - if (state.Buttons & GamepadButtons_Y) { - buttons |= (1 << SDL_CONTROLLER_BUTTON_Y); + 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(); + wgi_state.initialized = SDL_FALSE; } - return buttons; } +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT */ + static void -HIDAPI_DriverXbox360_GuessGamepad(SDL_DriverXbox360_Context *ctx, Uint8 buttons) +HIDAPI_DriverXbox360_PostUpdate(void) { - HRESULT hr; - __FIVectorView_1_Windows__CGaming__CInput__CGamepad *gamepads; - - 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; +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING + SDL_bool unmapped_guide_pressed = SDL_FALSE; - 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)) { - } +#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; } } - __FIVectorView_1_Windows__CGaming__CInput__CGamepad_Release(gamepads); } -} + wgi_state.dirty = SDL_TRUE; +#endif -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; + +#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 (ctx->coinitialized) { - WIN_CoUninitialize(); - ctx->coinitialized = SDL_FALSE; + 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 } -#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT */ - static SDL_bool HIDAPI_DriverXbox360_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number) { @@ -288,8 +618,9 @@ HIDAPI_DriverXbox360_InitDriver(SDL_HIDAPI_DriverData *context, Uint16 vendor_id return SDL_FALSE; } #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; @@ -310,7 +641,9 @@ static SDL_bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick) { /* Set the controller LED */ - SetSlotLED(context->device, (joystick->instance_id % 4)); + if (context->device) { + SetSlotLED(context->device, (joystick->instance_id % 4)); + } /* Initialize the joystick capabilities */ joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX; @@ -342,12 +675,12 @@ HIDAPI_DriverXbox360_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joysti 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; } @@ -355,7 +688,7 @@ HIDAPI_DriverXbox360_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joysti #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) { @@ -412,6 +745,13 @@ HIDAPI_DriverXbox360_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joysti 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; @@ -482,96 +822,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; +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING +#undef SDL_PrivateJoystickAxis +#endif - 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); - } +#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) { + has_trigger_data = SDL_TRUE; } +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT */ - 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; - } +#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 */ -#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; - } - } - } -#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT */ - 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_DriverData *context, SDL_Joystick *joystick, Uint8 *data, int size) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context->context; + HIDAPI_DriverXbox360_HandleStatePacket(joystick, NULL, ctx, data, size); +} +#endif + #else static void @@ -735,12 +1035,15 @@ HIDAPI_DriverXbox360_UpdateDriver(SDL_HIDAPI_DriverData *context, int *num_joyst SDL_Joystick *joystick = SDL_JoystickFromInstanceID(ctx->joystickID); Uint8 data[USB_PACKET_LENGTH]; int size; + SDL_bool has_trigger_data = SDL_FALSE; + SDL_bool correlated = SDL_FALSE; + WindowsMatchState match_state_xinput; if (joystick == NULL) { return SDL_TRUE; /* Nothing to do right now! */ } - while ((size = hid_read_timeout(context->device, data, sizeof(data), 0)) > 0) { + while (context->device && (size = hid_read_timeout(context->device, data, sizeof(data), 0)) > 0) { #ifdef __WIN32__ HIDAPI_DriverXbox360_HandleStatePacket(joystick, context->device, ctx, data, size); #else @@ -775,6 +1078,224 @@ HIDAPI_DriverXbox360_UpdateDriver(SDL_HIDAPI_DriverData *context, int *num_joyst } } + /* 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 + return (size >= 0); } @@ -783,14 +1304,26 @@ HIDAPI_DriverXbox360_QuitDriver(SDL_HIDAPI_DriverData *context, SDL_bool send_ev { SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context->context; +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_MATCHING + SDL_Joystick *joystick = SDL_JoystickFromInstanceID(ctx->joystickID); + + 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 #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT - HIDAPI_DriverXbox360_InitWindowsGamingInput(ctx); + HIDAPI_DriverXbox360_QuitWindowsGamingInput(ctx); #endif *num_joysticks -= 1; if (send_event) { @@ -811,7 +1344,11 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = HIDAPI_DriverXbox360_NumJoysticks, HIDAPI_DriverXbox360_InstanceIDForIndex, HIDAPI_DriverXbox360_OpenJoystick, - HIDAPI_DriverXbox360_Rumble + HIDAPI_DriverXbox360_Rumble, + HIDAPI_DriverXbox360_PostUpdate, +#ifdef SDL_JOYSTICK_RAWINPUT + HIDAPI_DriverXbox360_HandleStatePacketFromRAWINPUT, +#endif }; #endif /* SDL_JOYSTICK_HIDAPI_XBOX360 */ diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 7cbd9f44e2..193441b854 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -34,6 +34,7 @@ #if defined(__WIN32__) #include "../../core/windows/SDL_windows.h" +#include "../windows/SDL_rawinputjoystick_c.h" #endif #if defined(__MACOSX__) @@ -432,6 +433,7 @@ HIDAPI_XboxControllerName(Uint16 vendor_id, Uint16 product_id) { MAKE_VIDPID(0x045e, 0x028e), "Microsoft X-Box 360 pad" }, { MAKE_VIDPID(0x045e, 0x028f), "Microsoft X-Box 360 pad v2" }, { MAKE_VIDPID(0x045e, 0x0291), "Xbox 360 Wireless Receiver (XBOX)" }, + { MAKE_VIDPID(0x045e, 0x02a1), "Xbox 360 Wireless Receiver" }, { MAKE_VIDPID(0x045e, 0x02d1), "Microsoft X-Box One pad" }, { MAKE_VIDPID(0x045e, 0x02dd), "Microsoft X-Box One pad (Firmware 2015)" }, { MAKE_VIDPID(0x045e, 0x02e3), "Microsoft X-Box One Elite pad" }, @@ -613,6 +615,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; } @@ -711,6 +720,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); @@ -931,12 +945,19 @@ HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version) static void HIDAPI_JoystickDetect(void) { + int i; HIDAPI_UpdateDiscovery(); if (SDL_HIDAPI_discovery.m_bHaveDevicesChanged) { /* FIXME: We probably need to schedule an update in a few seconds as well */ HIDAPI_UpdateDeviceList(); SDL_HIDAPI_discovery.m_bHaveDevicesChanged = SDL_FALSE; } + 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(); + } + } } static const char * diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index 807a301cd9..c44659f2bf 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -35,8 +35,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__ @@ -74,6 +72,11 @@ typedef struct _SDL_HIDAPI_DeviceDriver Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); + void (*PostUpdate)(void); +#ifdef SDL_JOYSTICK_RAWINPUT + void (*HandleStatePacketFromRAWINPUT)(SDL_HIDAPI_DriverData *context, + SDL_Joystick *joystick, Uint8 *data, int size); +#endif } SDL_HIDAPI_DeviceDriver; diff --git a/src/joystick/windows/SDL_dinputjoystick.c b/src/joystick/windows/SDL_dinputjoystick.c index 5b3089b6cb..00aba7637f 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" @@ -542,6 +543,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_rawinputjoystick.c b/src/joystick/windows/SDL_rawinputjoystick.c new file mode 100644 index 0000000000..98086ab343 --- /dev/null +++ b/src/joystick/windows/SDL_rawinputjoystick.c @@ -0,0 +1,673 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2019 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ +/* + RAWINPUT Joystick API for better handling XInput-capable devices on Windows. + + XInput is limited to 4 devices. + Windows.Gaming.Input does not get inputs from XBox One controllers when not in the foreground. + DirectInput does not get inputs from XBox One controllers when not in the foreground, nor rumble or accurate triggers. + RawInput does not get rumble or accurate triggers. + + So, combine them as best we can! +*/ +#include "../../SDL_internal.h" + +#if SDL_JOYSTICK_RAWINPUT + +#include "SDL_assert.h" +#include "SDL_endian.h" +#include "SDL_hints.h" +#include "SDL_log.h" +#include "SDL_mutex.h" +#include "../SDL_sysjoystick.h" +#include "../../core/windows/SDL_windows.h" +#include "../hidapi/SDL_hidapijoystick_c.h" + +#ifndef SDL_JOYSTICK_HIDAPI_XBOX360 +#error RAWINPUT requires the XBOX360 HIDAPI driver +#endif + +#ifndef RIDEV_EXINPUTSINK +#define RIDEV_EXINPUTSINK 0x00001000 +#define RIDEV_DEVNOTIFY 0x00002000 +#endif + +#ifndef WM_INPUT_DEVICE_CHANGE +#define WM_INPUT_DEVICE_CHANGE 0x00FE +#endif +#ifndef WM_INPUT +#define WM_INPUT 0x00FF +#endif +#ifndef GIDC_ARRIVAL +#define GIDC_ARRIVAL 1 +#define GIDC_REMOVAL 2 +#endif + + +/* #define DEBUG_RAWINPUT */ + +#define USB_PACKET_LENGTH 64 + +#define SDL_callocStruct(type) (type *)SDL_calloc(1, sizeof(type)) +#define SDL_callocStructs(type, count) (type *)SDL_calloc((count), sizeof(type)) + +#define USAGE_PAGE_GENERIC_DESKTOP 0x0001 +#define USAGE_JOYSTICK 0x0004 +#define USAGE_GAMEPAD 0x0005 +#define USAGE_MULTIAXISCONTROLLER 0x0008 + + +/* external variables referenced. */ +extern HWND SDL_HelperWindow; + + +static SDL_HIDAPI_DeviceDriver *SDL_RAWINPUT_drivers[] = { +#ifdef SDL_JOYSTICK_HIDAPI_XBOX360 + &SDL_HIDAPI_DriverXbox360, +#endif +}; + +static SDL_bool SDL_RAWINPUT_inited = SDL_FALSE; +static int SDL_RAWINPUT_numjoysticks = 0; +static SDL_bool SDL_RAWINPUT_need_pump = SDL_TRUE; + +static void RAWINPUT_JoystickDetect(void); +static void RAWINPUT_PumpMessages(void); +static SDL_bool RAWINPUT_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version); + +typedef struct _SDL_RAWINPUT_Device +{ + char *name; + Uint16 vendor_id; + Uint16 product_id; + Uint16 version; + SDL_JoystickGUID guid; + Uint16 usage_page; + Uint16 usage; + SDL_HIDAPI_DriverData devdata; + SDL_HIDAPI_DeviceDriver *driver; + + HANDLE hDevice; + SDL_Joystick *joystick; + + struct _SDL_RAWINPUT_Device *next; +} SDL_RAWINPUT_Device; + +struct joystick_hwdata +{ + SDL_RAWINPUT_Device *device; + SDL_mutex *mutex; +}; + +SDL_RAWINPUT_Device *SDL_RAWINPUT_devices; + +static const Uint16 subscribed_devices[] = { + USAGE_GAMEPAD, + /* Don't need Joystick for any devices we're handling here (XInput-capable) + USAGE_JOYSTICK, + USAGE_MULTIAXISCONTROLLER, + */ +}; + +SDL_bool RAWINPUT_AllXInputDevicesSupported() { + UINT i, device_count = 0; + PRAWINPUTDEVICELIST devices; + SDL_bool any_unsupported = SDL_FALSE; + + if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!device_count)) { + return SDL_FALSE; + } + + devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count); + if (devices == NULL) { + return SDL_FALSE; + } + + if (GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) { + SDL_free(devices); + return SDL_FALSE; + } + + for (i = 0; i < device_count; i++) { + RID_DEVICE_INFO rdi; + char devName[128]; + UINT rdiSize = sizeof(rdi); + UINT nameSize = SDL_arraysize(devName); + + rdi.cbSize = sizeof(rdi); + if ((devices[i].dwType == RIM_TYPEHID) && + (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != ((UINT)-1)) && + (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != ((UINT)-1)) && + (SDL_strstr(devName, "IG_") != NULL) + ) { + /* XInput-capable */ + if (!RAWINPUT_IsDeviceSupported((Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber)) { + /* But not supported, probably Valve virtual controller */ + any_unsupported = SDL_TRUE; + } + } + } + SDL_free(devices); + if (any_unsupported) { + /* This happens with Valve virtual controllers that shows up in the RawInputDeviceList, but do not + generate WM_INPUT events, so we must use XInput or DInput to read from it, and with XInput if we + have some supported and some not, we can't easily tell which device is actually showing up in + RawInput, so we must just disable RawInput for now. Additionally, if these unsupported devices + are locally connected, they still show up in RawInput under a *different* HID path, with + different vendor/product IDs, so there's no way to reconcile. */ +#ifdef DEBUG_RAWINPUT + SDL_Log("Found some supported and some unsupported XInput devices, disabling RawInput\n"); +#endif + return SDL_FALSE; + } + return SDL_TRUE; +} + +static int +RAWINPUT_JoystickInit(void) +{ + int ii; + RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)]; + SDL_assert(!SDL_RAWINPUT_inited); + SDL_assert(SDL_HelperWindow); + + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, SDL_TRUE)) + return -1; + + if (!RAWINPUT_AllXInputDevicesSupported()) { + return -1; + } + + for (ii = 0; ii < SDL_arraysize(subscribed_devices); ii++) { + rid[ii].usUsagePage = USAGE_PAGE_GENERIC_DESKTOP; + rid[ii].usUsage = subscribed_devices[ii]; + rid[ii].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; /* Receive messages when in background, including device add/remove */ + rid[ii].hwndTarget = SDL_HelperWindow; + } + + if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) { + SDL_SetError("Couldn't initialize RAWINPUT"); + return -1; + } + + SDL_RAWINPUT_inited = SDL_TRUE; + + RAWINPUT_JoystickDetect(); + RAWINPUT_PumpMessages(); + return 0; +} + +static int +RAWINPUT_JoystickGetCount(void) +{ + return SDL_RAWINPUT_numjoysticks; +} + +static SDL_RAWINPUT_Device * +RAWINPUT_DeviceFromHandle(HANDLE hDevice) +{ + SDL_RAWINPUT_Device *curr; + + for (curr = SDL_RAWINPUT_devices; curr; curr = curr->next) { + if (curr->hDevice == hDevice) + return curr; + } + return NULL; +} + +static SDL_HIDAPI_DeviceDriver * +RAWINPUT_GetDeviceDriver(SDL_RAWINPUT_Device *device) +{ + int i; + + if (SDL_ShouldIgnoreJoystick(device->name, device->guid)) { + return NULL; + } + + if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) { + return NULL; + } + if (device->usage && device->usage != USAGE_JOYSTICK && device->usage != USAGE_GAMEPAD && device->usage != USAGE_MULTIAXISCONTROLLER) { + return NULL; + } + + for (i = 0; i < SDL_arraysize(SDL_RAWINPUT_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_RAWINPUT_drivers[i]; + if (/*driver->enabled && */driver->IsSupportedDevice(device->vendor_id, device->product_id, device->version, -1)) { + return driver; + } + } + return NULL; +} + +static void +RAWINPUT_AddDevice(HANDLE hDevice) +{ +#define CHECK(exp) { if(!(exp)) goto err; } + SDL_RAWINPUT_Device *device = NULL; + RID_DEVICE_INFO rdi; + UINT rdi_size = sizeof(rdi); + char dev_name[128]; + const char *name; + UINT name_size = SDL_arraysize(dev_name); + SDL_RAWINPUT_Device *curr, *last; + + SDL_assert(!RAWINPUT_DeviceFromHandle(hDevice)); + + /* Figure out what kind of device it is */ + CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICEINFO, &rdi, &rdi_size) != (UINT)-1); + CHECK(rdi.dwType == RIM_TYPEHID); + + /* Get the device "name" (HID Path) */ + CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, dev_name, &name_size) != (UINT)-1); + /* Only take XInput-capable devices */ + CHECK(SDL_strstr(dev_name, "IG_") != NULL); + + CHECK(device = SDL_callocStruct(SDL_RAWINPUT_Device)); + device->hDevice = hDevice; + device->vendor_id = (Uint16)rdi.hid.dwVendorId; + device->product_id = (Uint16)rdi.hid.dwProductId; + device->version = (Uint16)rdi.hid.dwVersionNumber; + device->usage = rdi.hid.usUsage; + device->usage_page = rdi.hid.usUsagePage; + + { + const Uint16 vendor = device->vendor_id; + const Uint16 product = device->product_id; + const Uint16 version = device->version; + Uint16 *guid16 = (Uint16 *)device->guid.data; + + *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB); + *guid16++ = 0; + *guid16++ = SDL_SwapLE16(vendor); + *guid16++ = 0; + *guid16++ = SDL_SwapLE16(product); + *guid16++ = 0; + *guid16++ = SDL_SwapLE16(version); + *guid16++ = 0; + + /* Note that this is a RAWINPUT device for special handling elsewhere */ + device->guid.data[14] = 'r'; + device->guid.data[15] = 0; + } + + if (!device->name) { + size_t name_size = (6 + 1 + 6 + 1); + CHECK(device->name = SDL_callocStructs(char, name_size)); + SDL_snprintf(device->name, name_size, "0x%.4x/0x%.4x", device->vendor_id, device->product_id); + } + + CHECK(device->driver = RAWINPUT_GetDeviceDriver(device)); + + name = device->driver->GetDeviceName(device->vendor_id, device->product_id); + if (name) { + SDL_free(device->name); + device->name = SDL_strdup(name); + } + +#ifdef DEBUG_RAWINPUT + SDL_Log("Adding RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle 0x%.8x\n", device->name, device->vendor_id, device->product_id, device->version, device->hDevice); +#endif + + /* Add it to the list */ + for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) { + continue; + } + if (last) { + last->next = device; + } else { + SDL_RAWINPUT_devices = device; + } + + /* InitDriver calls SDL_GetNextJoystickInstanceID() and SDL_PrivateJoystickAdded(), and calls back in to us, so + the device list must be updated before calling this. */ + CHECK(device->driver->InitDriver(&device->devdata, device->vendor_id, device->product_id, &SDL_RAWINPUT_numjoysticks)); + + return; + +err: + if (device) { + if (device->name) + SDL_free(device->name); + SDL_free(device); + } +} + + +static void +RAWINPUT_DelDevice(SDL_RAWINPUT_Device *device, SDL_bool send_event) +{ + SDL_RAWINPUT_Device *curr, *last; + for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) { + if (curr == device) { + if (last) { + last->next = curr->next; + } else { + SDL_RAWINPUT_devices = curr->next; + } + + if (device->joystick) { + /* Detach from joystick */ + struct joystick_hwdata *hwdata = device->joystick->hwdata; + SDL_assert(hwdata->device == device); + hwdata->device = NULL; + device->joystick = NULL; + } + + device->driver->QuitDriver(&device->devdata, send_event, &SDL_RAWINPUT_numjoysticks); + +#ifdef DEBUG_RAWINPUT + SDL_Log("Removing RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle 0x%.8x\n", device->name, device->vendor_id, device->product_id, device->version, device->hDevice); +#endif + SDL_free(device->name); + SDL_free(device); + return; + } + } +} + +static void +RAWINPUT_PumpMessages(void) +{ + if (SDL_RAWINPUT_need_pump) { + MSG msg; + while (PeekMessage(&msg, SDL_HelperWindow, WM_INPUT, WM_INPUT, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + SDL_RAWINPUT_need_pump = SDL_FALSE; + } +} + +static void +RAWINPUT_UpdateDeviceList(void) +{ + MSG msg; + /* In theory, want only WM_INPUT_DEVICE_CHANGE messages here, but PeekMessage returns nothing unless you also ask + for WM_INPUT */ + while (PeekMessage(&msg, SDL_HelperWindow, WM_INPUT_DEVICE_CHANGE, WM_INPUT, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +static SDL_bool +RAWINPUT_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version) +{ + int i; + + for (i = 0; i < SDL_arraysize(SDL_RAWINPUT_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_RAWINPUT_drivers[i]; + /* Ignoring driver->enabled here, and elsewhere in this file, as the if the driver is enabled by disabling HID, + we still want RawInput to use it. If we end up with more than one RawInput driver, we may need to rework + how the hints interact (separate enabled state, perhaps). + */ + if (/*driver->enabled && */driver->IsSupportedDevice(vendor_id, product_id, version, -1)) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +SDL_bool +RAWINPUT_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version) +{ + SDL_RAWINPUT_Device *device; + + /* Don't update the device list for devices we know aren't supported */ + if (!RAWINPUT_IsDeviceSupported(vendor_id, product_id, version)) { + return SDL_FALSE; + } + + /* Make sure the device list is completely up to date when we check for device presence */ + RAWINPUT_UpdateDeviceList(); + + device = SDL_RAWINPUT_devices; + while (device) { + if (device->vendor_id == vendor_id && device->product_id == product_id) { + return SDL_TRUE; + } + device = device->next; + } + return SDL_FALSE; +} + +static void +RAWINPUT_JoystickDetect(void) +{ + int i; + /* Just ensure the window's add/remove messages have been pumped */ + RAWINPUT_UpdateDeviceList(); + + for (i = 0; i < SDL_arraysize(SDL_RAWINPUT_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_RAWINPUT_drivers[i]; + /* Running PostUpdate here only if it's *not* enabled (and ran elsewhere) */ + if (!driver->enabled && driver->PostUpdate) { + driver->PostUpdate(); + } + } + SDL_RAWINPUT_need_pump = SDL_TRUE; +} + +static SDL_RAWINPUT_Device * +RAWINPUT_GetJoystickByIndex(int device_index) +{ + SDL_RAWINPUT_Device *device = SDL_RAWINPUT_devices; + while (device) { + if (device_index == 0) { + break; + } + --device_index; + device = device->next; + } + return device; +} + +static const char * +RAWINPUT_JoystickGetDeviceName(int device_index) +{ + return RAWINPUT_GetJoystickByIndex(device_index)->name; +} + +static int +RAWINPUT_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static SDL_JoystickGUID +RAWINPUT_JoystickGetDeviceGUID(int device_index) +{ + return RAWINPUT_GetJoystickByIndex(device_index)->guid; +} + +static SDL_JoystickID +RAWINPUT_JoystickGetDeviceInstanceID(int device_index) +{ + SDL_RAWINPUT_Device *device = RAWINPUT_GetJoystickByIndex(device_index); + return device->driver->InstanceIDForIndex(&device->devdata, 0); +} + +static int +RAWINPUT_JoystickOpen(SDL_Joystick * joystick, int device_index) +{ + SDL_RAWINPUT_Device *device = RAWINPUT_GetJoystickByIndex(device_index); + struct joystick_hwdata *hwdata = SDL_callocStruct(struct joystick_hwdata); + SDL_assert(!device->joystick); + + if (!hwdata) { + return SDL_OutOfMemory(); + } + + device->driver->OpenJoystick(&device->devdata, joystick); + + hwdata->device = device; + device->joystick = joystick; + + hwdata->mutex = SDL_CreateMutex(); + joystick->hwdata = hwdata; + + return 0; +} + +static int +RAWINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + SDL_RAWINPUT_Device *device = hwdata->device; + int result; + + SDL_LockMutex(hwdata->mutex); + result = device->driver->Rumble(&device->devdata, joystick, low_frequency_rumble, high_frequency_rumble, duration_ms); + SDL_UnlockMutex(hwdata->mutex); + return result; +} + +static void +RAWINPUT_JoystickUpdate(SDL_Joystick * joystick) +{ + struct joystick_hwdata *hwdata; + SDL_RAWINPUT_Device *device; + /* Ensure data messages have been pumped */ + RAWINPUT_PumpMessages(); + hwdata = joystick->hwdata; + device = hwdata->device; + + SDL_LockMutex(hwdata->mutex); + device->driver->UpdateDriver(&device->devdata, NULL); + SDL_UnlockMutex(hwdata->mutex); +} + +static void +RAWINPUT_JoystickClose(SDL_Joystick * joystick) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + SDL_RAWINPUT_Device *device; + + device = hwdata->device; + if (device) { + SDL_assert(device->joystick == joystick); + device->joystick = NULL; + } + + SDL_DestroyMutex(hwdata->mutex); + SDL_free(hwdata); + joystick->hwdata = NULL; +} + +LRESULT RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (!SDL_RAWINPUT_inited) + return -1; + + switch (msg) + { + case WM_INPUT_DEVICE_CHANGE: + { + HANDLE hDevice = (HANDLE)lParam; + switch (wParam) { + case GIDC_ARRIVAL: + RAWINPUT_AddDevice(hDevice); + break; + case GIDC_REMOVAL: { + SDL_RAWINPUT_Device *device; + device = RAWINPUT_DeviceFromHandle(hDevice); + if (device) { + RAWINPUT_DelDevice(device, SDL_TRUE); + } + } break; + default: + return 0; + } + } + return 0; + case WM_INPUT: + { + Uint8 data[sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + USB_PACKET_LENGTH]; + UINT buffer_size = SDL_arraysize(data); + + if ((int)GetRawInputData((HRAWINPUT)lParam, RID_INPUT, data, &buffer_size, sizeof(RAWINPUTHEADER)) > 0) { + PRAWINPUT raw_input = (PRAWINPUT)data; + SDL_RAWINPUT_Device *device = RAWINPUT_DeviceFromHandle(raw_input->header.hDevice); + if (device) { + SDL_HIDAPI_DeviceDriver *driver = device->driver; + SDL_Joystick *joystick = device->joystick; + if (joystick) { + driver->HandleStatePacketFromRAWINPUT(&device->devdata, joystick, &raw_input->data.hid.bRawData[1], raw_input->data.hid.dwSizeHid - 1); + } + } + } + } + return 0; + } + return -1; +} + +static void +RAWINPUT_JoystickQuit(void) +{ + int ii; + RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)]; + + if (!SDL_RAWINPUT_inited) + return; + + for (ii = 0; ii < SDL_arraysize(subscribed_devices); ii++) { + rid[ii].usUsagePage = USAGE_PAGE_GENERIC_DESKTOP; + rid[ii].usUsage = subscribed_devices[ii]; + rid[ii].dwFlags = RIDEV_REMOVE; + rid[ii].hwndTarget = NULL; + } + + if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) { + SDL_Log("Couldn't un-register RAWINPUT"); + } + + while (SDL_RAWINPUT_devices) { + RAWINPUT_DelDevice(SDL_RAWINPUT_devices, SDL_FALSE); + } + + SDL_RAWINPUT_numjoysticks = 0; + + SDL_RAWINPUT_inited = SDL_FALSE; +} + + +SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver = +{ + RAWINPUT_JoystickInit, + RAWINPUT_JoystickGetCount, + RAWINPUT_JoystickDetect, + RAWINPUT_JoystickGetDeviceName, + RAWINPUT_JoystickGetDevicePlayerIndex, + RAWINPUT_JoystickGetDeviceGUID, + RAWINPUT_JoystickGetDeviceInstanceID, + RAWINPUT_JoystickOpen, + RAWINPUT_JoystickRumble, + RAWINPUT_JoystickUpdate, + RAWINPUT_JoystickClose, + RAWINPUT_JoystickQuit, +}; + +#endif /* SDL_JOYSTICK_RAWINPUT */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/windows/SDL_rawinputjoystick_c.h b/src/joystick/windows/SDL_rawinputjoystick_c.h new file mode 100644 index 0000000000..f95e0debed --- /dev/null +++ b/src/joystick/windows/SDL_rawinputjoystick_c.h @@ -0,0 +1,30 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2019 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +/* Return true if a RawInput device is present and supported as a joystick */ +extern SDL_bool RAWINPUT_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version); + +/* Returns 0 if message was handled */ +extern LRESULT RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/windows/SDL_xinputjoystick.c b/src/joystick/windows/SDL_xinputjoystick.c index 22e557f5d0..456b97456f 100644 --- a/src/joystick/windows/SDL_xinputjoystick.c +++ b/src/joystick/windows/SDL_xinputjoystick.c @@ -29,6 +29,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" /* @@ -267,6 +268,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); } diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index 5ad8d9e68b..821053e135 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" @@ -803,9 +804,19 @@ 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) @@ -820,7 +831,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;