Path: blob/master/thirdparty/sdl/joystick/windows/SDL_rawinputjoystick.c
9905 views
/*1Simple DirectMedia Layer2Copyright (C) 2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.1920*/21/*22RAWINPUT Joystick API for better handling XInput-capable devices on Windows.2324XInput is limited to 4 devices.25Windows.Gaming.Input does not get inputs from XBox One controllers when not in the foreground.26DirectInput does not get inputs from XBox One controllers when not in the foreground, nor rumble or accurate triggers.27RawInput does not get rumble or accurate triggers.2829So, combine them as best we can!30*/31#include "SDL_internal.h"3233#ifdef SDL_JOYSTICK_RAWINPUT3435#include "../usb_ids.h"36#include "../SDL_sysjoystick.h"37#include "../../core/windows/SDL_windows.h"38#include "../../core/windows/SDL_hid.h"39#include "../hidapi/SDL_hidapijoystick_c.h"4041/* SDL_JOYSTICK_RAWINPUT_XINPUT is disabled because using XInput at the same time as42raw input will turn off the Xbox Series X controller when it is connected via the43Xbox One Wireless Adapter.44*/45#ifdef HAVE_XINPUT_H46#define SDL_JOYSTICK_RAWINPUT_XINPUT47#endif48#ifdef HAVE_WINDOWS_GAMING_INPUT_H49#define SDL_JOYSTICK_RAWINPUT_WGI50#endif5152#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT53#include "../../core/windows/SDL_xinput.h"54#endif5556#ifdef SDL_JOYSTICK_RAWINPUT_WGI57#include "../../core/windows/SDL_windows.h"58typedef struct WindowsGamingInputGamepadState WindowsGamingInputGamepadState;59#define GamepadButtons_GUIDE 0x4000000060#define COBJMACROS61#include "windows.gaming.input.h"62#include <roapi.h>63#endif6465#if defined(SDL_JOYSTICK_RAWINPUT_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT_WGI)66#define SDL_JOYSTICK_RAWINPUT_MATCHING67#define SDL_JOYSTICK_RAWINPUT_MATCH_AXES68#define SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS69#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS70#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 6 // stick + trigger axes71#else72#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 4 // stick axes73#endif74#endif7576#if 077#define DEBUG_RAWINPUT78#endif7980#ifndef RIDEV_EXINPUTSINK81#define RIDEV_EXINPUTSINK 0x0000100082#define RIDEV_DEVNOTIFY 0x0000200083#endif8485#ifndef WM_INPUT_DEVICE_CHANGE86#define WM_INPUT_DEVICE_CHANGE 0x00FE87#endif88#ifndef WM_INPUT89#define WM_INPUT 0x00FF90#endif91#ifndef GIDC_ARRIVAL92#define GIDC_ARRIVAL 193#define GIDC_REMOVAL 294#endif9596extern void WINDOWS_RAWINPUTEnabledChanged(void);97extern void WINDOWS_JoystickDetect(void);9899static bool SDL_RAWINPUT_inited = false;100static bool SDL_RAWINPUT_remote_desktop = false;101static int SDL_RAWINPUT_numjoysticks = 0;102103static void RAWINPUT_JoystickClose(SDL_Joystick *joystick);104105typedef struct SDL_RAWINPUT_Device106{107SDL_AtomicInt refcount;108char *name;109char *path;110Uint16 vendor_id;111Uint16 product_id;112Uint16 version;113SDL_GUID guid;114bool is_xinput;115bool is_xboxone;116int steam_virtual_gamepad_slot;117PHIDP_PREPARSED_DATA preparsed_data;118119HANDLE hDevice;120SDL_Joystick *joystick;121SDL_JoystickID joystick_id;122123struct SDL_RAWINPUT_Device *next;124} SDL_RAWINPUT_Device;125126struct joystick_hwdata127{128bool is_xinput;129bool is_xboxone;130PHIDP_PREPARSED_DATA preparsed_data;131ULONG max_data_length;132HIDP_DATA *data;133USHORT *button_indices;134USHORT *axis_indices;135USHORT *hat_indices;136bool guide_hack;137bool trigger_hack;138USHORT trigger_hack_index;139140#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING141Uint64 match_state; // Lowest 16 bits for button states, higher 24 for 6 4bit axes142Uint64 last_state_packet;143#endif144145#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT146bool xinput_enabled;147bool xinput_correlated;148Uint8 xinput_correlation_id;149Uint8 xinput_correlation_count;150Uint8 xinput_uncorrelate_count;151Uint8 xinput_slot;152#endif153154#ifdef SDL_JOYSTICK_RAWINPUT_WGI155bool wgi_correlated;156Uint8 wgi_correlation_id;157Uint8 wgi_correlation_count;158Uint8 wgi_uncorrelate_count;159WindowsGamingInputGamepadState *wgi_slot;160struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration;161#endif162163bool triggers_rumbling;164165SDL_RAWINPUT_Device *device;166};167typedef struct joystick_hwdata RAWINPUT_DeviceContext;168169SDL_RAWINPUT_Device *SDL_RAWINPUT_devices;170171static const Uint16 subscribed_devices[] = {172USB_USAGE_GENERIC_GAMEPAD,173/* Don't need Joystick for any devices we're handling here (XInput-capable)174USB_USAGE_GENERIC_JOYSTICK,175USB_USAGE_GENERIC_MULTIAXISCONTROLLER,176*/177};178179#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING180181static struct182{183Uint64 last_state_packet;184SDL_Joystick *joystick;185SDL_Joystick *last_joystick;186} guide_button_candidate;187188typedef struct WindowsMatchState189{190#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES191SHORT match_axes[SDL_JOYSTICK_RAWINPUT_MATCH_COUNT];192#endif193#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT194WORD xinput_buttons;195#endif196#ifdef SDL_JOYSTICK_RAWINPUT_WGI197Uint32 wgi_buttons;198#endif199bool any_data;200} WindowsMatchState;201202static void RAWINPUT_FillMatchState(WindowsMatchState *state, Uint64 match_state)203{204#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES205int ii;206#endif207208bool any_axes_data = false;209#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES210/* SHORT state->match_axes[4] = {211(match_state & 0x000F0000) >> 4,212(match_state & 0x00F00000) >> 8,213(match_state & 0x0F000000) >> 12,214(match_state & 0xF0000000) >> 16,215}; */216for (ii = 0; ii < 4; ii++) {217state->match_axes[ii] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4));218any_axes_data |= ((Uint32)(state->match_axes[ii] + 0x1000) > 0x2000); // match_state bit is not 0xF, 0x1, or 0x2219}220#endif // SDL_JOYSTICK_RAWINPUT_MATCH_AXES221#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS222for (; ii < SDL_JOYSTICK_RAWINPUT_MATCH_COUNT; ii++) {223state->match_axes[ii] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4));224any_axes_data |= (state->match_axes[ii] != SDL_MIN_SINT16);225}226#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS227228state->any_data = any_axes_data;229230#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT231// Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less232#define XInputAxesMatch(gamepad) ( \233(Uint32)(gamepad.sThumbLX - state->match_axes[0] + 0x1000) <= 0x2fff && \234(Uint32)(~gamepad.sThumbLY - state->match_axes[1] + 0x1000) <= 0x2fff && \235(Uint32)(gamepad.sThumbRX - state->match_axes[2] + 0x1000) <= 0x2fff && \236(Uint32)(~gamepad.sThumbRY - state->match_axes[3] + 0x1000) <= 0x2fff)237/* Explicit238#define XInputAxesMatch(gamepad) (\239SDL_abs((Sint8)((gamepad.sThumbLX & 0xF000) >> 8) - ((match_state & 0x000F0000) >> 12)) <= 0x10 && \240SDL_abs((Sint8)((~gamepad.sThumbLY & 0xF000) >> 8) - ((match_state & 0x00F00000) >> 16)) <= 0x10 && \241SDL_abs((Sint8)((gamepad.sThumbRX & 0xF000) >> 8) - ((match_state & 0x0F000000) >> 20)) <= 0x10 && \242SDL_abs((Sint8)((~gamepad.sThumbRY & 0xF000) >> 8) - ((match_state & 0xF0000000) >> 24)) <= 0x10) */243244// Can only match trigger values if a single trigger has a value.245#define XInputTriggersMatch(gamepad) ( \246((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \247((gamepad.bLeftTrigger != 0) && (gamepad.bRightTrigger != 0)) || \248((Uint32)((((int)gamepad.bLeftTrigger * 257) - 32768) - state->match_axes[4]) <= 0x2fff) || \249((Uint32)((((int)gamepad.bRightTrigger * 257) - 32768) - state->match_axes[5]) <= 0x2fff))250251state->xinput_buttons =252// Bitwise map .RLDUWVQTS.KYXBA -> YXBA..WVQTKSRLDU253(WORD)(match_state << 12 | (match_state & 0x0780) >> 1 | (match_state & 0x0010) << 1 | (match_state & 0x0040) >> 2 | (match_state & 0x7800) >> 11);254/* Explicit255((match_state & (1<<SDL_GAMEPAD_BUTTON_SOUTH)) ? XINPUT_GAMEPAD_A : 0) |256((match_state & (1<<SDL_GAMEPAD_BUTTON_EAST)) ? XINPUT_GAMEPAD_B : 0) |257((match_state & (1<<SDL_GAMEPAD_BUTTON_WEST)) ? XINPUT_GAMEPAD_X : 0) |258((match_state & (1<<SDL_GAMEPAD_BUTTON_NORTH)) ? XINPUT_GAMEPAD_Y : 0) |259((match_state & (1<<SDL_GAMEPAD_BUTTON_BACK)) ? XINPUT_GAMEPAD_BACK : 0) |260((match_state & (1<<SDL_GAMEPAD_BUTTON_START)) ? XINPUT_GAMEPAD_START : 0) |261((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_STICK)) ? XINPUT_GAMEPAD_LEFT_THUMB : 0) |262((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_STICK)) ? XINPUT_GAMEPAD_RIGHT_THUMB: 0) |263((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) ? XINPUT_GAMEPAD_LEFT_SHOULDER : 0) |264((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) ? XINPUT_GAMEPAD_RIGHT_SHOULDER : 0) |265((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_UP)) ? XINPUT_GAMEPAD_DPAD_UP : 0) |266((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_DOWN)) ? XINPUT_GAMEPAD_DPAD_DOWN : 0) |267((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_LEFT)) ? XINPUT_GAMEPAD_DPAD_LEFT : 0) |268((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) ? XINPUT_GAMEPAD_DPAD_RIGHT : 0);269*/270271if (state->xinput_buttons) {272state->any_data = true;273}274#endif275276#ifdef SDL_JOYSTICK_RAWINPUT_WGI277// Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less278#define WindowsGamingInputAxesMatch(gamepad) ( \279(Uint16)(((Sint16)(gamepad.LeftThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[0] + 0x1000) <= 0x2fff && \280(Uint16)((~(Sint16)(gamepad.LeftThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[1] + 0x1000) <= 0x2fff && \281(Uint16)(((Sint16)(gamepad.RightThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[2] + 0x1000) <= 0x2fff && \282(Uint16)((~(Sint16)(gamepad.RightThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[3] + 0x1000) <= 0x2fff)283284#define WindowsGamingInputTriggersMatch(gamepad) ( \285((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \286((gamepad.LeftTrigger == 0.0f) && (gamepad.RightTrigger == 0.0f)) || \287((Uint16)((((int)(gamepad.LeftTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[4]) <= 0x2fff) || \288((Uint16)((((int)(gamepad.RightTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[5]) <= 0x2fff))289290state->wgi_buttons =291// Bitwise map .RLD UWVQ TS.K YXBA -> ..QT WVRL DUYX BAKS292// RStick/LStick (QT) RShould/LShould (WV) DPad R/L/D/U YXBA bac(K) (S)tart293(match_state & 0x0180) << 5 | (match_state & 0x0600) << 1 | (match_state & 0x7800) >> 5 | (match_state & 0x000F) << 2 | (match_state & 0x0010) >> 3 | (match_state & 0x0040) >> 6;294/* Explicit295((match_state & (1<<SDL_GAMEPAD_BUTTON_SOUTH)) ? GamepadButtons_A : 0) |296((match_state & (1<<SDL_GAMEPAD_BUTTON_EAST)) ? GamepadButtons_B : 0) |297((match_state & (1<<SDL_GAMEPAD_BUTTON_WEST)) ? GamepadButtons_X : 0) |298((match_state & (1<<SDL_GAMEPAD_BUTTON_NORTH)) ? GamepadButtons_Y : 0) |299((match_state & (1<<SDL_GAMEPAD_BUTTON_BACK)) ? GamepadButtons_View : 0) |300((match_state & (1<<SDL_GAMEPAD_BUTTON_START)) ? GamepadButtons_Menu : 0) |301((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_STICK)) ? GamepadButtons_LeftThumbstick : 0) |302((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_STICK)) ? GamepadButtons_RightThumbstick: 0) |303((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) ? GamepadButtons_LeftShoulder: 0) |304((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) ? GamepadButtons_RightShoulder: 0) |305((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_UP)) ? GamepadButtons_DPadUp : 0) |306((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_DOWN)) ? GamepadButtons_DPadDown : 0) |307((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_LEFT)) ? GamepadButtons_DPadLeft : 0) |308((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) ? GamepadButtons_DPadRight : 0); */309310if (state->wgi_buttons) {311state->any_data = true;312}313#endif314}315316#endif // SDL_JOYSTICK_RAWINPUT_MATCHING317318#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT319320static struct321{322XINPUT_STATE state;323XINPUT_BATTERY_INFORMATION_EX battery;324bool connected; // Currently has an active XInput device325bool used; // Is currently mapped to an SDL device326Uint8 correlation_id;327} xinput_state[XUSER_MAX_COUNT];328static bool xinput_device_change = true;329static bool xinput_state_dirty = true;330331static void RAWINPUT_UpdateXInput(void)332{333DWORD user_index;334if (xinput_device_change) {335for (user_index = 0; user_index < XUSER_MAX_COUNT; user_index++) {336XINPUT_CAPABILITIES capabilities;337xinput_state[user_index].connected = (XINPUTGETCAPABILITIES(user_index, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS);338}339xinput_device_change = false;340xinput_state_dirty = true;341}342if (xinput_state_dirty) {343xinput_state_dirty = false;344for (user_index = 0; user_index < SDL_arraysize(xinput_state); ++user_index) {345if (xinput_state[user_index].connected) {346if (XINPUTGETSTATE(user_index, &xinput_state[user_index].state) != ERROR_SUCCESS) {347xinput_state[user_index].connected = false;348}349xinput_state[user_index].battery.BatteryType = BATTERY_TYPE_UNKNOWN;350if (XINPUTGETBATTERYINFORMATION) {351XINPUTGETBATTERYINFORMATION(user_index, BATTERY_DEVTYPE_GAMEPAD, &xinput_state[user_index].battery);352}353}354}355}356}357358static void RAWINPUT_MarkXInputSlotUsed(Uint8 xinput_slot)359{360if (xinput_slot != XUSER_INDEX_ANY) {361xinput_state[xinput_slot].used = true;362}363}364365static void RAWINPUT_MarkXInputSlotFree(Uint8 xinput_slot)366{367if (xinput_slot != XUSER_INDEX_ANY) {368xinput_state[xinput_slot].used = false;369}370}371static bool RAWINPUT_MissingXInputSlot(void)372{373int ii;374for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) {375if (xinput_state[ii].connected && !xinput_state[ii].used) {376return true;377}378}379return false;380}381382static bool RAWINPUT_XInputSlotMatches(const WindowsMatchState *state, Uint8 slot_idx)383{384if (xinput_state[slot_idx].connected) {385WORD xinput_buttons = xinput_state[slot_idx].state.Gamepad.wButtons;386if ((xinput_buttons & ~XINPUT_GAMEPAD_GUIDE) == state->xinput_buttons387#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES388&& XInputAxesMatch(xinput_state[slot_idx].state.Gamepad)389#endif390#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS391&& XInputTriggersMatch(xinput_state[slot_idx].state.Gamepad)392#endif393) {394return true;395}396}397return false;398}399400static bool RAWINPUT_GuessXInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, Uint8 *slot_idx)401{402Uint8 user_index;403int match_count;404405/* If there is only one available slot, let's use that406* That will be right most of the time, and uncorrelation will fix any bad guesses407*/408match_count = 0;409for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) {410if (xinput_state[user_index].connected && !xinput_state[user_index].used) {411*slot_idx = user_index;412++match_count;413}414}415if (match_count == 1) {416*correlation_id = ++xinput_state[*slot_idx].correlation_id;417return true;418}419420*slot_idx = 0;421422match_count = 0;423for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) {424if (!xinput_state[user_index].used && RAWINPUT_XInputSlotMatches(state, user_index)) {425++match_count;426*slot_idx = user_index;427// Incrementing correlation_id for any match, as negative evidence for others being correlated428*correlation_id = ++xinput_state[user_index].correlation_id;429}430}431/* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched.432Note that we're still invalidating *other* potential correlations if we have more than one match or we have no433data. */434if (match_count == 1 && state->any_data) {435return true;436}437return false;438}439440#endif // SDL_JOYSTICK_RAWINPUT_XINPUT441442#ifdef SDL_JOYSTICK_RAWINPUT_WGI443444typedef struct WindowsGamingInputGamepadState445{446__x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad;447struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state;448RAWINPUT_DeviceContext *correlated_context;449bool used; // Is currently mapped to an SDL device450bool connected; // Just used during update to track disconnected451Uint8 correlation_id;452} WindowsGamingInputGamepadState;453454static struct455{456WindowsGamingInputGamepadState **per_gamepad;457int per_gamepad_count;458bool initialized;459bool dirty;460bool need_device_list_update;461int ref_count;462__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics;463EventRegistrationToken gamepad_added_token;464EventRegistrationToken gamepad_removed_token;465} wgi_state;466467typedef struct GamepadDelegate468{469__FIEventHandler_1_Windows__CGaming__CInput__CGamepad iface;470SDL_AtomicInt refcount;471} GamepadDelegate;472473static const IID IID_IEventHandler_Gamepad = { 0x8a7639ee, 0x624a, 0x501a, { 0xbb, 0x53, 0x56, 0x2d, 0x1e, 0xc1, 0x1b, 0x52 } };474475static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, REFIID riid, void **ppvObject)476{477if (!ppvObject) {478return E_INVALIDARG;479}480481*ppvObject = NULL;482if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID_IEventHandler_Gamepad)) {483*ppvObject = This;484__FIEventHandler_1_Windows__CGaming__CInput__CGamepad_AddRef(This);485return S_OK;486} else if (WIN_IsEqualIID(riid, &IID_IMarshal)) {487// This seems complicated. Let's hope it doesn't happen.488return E_OUTOFMEMORY;489} else {490return E_NOINTERFACE;491}492}493494static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_AddRef(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This)495{496GamepadDelegate *self = (GamepadDelegate *)This;497return SDL_AddAtomicInt(&self->refcount, 1) + 1UL;498}499500static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_Release(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This)501{502GamepadDelegate *self = (GamepadDelegate *)This;503int rc = SDL_AddAtomicInt(&self->refcount, -1) - 1;504// Should never free the static delegate objects505SDL_assert(rc > 0);506return rc;507}508509static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e)510{511wgi_state.need_device_list_update = true;512return S_OK;513}514515static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeRemoved(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e)516{517wgi_state.need_device_list_update = true;518return S_OK;519}520521#ifdef _MSC_VER522#pragma warning(push)523#pragma warning(disable : 4028) // formal parameter 3 different from declaration, when using older buggy WGI headers524#pragma warning(disable : 4113) // X differs in parameter lists from Y, when using older buggy WGI headers525#endif526527static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_added_vtbl = {528IEventHandler_CGamepadVtbl_QueryInterface,529IEventHandler_CGamepadVtbl_AddRef,530IEventHandler_CGamepadVtbl_Release,531IEventHandler_CGamepadVtbl_InvokeAdded532};533static GamepadDelegate gamepad_added = {534{ &gamepad_added_vtbl },535{ 1 }536};537538static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_removed_vtbl = {539IEventHandler_CGamepadVtbl_QueryInterface,540IEventHandler_CGamepadVtbl_AddRef,541IEventHandler_CGamepadVtbl_Release,542IEventHandler_CGamepadVtbl_InvokeRemoved543};544static GamepadDelegate gamepad_removed = {545{ &gamepad_removed_vtbl },546{ 1 }547};548549#ifdef _MSC_VER550#pragma warning(pop)551#endif552553static void RAWINPUT_MarkWindowsGamingInputSlotUsed(WindowsGamingInputGamepadState *wgi_slot, RAWINPUT_DeviceContext *ctx)554{555wgi_slot->used = true;556wgi_slot->correlated_context = ctx;557}558559static void RAWINPUT_MarkWindowsGamingInputSlotFree(WindowsGamingInputGamepadState *wgi_slot)560{561wgi_slot->used = false;562wgi_slot->correlated_context = NULL;563}564565static bool RAWINPUT_MissingWindowsGamingInputSlot(void)566{567int ii;568for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {569if (!wgi_state.per_gamepad[ii]->used) {570return true;571}572}573return false;574}575576static bool RAWINPUT_UpdateWindowsGamingInput(void)577{578int ii;579if (!wgi_state.gamepad_statics) {580return true;581}582583if (!wgi_state.dirty) {584return true;585}586587wgi_state.dirty = false;588589if (wgi_state.need_device_list_update) {590HRESULT hr;591__FIVectorView_1_Windows__CGaming__CInput__CGamepad *gamepads;592wgi_state.need_device_list_update = false;593for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {594wgi_state.per_gamepad[ii]->connected = false;595}596597hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_get_Gamepads(wgi_state.gamepad_statics, &gamepads);598if (SUCCEEDED(hr)) {599unsigned int num_gamepads;600601hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_get_Size(gamepads, &num_gamepads);602if (SUCCEEDED(hr)) {603unsigned int i;604for (i = 0; i < num_gamepads; ++i) {605__x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad;606607hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_GetAt(gamepads, i, &gamepad);608if (SUCCEEDED(hr)) {609bool found = false;610int jj;611for (jj = 0; jj < wgi_state.per_gamepad_count; jj++) {612if (wgi_state.per_gamepad[jj]->gamepad == gamepad) {613found = true;614wgi_state.per_gamepad[jj]->connected = true;615break;616}617}618if (!found) {619// New device, add it620WindowsGamingInputGamepadState *gamepad_state;621WindowsGamingInputGamepadState **new_per_gamepad;622gamepad_state = SDL_calloc(1, sizeof(*gamepad_state));623if (!gamepad_state) {624return false;625}626new_per_gamepad = SDL_realloc(wgi_state.per_gamepad, sizeof(wgi_state.per_gamepad[0]) * (wgi_state.per_gamepad_count + 1));627if (!new_per_gamepad) {628SDL_free(gamepad_state);629return false;630}631wgi_state.per_gamepad = new_per_gamepad;632wgi_state.per_gamepad_count++;633wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1] = gamepad_state;634gamepad_state->gamepad = gamepad;635gamepad_state->connected = true;636} else {637// Already tracked638__x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad);639}640}641}642for (ii = wgi_state.per_gamepad_count - 1; ii >= 0; ii--) {643WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii];644if (!gamepad_state->connected) {645// Device missing, must be disconnected646if (gamepad_state->correlated_context) {647gamepad_state->correlated_context->wgi_correlated = false;648gamepad_state->correlated_context->wgi_slot = NULL;649}650__x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad_state->gamepad);651SDL_free(gamepad_state);652wgi_state.per_gamepad[ii] = wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1];653--wgi_state.per_gamepad_count;654}655}656}657__FIVectorView_1_Windows__CGaming__CInput__CGamepad_Release(gamepads);658}659} // need_device_list_update660661for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {662HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(wgi_state.per_gamepad[ii]->gamepad, &wgi_state.per_gamepad[ii]->state);663if (!SUCCEEDED(hr)) {664wgi_state.per_gamepad[ii]->connected = false; // Not used by anything, currently665}666}667return true;668}669static void RAWINPUT_InitWindowsGamingInput(RAWINPUT_DeviceContext *ctx)670{671if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) {672return;673}674675wgi_state.ref_count++;676if (!wgi_state.initialized) {677static const IID SDL_IID_IGamepadStatics = { 0x8BBCE529, 0xD49C, 0x39E9, { 0x95, 0x60, 0xE4, 0x7D, 0xDE, 0x96, 0xB7, 0xC8 } };678HRESULT hr;679680if (FAILED(WIN_RoInitialize())) {681return;682}683wgi_state.initialized = true;684wgi_state.dirty = true;685686{687typedef HRESULT(WINAPI * WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER * hstringHeader, HSTRING * string);688typedef HRESULT(WINAPI * RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory);689690WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference");691RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory");692if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc) {693PCWSTR pNamespace = L"Windows.Gaming.Input.Gamepad";694HSTRING_HEADER hNamespaceStringHeader;695HSTRING hNamespaceString;696697hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString);698if (SUCCEEDED(hr)) {699RoGetActivationFactoryFunc(hNamespaceString, &SDL_IID_IGamepadStatics, (void **)&wgi_state.gamepad_statics);700}701702if (wgi_state.gamepad_statics) {703wgi_state.need_device_list_update = true;704705hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadAdded(wgi_state.gamepad_statics, &gamepad_added.iface, &wgi_state.gamepad_added_token);706if (!SUCCEEDED(hr)) {707SDL_SetError("add_GamepadAdded() failed: 0x%lx", hr);708}709710hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadRemoved(wgi_state.gamepad_statics, &gamepad_removed.iface, &wgi_state.gamepad_removed_token);711if (!SUCCEEDED(hr)) {712SDL_SetError("add_GamepadRemoved() failed: 0x%lx", hr);713}714}715}716}717}718}719720static bool RAWINPUT_WindowsGamingInputSlotMatches(const WindowsMatchState *state, WindowsGamingInputGamepadState *slot, bool xinput_correlated)721{722Uint32 wgi_buttons = slot->state.Buttons;723if ((wgi_buttons & 0x3FFF) == state->wgi_buttons724#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES725&& WindowsGamingInputAxesMatch(slot->state)726#endif727#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS728// Don't try to match WGI triggers if getting values from XInput729&& (xinput_correlated || WindowsGamingInputTriggersMatch(slot->state))730#endif731) {732return true;733}734return false;735}736737static bool RAWINPUT_GuessWindowsGamingInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, WindowsGamingInputGamepadState **slot, bool xinput_correlated)738{739int match_count, user_index;740WindowsGamingInputGamepadState *gamepad_state = NULL;741742/* If there is only one available slot, let's use that743* That will be right most of the time, and uncorrelation will fix any bad guesses744*/745match_count = 0;746for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) {747gamepad_state = wgi_state.per_gamepad[user_index];748if (gamepad_state->connected && !gamepad_state->used) {749*slot = gamepad_state;750++match_count;751}752}753if (match_count == 1) {754*correlation_id = ++gamepad_state->correlation_id;755return true;756}757758match_count = 0;759for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) {760gamepad_state = wgi_state.per_gamepad[user_index];761if (RAWINPUT_WindowsGamingInputSlotMatches(state, gamepad_state, xinput_correlated)) {762++match_count;763*slot = gamepad_state;764// Incrementing correlation_id for any match, as negative evidence for others being correlated765*correlation_id = ++gamepad_state->correlation_id;766}767}768/* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched.769Note that we're still invalidating *other* potential correlations if we have more than one match or we have no770data. */771if (match_count == 1 && state->any_data) {772return true;773}774return false;775}776777static void RAWINPUT_QuitWindowsGamingInput(RAWINPUT_DeviceContext *ctx)778{779--wgi_state.ref_count;780if (!wgi_state.ref_count && wgi_state.initialized) {781int ii;782for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {783__x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(wgi_state.per_gamepad[ii]->gamepad);784}785if (wgi_state.per_gamepad) {786SDL_free(wgi_state.per_gamepad);787wgi_state.per_gamepad = NULL;788}789wgi_state.per_gamepad_count = 0;790if (wgi_state.gamepad_statics) {791__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_remove_GamepadAdded(wgi_state.gamepad_statics, wgi_state.gamepad_added_token);792__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_remove_GamepadRemoved(wgi_state.gamepad_statics, wgi_state.gamepad_removed_token);793__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi_state.gamepad_statics);794wgi_state.gamepad_statics = NULL;795}796WIN_RoUninitialize();797wgi_state.initialized = false;798}799}800801#endif // SDL_JOYSTICK_RAWINPUT_WGI802803static SDL_RAWINPUT_Device *RAWINPUT_AcquireDevice(SDL_RAWINPUT_Device *device)804{805SDL_AtomicIncRef(&device->refcount);806return device;807}808809static void RAWINPUT_ReleaseDevice(SDL_RAWINPUT_Device *device)810{811#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT812if (device->joystick) {813RAWINPUT_DeviceContext *ctx = device->joystick->hwdata;814815if (ctx->xinput_enabled && ctx->xinput_correlated) {816RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot);817ctx->xinput_correlated = false;818}819}820#endif // SDL_JOYSTICK_RAWINPUT_XINPUT821822if (SDL_AtomicDecRef(&device->refcount)) {823SDL_free(device->preparsed_data);824SDL_free(device->name);825SDL_free(device->path);826SDL_free(device);827}828}829830static SDL_RAWINPUT_Device *RAWINPUT_DeviceFromHandle(HANDLE hDevice)831{832SDL_RAWINPUT_Device *curr;833834for (curr = SDL_RAWINPUT_devices; curr; curr = curr->next) {835if (curr->hDevice == hDevice) {836return curr;837}838}839return NULL;840}841842static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path)843{844int slot = -1;845846// The format for the raw input device path is documented here:847// https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices848if (vendor_id == USB_VENDOR_VALVE &&849product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {850(void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot);851}852return slot;853}854855static void RAWINPUT_AddDevice(HANDLE hDevice)856{857#define CHECK(expression) \858{ \859if (!(expression)) \860goto err; \861}862SDL_RAWINPUT_Device *device = NULL;863SDL_RAWINPUT_Device *curr, *last;864RID_DEVICE_INFO rdi;865UINT size;866char dev_name[MAX_PATH] = { 0 };867HANDLE hFile = INVALID_HANDLE_VALUE;868869// Make sure we're not trying to add the same device twice870if (RAWINPUT_DeviceFromHandle(hDevice)) {871return;872}873874// Figure out what kind of device it is875size = sizeof(rdi);876SDL_zero(rdi);877CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICEINFO, &rdi, &size) != (UINT)-1);878CHECK(rdi.dwType == RIM_TYPEHID);879880// Get the device "name" (HID Path)881size = SDL_arraysize(dev_name);882CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, dev_name, &size) != (UINT)-1);883// Only take XInput-capable devices884CHECK(SDL_strstr(dev_name, "IG_") != NULL);885CHECK(!SDL_ShouldIgnoreJoystick((Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, ""));886CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_RAWINPUT_JoystickDriver, (Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, ""));887888device = (SDL_RAWINPUT_Device *)SDL_calloc(1, sizeof(SDL_RAWINPUT_Device));889CHECK(device);890device->hDevice = hDevice;891device->vendor_id = (Uint16)rdi.hid.dwVendorId;892device->product_id = (Uint16)rdi.hid.dwProductId;893device->version = (Uint16)rdi.hid.dwVersionNumber;894device->is_xinput = true;895device->is_xboxone = SDL_IsJoystickXboxOne(device->vendor_id, device->product_id);896device->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(device->vendor_id, device->product_id, dev_name);897898// Get HID Top-Level Collection Preparsed Data899size = 0;900CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, NULL, &size) != (UINT)-1);901device->preparsed_data = (PHIDP_PREPARSED_DATA)SDL_calloc(size, sizeof(BYTE));902CHECK(device->preparsed_data);903CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, device->preparsed_data, &size) != (UINT)-1);904905hFile = CreateFileA(dev_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);906CHECK(hFile != INVALID_HANDLE_VALUE);907908{909char *manufacturer_string = NULL;910char *product_string = NULL;911WCHAR string[128];912913string[0] = 0;914if (SDL_HidD_GetManufacturerString(hFile, string, sizeof(string))) {915manufacturer_string = WIN_StringToUTF8W(string);916}917string[0] = 0;918if (SDL_HidD_GetProductString(hFile, string, sizeof(string))) {919product_string = WIN_StringToUTF8W(string);920}921922device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string);923device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, manufacturer_string, product_string, 'r', 0);924925if (manufacturer_string) {926SDL_free(manufacturer_string);927}928if (product_string) {929SDL_free(product_string);930}931}932933device->path = SDL_strdup(dev_name);934935CloseHandle(hFile);936hFile = INVALID_HANDLE_VALUE;937938device->joystick_id = SDL_GetNextObjectID();939940#ifdef DEBUG_RAWINPUT941SDL_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);942#endif943944// Add it to the list945RAWINPUT_AcquireDevice(device);946for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) {947}948if (last) {949last->next = device;950} else {951SDL_RAWINPUT_devices = device;952}953954++SDL_RAWINPUT_numjoysticks;955956SDL_PrivateJoystickAdded(device->joystick_id);957958return;959960err:961if (hFile != INVALID_HANDLE_VALUE) {962CloseHandle(hFile);963}964if (device) {965if (device->name) {966SDL_free(device->name);967}968if (device->path) {969SDL_free(device->path);970}971SDL_free(device);972}973#undef CHECK974}975976static void RAWINPUT_DelDevice(SDL_RAWINPUT_Device *device, bool send_event)977{978SDL_RAWINPUT_Device *curr, *last;979for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) {980if (curr == device) {981if (last) {982last->next = curr->next;983} else {984SDL_RAWINPUT_devices = curr->next;985}986--SDL_RAWINPUT_numjoysticks;987988SDL_PrivateJoystickRemoved(device->joystick_id);989990#ifdef DEBUG_RAWINPUT991SDL_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);992#endif993RAWINPUT_ReleaseDevice(device);994return;995}996}997}998999static void RAWINPUT_DetectDevices(void)1000{1001UINT device_count = 0;10021003if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) != -1) && device_count > 0) {1004PRAWINPUTDEVICELIST devices = NULL;1005UINT i;10061007devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count);1008if (devices) {1009device_count = GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST));1010if (device_count != (UINT)-1) {1011for (i = 0; i < device_count; ++i) {1012RAWINPUT_AddDevice(devices[i].hDevice);1013}1014}1015SDL_free(devices);1016}1017}1018}10191020static void RAWINPUT_RemoveDevices(void)1021{1022while (SDL_RAWINPUT_devices) {1023RAWINPUT_DelDevice(SDL_RAWINPUT_devices, false);1024}1025SDL_assert(SDL_RAWINPUT_numjoysticks == 0);1026}10271028static bool RAWINPUT_JoystickInit(void)1029{1030SDL_assert(!SDL_RAWINPUT_inited);10311032if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, false)) {1033return true;1034}10351036if (!WIN_IsWindowsVistaOrGreater()) {1037// According to bug 6400, this doesn't work on Windows XP1038return false;1039}10401041if (!WIN_LoadHIDDLL()) {1042return false;1043}10441045SDL_RAWINPUT_inited = true;10461047RAWINPUT_DetectDevices();10481049return true;1050}10511052static int RAWINPUT_JoystickGetCount(void)1053{1054return SDL_RAWINPUT_numjoysticks;1055}10561057bool RAWINPUT_IsEnabled(void)1058{1059return SDL_RAWINPUT_inited && !SDL_RAWINPUT_remote_desktop;1060}10611062static void RAWINPUT_PostUpdate(void)1063{1064#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING1065bool unmapped_guide_pressed = false;10661067#ifdef SDL_JOYSTICK_RAWINPUT_WGI1068if (!wgi_state.dirty) {1069int ii;1070for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {1071WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii];1072if (!gamepad_state->used && (gamepad_state->state.Buttons & GamepadButtons_GUIDE)) {1073unmapped_guide_pressed = true;1074break;1075}1076}1077}1078wgi_state.dirty = true;1079#endif10801081#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT1082if (!xinput_state_dirty) {1083int ii;1084for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) {1085if (xinput_state[ii].connected && !xinput_state[ii].used && (xinput_state[ii].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE)) {1086unmapped_guide_pressed = true;1087break;1088}1089}1090}1091xinput_state_dirty = true;1092#endif10931094if (unmapped_guide_pressed) {1095if (guide_button_candidate.joystick && !guide_button_candidate.last_joystick) {1096SDL_Joystick *joystick = guide_button_candidate.joystick;1097RAWINPUT_DeviceContext *ctx = joystick->hwdata;1098if (ctx->guide_hack) {1099int guide_button = joystick->nbuttons - 1;11001101SDL_SendJoystickButton(SDL_GetTicksNS(), guide_button_candidate.joystick, (Uint8)guide_button, true);1102}1103guide_button_candidate.last_joystick = guide_button_candidate.joystick;1104}1105} else if (guide_button_candidate.last_joystick) {1106SDL_Joystick *joystick = guide_button_candidate.last_joystick;1107RAWINPUT_DeviceContext *ctx = joystick->hwdata;1108if (ctx->guide_hack) {1109int guide_button = joystick->nbuttons - 1;11101111SDL_SendJoystickButton(SDL_GetTicksNS(), joystick, (Uint8)guide_button, false);1112}1113guide_button_candidate.last_joystick = NULL;1114}1115guide_button_candidate.joystick = NULL;11161117#endif // SDL_JOYSTICK_RAWINPUT_MATCHING1118}11191120static void RAWINPUT_JoystickDetect(void)1121{1122bool remote_desktop;11231124if (!SDL_RAWINPUT_inited) {1125return;1126}11271128remote_desktop = GetSystemMetrics(SM_REMOTESESSION) ? true : false;1129if (remote_desktop != SDL_RAWINPUT_remote_desktop) {1130SDL_RAWINPUT_remote_desktop = remote_desktop;11311132WINDOWS_RAWINPUTEnabledChanged();11331134if (remote_desktop) {1135RAWINPUT_RemoveDevices();1136WINDOWS_JoystickDetect();1137} else {1138WINDOWS_JoystickDetect();1139RAWINPUT_DetectDevices();1140}1141}1142RAWINPUT_PostUpdate();1143}11441145static bool RAWINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)1146{1147SDL_RAWINPUT_Device *device;11481149// If we're being asked about a device, that means another API just detected one, so rescan1150#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT1151xinput_device_change = true;1152#endif11531154device = SDL_RAWINPUT_devices;1155while (device) {1156if (vendor_id == device->vendor_id && product_id == device->product_id) {1157return true;1158}11591160/* The Xbox 360 wireless controller shows up as product 0 in WGI.1161Try to match it to a Raw Input device via name or known product ID. */1162if (vendor_id == device->vendor_id && product_id == 0 &&1163((name && SDL_strstr(device->name, name) != NULL) ||1164(device->vendor_id == USB_VENDOR_MICROSOFT &&1165device->product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER))) {1166return true;1167}11681169// The Xbox One controller shows up as a hardcoded raw input VID/PID1170if (name && SDL_strcmp(name, "Xbox One Game Controller") == 0 &&1171device->vendor_id == USB_VENDOR_MICROSOFT &&1172device->product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) {1173return true;1174}11751176device = device->next;1177}1178return false;1179}11801181static SDL_RAWINPUT_Device *RAWINPUT_GetDeviceByIndex(int device_index)1182{1183SDL_RAWINPUT_Device *device = SDL_RAWINPUT_devices;1184while (device) {1185if (device_index == 0) {1186break;1187}1188--device_index;1189device = device->next;1190}1191return device;1192}11931194static const char *RAWINPUT_JoystickGetDeviceName(int device_index)1195{1196return RAWINPUT_GetDeviceByIndex(device_index)->name;1197}11981199static const char *RAWINPUT_JoystickGetDevicePath(int device_index)1200{1201return RAWINPUT_GetDeviceByIndex(device_index)->path;1202}12031204static int RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)1205{1206return RAWINPUT_GetDeviceByIndex(device_index)->steam_virtual_gamepad_slot;1207}12081209static int RAWINPUT_JoystickGetDevicePlayerIndex(int device_index)1210{1211return false;1212}12131214static void RAWINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index)1215{1216}12171218static SDL_GUID RAWINPUT_JoystickGetDeviceGUID(int device_index)1219{1220return RAWINPUT_GetDeviceByIndex(device_index)->guid;1221}12221223static SDL_JoystickID RAWINPUT_JoystickGetDeviceInstanceID(int device_index)1224{1225return RAWINPUT_GetDeviceByIndex(device_index)->joystick_id;1226}12271228static int SDLCALL RAWINPUT_SortValueCaps(const void *A, const void *B)1229{1230HIDP_VALUE_CAPS *capsA = (HIDP_VALUE_CAPS *)A;1231HIDP_VALUE_CAPS *capsB = (HIDP_VALUE_CAPS *)B;12321233// Sort by Usage for single values, or UsageMax for range of values1234return (int)capsA->NotRange.Usage - capsB->NotRange.Usage;1235}12361237static bool RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)1238{1239SDL_RAWINPUT_Device *device = RAWINPUT_GetDeviceByIndex(device_index);1240RAWINPUT_DeviceContext *ctx;1241HIDP_CAPS caps;1242HIDP_BUTTON_CAPS *button_caps;1243HIDP_VALUE_CAPS *value_caps;1244ULONG i;12451246ctx = (RAWINPUT_DeviceContext *)SDL_calloc(1, sizeof(RAWINPUT_DeviceContext));1247if (!ctx) {1248return false;1249}1250joystick->hwdata = ctx;12511252ctx->device = RAWINPUT_AcquireDevice(device);1253device->joystick = joystick;12541255if (device->is_xinput) {1256// We'll try to get guide button and trigger axes from XInput1257#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT1258xinput_device_change = true;1259ctx->xinput_enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, true);1260if (ctx->xinput_enabled && (!WIN_LoadXInputDLL() || !XINPUTGETSTATE)) {1261ctx->xinput_enabled = false;1262}1263ctx->xinput_slot = XUSER_INDEX_ANY;1264#endif1265#ifdef SDL_JOYSTICK_RAWINPUT_WGI1266RAWINPUT_InitWindowsGamingInput(ctx);1267#endif1268}12691270ctx->is_xinput = device->is_xinput;1271ctx->is_xboxone = device->is_xboxone;1272#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING1273ctx->match_state = 0x0000008800000000ULL; // Trigger axes at rest1274#endif1275ctx->preparsed_data = device->preparsed_data;1276ctx->max_data_length = SDL_HidP_MaxDataListLength(HidP_Input, ctx->preparsed_data);1277ctx->data = (HIDP_DATA *)SDL_malloc(ctx->max_data_length * sizeof(*ctx->data));1278if (!ctx->data) {1279RAWINPUT_JoystickClose(joystick);1280return false;1281}12821283if (SDL_HidP_GetCaps(ctx->preparsed_data, &caps) != HIDP_STATUS_SUCCESS) {1284RAWINPUT_JoystickClose(joystick);1285return SDL_SetError("Couldn't get device capabilities");1286}12871288button_caps = SDL_stack_alloc(HIDP_BUTTON_CAPS, caps.NumberInputButtonCaps);1289if (SDL_HidP_GetButtonCaps(HidP_Input, button_caps, &caps.NumberInputButtonCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) {1290RAWINPUT_JoystickClose(joystick);1291return SDL_SetError("Couldn't get device button capabilities");1292}12931294value_caps = SDL_stack_alloc(HIDP_VALUE_CAPS, caps.NumberInputValueCaps);1295if (SDL_HidP_GetValueCaps(HidP_Input, value_caps, &caps.NumberInputValueCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) {1296RAWINPUT_JoystickClose(joystick);1297SDL_stack_free(button_caps);1298return SDL_SetError("Couldn't get device value capabilities");1299}13001301// Sort the axes by usage, so X comes before Y, etc.1302SDL_qsort(value_caps, caps.NumberInputValueCaps, sizeof(*value_caps), RAWINPUT_SortValueCaps);13031304for (i = 0; i < caps.NumberInputButtonCaps; ++i) {1305HIDP_BUTTON_CAPS *cap = &button_caps[i];13061307if (cap->UsagePage == USB_USAGEPAGE_BUTTON) {1308int count;13091310if (cap->IsRange) {1311count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin);1312} else {1313count = 1;1314}13151316joystick->nbuttons += count;1317}1318}13191320if (joystick->nbuttons > 0) {1321int button_index = 0;13221323ctx->button_indices = (USHORT *)SDL_malloc(joystick->nbuttons * sizeof(*ctx->button_indices));1324if (!ctx->button_indices) {1325RAWINPUT_JoystickClose(joystick);1326SDL_stack_free(value_caps);1327SDL_stack_free(button_caps);1328return false;1329}13301331for (i = 0; i < caps.NumberInputButtonCaps; ++i) {1332HIDP_BUTTON_CAPS *cap = &button_caps[i];13331334if (cap->UsagePage == USB_USAGEPAGE_BUTTON) {1335if (cap->IsRange) {1336int j, count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin);13371338for (j = 0; j < count; ++j) {1339ctx->button_indices[button_index++] = (USHORT)(cap->Range.DataIndexMin + j);1340}1341} else {1342ctx->button_indices[button_index++] = cap->NotRange.DataIndex;1343}1344}1345}1346}1347if (ctx->is_xinput && joystick->nbuttons == 10) {1348ctx->guide_hack = true;1349joystick->nbuttons += 1;1350}13511352SDL_stack_free(button_caps);13531354for (i = 0; i < caps.NumberInputValueCaps; ++i) {1355HIDP_VALUE_CAPS *cap = &value_caps[i];13561357if (cap->IsRange) {1358continue;1359}13601361if (ctx->trigger_hack && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) {1362continue;1363}13641365if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) {1366joystick->nhats += 1;1367continue;1368}13691370if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) {1371continue;1372}13731374joystick->naxes += 1;1375}13761377if (joystick->naxes > 0) {1378int axis_index = 0;13791380ctx->axis_indices = (USHORT *)SDL_malloc(joystick->naxes * sizeof(*ctx->axis_indices));1381if (!ctx->axis_indices) {1382RAWINPUT_JoystickClose(joystick);1383SDL_stack_free(value_caps);1384return false;1385}13861387for (i = 0; i < caps.NumberInputValueCaps; ++i) {1388HIDP_VALUE_CAPS *cap = &value_caps[i];13891390if (cap->IsRange) {1391continue;1392}13931394if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) {1395continue;1396}13971398if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) {1399ctx->trigger_hack = true;1400ctx->trigger_hack_index = cap->NotRange.DataIndex;1401continue;1402}14031404ctx->axis_indices[axis_index++] = cap->NotRange.DataIndex;1405}1406}1407if (ctx->trigger_hack) {1408joystick->naxes += 2;1409}14101411if (joystick->nhats > 0) {1412int hat_index = 0;14131414ctx->hat_indices = (USHORT *)SDL_malloc(joystick->nhats * sizeof(*ctx->hat_indices));1415if (!ctx->hat_indices) {1416RAWINPUT_JoystickClose(joystick);1417SDL_stack_free(value_caps);1418return false;1419}14201421for (i = 0; i < caps.NumberInputValueCaps; ++i) {1422HIDP_VALUE_CAPS *cap = &value_caps[i];14231424if (cap->IsRange) {1425continue;1426}14271428if (cap->NotRange.Usage != USB_USAGE_GENERIC_HAT) {1429continue;1430}14311432ctx->hat_indices[hat_index++] = cap->NotRange.DataIndex;1433}1434}14351436SDL_stack_free(value_caps);14371438#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT1439if (ctx->is_xinput) {1440SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);1441}1442#endif1443#ifdef SDL_JOYSTICK_RAWINPUT_WGI1444if (ctx->is_xinput) {1445SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);14461447if (ctx->is_xboxone) {1448SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);1449}1450}1451#endif14521453return true;1454}14551456static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)1457{1458#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT)1459RAWINPUT_DeviceContext *ctx = joystick->hwdata;1460#endif1461bool rumbled = false;14621463#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT1464// Prefer XInput over WGI because it allows rumble in the background1465if (!rumbled && ctx->xinput_correlated && !ctx->triggers_rumbling) {1466XINPUT_VIBRATION XVibration;14671468if (!XINPUTSETSTATE) {1469return SDL_Unsupported();1470}14711472XVibration.wLeftMotorSpeed = low_frequency_rumble;1473XVibration.wRightMotorSpeed = high_frequency_rumble;1474if (XINPUTSETSTATE(ctx->xinput_slot, &XVibration) == ERROR_SUCCESS) {1475rumbled = true;1476} else {1477return SDL_SetError("XInputSetState() failed");1478}1479}1480#endif // SDL_JOYSTICK_RAWINPUT_XINPUT14811482#ifdef SDL_JOYSTICK_RAWINPUT_WGI1483// Save off the motor state in case trigger rumble is started1484ctx->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16;1485ctx->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16;1486if (!rumbled && ctx->wgi_correlated) {1487WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot;1488HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration);1489if (SUCCEEDED(hr)) {1490rumbled = true;1491}1492}1493#endif14941495if (!rumbled) {1496#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT)1497return SDL_SetError("Controller isn't correlated yet, try hitting a button first");1498#else1499return SDL_Unsupported();1500#endif1501}1502return true;1503}15041505static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)1506{1507#ifdef SDL_JOYSTICK_RAWINPUT_WGI1508RAWINPUT_DeviceContext *ctx = joystick->hwdata;15091510ctx->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16;1511ctx->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16;1512if (ctx->wgi_correlated) {1513WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot;1514HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration);1515if (!SUCCEEDED(hr)) {1516return SDL_SetError("Setting vibration failed: 0x%lx", hr);1517}1518ctx->triggers_rumbling = (left_rumble > 0 || right_rumble > 0);1519return true;1520} else {1521return SDL_SetError("Controller isn't correlated yet, try hitting a button first");1522}1523#else1524return SDL_Unsupported();1525#endif1526}15271528static bool RAWINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)1529{1530return SDL_Unsupported();1531}15321533static bool RAWINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)1534{1535return SDL_Unsupported();1536}15371538static bool RAWINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)1539{1540return SDL_Unsupported();1541}15421543static HIDP_DATA *GetData(USHORT index, HIDP_DATA *data, ULONG length)1544{1545ULONG i;15461547// Check to see if the data is at the expected offset1548if (index < length && data[index].DataIndex == index) {1549return &data[index];1550}15511552// Loop through the data to find it1553for (i = 0; i < length; ++i) {1554if (data[i].DataIndex == index) {1555return &data[i];1556}1557}1558return NULL;1559}15601561/* This is the packet format for Xbox 360 and Xbox One controllers on Windows,1562however with this interface there is no rumble support, no guide button,1563and the left and right triggers are tied together as a single axis.15641565We use XInput and Windows.Gaming.Input to make up for these shortcomings.1566*/1567static void RAWINPUT_HandleStatePacket(SDL_Joystick *joystick, Uint8 *data, int size)1568{1569RAWINPUT_DeviceContext *ctx = joystick->hwdata;1570#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING1571// Map new buttons and axes into game controller controls1572static const int button_map[] = {1573SDL_GAMEPAD_BUTTON_SOUTH,1574SDL_GAMEPAD_BUTTON_EAST,1575SDL_GAMEPAD_BUTTON_WEST,1576SDL_GAMEPAD_BUTTON_NORTH,1577SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,1578SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,1579SDL_GAMEPAD_BUTTON_BACK,1580SDL_GAMEPAD_BUTTON_START,1581SDL_GAMEPAD_BUTTON_LEFT_STICK,1582SDL_GAMEPAD_BUTTON_RIGHT_STICK1583};1584#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))1585static const int hat_map[] = {15860,1587(1 << SDL_GAMEPAD_BUTTON_DPAD_UP),1588(1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT),1589(1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT),1590(1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT),1591(1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN),1592(1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT),1593(1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT),1594(1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT),15950,1596};1597Uint64 match_state = ctx->match_state;1598// Update match_state with button bit, then fall through1599#define SDL_SendJoystickButton(timestamp, joystick, button, down) \1600if (button < SDL_arraysize(button_map)) { \1601Uint64 button_bit = 1ull << button_map[button]; \1602match_state = (match_state & ~button_bit) | (button_bit * (down)); \1603} \1604SDL_SendJoystickButton(timestamp, joystick, button, down)1605#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES1606// Grab high 4 bits of value, then fall through1607#define AddAxisToMatchState(axis, value) \1608{ \1609match_state = (match_state & ~(0xFull << (4 * axis + 16))) | ((value)&0xF000ull) << (4 * axis + 4); \1610}1611#define SDL_SendJoystickAxis(timestamp, joystick, axis, value) \1612if (axis < 4) \1613AddAxisToMatchState(axis, value); \1614SDL_SendJoystickAxis(timestamp, joystick, axis, value)1615#endif1616#endif // SDL_JOYSTICK_RAWINPUT_MATCHING16171618ULONG data_length = ctx->max_data_length;1619int i;1620int nbuttons = joystick->nbuttons - (ctx->guide_hack * 1);1621int naxes = joystick->naxes - (ctx->trigger_hack * 2);1622int nhats = joystick->nhats;1623Uint32 button_mask = 0;1624Uint64 timestamp = SDL_GetTicksNS();16251626if (SDL_HidP_GetData(HidP_Input, ctx->data, &data_length, ctx->preparsed_data, (PCHAR)data, size) != HIDP_STATUS_SUCCESS) {1627return;1628}16291630for (i = 0; i < nbuttons; ++i) {1631HIDP_DATA *item = GetData(ctx->button_indices[i], ctx->data, data_length);1632if (item && item->On) {1633button_mask |= (1 << i);1634}1635}1636for (i = 0; i < nbuttons; ++i) {1637SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, ((button_mask & (1 << i)) != 0));1638}16391640for (i = 0; i < naxes; ++i) {1641HIDP_DATA *item = GetData(ctx->axis_indices[i], ctx->data, data_length);1642if (item) {1643Sint16 axis = (int)(Uint16)item->RawValue - 0x8000;1644SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, axis);1645}1646}16471648for (i = 0; i < nhats; ++i) {1649HIDP_DATA *item = GetData(ctx->hat_indices[i], ctx->data, data_length);1650if (item) {1651Uint8 hat = SDL_HAT_CENTERED;1652const Uint8 hat_states[] = {1653SDL_HAT_CENTERED,1654SDL_HAT_UP,1655SDL_HAT_UP | SDL_HAT_RIGHT,1656SDL_HAT_RIGHT,1657SDL_HAT_DOWN | SDL_HAT_RIGHT,1658SDL_HAT_DOWN,1659SDL_HAT_DOWN | SDL_HAT_LEFT,1660SDL_HAT_LEFT,1661SDL_HAT_UP | SDL_HAT_LEFT,1662SDL_HAT_CENTERED,1663};1664ULONG state = item->RawValue;16651666if (state < SDL_arraysize(hat_states)) {1667#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING1668match_state = (match_state & ~HAT_MASK) | hat_map[state];1669#endif1670hat = hat_states[state];1671}1672SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat);1673}1674}16751676#ifdef SDL_SendJoystickButton1677#undef SDL_SendJoystickButton1678#endif1679#ifdef SDL_SendJoystickAxis1680#undef SDL_SendJoystickAxis1681#endif16821683#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS1684#define AddTriggerToMatchState(axis, value) \1685{ \1686int match_axis = axis + SDL_JOYSTICK_RAWINPUT_MATCH_COUNT - joystick->naxes; \1687AddAxisToMatchState(match_axis, value); \1688}1689#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS16901691if (ctx->trigger_hack) {1692bool has_trigger_data = false;1693int left_trigger = joystick->naxes - 2;1694int right_trigger = joystick->naxes - 1;16951696#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT1697// Prefer XInput over WindowsGamingInput, it continues to provide data in the background1698if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) {1699has_trigger_data = true;1700}1701#endif // SDL_JOYSTICK_RAWINPUT_XINPUT17021703#ifdef SDL_JOYSTICK_RAWINPUT_WGI1704if (!has_trigger_data && ctx->wgi_correlated) {1705has_trigger_data = true;1706}1707#endif // SDL_JOYSTICK_RAWINPUT_WGI17081709#ifndef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS1710if (!has_trigger_data)1711#endif1712{1713HIDP_DATA *item = GetData(ctx->trigger_hack_index, ctx->data, data_length);1714if (item) {1715Sint16 value = (int)(Uint16)item->RawValue - 0x8000;1716Sint16 left_value = (value > 0) ? (value * 2 - 32767) : SDL_MIN_SINT16;1717Sint16 right_value = (value < 0) ? (-value * 2 - 32769) : SDL_MIN_SINT16;17181719#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS1720AddTriggerToMatchState(left_trigger, left_value);1721AddTriggerToMatchState(right_trigger, right_value);1722if (!has_trigger_data)1723#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS1724{1725SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, left_value);1726SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, right_value);1727}1728}1729}1730}17311732#ifdef AddAxisToMatchState1733#undef AddAxisToMatchState1734#endif1735#ifdef AddTriggerToMatchState1736#undef AddTriggerToMatchState1737#endif17381739#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING1740if (ctx->is_xinput) {1741ctx->match_state = match_state;1742ctx->last_state_packet = SDL_GetTicks();1743}1744#endif1745}17461747static void RAWINPUT_UpdateOtherAPIs(SDL_Joystick *joystick)1748{1749#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING1750RAWINPUT_DeviceContext *ctx = joystick->hwdata;1751bool has_trigger_data = false;1752bool correlated = false;1753WindowsMatchState match_state_xinput;1754int guide_button = joystick->nbuttons - 1;1755int left_trigger = joystick->naxes - 2;1756int right_trigger = joystick->naxes - 1;1757#ifdef SDL_JOYSTICK_RAWINPUT_WGI1758bool xinput_correlated;1759#endif17601761RAWINPUT_FillMatchState(&match_state_xinput, ctx->match_state);17621763#ifdef SDL_JOYSTICK_RAWINPUT_WGI1764#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT1765xinput_correlated = ctx->xinput_correlated;1766#else1767xinput_correlated = false;1768#endif1769// Parallel logic to WINDOWS_XINPUT below1770RAWINPUT_UpdateWindowsGamingInput();1771if (ctx->wgi_correlated &&1772!joystick->low_frequency_rumble && !joystick->high_frequency_rumble &&1773!joystick->left_trigger_rumble && !joystick->right_trigger_rumble) {1774// We have been previously correlated, ensure we are still matching, see comments in XINPUT section1775if (RAWINPUT_WindowsGamingInputSlotMatches(&match_state_xinput, ctx->wgi_slot, xinput_correlated)) {1776ctx->wgi_uncorrelate_count = 0;1777} else {1778++ctx->wgi_uncorrelate_count;1779/* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event1780pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but1781let's set it to 5 to be safe. An incorrect un-correlation will simply result in lower precision1782triggers for a frame. */1783if (ctx->wgi_uncorrelate_count >= 5) {1784#ifdef DEBUG_RAWINPUT1785SDL_Log("UN-Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, ctx->wgi_slot);1786#endif1787RAWINPUT_MarkWindowsGamingInputSlotFree(ctx->wgi_slot);1788ctx->wgi_correlated = false;1789ctx->wgi_correlation_count = 0;1790// Force release of Guide button, it can't possibly be down on this device now.1791/* It gets left down if we were actually correlated incorrectly and it was released on the WindowsGamingInput1792device but we didn't get a state packet. */1793if (ctx->guide_hack) {1794SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false);1795}1796}1797}1798}1799if (!ctx->wgi_correlated) {1800Uint8 new_correlation_count = 0;1801if (RAWINPUT_MissingWindowsGamingInputSlot()) {1802Uint8 correlation_id = 0;1803WindowsGamingInputGamepadState *slot_idx = NULL;1804if (RAWINPUT_GuessWindowsGamingInputSlot(&match_state_xinput, &correlation_id, &slot_idx, xinput_correlated)) {1805// we match exactly one WindowsGamingInput device1806/* Probably can do without wgi_correlation_count, just check and clear wgi_slot to NULL, unless we need1807even more frames to be sure. */1808if (ctx->wgi_correlation_count && ctx->wgi_slot == slot_idx) {1809// was correlated previously, and still the same device1810if (ctx->wgi_correlation_id + 1 == correlation_id) {1811// no one else was correlated in the meantime1812new_correlation_count = ctx->wgi_correlation_count + 1;1813if (new_correlation_count == 2) {1814// correlation stayed steady and uncontested across multiple frames, guaranteed match1815ctx->wgi_correlated = true;1816#ifdef DEBUG_RAWINPUT1817SDL_Log("Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, slot_idx);1818#endif1819correlated = true;1820RAWINPUT_MarkWindowsGamingInputSlotUsed(ctx->wgi_slot, ctx);1821// If the generalized Guide button was using us, it doesn't need to anymore1822if (guide_button_candidate.joystick == joystick) {1823guide_button_candidate.joystick = NULL;1824}1825if (guide_button_candidate.last_joystick == joystick) {1826guide_button_candidate.last_joystick = NULL;1827}1828}1829} else {1830// someone else also possibly correlated to this device, start over1831new_correlation_count = 1;1832}1833} else {1834// new possible correlation1835new_correlation_count = 1;1836ctx->wgi_slot = slot_idx;1837}1838ctx->wgi_correlation_id = correlation_id;1839} else {1840// Match multiple WindowsGamingInput devices, or none (possibly due to no buttons pressed)1841}1842}1843ctx->wgi_correlation_count = new_correlation_count;1844} else {1845correlated = true;1846}1847#endif // SDL_JOYSTICK_RAWINPUT_WGI18481849#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT1850// Parallel logic to WINDOWS_GAMING_INPUT above1851if (ctx->xinput_enabled) {1852RAWINPUT_UpdateXInput();1853if (ctx->xinput_correlated &&1854!joystick->low_frequency_rumble && !joystick->high_frequency_rumble) {1855// We have been previously correlated, ensure we are still matching1856/* This is required to deal with two (mostly) un-preventable mis-correlation situations:1857A) Since the HID data stream does not provide an initial state (but polling XInput does), if we open18585 controllers (#1-4 XInput mapped, #5 is not), and controller 1 had the A button down (and we don't1859know), and the user presses A on controller #5, we'll see exactly 1 controller with A down (#5) and1860exactly 1 XInput device with A down (#1), and incorrectly correlate. This code will then un-correlate1861when A is released from either controller #1 or #5.1862B) Since the app may not open all controllers, we could have a similar situation where only controller #51863is opened, and the user holds A on controllers #1 and #5 simultaneously - again we see only 1 controller1864with A down and 1 XInput device with A down, and incorrectly correlate. This should be very unusual1865(only when apps do not open all controllers, yet are listening to Guide button presses, yet1866for some reason want to ignore guide button presses on the un-opened controllers, yet users are1867pressing buttons on the unopened controllers), and will resolve itself when either button is released1868and we un-correlate. We could prevent this by processing the state packets for *all* controllers,1869even un-opened ones, as that would allow more precise correlation.1870*/1871if (RAWINPUT_XInputSlotMatches(&match_state_xinput, ctx->xinput_slot)) {1872ctx->xinput_uncorrelate_count = 0;1873} else {1874++ctx->xinput_uncorrelate_count;1875/* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event1876pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but1877let's set it to 5 to be safe. An incorrect un-correlation will simply result in lower precision1878triggers for a frame. */1879if (ctx->xinput_uncorrelate_count >= 5) {1880#ifdef DEBUG_RAWINPUT1881SDL_Log("UN-Correlated joystick %d to XInput device #%d", joystick->instance_id, ctx->xinput_slot);1882#endif1883RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot);1884ctx->xinput_correlated = false;1885ctx->xinput_correlation_count = 0;1886// Force release of Guide button, it can't possibly be down on this device now.1887/* It gets left down if we were actually correlated incorrectly and it was released on the XInput1888device but we didn't get a state packet. */1889if (ctx->guide_hack) {1890SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false);1891}1892}1893}1894}1895if (!ctx->xinput_correlated) {1896Uint8 new_correlation_count = 0;1897if (RAWINPUT_MissingXInputSlot()) {1898Uint8 correlation_id = 0;1899Uint8 slot_idx = 0;1900if (RAWINPUT_GuessXInputSlot(&match_state_xinput, &correlation_id, &slot_idx)) {1901// we match exactly one XInput device1902/* Probably can do without xinput_correlation_count, just check and clear xinput_slot to ANY, unless1903we need even more frames to be sure */1904if (ctx->xinput_correlation_count && ctx->xinput_slot == slot_idx) {1905// was correlated previously, and still the same device1906if (ctx->xinput_correlation_id + 1 == correlation_id) {1907// no one else was correlated in the meantime1908new_correlation_count = ctx->xinput_correlation_count + 1;1909if (new_correlation_count == 2) {1910// correlation stayed steady and uncontested across multiple frames, guaranteed match1911ctx->xinput_correlated = true;1912#ifdef DEBUG_RAWINPUT1913SDL_Log("Correlated joystick %d to XInput device #%d", joystick->instance_id, slot_idx);1914#endif1915correlated = true;1916RAWINPUT_MarkXInputSlotUsed(ctx->xinput_slot);1917// If the generalized Guide button was using us, it doesn't need to anymore1918if (guide_button_candidate.joystick == joystick) {1919guide_button_candidate.joystick = NULL;1920}1921if (guide_button_candidate.last_joystick == joystick) {1922guide_button_candidate.last_joystick = NULL;1923}1924}1925} else {1926// someone else also possibly correlated to this device, start over1927new_correlation_count = 1;1928}1929} else {1930// new possible correlation1931new_correlation_count = 1;1932ctx->xinput_slot = slot_idx;1933}1934ctx->xinput_correlation_id = correlation_id;1935} else {1936// Match multiple XInput devices, or none (possibly due to no buttons pressed)1937}1938}1939ctx->xinput_correlation_count = new_correlation_count;1940} else {1941correlated = true;1942}1943}1944#endif // SDL_JOYSTICK_RAWINPUT_XINPUT19451946// Poll for trigger data once (not per-state-packet)1947#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT1948// Prefer XInput over WindowsGamingInput, it continues to provide data in the background1949if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) {1950RAWINPUT_UpdateXInput();1951if (xinput_state[ctx->xinput_slot].connected) {1952XINPUT_BATTERY_INFORMATION_EX *battery_info = &xinput_state[ctx->xinput_slot].battery;1953Uint64 timestamp;19541955if (ctx->guide_hack || ctx->trigger_hack) {1956timestamp = SDL_GetTicksNS();1957} else {1958// timestamp won't be used1959timestamp = 0;1960}19611962if (ctx->guide_hack) {1963bool down = ((xinput_state[ctx->xinput_slot].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE) != 0);1964SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down);1965}1966if (ctx->trigger_hack) {1967SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bLeftTrigger * 257) - 32768);1968SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bRightTrigger * 257) - 32768);1969}1970has_trigger_data = true;19711972SDL_PowerState state;1973int percent;1974switch (battery_info->BatteryType) {1975case BATTERY_TYPE_WIRED:1976state = SDL_POWERSTATE_CHARGING;1977break;1978case BATTERY_TYPE_UNKNOWN:1979case BATTERY_TYPE_DISCONNECTED:1980state = SDL_POWERSTATE_UNKNOWN;1981break;1982default:1983state = SDL_POWERSTATE_ON_BATTERY;1984break;1985}1986switch (battery_info->BatteryLevel) {1987case BATTERY_LEVEL_EMPTY:1988percent = 10;1989break;1990case BATTERY_LEVEL_LOW:1991percent = 40;1992break;1993case BATTERY_LEVEL_MEDIUM:1994percent = 70;1995break;1996default:1997case BATTERY_LEVEL_FULL:1998percent = 100;1999break;2000}2001SDL_SendJoystickPowerInfo(joystick, state, percent);2002}2003}2004#endif // SDL_JOYSTICK_RAWINPUT_XINPUT20052006#ifdef SDL_JOYSTICK_RAWINPUT_WGI2007if (!has_trigger_data && ctx->wgi_correlated) {2008RAWINPUT_UpdateWindowsGamingInput(); // May detect disconnect / cause uncorrelation2009if (ctx->wgi_correlated) { // Still connected2010struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading *state = &ctx->wgi_slot->state;2011Uint64 timestamp;20122013if (ctx->guide_hack || ctx->trigger_hack) {2014timestamp = SDL_GetTicksNS();2015} else {2016// timestamp won't be used2017timestamp = 0;2018}20192020if (ctx->guide_hack) {2021bool down = ((state->Buttons & GamepadButtons_GUIDE) != 0);2022SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down);2023}2024if (ctx->trigger_hack) {2025SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, (Sint16)(((int)(state->LeftTrigger * SDL_MAX_UINT16)) - 32768));2026SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, (Sint16)(((int)(state->RightTrigger * SDL_MAX_UINT16)) - 32768));2027}2028has_trigger_data = true;2029}2030}2031#endif // SDL_JOYSTICK_RAWINPUT_WGI20322033if (!correlated) {2034if (!guide_button_candidate.joystick ||2035(ctx->last_state_packet && (!guide_button_candidate.last_state_packet ||2036ctx->last_state_packet >= guide_button_candidate.last_state_packet))) {2037guide_button_candidate.joystick = joystick;2038guide_button_candidate.last_state_packet = ctx->last_state_packet;2039}2040}2041#endif // SDL_JOYSTICK_RAWINPUT_MATCHING2042}20432044static void RAWINPUT_JoystickUpdate(SDL_Joystick *joystick)2045{2046RAWINPUT_UpdateOtherAPIs(joystick);2047}20482049static void RAWINPUT_JoystickClose(SDL_Joystick *joystick)2050{2051RAWINPUT_DeviceContext *ctx = joystick->hwdata;20522053#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING2054if (guide_button_candidate.joystick == joystick) {2055guide_button_candidate.joystick = NULL;2056}2057if (guide_button_candidate.last_joystick == joystick) {2058guide_button_candidate.last_joystick = NULL;2059}2060#endif20612062if (ctx) {2063SDL_RAWINPUT_Device *device;20642065#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT2066xinput_device_change = true;2067if (ctx->xinput_enabled) {2068if (ctx->xinput_correlated) {2069RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot);2070}2071WIN_UnloadXInputDLL();2072}2073#endif2074#ifdef SDL_JOYSTICK_RAWINPUT_WGI2075RAWINPUT_QuitWindowsGamingInput(ctx);2076#endif20772078device = ctx->device;2079if (device) {2080SDL_assert(device->joystick == joystick);2081device->joystick = NULL;2082RAWINPUT_ReleaseDevice(device);2083}20842085SDL_free(ctx->data);2086SDL_free(ctx->button_indices);2087SDL_free(ctx->axis_indices);2088SDL_free(ctx->hat_indices);2089SDL_free(ctx);2090joystick->hwdata = NULL;2091}2092}20932094bool RAWINPUT_RegisterNotifications(HWND hWnd)2095{2096int i;2097RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)];20982099if (!SDL_RAWINPUT_inited) {2100return true;2101}21022103for (i = 0; i < SDL_arraysize(subscribed_devices); i++) {2104rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;2105rid[i].usUsage = subscribed_devices[i];2106rid[i].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; // Receive messages when in background, including device add/remove2107rid[i].hwndTarget = hWnd;2108}21092110if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) {2111return SDL_SetError("Couldn't register for raw input events");2112}2113return true;2114}21152116bool RAWINPUT_UnregisterNotifications(void)2117{2118int i;2119RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)];21202121if (!SDL_RAWINPUT_inited) {2122return true;2123}21242125for (i = 0; i < SDL_arraysize(subscribed_devices); i++) {2126rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;2127rid[i].usUsage = subscribed_devices[i];2128rid[i].dwFlags = RIDEV_REMOVE;2129rid[i].hwndTarget = NULL;2130}21312132if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) {2133return SDL_SetError("Couldn't unregister for raw input events");2134}2135return true;2136}21372138LRESULT CALLBACK2139RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)2140{2141LRESULT result = -1;21422143if (SDL_RAWINPUT_inited) {2144SDL_LockJoysticks();21452146switch (msg) {2147case WM_INPUT_DEVICE_CHANGE:2148{2149HANDLE hDevice = (HANDLE)lParam;2150switch (wParam) {2151case GIDC_ARRIVAL:2152RAWINPUT_AddDevice(hDevice);2153break;2154case GIDC_REMOVAL:2155{2156SDL_RAWINPUT_Device *device;2157device = RAWINPUT_DeviceFromHandle(hDevice);2158if (device) {2159RAWINPUT_DelDevice(device, true);2160}2161break;2162}2163default:2164break;2165}2166}2167result = 0;2168break;21692170case WM_INPUT:2171{2172Uint8 data[sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + USB_PACKET_LENGTH];2173UINT buffer_size = SDL_arraysize(data);21742175if ((int)GetRawInputData((HRAWINPUT)lParam, RID_INPUT, data, &buffer_size, sizeof(RAWINPUTHEADER)) > 0) {2176PRAWINPUT raw_input = (PRAWINPUT)data;2177SDL_RAWINPUT_Device *device = RAWINPUT_DeviceFromHandle(raw_input->header.hDevice);2178if (device) {2179SDL_Joystick *joystick = device->joystick;2180if (joystick) {2181RAWINPUT_HandleStatePacket(joystick, raw_input->data.hid.bRawData, raw_input->data.hid.dwSizeHid);2182}2183}2184}2185}2186result = 0;2187break;2188}21892190SDL_UnlockJoysticks();2191}21922193if (result >= 0) {2194return result;2195}2196return CallWindowProc(DefWindowProc, hWnd, msg, wParam, lParam);2197}21982199static void RAWINPUT_JoystickQuit(void)2200{2201if (!SDL_RAWINPUT_inited) {2202return;2203}22042205RAWINPUT_RemoveDevices();22062207WIN_UnloadHIDDLL();22082209SDL_RAWINPUT_inited = false;2210}22112212static bool RAWINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)2213{2214return false;2215}22162217SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver = {2218RAWINPUT_JoystickInit,2219RAWINPUT_JoystickGetCount,2220RAWINPUT_JoystickDetect,2221RAWINPUT_JoystickIsDevicePresent,2222RAWINPUT_JoystickGetDeviceName,2223RAWINPUT_JoystickGetDevicePath,2224RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot,2225RAWINPUT_JoystickGetDevicePlayerIndex,2226RAWINPUT_JoystickSetDevicePlayerIndex,2227RAWINPUT_JoystickGetDeviceGUID,2228RAWINPUT_JoystickGetDeviceInstanceID,2229RAWINPUT_JoystickOpen,2230RAWINPUT_JoystickRumble,2231RAWINPUT_JoystickRumbleTriggers,2232RAWINPUT_JoystickSetLED,2233RAWINPUT_JoystickSendEffect,2234RAWINPUT_JoystickSetSensorsEnabled,2235RAWINPUT_JoystickUpdate,2236RAWINPUT_JoystickClose,2237RAWINPUT_JoystickQuit,2238RAWINPUT_JoystickGetGamepadMapping2239};22402241#endif // SDL_JOYSTICK_RAWINPUT224222432244