From 30f41c02aec763d32e62351452da9ef582bc3472 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 6 Mar 2026 13:30:59 -0800 Subject: Move contrib libraries to contrib repo --- .../src/joystick/windows/SDL_rawinputjoystick.c | 2238 -------------------- 1 file changed, 2238 deletions(-) delete mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c (limited to 'contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c') diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c deleted file mode 100644 index d5166de..0000000 --- a/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c +++ /dev/null @@ -1,2238 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 2025 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" - -#ifdef SDL_JOYSTICK_RAWINPUT - -#include "../usb_ids.h" -#include "../SDL_sysjoystick.h" -#include "../../core/windows/SDL_windows.h" -#include "../../core/windows/SDL_hid.h" -#include "../hidapi/SDL_hidapijoystick_c.h" - -/* SDL_JOYSTICK_RAWINPUT_XINPUT is disabled because using XInput at the same time as - raw input will turn off the Xbox Series X controller when it is connected via the - Xbox One Wireless Adapter. - */ -#ifdef HAVE_XINPUT_H -#define SDL_JOYSTICK_RAWINPUT_XINPUT -#endif -#ifdef HAVE_WINDOWS_GAMING_INPUT_H -#define SDL_JOYSTICK_RAWINPUT_WGI -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT -#include "../../core/windows/SDL_xinput.h" -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI -#include "../../core/windows/SDL_windows.h" -typedef struct WindowsGamingInputGamepadState WindowsGamingInputGamepadState; -#define GamepadButtons_GUIDE 0x40000000 -#define COBJMACROS -#include "windows.gaming.input.h" -#include -#endif - -#if defined(SDL_JOYSTICK_RAWINPUT_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT_WGI) -#define SDL_JOYSTICK_RAWINPUT_MATCHING -#define SDL_JOYSTICK_RAWINPUT_MATCH_AXES -#define SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS -#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 6 // stick + trigger axes -#else -#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 4 // stick axes -#endif -#endif - -#if 0 -#define DEBUG_RAWINPUT -#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 - -extern void WINDOWS_RAWINPUTEnabledChanged(void); -extern void WINDOWS_JoystickDetect(void); - -static bool SDL_RAWINPUT_inited = false; -static bool SDL_RAWINPUT_remote_desktop = false; -static int SDL_RAWINPUT_numjoysticks = 0; - -static void RAWINPUT_JoystickClose(SDL_Joystick *joystick); - -typedef struct SDL_RAWINPUT_Device -{ - SDL_AtomicInt refcount; - char *name; - char *path; - Uint16 vendor_id; - Uint16 product_id; - Uint16 version; - SDL_GUID guid; - bool is_xinput; - bool is_xboxone; - int steam_virtual_gamepad_slot; - PHIDP_PREPARSED_DATA preparsed_data; - - HANDLE hDevice; - SDL_Joystick *joystick; - SDL_JoystickID joystick_id; - - struct SDL_RAWINPUT_Device *next; -} SDL_RAWINPUT_Device; - -struct joystick_hwdata -{ - bool is_xinput; - bool is_xboxone; - PHIDP_PREPARSED_DATA preparsed_data; - ULONG max_data_length; - HIDP_DATA *data; - USHORT *button_indices; - USHORT *axis_indices; - USHORT *hat_indices; - bool guide_hack; - bool trigger_hack; - USHORT trigger_hack_index; - -#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING - Uint64 match_state; // Lowest 16 bits for button states, higher 24 for 6 4bit axes - Uint64 last_state_packet; -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - bool xinput_enabled; - bool xinput_correlated; - Uint8 xinput_correlation_id; - Uint8 xinput_correlation_count; - Uint8 xinput_uncorrelate_count; - Uint8 xinput_slot; -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - bool wgi_correlated; - Uint8 wgi_correlation_id; - Uint8 wgi_correlation_count; - Uint8 wgi_uncorrelate_count; - WindowsGamingInputGamepadState *wgi_slot; -#endif - - SDL_RAWINPUT_Device *device; -}; -typedef struct joystick_hwdata RAWINPUT_DeviceContext; - -SDL_RAWINPUT_Device *SDL_RAWINPUT_devices; - -static const Uint16 subscribed_devices[] = { - USB_USAGE_GENERIC_GAMEPAD, - /* Don't need Joystick for any devices we're handling here (XInput-capable) - USB_USAGE_GENERIC_JOYSTICK, - USB_USAGE_GENERIC_MULTIAXISCONTROLLER, - */ -}; - -#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING - -static struct -{ - Uint64 last_state_packet; - SDL_Joystick *joystick; - SDL_Joystick *last_joystick; -} guide_button_candidate; - -typedef struct WindowsMatchState -{ -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES - SHORT match_axes[SDL_JOYSTICK_RAWINPUT_MATCH_COUNT]; -#endif -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - WORD xinput_buttons; -#endif -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - Uint32 wgi_buttons; -#endif - bool any_data; -} WindowsMatchState; - -static void RAWINPUT_FillMatchState(WindowsMatchState *state, Uint64 match_state) -{ -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES - int ii; -#endif - - bool any_axes_data = false; -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES - /* 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] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4)); - any_axes_data |= ((Uint32)(state->match_axes[ii] + 0x1000) > 0x2000); // match_state bit is not 0xF, 0x1, or 0x2 - } -#endif // SDL_JOYSTICK_RAWINPUT_MATCH_AXES -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS - for (; ii < SDL_JOYSTICK_RAWINPUT_MATCH_COUNT; ii++) { - state->match_axes[ii] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4)); - any_axes_data |= (state->match_axes[ii] != SDL_MIN_SINT16); - } -#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS - - state->any_data = any_axes_data; - -#ifdef SDL_JOYSTICK_RAWINPUT_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) */ - - // Can only match trigger values if a single trigger has a value. -#define XInputTriggersMatch(gamepad) ( \ - ((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \ - ((gamepad.bLeftTrigger != 0) && (gamepad.bRightTrigger != 0)) || \ - ((Uint32)((((int)gamepad.bLeftTrigger * 257) - 32768) - state->match_axes[4]) <= 0x2fff) || \ - ((Uint32)((((int)gamepad.bRightTrigger * 257) - 32768) - state->match_axes[5]) <= 0x2fff)) - - state->xinput_buttons = - // Bitwise map .RLDUWVQTS.KYXBA -> YXBA..WVQTKSRLDU - (WORD)(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 = true; - } -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - // 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) - -#define WindowsGamingInputTriggersMatch(gamepad) ( \ - ((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \ - ((gamepad.LeftTrigger == 0.0f) && (gamepad.RightTrigger == 0.0f)) || \ - ((Uint16)((((int)(gamepad.LeftTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[4]) <= 0x2fff) || \ - ((Uint16)((((int)(gamepad.RightTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[5]) <= 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 = true; - } -#endif -} - -#endif // SDL_JOYSTICK_RAWINPUT_MATCHING - -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - -static struct -{ - XINPUT_STATE state; - XINPUT_BATTERY_INFORMATION_EX battery; - bool connected; // Currently has an active XInput device - bool used; // Is currently mapped to an SDL device - Uint8 correlation_id; -} xinput_state[XUSER_MAX_COUNT]; -static bool xinput_device_change = true; -static bool xinput_state_dirty = true; - -static void RAWINPUT_UpdateXInput(void) -{ - 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 = false; - xinput_state_dirty = true; - } - if (xinput_state_dirty) { - xinput_state_dirty = 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 = false; - } - xinput_state[user_index].battery.BatteryType = BATTERY_TYPE_UNKNOWN; - if (XINPUTGETBATTERYINFORMATION) { - XINPUTGETBATTERYINFORMATION(user_index, BATTERY_DEVTYPE_GAMEPAD, &xinput_state[user_index].battery); - } - } - } - } -} - -static void RAWINPUT_MarkXInputSlotUsed(Uint8 xinput_slot) -{ - if (xinput_slot != XUSER_INDEX_ANY) { - xinput_state[xinput_slot].used = true; - } -} - -static void RAWINPUT_MarkXInputSlotFree(Uint8 xinput_slot) -{ - if (xinput_slot != XUSER_INDEX_ANY) { - xinput_state[xinput_slot].used = false; - } -} -static bool RAWINPUT_MissingXInputSlot(void) -{ - int ii; - for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) { - if (xinput_state[ii].connected && !xinput_state[ii].used) { - return true; - } - } - return false; -} - -static bool RAWINPUT_XInputSlotMatches(const WindowsMatchState *state, Uint8 slot_idx) -{ - 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 -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES - && XInputAxesMatch(xinput_state[slot_idx].state.Gamepad) -#endif -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS - && XInputTriggersMatch(xinput_state[slot_idx].state.Gamepad) -#endif - ) { - return true; - } - } - return false; -} - -static bool RAWINPUT_GuessXInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, Uint8 *slot_idx) -{ - Uint8 user_index; - int match_count; - - /* If there is only one available slot, let's use that - * That will be right most of the time, and uncorrelation will fix any bad guesses - */ - match_count = 0; - for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) { - if (xinput_state[user_index].connected && !xinput_state[user_index].used) { - *slot_idx = user_index; - ++match_count; - } - } - if (match_count == 1) { - *correlation_id = ++xinput_state[*slot_idx].correlation_id; - return true; - } - - *slot_idx = 0; - - match_count = 0; - for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) { - if (!xinput_state[user_index].used && RAWINPUT_XInputSlotMatches(state, user_index)) { - ++match_count; - *slot_idx = user_index; - // Incrementing correlation_id for any match, as negative evidence for others being correlated - *correlation_id = ++xinput_state[user_index].correlation_id; - } - } - /* 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 true; - } - return false; -} - -#endif // SDL_JOYSTICK_RAWINPUT_XINPUT - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - -typedef struct WindowsGamingInputGamepadState -{ - __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; - struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state; - RAWINPUT_DeviceContext *correlated_context; - bool used; // Is currently mapped to an SDL device - 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; - bool initialized; - bool dirty; - bool need_device_list_update; - int ref_count; - __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics; - EventRegistrationToken gamepad_added_token; - EventRegistrationToken gamepad_removed_token; -} wgi_state; - -typedef struct GamepadDelegate -{ - __FIEventHandler_1_Windows__CGaming__CInput__CGamepad iface; - SDL_AtomicInt refcount; -} GamepadDelegate; - -static const IID IID_IEventHandler_Gamepad = { 0x8a7639ee, 0x624a, 0x501a, { 0xbb, 0x53, 0x56, 0x2d, 0x1e, 0xc1, 0x1b, 0x52 } }; - -static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, REFIID riid, void **ppvObject) -{ - if (!ppvObject) { - return E_INVALIDARG; - } - - *ppvObject = NULL; - if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID_IEventHandler_Gamepad)) { - *ppvObject = This; - __FIEventHandler_1_Windows__CGaming__CInput__CGamepad_AddRef(This); - return S_OK; - } else if (WIN_IsEqualIID(riid, &IID_IMarshal)) { - // This seems complicated. Let's hope it doesn't happen. - return E_OUTOFMEMORY; - } else { - return E_NOINTERFACE; - } -} - -static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_AddRef(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This) -{ - GamepadDelegate *self = (GamepadDelegate *)This; - return SDL_AddAtomicInt(&self->refcount, 1) + 1UL; -} - -static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_Release(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This) -{ - GamepadDelegate *self = (GamepadDelegate *)This; - int rc = SDL_AddAtomicInt(&self->refcount, -1) - 1; - // Should never free the static delegate objects - SDL_assert(rc > 0); - return rc; -} - -static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e) -{ - wgi_state.need_device_list_update = true; - return S_OK; -} - -static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeRemoved(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e) -{ - wgi_state.need_device_list_update = true; - return S_OK; -} - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4028) // formal parameter 3 different from declaration, when using older buggy WGI headers -#pragma warning(disable : 4113) // X differs in parameter lists from Y, when using older buggy WGI headers -#endif - -static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_added_vtbl = { - IEventHandler_CGamepadVtbl_QueryInterface, - IEventHandler_CGamepadVtbl_AddRef, - IEventHandler_CGamepadVtbl_Release, - IEventHandler_CGamepadVtbl_InvokeAdded -}; -static GamepadDelegate gamepad_added = { - { &gamepad_added_vtbl }, - { 1 } -}; - -static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_removed_vtbl = { - IEventHandler_CGamepadVtbl_QueryInterface, - IEventHandler_CGamepadVtbl_AddRef, - IEventHandler_CGamepadVtbl_Release, - IEventHandler_CGamepadVtbl_InvokeRemoved -}; -static GamepadDelegate gamepad_removed = { - { &gamepad_removed_vtbl }, - { 1 } -}; - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -static void RAWINPUT_MarkWindowsGamingInputSlotUsed(WindowsGamingInputGamepadState *wgi_slot, RAWINPUT_DeviceContext *ctx) -{ - wgi_slot->used = true; - wgi_slot->correlated_context = ctx; -} - -static void RAWINPUT_MarkWindowsGamingInputSlotFree(WindowsGamingInputGamepadState *wgi_slot) -{ - wgi_slot->used = false; - wgi_slot->correlated_context = NULL; -} - -static bool RAWINPUT_MissingWindowsGamingInputSlot(void) -{ - int ii; - for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { - if (!wgi_state.per_gamepad[ii]->used) { - return true; - } - } - return false; -} - -static bool RAWINPUT_UpdateWindowsGamingInput(void) -{ - int ii; - if (!wgi_state.gamepad_statics) { - return true; - } - - if (!wgi_state.dirty) { - return true; - } - - wgi_state.dirty = false; - - if (wgi_state.need_device_list_update) { - HRESULT hr; - __FIVectorView_1_Windows__CGaming__CInput__CGamepad *gamepads; - wgi_state.need_device_list_update = false; - for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) { - wgi_state.per_gamepad[ii]->connected = false; - } - - 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)) { - bool found = false; - int jj; - for (jj = 0; jj < wgi_state.per_gamepad_count; jj++) { - if (wgi_state.per_gamepad[jj]->gamepad == gamepad) { - found = true; - wgi_state.per_gamepad[jj]->connected = true; - break; - } - } - if (!found) { - // New device, add it - WindowsGamingInputGamepadState *gamepad_state; - WindowsGamingInputGamepadState **new_per_gamepad; - gamepad_state = SDL_calloc(1, sizeof(*gamepad_state)); - if (!gamepad_state) { - return false; - } - new_per_gamepad = SDL_realloc(wgi_state.per_gamepad, sizeof(wgi_state.per_gamepad[0]) * (wgi_state.per_gamepad_count + 1)); - if (!new_per_gamepad) { - SDL_free(gamepad_state); - return false; - } - wgi_state.per_gamepad = new_per_gamepad; - wgi_state.per_gamepad_count++; - wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1] = gamepad_state; - gamepad_state->gamepad = gamepad; - gamepad_state->connected = 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 = 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 = false; // Not used by anything, currently - } - } - return true; -} -static void RAWINPUT_InitWindowsGamingInput(RAWINPUT_DeviceContext *ctx) -{ - if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) { - return; - } - - wgi_state.ref_count++; - if (!wgi_state.initialized) { - static const IID SDL_IID_IGamepadStatics = { 0x8BBCE529, 0xD49C, 0x39E9, { 0x95, 0x60, 0xE4, 0x7D, 0xDE, 0x96, 0xB7, 0xC8 } }; - HRESULT hr; - - if (FAILED(WIN_RoInitialize())) { - return; - } - wgi_state.initialized = true; - wgi_state.dirty = true; - - { - typedef HRESULT(WINAPI * WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER * hstringHeader, HSTRING * string); - typedef HRESULT(WINAPI * RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory); - - WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference"); - RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory"); - if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc) { - PCWSTR pNamespace = L"Windows.Gaming.Input.Gamepad"; - HSTRING_HEADER hNamespaceStringHeader; - HSTRING hNamespaceString; - - hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString); - if (SUCCEEDED(hr)) { - RoGetActivationFactoryFunc(hNamespaceString, &SDL_IID_IGamepadStatics, (void **)&wgi_state.gamepad_statics); - } - - if (wgi_state.gamepad_statics) { - wgi_state.need_device_list_update = true; - - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadAdded(wgi_state.gamepad_statics, &gamepad_added.iface, &wgi_state.gamepad_added_token); - if (!SUCCEEDED(hr)) { - SDL_SetError("add_GamepadAdded() failed: 0x%lx", hr); - } - - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadRemoved(wgi_state.gamepad_statics, &gamepad_removed.iface, &wgi_state.gamepad_removed_token); - if (!SUCCEEDED(hr)) { - SDL_SetError("add_GamepadRemoved() failed: 0x%lx", hr); - } - } - } - } - } -} - -static bool RAWINPUT_WindowsGamingInputSlotMatches(const WindowsMatchState *state, WindowsGamingInputGamepadState *slot, bool xinput_correlated) -{ - Uint32 wgi_buttons = slot->state.Buttons; - if ((wgi_buttons & 0x3FFF) == state->wgi_buttons -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES - && WindowsGamingInputAxesMatch(slot->state) -#endif -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS - // Don't try to match WGI triggers if getting values from XInput - && (xinput_correlated || WindowsGamingInputTriggersMatch(slot->state)) -#endif - ) { - return true; - } - return false; -} - -static bool RAWINPUT_GuessWindowsGamingInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, WindowsGamingInputGamepadState **slot, bool xinput_correlated) -{ - int match_count, user_index; - WindowsGamingInputGamepadState *gamepad_state = NULL; - - /* If there is only one available slot, let's use that - * That will be right most of the time, and uncorrelation will fix any bad guesses - */ - match_count = 0; - for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) { - gamepad_state = wgi_state.per_gamepad[user_index]; - if (gamepad_state->connected && !gamepad_state->used) { - *slot = gamepad_state; - ++match_count; - } - } - if (match_count == 1) { - *correlation_id = ++gamepad_state->correlation_id; - return true; - } - - match_count = 0; - for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) { - gamepad_state = wgi_state.per_gamepad[user_index]; - if (RAWINPUT_WindowsGamingInputSlotMatches(state, gamepad_state, xinput_correlated)) { - ++match_count; - *slot = gamepad_state; - // Incrementing correlation_id for any match, as negative evidence for others being correlated - *correlation_id = ++gamepad_state->correlation_id; - } - } - /* 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 true; - } - return false; -} - -static void RAWINPUT_QuitWindowsGamingInput(RAWINPUT_DeviceContext *ctx) -{ - --wgi_state.ref_count; - if (!wgi_state.ref_count && wgi_state.initialized) { - int ii; - for (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_remove_GamepadAdded(wgi_state.gamepad_statics, wgi_state.gamepad_added_token); - __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_remove_GamepadRemoved(wgi_state.gamepad_statics, wgi_state.gamepad_removed_token); - __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi_state.gamepad_statics); - wgi_state.gamepad_statics = NULL; - } - WIN_RoUninitialize(); - wgi_state.initialized = false; - } -} - -#endif // SDL_JOYSTICK_RAWINPUT_WGI - -static SDL_RAWINPUT_Device *RAWINPUT_AcquireDevice(SDL_RAWINPUT_Device *device) -{ - SDL_AtomicIncRef(&device->refcount); - return device; -} - -static void RAWINPUT_ReleaseDevice(SDL_RAWINPUT_Device *device) -{ -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - if (device->joystick) { - RAWINPUT_DeviceContext *ctx = device->joystick->hwdata; - - if (ctx->xinput_enabled && ctx->xinput_correlated) { - RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot); - ctx->xinput_correlated = false; - } - } -#endif // SDL_JOYSTICK_RAWINPUT_XINPUT - - if (SDL_AtomicDecRef(&device->refcount)) { - SDL_free(device->preparsed_data); - SDL_free(device->name); - SDL_free(device->path); - SDL_free(device); - } -} - -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 int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path) -{ - int slot = -1; - - // The format for the raw input device path is documented here: - // https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices - if (vendor_id == USB_VENDOR_VALVE && - product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { - (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot); - } - return slot; -} - -static void RAWINPUT_AddDevice(HANDLE hDevice) -{ -#define CHECK(expression) \ - { \ - if (!(expression)) \ - goto err; \ - } - SDL_RAWINPUT_Device *device = NULL; - SDL_RAWINPUT_Device *curr, *last; - RID_DEVICE_INFO rdi; - UINT size; - char dev_name[MAX_PATH] = { 0 }; - HANDLE hFile = INVALID_HANDLE_VALUE; - - // Make sure we're not trying to add the same device twice - if (RAWINPUT_DeviceFromHandle(hDevice)) { - return; - } - - // Figure out what kind of device it is - size = sizeof(rdi); - SDL_zero(rdi); - CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICEINFO, &rdi, &size) != (UINT)-1); - CHECK(rdi.dwType == RIM_TYPEHID); - - // Get the device "name" (HID Path) - size = SDL_arraysize(dev_name); - CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, dev_name, &size) != (UINT)-1); - // Only take XInput-capable devices - CHECK(SDL_strstr(dev_name, "IG_") != NULL); - CHECK(!SDL_ShouldIgnoreJoystick((Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, "")); - CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_RAWINPUT_JoystickDriver, (Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, "")); - - device = (SDL_RAWINPUT_Device *)SDL_calloc(1, sizeof(SDL_RAWINPUT_Device)); - CHECK(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->is_xinput = true; - device->is_xboxone = SDL_IsJoystickXboxOne(device->vendor_id, device->product_id); - device->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(device->vendor_id, device->product_id, dev_name); - - // Get HID Top-Level Collection Preparsed Data - size = 0; - CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, NULL, &size) != (UINT)-1); - device->preparsed_data = (PHIDP_PREPARSED_DATA)SDL_calloc(size, sizeof(BYTE)); - CHECK(device->preparsed_data); - CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, device->preparsed_data, &size) != (UINT)-1); - - hFile = CreateFileA(dev_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); - CHECK(hFile != INVALID_HANDLE_VALUE); - - { - char *manufacturer_string = NULL; - char *product_string = NULL; - WCHAR string[128]; - - if (SDL_HidD_GetManufacturerString(hFile, string, sizeof(string))) { - manufacturer_string = WIN_StringToUTF8W(string); - } - if (SDL_HidD_GetProductString(hFile, string, sizeof(string))) { - product_string = WIN_StringToUTF8W(string); - } - - device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string); - device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, manufacturer_string, product_string, 'r', 0); - - if (manufacturer_string) { - SDL_free(manufacturer_string); - } - if (product_string) { - SDL_free(product_string); - } - } - - device->path = SDL_strdup(dev_name); - - CloseHandle(hFile); - hFile = INVALID_HANDLE_VALUE; - - device->joystick_id = SDL_GetNextObjectID(); - -#ifdef DEBUG_RAWINPUT - SDL_Log("Adding RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle 0x%.8x", device->name, device->vendor_id, device->product_id, device->version, device->hDevice); -#endif - - // Add it to the list - RAWINPUT_AcquireDevice(device); - for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) { - } - if (last) { - last->next = device; - } else { - SDL_RAWINPUT_devices = device; - } - - ++SDL_RAWINPUT_numjoysticks; - - SDL_PrivateJoystickAdded(device->joystick_id); - - return; - -err: - if (hFile != INVALID_HANDLE_VALUE) { - CloseHandle(hFile); - } - if (device) { - if (device->name) { - SDL_free(device->name); - } - if (device->path) { - SDL_free(device->path); - } - SDL_free(device); - } -#undef CHECK -} - -static void RAWINPUT_DelDevice(SDL_RAWINPUT_Device *device, 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; - } - --SDL_RAWINPUT_numjoysticks; - - SDL_PrivateJoystickRemoved(device->joystick_id); - -#ifdef DEBUG_RAWINPUT - SDL_Log("Removing RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle %p", device->name, device->vendor_id, device->product_id, device->version, device->hDevice); -#endif - RAWINPUT_ReleaseDevice(device); - return; - } - } -} - -static void RAWINPUT_DetectDevices(void) -{ - UINT device_count = 0; - - if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) != -1) && device_count > 0) { - PRAWINPUTDEVICELIST devices = NULL; - UINT i; - - devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count); - if (devices) { - device_count = GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)); - if (device_count != (UINT)-1) { - for (i = 0; i < device_count; ++i) { - RAWINPUT_AddDevice(devices[i].hDevice); - } - } - SDL_free(devices); - } - } -} - -static void RAWINPUT_RemoveDevices(void) -{ - while (SDL_RAWINPUT_devices) { - RAWINPUT_DelDevice(SDL_RAWINPUT_devices, false); - } - SDL_assert(SDL_RAWINPUT_numjoysticks == 0); -} - -static bool RAWINPUT_JoystickInit(void) -{ - SDL_assert(!SDL_RAWINPUT_inited); - - if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, true)) { - return true; - } - - if (!WIN_IsWindowsVistaOrGreater()) { - // According to bug 6400, this doesn't work on Windows XP - return false; - } - - if (!WIN_LoadHIDDLL()) { - return false; - } - - SDL_RAWINPUT_inited = true; - - RAWINPUT_DetectDevices(); - - return true; -} - -static int RAWINPUT_JoystickGetCount(void) -{ - return SDL_RAWINPUT_numjoysticks; -} - -bool RAWINPUT_IsEnabled(void) -{ - return SDL_RAWINPUT_inited && !SDL_RAWINPUT_remote_desktop; -} - -static void RAWINPUT_PostUpdate(void) -{ -#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING - bool unmapped_guide_pressed = false; - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - 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 = true; - break; - } - } - } - wgi_state.dirty = true; -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_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 = true; - break; - } - } - } - xinput_state_dirty = true; -#endif - - if (unmapped_guide_pressed) { - if (guide_button_candidate.joystick && !guide_button_candidate.last_joystick) { - SDL_Joystick *joystick = guide_button_candidate.joystick; - RAWINPUT_DeviceContext *ctx = joystick->hwdata; - if (ctx->guide_hack) { - int guide_button = joystick->nbuttons - 1; - - SDL_SendJoystickButton(SDL_GetTicksNS(), guide_button_candidate.joystick, (Uint8)guide_button, true); - } - guide_button_candidate.last_joystick = guide_button_candidate.joystick; - } - } else if (guide_button_candidate.last_joystick) { - SDL_Joystick *joystick = guide_button_candidate.last_joystick; - RAWINPUT_DeviceContext *ctx = joystick->hwdata; - if (ctx->guide_hack) { - int guide_button = joystick->nbuttons - 1; - - SDL_SendJoystickButton(SDL_GetTicksNS(), joystick, (Uint8)guide_button, false); - } - guide_button_candidate.last_joystick = NULL; - } - guide_button_candidate.joystick = NULL; - -#endif // SDL_JOYSTICK_RAWINPUT_MATCHING -} - -static void RAWINPUT_JoystickDetect(void) -{ - bool remote_desktop; - - if (!SDL_RAWINPUT_inited) { - return; - } - - remote_desktop = GetSystemMetrics(SM_REMOTESESSION) ? true : false; - if (remote_desktop != SDL_RAWINPUT_remote_desktop) { - SDL_RAWINPUT_remote_desktop = remote_desktop; - - WINDOWS_RAWINPUTEnabledChanged(); - - if (remote_desktop) { - RAWINPUT_RemoveDevices(); - WINDOWS_JoystickDetect(); - } else { - WINDOWS_JoystickDetect(); - RAWINPUT_DetectDevices(); - } - } - RAWINPUT_PostUpdate(); -} - -static bool RAWINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) -{ - SDL_RAWINPUT_Device *device; - - // If we're being asked about a device, that means another API just detected one, so rescan -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - xinput_device_change = true; -#endif - - device = SDL_RAWINPUT_devices; - while (device) { - if (vendor_id == device->vendor_id && product_id == device->product_id) { - return true; - } - - /* The Xbox 360 wireless controller shows up as product 0 in WGI. - Try to match it to a Raw Input device via name or known product ID. */ - if (vendor_id == device->vendor_id && product_id == 0 && - ((name && SDL_strstr(device->name, name) != NULL) || - (device->vendor_id == USB_VENDOR_MICROSOFT && - device->product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER))) { - return true; - } - - // The Xbox One controller shows up as a hardcoded raw input VID/PID - if (name && SDL_strcmp(name, "Xbox One Game Controller") == 0 && - device->vendor_id == USB_VENDOR_MICROSOFT && - device->product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) { - return true; - } - - device = device->next; - } - return false; -} - -static SDL_RAWINPUT_Device *RAWINPUT_GetDeviceByIndex(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_GetDeviceByIndex(device_index)->name; -} - -static const char *RAWINPUT_JoystickGetDevicePath(int device_index) -{ - return RAWINPUT_GetDeviceByIndex(device_index)->path; -} - -static int RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) -{ - return RAWINPUT_GetDeviceByIndex(device_index)->steam_virtual_gamepad_slot; -} - -static int RAWINPUT_JoystickGetDevicePlayerIndex(int device_index) -{ - return false; -} - -static void RAWINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index) -{ -} - -static SDL_GUID RAWINPUT_JoystickGetDeviceGUID(int device_index) -{ - return RAWINPUT_GetDeviceByIndex(device_index)->guid; -} - -static SDL_JoystickID RAWINPUT_JoystickGetDeviceInstanceID(int device_index) -{ - return RAWINPUT_GetDeviceByIndex(device_index)->joystick_id; -} - -static int SDLCALL RAWINPUT_SortValueCaps(const void *A, const void *B) -{ - HIDP_VALUE_CAPS *capsA = (HIDP_VALUE_CAPS *)A; - HIDP_VALUE_CAPS *capsB = (HIDP_VALUE_CAPS *)B; - - // Sort by Usage for single values, or UsageMax for range of values - return (int)capsA->NotRange.Usage - capsB->NotRange.Usage; -} - -static bool RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) -{ - SDL_RAWINPUT_Device *device = RAWINPUT_GetDeviceByIndex(device_index); - RAWINPUT_DeviceContext *ctx; - HIDP_CAPS caps; - HIDP_BUTTON_CAPS *button_caps; - HIDP_VALUE_CAPS *value_caps; - ULONG i; - - ctx = (RAWINPUT_DeviceContext *)SDL_calloc(1, sizeof(RAWINPUT_DeviceContext)); - if (!ctx) { - return false; - } - joystick->hwdata = ctx; - - ctx->device = RAWINPUT_AcquireDevice(device); - device->joystick = joystick; - - if (device->is_xinput) { - // We'll try to get guide button and trigger axes from XInput -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - xinput_device_change = true; - ctx->xinput_enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, true); - if (ctx->xinput_enabled && (!WIN_LoadXInputDLL() || !XINPUTGETSTATE)) { - ctx->xinput_enabled = false; - } - ctx->xinput_slot = XUSER_INDEX_ANY; -#endif -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - RAWINPUT_InitWindowsGamingInput(ctx); -#endif - } - - ctx->is_xinput = device->is_xinput; - ctx->is_xboxone = device->is_xboxone; -#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING - ctx->match_state = 0x0000008800000000ULL; // Trigger axes at rest -#endif - ctx->preparsed_data = device->preparsed_data; - ctx->max_data_length = SDL_HidP_MaxDataListLength(HidP_Input, ctx->preparsed_data); - ctx->data = (HIDP_DATA *)SDL_malloc(ctx->max_data_length * sizeof(*ctx->data)); - if (!ctx->data) { - RAWINPUT_JoystickClose(joystick); - return false; - } - - if (SDL_HidP_GetCaps(ctx->preparsed_data, &caps) != HIDP_STATUS_SUCCESS) { - RAWINPUT_JoystickClose(joystick); - return SDL_SetError("Couldn't get device capabilities"); - } - - button_caps = SDL_stack_alloc(HIDP_BUTTON_CAPS, caps.NumberInputButtonCaps); - if (SDL_HidP_GetButtonCaps(HidP_Input, button_caps, &caps.NumberInputButtonCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) { - RAWINPUT_JoystickClose(joystick); - return SDL_SetError("Couldn't get device button capabilities"); - } - - value_caps = SDL_stack_alloc(HIDP_VALUE_CAPS, caps.NumberInputValueCaps); - if (SDL_HidP_GetValueCaps(HidP_Input, value_caps, &caps.NumberInputValueCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) { - RAWINPUT_JoystickClose(joystick); - SDL_stack_free(button_caps); - return SDL_SetError("Couldn't get device value capabilities"); - } - - // Sort the axes by usage, so X comes before Y, etc. - SDL_qsort(value_caps, caps.NumberInputValueCaps, sizeof(*value_caps), RAWINPUT_SortValueCaps); - - for (i = 0; i < caps.NumberInputButtonCaps; ++i) { - HIDP_BUTTON_CAPS *cap = &button_caps[i]; - - if (cap->UsagePage == USB_USAGEPAGE_BUTTON) { - int count; - - if (cap->IsRange) { - count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin); - } else { - count = 1; - } - - joystick->nbuttons += count; - } - } - - if (joystick->nbuttons > 0) { - int button_index = 0; - - ctx->button_indices = (USHORT *)SDL_malloc(joystick->nbuttons * sizeof(*ctx->button_indices)); - if (!ctx->button_indices) { - RAWINPUT_JoystickClose(joystick); - SDL_stack_free(value_caps); - SDL_stack_free(button_caps); - return false; - } - - for (i = 0; i < caps.NumberInputButtonCaps; ++i) { - HIDP_BUTTON_CAPS *cap = &button_caps[i]; - - if (cap->UsagePage == USB_USAGEPAGE_BUTTON) { - if (cap->IsRange) { - int j, count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin); - - for (j = 0; j < count; ++j) { - ctx->button_indices[button_index++] = (USHORT)(cap->Range.DataIndexMin + j); - } - } else { - ctx->button_indices[button_index++] = cap->NotRange.DataIndex; - } - } - } - } - if (ctx->is_xinput && joystick->nbuttons == 10) { - ctx->guide_hack = true; - joystick->nbuttons += 1; - } - - SDL_stack_free(button_caps); - - for (i = 0; i < caps.NumberInputValueCaps; ++i) { - HIDP_VALUE_CAPS *cap = &value_caps[i]; - - if (cap->IsRange) { - continue; - } - - if (ctx->trigger_hack && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) { - continue; - } - - if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) { - joystick->nhats += 1; - continue; - } - - if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) { - continue; - } - - joystick->naxes += 1; - } - - if (joystick->naxes > 0) { - int axis_index = 0; - - ctx->axis_indices = (USHORT *)SDL_malloc(joystick->naxes * sizeof(*ctx->axis_indices)); - if (!ctx->axis_indices) { - RAWINPUT_JoystickClose(joystick); - SDL_stack_free(value_caps); - return false; - } - - for (i = 0; i < caps.NumberInputValueCaps; ++i) { - HIDP_VALUE_CAPS *cap = &value_caps[i]; - - if (cap->IsRange) { - continue; - } - - if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) { - continue; - } - - if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) { - ctx->trigger_hack = true; - ctx->trigger_hack_index = cap->NotRange.DataIndex; - continue; - } - - ctx->axis_indices[axis_index++] = cap->NotRange.DataIndex; - } - } - if (ctx->trigger_hack) { - joystick->naxes += 2; - } - - if (joystick->nhats > 0) { - int hat_index = 0; - - ctx->hat_indices = (USHORT *)SDL_malloc(joystick->nhats * sizeof(*ctx->hat_indices)); - if (!ctx->hat_indices) { - RAWINPUT_JoystickClose(joystick); - SDL_stack_free(value_caps); - return false; - } - - for (i = 0; i < caps.NumberInputValueCaps; ++i) { - HIDP_VALUE_CAPS *cap = &value_caps[i]; - - if (cap->IsRange) { - continue; - } - - if (cap->NotRange.Usage != USB_USAGE_GENERIC_HAT) { - continue; - } - - ctx->hat_indices[hat_index++] = cap->NotRange.DataIndex; - } - } - - SDL_stack_free(value_caps); - -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - if (ctx->is_xinput) { - SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); - } -#endif -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - if (ctx->is_xinput) { - SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); - - if (ctx->is_xboxone) { - SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); - } - } -#endif - - return true; -} - -static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) -{ -#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT) - RAWINPUT_DeviceContext *ctx = joystick->hwdata; -#endif - bool rumbled = false; - -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - // Prefer XInput over WGI because it allows rumble in the background - if (!rumbled && ctx->xinput_correlated) { - XINPUT_VIBRATION XVibration; - - if (!XINPUTSETSTATE) { - return SDL_Unsupported(); - } - - XVibration.wLeftMotorSpeed = low_frequency_rumble; - XVibration.wRightMotorSpeed = high_frequency_rumble; - if (XINPUTSETSTATE(ctx->xinput_slot, &XVibration) == ERROR_SUCCESS) { - rumbled = true; - } else { - return SDL_SetError("XInputSetState() failed"); - } - } -#endif // SDL_JOYSTICK_RAWINPUT_XINPUT - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - if (!rumbled && ctx->wgi_correlated) { - WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr; - 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 = true; - } - } -#endif - - if (!rumbled) { -#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT) - return SDL_SetError("Controller isn't correlated yet, try hitting a button first"); -#else - return SDL_Unsupported(); -#endif - } - return true; -} - -static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) -{ -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - RAWINPUT_DeviceContext *ctx = joystick->hwdata; - - if (ctx->wgi_correlated) { - WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr; - gamepad_state->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; - gamepad_state->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); - if (!SUCCEEDED(hr)) { - return SDL_SetError("Setting vibration failed: 0x%lx", hr); - } - return true; - } else { - return SDL_SetError("Controller isn't correlated yet, try hitting a button first"); - } -#else - return SDL_Unsupported(); -#endif -} - -static bool RAWINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) -{ - return SDL_Unsupported(); -} - -static bool RAWINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) -{ - return SDL_Unsupported(); -} - -static bool RAWINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) -{ - return SDL_Unsupported(); -} - -static HIDP_DATA *GetData(USHORT index, HIDP_DATA *data, ULONG length) -{ - ULONG i; - - // Check to see if the data is at the expected offset - if (index < length && data[index].DataIndex == index) { - return &data[index]; - } - - // Loop through the data to find it - for (i = 0; i < length; ++i) { - if (data[i].DataIndex == index) { - return &data[i]; - } - } - return NULL; -} - -/* This is the packet format for Xbox 360 and Xbox One controllers on Windows, - however with this interface there is no rumble support, no guide button, - and the left and right triggers are tied together as a single axis. - - We use XInput and Windows.Gaming.Input to make up for these shortcomings. - */ -static void RAWINPUT_HandleStatePacket(SDL_Joystick *joystick, Uint8 *data, int size) -{ - RAWINPUT_DeviceContext *ctx = joystick->hwdata; -#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING - // Map new buttons and axes into game controller controls - static const int button_map[] = { - SDL_GAMEPAD_BUTTON_SOUTH, - SDL_GAMEPAD_BUTTON_EAST, - SDL_GAMEPAD_BUTTON_WEST, - SDL_GAMEPAD_BUTTON_NORTH, - SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, - SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, - SDL_GAMEPAD_BUTTON_BACK, - SDL_GAMEPAD_BUTTON_START, - SDL_GAMEPAD_BUTTON_LEFT_STICK, - SDL_GAMEPAD_BUTTON_RIGHT_STICK - }; -#define HAT_MASK ((1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) - static const int hat_map[] = { - 0, - (1 << SDL_GAMEPAD_BUTTON_DPAD_UP), - (1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT), - (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT), - (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT), - (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN), - (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT), - (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT), - (1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT), - 0, - }; - Uint64 match_state = ctx->match_state; - // Update match_state with button bit, then fall through -#define SDL_SendJoystickButton(timestamp, joystick, button, down) \ - if (button < SDL_arraysize(button_map)) { \ - Uint64 button_bit = 1ull << button_map[button]; \ - match_state = (match_state & ~button_bit) | (button_bit * (down)); \ - } \ - SDL_SendJoystickButton(timestamp, joystick, button, down) -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES - // Grab high 4 bits of value, then fall through -#define AddAxisToMatchState(axis, value) \ - { \ - match_state = (match_state & ~(0xFull << (4 * axis + 16))) | ((value)&0xF000ull) << (4 * axis + 4); \ - } -#define SDL_SendJoystickAxis(timestamp, joystick, axis, value) \ - if (axis < 4) \ - AddAxisToMatchState(axis, value); \ - SDL_SendJoystickAxis(timestamp, joystick, axis, value) -#endif -#endif // SDL_JOYSTICK_RAWINPUT_MATCHING - - ULONG data_length = ctx->max_data_length; - int i; - int nbuttons = joystick->nbuttons - (ctx->guide_hack * 1); - int naxes = joystick->naxes - (ctx->trigger_hack * 2); - int nhats = joystick->nhats; - Uint32 button_mask = 0; - Uint64 timestamp = SDL_GetTicksNS(); - - if (SDL_HidP_GetData(HidP_Input, ctx->data, &data_length, ctx->preparsed_data, (PCHAR)data, size) != HIDP_STATUS_SUCCESS) { - return; - } - - for (i = 0; i < nbuttons; ++i) { - HIDP_DATA *item = GetData(ctx->button_indices[i], ctx->data, data_length); - if (item && item->On) { - button_mask |= (1 << i); - } - } - for (i = 0; i < nbuttons; ++i) { - SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, ((button_mask & (1 << i)) != 0)); - } - - for (i = 0; i < naxes; ++i) { - HIDP_DATA *item = GetData(ctx->axis_indices[i], ctx->data, data_length); - if (item) { - Sint16 axis = (int)(Uint16)item->RawValue - 0x8000; - SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, axis); - } - } - - for (i = 0; i < nhats; ++i) { - HIDP_DATA *item = GetData(ctx->hat_indices[i], ctx->data, data_length); - if (item) { - Uint8 hat = SDL_HAT_CENTERED; - const Uint8 hat_states[] = { - SDL_HAT_CENTERED, - SDL_HAT_UP, - SDL_HAT_UP | SDL_HAT_RIGHT, - SDL_HAT_RIGHT, - SDL_HAT_DOWN | SDL_HAT_RIGHT, - SDL_HAT_DOWN, - SDL_HAT_DOWN | SDL_HAT_LEFT, - SDL_HAT_LEFT, - SDL_HAT_UP | SDL_HAT_LEFT, - SDL_HAT_CENTERED, - }; - ULONG state = item->RawValue; - - if (state < SDL_arraysize(hat_states)) { -#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING - match_state = (match_state & ~HAT_MASK) | hat_map[state]; -#endif - hat = hat_states[state]; - } - SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat); - } - } - -#ifdef SDL_SendJoystickButton -#undef SDL_SendJoystickButton -#endif -#ifdef SDL_SendJoystickAxis -#undef SDL_SendJoystickAxis -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS -#define AddTriggerToMatchState(axis, value) \ - { \ - int match_axis = axis + SDL_JOYSTICK_RAWINPUT_MATCH_COUNT - joystick->naxes; \ - AddAxisToMatchState(match_axis, value); \ - } -#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS - - if (ctx->trigger_hack) { - bool has_trigger_data = false; - int left_trigger = joystick->naxes - 2; - int right_trigger = joystick->naxes - 1; - -#ifdef SDL_JOYSTICK_RAWINPUT_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 = true; - } -#endif // SDL_JOYSTICK_RAWINPUT_XINPUT - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - if (!has_trigger_data && ctx->wgi_correlated) { - has_trigger_data = true; - } -#endif // SDL_JOYSTICK_RAWINPUT_WGI - -#ifndef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS - if (!has_trigger_data) -#endif - { - HIDP_DATA *item = GetData(ctx->trigger_hack_index, ctx->data, data_length); - if (item) { - Sint16 value = (int)(Uint16)item->RawValue - 0x8000; - Sint16 left_value = (value > 0) ? (value * 2 - 32767) : SDL_MIN_SINT16; - Sint16 right_value = (value < 0) ? (-value * 2 - 32769) : SDL_MIN_SINT16; - -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS - AddTriggerToMatchState(left_trigger, left_value); - AddTriggerToMatchState(right_trigger, right_value); - if (!has_trigger_data) -#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS - { - SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, left_value); - SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, right_value); - } - } - } - } - -#ifdef AddAxisToMatchState -#undef AddAxisToMatchState -#endif -#ifdef AddTriggerToMatchState -#undef AddTriggerToMatchState -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING - if (ctx->is_xinput) { - ctx->match_state = match_state; - ctx->last_state_packet = SDL_GetTicks(); - } -#endif -} - -static void RAWINPUT_UpdateOtherAPIs(SDL_Joystick *joystick) -{ -#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING - RAWINPUT_DeviceContext *ctx = joystick->hwdata; - bool has_trigger_data = false; - bool correlated = false; - WindowsMatchState match_state_xinput; - int guide_button = joystick->nbuttons - 1; - int left_trigger = joystick->naxes - 2; - int right_trigger = joystick->naxes - 1; -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - bool xinput_correlated; -#endif - - RAWINPUT_FillMatchState(&match_state_xinput, ctx->match_state); - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - xinput_correlated = ctx->xinput_correlated; -#else - xinput_correlated = false; -#endif - // Parallel logic to WINDOWS_XINPUT below - RAWINPUT_UpdateWindowsGamingInput(); - if (ctx->wgi_correlated && - !joystick->low_frequency_rumble && !joystick->high_frequency_rumble && - !joystick->left_trigger_rumble && !joystick->right_trigger_rumble) { - // We have been previously correlated, ensure we are still matching, see comments in XINPUT section - if (RAWINPUT_WindowsGamingInputSlotMatches(&match_state_xinput, ctx->wgi_slot, xinput_correlated)) { - 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 5 to be safe. An incorrect un-correlation will simply result in lower precision - triggers for a frame. */ - if (ctx->wgi_uncorrelate_count >= 5) { -#ifdef DEBUG_RAWINPUT - SDL_Log("UN-Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, ctx->wgi_slot); -#endif - RAWINPUT_MarkWindowsGamingInputSlotFree(ctx->wgi_slot); - ctx->wgi_correlated = false; - ctx->wgi_correlation_count = 0; - // 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. */ - if (ctx->guide_hack) { - SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false); - } - } - } - } - if (!ctx->wgi_correlated) { - Uint8 new_correlation_count = 0; - if (RAWINPUT_MissingWindowsGamingInputSlot()) { - Uint8 correlation_id = 0; - WindowsGamingInputGamepadState *slot_idx = NULL; - if (RAWINPUT_GuessWindowsGamingInputSlot(&match_state_xinput, &correlation_id, &slot_idx, xinput_correlated)) { - // 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 = true; -#ifdef DEBUG_RAWINPUT - SDL_Log("Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, slot_idx); -#endif - correlated = true; - RAWINPUT_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; - } - } - } 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 = true; - } -#endif // SDL_JOYSTICK_RAWINPUT_WGI - -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - // Parallel logic to WINDOWS_GAMING_INPUT above - if (ctx->xinput_enabled) { - RAWINPUT_UpdateXInput(); - if (ctx->xinput_correlated && - !joystick->low_frequency_rumble && !joystick->high_frequency_rumble) { - // 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 (RAWINPUT_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 5 to be safe. An incorrect un-correlation will simply result in lower precision - triggers for a frame. */ - if (ctx->xinput_uncorrelate_count >= 5) { -#ifdef DEBUG_RAWINPUT - SDL_Log("UN-Correlated joystick %d to XInput device #%d", joystick->instance_id, ctx->xinput_slot); -#endif - RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot); - ctx->xinput_correlated = false; - ctx->xinput_correlation_count = 0; - // 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. */ - if (ctx->guide_hack) { - SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false); - } - } - } - } - if (!ctx->xinput_correlated) { - Uint8 new_correlation_count = 0; - if (RAWINPUT_MissingXInputSlot()) { - Uint8 correlation_id = 0; - Uint8 slot_idx = 0; - if (RAWINPUT_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 = true; -#ifdef DEBUG_RAWINPUT - SDL_Log("Correlated joystick %d to XInput device #%d", joystick->instance_id, slot_idx); -#endif - correlated = true; - RAWINPUT_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; - } - } - } 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 = true; - } - } -#endif // SDL_JOYSTICK_RAWINPUT_XINPUT - - // Poll for trigger data once (not per-state-packet) -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - // Prefer XInput over WindowsGamingInput, it continues to provide data in the background - if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) { - RAWINPUT_UpdateXInput(); - if (xinput_state[ctx->xinput_slot].connected) { - XINPUT_BATTERY_INFORMATION_EX *battery_info = &xinput_state[ctx->xinput_slot].battery; - Uint64 timestamp; - - if (ctx->guide_hack || ctx->trigger_hack) { - timestamp = SDL_GetTicksNS(); - } else { - // timestamp won't be used - timestamp = 0; - } - - if (ctx->guide_hack) { - bool down = ((xinput_state[ctx->xinput_slot].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE) != 0); - SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down); - } - if (ctx->trigger_hack) { - SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bLeftTrigger * 257) - 32768); - SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bRightTrigger * 257) - 32768); - } - has_trigger_data = true; - - SDL_PowerState state; - int percent; - switch (battery_info->BatteryType) { - case BATTERY_TYPE_WIRED: - state = SDL_POWERSTATE_CHARGING; - break; - case BATTERY_TYPE_UNKNOWN: - case BATTERY_TYPE_DISCONNECTED: - state = SDL_POWERSTATE_UNKNOWN; - break; - default: - state = SDL_POWERSTATE_ON_BATTERY; - break; - } - switch (battery_info->BatteryLevel) { - case BATTERY_LEVEL_EMPTY: - percent = 10; - break; - case BATTERY_LEVEL_LOW: - percent = 40; - break; - case BATTERY_LEVEL_MEDIUM: - percent = 70; - break; - default: - case BATTERY_LEVEL_FULL: - percent = 100; - break; - } - SDL_SendJoystickPowerInfo(joystick, state, percent); - } - } -#endif // SDL_JOYSTICK_RAWINPUT_XINPUT - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - if (!has_trigger_data && ctx->wgi_correlated) { - RAWINPUT_UpdateWindowsGamingInput(); // May detect disconnect / cause uncorrelation - if (ctx->wgi_correlated) { // Still connected - struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading *state = &ctx->wgi_slot->state; - Uint64 timestamp; - - if (ctx->guide_hack || ctx->trigger_hack) { - timestamp = SDL_GetTicksNS(); - } else { - // timestamp won't be used - timestamp = 0; - } - - if (ctx->guide_hack) { - bool down = ((state->Buttons & GamepadButtons_GUIDE) != 0); - SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down); - } - if (ctx->trigger_hack) { - SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, (Sint16)(((int)(state->LeftTrigger * SDL_MAX_UINT16)) - 32768)); - SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, (Sint16)(((int)(state->RightTrigger * SDL_MAX_UINT16)) - 32768)); - } - has_trigger_data = true; - } - } -#endif // SDL_JOYSTICK_RAWINPUT_WGI - - if (!correlated) { - if (!guide_button_candidate.joystick || - (ctx->last_state_packet && (!guide_button_candidate.last_state_packet || - 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 // SDL_JOYSTICK_RAWINPUT_MATCHING -} - -static void RAWINPUT_JoystickUpdate(SDL_Joystick *joystick) -{ - RAWINPUT_UpdateOtherAPIs(joystick); -} - -static void RAWINPUT_JoystickClose(SDL_Joystick *joystick) -{ - RAWINPUT_DeviceContext *ctx = joystick->hwdata; - -#ifdef SDL_JOYSTICK_RAWINPUT_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 - - if (ctx) { - SDL_RAWINPUT_Device *device; - -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - xinput_device_change = true; - if (ctx->xinput_enabled) { - if (ctx->xinput_correlated) { - RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot); - } - WIN_UnloadXInputDLL(); - } -#endif -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - RAWINPUT_QuitWindowsGamingInput(ctx); -#endif - - device = ctx->device; - if (device) { - SDL_assert(device->joystick == joystick); - device->joystick = NULL; - RAWINPUT_ReleaseDevice(device); - } - - SDL_free(ctx->data); - SDL_free(ctx->button_indices); - SDL_free(ctx->axis_indices); - SDL_free(ctx->hat_indices); - SDL_free(ctx); - joystick->hwdata = NULL; - } -} - -bool RAWINPUT_RegisterNotifications(HWND hWnd) -{ - int i; - RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)]; - - if (!SDL_RAWINPUT_inited) { - return true; - } - - for (i = 0; i < SDL_arraysize(subscribed_devices); i++) { - rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP; - rid[i].usUsage = subscribed_devices[i]; - rid[i].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; // Receive messages when in background, including device add/remove - rid[i].hwndTarget = hWnd; - } - - if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) { - return SDL_SetError("Couldn't register for raw input events"); - } - return true; -} - -bool RAWINPUT_UnregisterNotifications(void) -{ - int i; - RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)]; - - if (!SDL_RAWINPUT_inited) { - return true; - } - - for (i = 0; i < SDL_arraysize(subscribed_devices); i++) { - rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP; - rid[i].usUsage = subscribed_devices[i]; - rid[i].dwFlags = RIDEV_REMOVE; - rid[i].hwndTarget = NULL; - } - - if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) { - return SDL_SetError("Couldn't unregister for raw input events"); - } - return true; -} - -LRESULT CALLBACK -RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - LRESULT result = -1; - - if (SDL_RAWINPUT_inited) { - SDL_LockJoysticks(); - - 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, true); - } - break; - } - default: - break; - } - } - result = 0; - break; - - 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_Joystick *joystick = device->joystick; - if (joystick) { - RAWINPUT_HandleStatePacket(joystick, raw_input->data.hid.bRawData, raw_input->data.hid.dwSizeHid); - } - } - } - } - result = 0; - break; - } - - SDL_UnlockJoysticks(); - } - - if (result >= 0) { - return result; - } - return CallWindowProc(DefWindowProc, hWnd, msg, wParam, lParam); -} - -static void RAWINPUT_JoystickQuit(void) -{ - if (!SDL_RAWINPUT_inited) { - return; - } - - RAWINPUT_RemoveDevices(); - - WIN_UnloadHIDDLL(); - - SDL_RAWINPUT_inited = false; -} - -static bool RAWINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) -{ - return false; -} - -SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver = { - RAWINPUT_JoystickInit, - RAWINPUT_JoystickGetCount, - RAWINPUT_JoystickDetect, - RAWINPUT_JoystickIsDevicePresent, - RAWINPUT_JoystickGetDeviceName, - RAWINPUT_JoystickGetDevicePath, - RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot, - RAWINPUT_JoystickGetDevicePlayerIndex, - RAWINPUT_JoystickSetDevicePlayerIndex, - RAWINPUT_JoystickGetDeviceGUID, - RAWINPUT_JoystickGetDeviceInstanceID, - RAWINPUT_JoystickOpen, - RAWINPUT_JoystickRumble, - RAWINPUT_JoystickRumbleTriggers, - RAWINPUT_JoystickSetLED, - RAWINPUT_JoystickSendEffect, - RAWINPUT_JoystickSetSensorsEnabled, - RAWINPUT_JoystickUpdate, - RAWINPUT_JoystickClose, - RAWINPUT_JoystickQuit, - RAWINPUT_JoystickGetGamepadMapping -}; - -#endif // SDL_JOYSTICK_RAWINPUT -- cgit v1.2.3