From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- .../src/joystick/windows/SDL_dinputjoystick.c | 1210 +++++++++++ .../src/joystick/windows/SDL_dinputjoystick_c.h | 40 + .../src/joystick/windows/SDL_rawinputjoystick.c | 2238 ++++++++++++++++++++ .../src/joystick/windows/SDL_rawinputjoystick_c.h | 32 + .../joystick/windows/SDL_windows_gaming_input.c | 1039 +++++++++ .../src/joystick/windows/SDL_windowsjoystick.c | 693 ++++++ .../src/joystick/windows/SDL_windowsjoystick_c.h | 103 + .../src/joystick/windows/SDL_xinputjoystick.c | 473 +++++ .../src/joystick/windows/SDL_xinputjoystick_c.h | 44 + 9 files changed, 5872 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c create mode 100644 contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h (limited to 'contrib/SDL-3.2.8/src/joystick/windows') diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c new file mode 100644 index 0000000..b00218d --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c @@ -0,0 +1,1210 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-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. +*/ +#include "SDL_internal.h" + +#include "../SDL_sysjoystick.h" + +#ifdef SDL_JOYSTICK_DINPUT + +#include "SDL_windowsjoystick_c.h" +#include "SDL_dinputjoystick_c.h" +#include "SDL_rawinputjoystick_c.h" +#include "SDL_xinputjoystick_c.h" +#include "../hidapi/SDL_hidapijoystick_c.h" + +#ifndef DIDFT_OPTIONAL +#define DIDFT_OPTIONAL 0x80000000 +#endif + +#define INPUT_QSIZE 128 // Buffer up to 128 input messages +#define JOY_AXIS_THRESHOLD (((SDL_JOYSTICK_AXIS_MAX) - (SDL_JOYSTICK_AXIS_MIN)) / 100) // 1% motion + +#define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF) + +// external variables referenced. +#ifdef SDL_VIDEO_DRIVER_WINDOWS +extern HWND SDL_HelperWindow; +#else +static const HWND SDL_HelperWindow = NULL; +#endif + +// local variables +static bool coinitialized = false; +static LPDIRECTINPUT8 dinput = NULL; + +// Taken from Wine - Thanks! +static DIOBJECTDATAFORMAT dfDIJoystick2[] = { + { &GUID_XAxis, DIJOFS_X, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_YAxis, DIJOFS_Y, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_ZAxis, DIJOFS_Z, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_RxAxis, DIJOFS_RX, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_RyAxis, DIJOFS_RY, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_RzAxis, DIJOFS_RZ, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION }, + { &GUID_POV, DIJOFS_POV(0), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 }, + { &GUID_POV, DIJOFS_POV(1), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 }, + { &GUID_POV, DIJOFS_POV(2), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 }, + { &GUID_POV, DIJOFS_POV(3), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(0), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(1), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(2), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(3), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(4), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(5), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(6), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(7), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(8), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(9), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(10), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(11), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(12), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(13), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(14), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(15), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(16), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(17), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(18), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(19), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(20), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(21), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(22), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(23), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(24), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(25), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(26), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(27), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(28), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(29), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(30), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(31), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(32), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(33), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(34), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(35), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(36), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(37), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(38), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(39), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(40), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(41), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(42), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(43), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(44), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(45), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(46), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(47), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(48), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(49), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(50), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(51), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(52), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(53), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(54), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(55), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(56), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(57), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(58), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(59), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(60), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(61), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(62), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(63), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(64), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(65), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(66), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(67), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(68), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(69), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(70), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(71), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(72), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(73), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(74), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(75), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(76), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(77), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(78), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(79), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(80), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(81), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(82), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(83), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(84), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(85), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(86), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(87), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(88), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(89), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(90), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(91), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(92), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(93), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(94), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(95), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(96), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(97), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(98), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(99), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(100), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(101), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(102), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(103), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(104), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(105), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(106), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(107), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(108), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(109), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(110), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(111), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(112), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(113), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(114), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(115), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(116), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(117), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(118), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(119), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(120), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(121), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(122), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(123), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(124), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(125), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(126), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { NULL, DIJOFS_BUTTON(127), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 }, + { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lVX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lVY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lVZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lVRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lVRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lVRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + // note: dwOfs value matches Windows + { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY }, + { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lAX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lAY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lAZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lARx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lARy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lARz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + // note: dwOfs value matches Windows + { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL }, + { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lFX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lFY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lFZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lFRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lFRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lFRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + // note: dwOfs value matches Windows + { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, + { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE }, +}; + +const DIDATAFORMAT SDL_c_dfDIJoystick2 = { + sizeof(DIDATAFORMAT), + sizeof(DIOBJECTDATAFORMAT), + DIDF_ABSAXIS, + sizeof(DIJOYSTATE2), + SDL_arraysize(dfDIJoystick2), + dfDIJoystick2 +}; + +// Convert a DirectInput return code to a text message +static bool SetDIerror(const char *function, HRESULT code) +{ + return SDL_SetError("%s() DirectX error 0x%8.8lx", function, code); +} + +static bool SDL_IsXInputDevice(Uint16 vendor_id, Uint16 product_id, const char *hidPath) +{ +#if defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT) + SDL_GamepadType type; + + // XInput and RawInput backends will pick up XInput-compatible devices + if (!SDL_XINPUT_Enabled() +#ifdef SDL_JOYSTICK_RAWINPUT + && !RAWINPUT_IsEnabled() +#endif + ) { + return false; + } + + // If device path contains "IG_" then its an XInput device + // See: https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput + if (SDL_strstr(hidPath, "IG_") != NULL) { + return true; + } + + type = SDL_GetGamepadTypeFromVIDPID(vendor_id, product_id, NULL, false); + if (type == SDL_GAMEPAD_TYPE_XBOX360 || + type == SDL_GAMEPAD_TYPE_XBOXONE || + (vendor_id == USB_VENDOR_VALVE && product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) { + return true; + } +#endif // SDL_JOYSTICK_XINPUT || SDL_JOYSTICK_RAWINPUT + + return false; +} + +static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, Uint16 vendor_id, Uint16 product_id, char **manufacturer_string, char **product_string) +{ + DIPROPSTRING dipstr; + + if (!device || !manufacturer_string || !product_string) { + return false; + } + +#ifdef SDL_JOYSTICK_HIDAPI + *manufacturer_string = HIDAPI_GetDeviceManufacturerName(vendor_id, product_id); + *product_string = HIDAPI_GetDeviceProductName(vendor_id, product_id); + if (*product_string) { + return true; + } +#endif + + dipstr.diph.dwSize = sizeof(dipstr); + dipstr.diph.dwHeaderSize = sizeof(dipstr.diph); + dipstr.diph.dwObj = 0; + dipstr.diph.dwHow = DIPH_DEVICE; + + if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_PRODUCTNAME, &dipstr.diph))) { + return false; + } + + *manufacturer_string = NULL; + *product_string = WIN_StringToUTF8(dipstr.wsz); + + return true; +} + +static bool QueryDevicePath(LPDIRECTINPUTDEVICE8 device, char **device_path) +{ + DIPROPGUIDANDPATH dippath; + + if (!device || !device_path) { + return false; + } + + dippath.diph.dwSize = sizeof(dippath); + dippath.diph.dwHeaderSize = sizeof(dippath.diph); + dippath.diph.dwObj = 0; + dippath.diph.dwHow = DIPH_DEVICE; + + if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_GUIDANDPATH, &dippath.diph))) { + return false; + } + + *device_path = WIN_StringToUTF8W(dippath.wszPath); + + // Normalize path to upper case. + SDL_strupr(*device_path); + + return true; +} + +static bool QueryDeviceInfo(LPDIRECTINPUTDEVICE8 device, Uint16 *vendor_id, Uint16 *product_id) +{ + DIPROPDWORD dipdw; + + if (!device || !vendor_id || !product_id) { + return false; + } + + dipdw.diph.dwSize = sizeof(dipdw); + dipdw.diph.dwHeaderSize = sizeof(dipdw.diph); + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = 0; + + if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_VIDPID, &dipdw.diph))) { + return false; + } + + *vendor_id = LOWORD(dipdw.dwData); + *product_id = HIWORD(dipdw.dwData); + + return true; +} + +void FreeRumbleEffectData(DIEFFECT *effect) +{ + if (!effect) { + return; + } + SDL_free(effect->rgdwAxes); + SDL_free(effect->rglDirection); + SDL_free(effect->lpvTypeSpecificParams); + SDL_free(effect); +} + +DIEFFECT *CreateRumbleEffectData(Sint16 magnitude) +{ + DIEFFECT *effect; + DIPERIODIC *periodic; + + // Create the effect + effect = (DIEFFECT *)SDL_calloc(1, sizeof(*effect)); + if (!effect) { + return NULL; + } + effect->dwSize = sizeof(*effect); + effect->dwGain = 10000; + effect->dwFlags = DIEFF_OBJECTOFFSETS; + effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; // In microseconds. + effect->dwTriggerButton = DIEB_NOTRIGGER; + + effect->cAxes = 2; + effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD)); + if (!effect->rgdwAxes) { + FreeRumbleEffectData(effect); + return NULL; + } + + effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG)); + if (!effect->rglDirection) { + FreeRumbleEffectData(effect); + return NULL; + } + effect->dwFlags |= DIEFF_CARTESIAN; + + periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(*periodic)); + if (!periodic) { + FreeRumbleEffectData(effect); + return NULL; + } + periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude); + periodic->dwPeriod = 1000000; + + effect->cbTypeSpecificParams = sizeof(*periodic); + effect->lpvTypeSpecificParams = periodic; + + return effect; +} + +bool SDL_DINPUT_JoystickInit(void) +{ + HRESULT result; + HINSTANCE instance; + + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true)) { + // In some environments, IDirectInput8_Initialize / _EnumDevices can take a minute even with no controllers. + dinput = NULL; + return true; + } + + result = WIN_CoInitialize(); + if (FAILED(result)) { + return SetDIerror("CoInitialize", result); + } + + coinitialized = true; + + result = CoCreateInstance(&CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER, + &IID_IDirectInput8, (LPVOID *)&dinput); + + if (FAILED(result)) { + return SetDIerror("CoCreateInstance", result); + } + + // Because we used CoCreateInstance, we need to Initialize it, first. + instance = GetModuleHandle(NULL); + if (!instance) { + IDirectInput8_Release(dinput); + dinput = NULL; + return SDL_SetError("GetModuleHandle() failed with error code %lu.", GetLastError()); + } + result = IDirectInput8_Initialize(dinput, instance, DIRECTINPUT_VERSION); + + if (FAILED(result)) { + IDirectInput8_Release(dinput); + dinput = NULL; + return SetDIerror("IDirectInput::Initialize", result); + } + return true; +} + +static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path) +{ + int slot = -1; + + if (vendor_id == USB_VENDOR_VALVE && + product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + (void)SDL_sscanf(device_path, "\\\\?\\HID#VID_28DE&PID_11FF&IG_0%d", &slot); + } + return slot; +} + +// helper function for direct input, gets called for each connected joystick +static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext) +{ +#define CHECK(expression) \ + { \ + if (!(expression)) \ + goto err; \ + } + JoyStick_DeviceData *pNewJoystick = NULL; + JoyStick_DeviceData *pPrevJoystick = NULL; + Uint16 vendor = 0; + Uint16 product = 0; + Uint16 version = 0; + char *hidPath = NULL; + char *manufacturer_string = NULL; + char *product_string = NULL; + LPDIRECTINPUTDEVICE8 device = NULL; + + // We are only supporting HID devices. + CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID); + + CHECK(SUCCEEDED(IDirectInput8_CreateDevice(dinput, &pDeviceInstance->guidInstance, &device, NULL))); + CHECK(QueryDevicePath(device, &hidPath)); + CHECK(QueryDeviceInfo(device, &vendor, &product)); + CHECK(QueryDeviceName(device, vendor, product, &manufacturer_string, &product_string)); + + CHECK(!SDL_IsXInputDevice(vendor, product, hidPath)); + CHECK(!SDL_ShouldIgnoreJoystick(vendor, product, version, product_string)); + CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, product_string)); + + pNewJoystick = *(JoyStick_DeviceData **)pContext; + while (pNewJoystick) { + // update GUIDs of joysticks with matching paths, in case they're not open yet + if (SDL_strcmp(pNewJoystick->path, hidPath) == 0) { + // if we are replacing the front of the list then update it + if (pNewJoystick == *(JoyStick_DeviceData **)pContext) { + *(JoyStick_DeviceData **)pContext = pNewJoystick->pNext; + } else if (pPrevJoystick) { + pPrevJoystick->pNext = pNewJoystick->pNext; + } + + // Update with new guid/etc, if it has changed + SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE)); + + pNewJoystick->pNext = SYS_Joystick; + SYS_Joystick = pNewJoystick; + + pNewJoystick = NULL; + CHECK(FALSE); + } + + pPrevJoystick = pNewJoystick; + pNewJoystick = pNewJoystick->pNext; + } + + pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData)); + CHECK(pNewJoystick); + + pNewJoystick->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(vendor, product, hidPath); + SDL_strlcpy(pNewJoystick->path, hidPath, SDL_arraysize(pNewJoystick->path)); + SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE)); + + pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); + CHECK(pNewJoystick->joystickname); + + if (vendor && product) { + pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, manufacturer_string, product_string, 0, 0); + } else { + pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, version, manufacturer_string, product_string, 0, 0); + } + + WINDOWS_AddJoystickDevice(pNewJoystick); + pNewJoystick = NULL; + +err: + if (pNewJoystick) { + SDL_free(pNewJoystick->joystickname); + SDL_free(pNewJoystick); + } + + SDL_free(hidPath); + SDL_free(manufacturer_string); + SDL_free(product_string); + + if (device) { + IDirectInputDevice8_Release(device); + } + + return DIENUM_CONTINUE; // get next device, please +#undef CHECK +} + +void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext) +{ + if (!dinput) { + return; + } + + IDirectInput8_EnumDevices(dinput, DI8DEVCLASS_GAMECTRL, EnumJoystickDetectCallback, pContext, DIEDFL_ATTACHEDONLY); +} + +// helper function for direct input, gets called for each connected joystick +typedef struct +{ + Uint16 vendor; + Uint16 product; + bool present; +} Joystick_PresentData; + +static BOOL CALLBACK EnumJoystickPresentCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext) +{ +#define CHECK(expression) \ + { \ + if (!(expression)) \ + goto err; \ + } + Joystick_PresentData *pData = (Joystick_PresentData *)pContext; + Uint16 vendor = 0; + Uint16 product = 0; + LPDIRECTINPUTDEVICE8 device = NULL; + BOOL result = DIENUM_CONTINUE; + + // We are only supporting HID devices. + CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID); + + CHECK(SUCCEEDED(IDirectInput8_CreateDevice(dinput, &pDeviceInstance->guidInstance, &device, NULL))); + CHECK(QueryDeviceInfo(device, &vendor, &product)); + + if (vendor == pData->vendor && product == pData->product) { + pData->present = true; + result = DIENUM_STOP; // found it + } + +err: + if (device) { + IDirectInputDevice8_Release(device); + } + + return result; +#undef CHECK +} + +bool SDL_DINPUT_JoystickPresent(Uint16 vendor_id, Uint16 product_id, Uint16 version_number) +{ + Joystick_PresentData data; + + if (!dinput) { + return false; + } + + data.vendor = vendor_id; + data.product = product_id; + data.present = false; + IDirectInput8_EnumDevices(dinput, DI8DEVCLASS_GAMECTRL, EnumJoystickPresentCallback, &data, DIEDFL_ATTACHEDONLY); + return data.present; +} + +static BOOL CALLBACK EnumDevObjectsCallback(LPCDIDEVICEOBJECTINSTANCE pDeviceObject, LPVOID pContext) +{ + SDL_Joystick *joystick = (SDL_Joystick *)pContext; + HRESULT result; + input_t *in = &joystick->hwdata->Inputs[joystick->hwdata->NumInputs]; + + if (pDeviceObject->dwType & DIDFT_BUTTON) { + in->type = BUTTON; + in->num = (Uint8)joystick->nbuttons; + in->ofs = DIJOFS_BUTTON(in->num); + joystick->nbuttons++; + } else if (pDeviceObject->dwType & DIDFT_POV) { + in->type = HAT; + in->num = (Uint8)joystick->nhats; + in->ofs = DIJOFS_POV(in->num); + joystick->nhats++; + } else if (pDeviceObject->dwType & DIDFT_AXIS) { + DIPROPRANGE diprg; + DIPROPDWORD dilong; + + in->type = AXIS; + in->num = (Uint8)joystick->naxes; + if (SDL_memcmp(&pDeviceObject->guidType, &GUID_XAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_X; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_YAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_Y; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_ZAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_Z; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RxAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_RX; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RyAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_RY; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RzAxis, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_RZ; + } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_Slider, sizeof(pDeviceObject->guidType)) == 0) { + in->ofs = DIJOFS_SLIDER(joystick->hwdata->NumSliders); + ++joystick->hwdata->NumSliders; + } else { + return DIENUM_CONTINUE; // not an axis we can grok + } + + diprg.diph.dwSize = sizeof(diprg); + diprg.diph.dwHeaderSize = sizeof(diprg.diph); + diprg.diph.dwObj = pDeviceObject->dwType; + diprg.diph.dwHow = DIPH_BYID; + diprg.lMin = SDL_JOYSTICK_AXIS_MIN; + diprg.lMax = SDL_JOYSTICK_AXIS_MAX; + + result = + IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice, + DIPROP_RANGE, &diprg.diph); + if (FAILED(result)) { + return DIENUM_CONTINUE; // don't use this axis + } + + // Set dead zone to 0. + dilong.diph.dwSize = sizeof(dilong); + dilong.diph.dwHeaderSize = sizeof(dilong.diph); + dilong.diph.dwObj = pDeviceObject->dwType; + dilong.diph.dwHow = DIPH_BYID; + dilong.dwData = 0; + result = + IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice, + DIPROP_DEADZONE, &dilong.diph); + if (FAILED(result)) { + return DIENUM_CONTINUE; // don't use this axis + } + + joystick->naxes++; + } else { + // not supported at this time + return DIENUM_CONTINUE; + } + + joystick->hwdata->NumInputs++; + + if (joystick->hwdata->NumInputs == MAX_INPUTS) { + return DIENUM_STOP; // too many + } + + return DIENUM_CONTINUE; +} + +/* Sort using the data offset into the DInput struct. + * This gives a reasonable ordering for the inputs. + */ +static int SDLCALL SortDevFunc(const void *a, const void *b) +{ + const input_t *inputA = (const input_t *)a; + const input_t *inputB = (const input_t *)b; + + if (inputA->ofs < inputB->ofs) { + return -1; + } + if (inputA->ofs > inputB->ofs) { + return 1; + } + return 0; +} + +// Sort the input objects and recalculate the indices for each input. +static void SortDevObjects(SDL_Joystick *joystick) +{ + input_t *inputs = joystick->hwdata->Inputs; + Uint8 nButtons = 0; + Uint8 nHats = 0; + Uint8 nAxis = 0; + int n; + + SDL_qsort(inputs, joystick->hwdata->NumInputs, sizeof(input_t), SortDevFunc); + + for (n = 0; n < joystick->hwdata->NumInputs; n++) { + switch (inputs[n].type) { + case BUTTON: + inputs[n].num = nButtons; + nButtons++; + break; + + case HAT: + inputs[n].num = nHats; + nHats++; + break; + + case AXIS: + inputs[n].num = nAxis; + nAxis++; + break; + } + } +} + +bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) +{ + HRESULT result; + DIPROPDWORD dipdw; + + joystick->hwdata->buffered = true; + joystick->hwdata->Capabilities.dwSize = sizeof(DIDEVCAPS); + + SDL_zero(dipdw); + dipdw.diph.dwSize = sizeof(DIPROPDWORD); + dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + + result = + IDirectInput8_CreateDevice(dinput, + &joystickdevice->dxdevice.guidInstance, + &joystick->hwdata->InputDevice, + NULL); + if (FAILED(result)) { + return SetDIerror("IDirectInput::CreateDevice", result); + } + + /* Acquire shared access. Exclusive access is required for forces, + * though. */ + result = + IDirectInputDevice8_SetCooperativeLevel(joystick->hwdata->InputDevice, SDL_HelperWindow, + DISCL_EXCLUSIVE | + DISCL_BACKGROUND); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetCooperativeLevel", result); + } + + // Use the extended data structure: DIJOYSTATE2. + result = + IDirectInputDevice8_SetDataFormat(joystick->hwdata->InputDevice, + &SDL_c_dfDIJoystick2); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetDataFormat", result); + } + + // Get device capabilities + result = + IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice, + &joystick->hwdata->Capabilities); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::GetCapabilities", result); + } + + // Force capable? + if (joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK) { + result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::Acquire", result); + } + + // reset all actuators. + result = + IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, + DISFFC_RESET); + + /* Not necessarily supported, ignore if not supported. + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand", result); + } + */ + + result = IDirectInputDevice8_Unacquire(joystick->hwdata->InputDevice); + + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::Unacquire", result); + } + + /* Turn on auto-centering for a ForceFeedback device (until told + * otherwise). */ + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = DIPROPAUTOCENTER_ON; + + result = + IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice, + DIPROP_AUTOCENTER, &dipdw.diph); + + /* Not necessarily supported, ignore if not supported. + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetProperty", result); + } + */ + + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + + // What buttons and axes does it have? + IDirectInputDevice8_EnumObjects(joystick->hwdata->InputDevice, + EnumDevObjectsCallback, joystick, + DIDFT_BUTTON | DIDFT_AXIS | DIDFT_POV); + + /* Reorder the input objects. Some devices do not report the X axis as + * the first axis, for example. */ + SortDevObjects(joystick); + + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = INPUT_QSIZE; + + // Set the buffer size + result = + IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice, + DIPROP_BUFFERSIZE, &dipdw.diph); + + if (result == DI_POLLEDDEVICE) { + /* This device doesn't support buffering, so we're forced + * to use less reliable polling. */ + joystick->hwdata->buffered = false; + } else if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetProperty", result); + } + joystick->hwdata->first_update = true; + + // Poll and wait for initial device state to be populated + result = IDirectInputDevice8_Poll(joystick->hwdata->InputDevice); + if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) { + IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + IDirectInputDevice8_Poll(joystick->hwdata->InputDevice); + } + SDL_Delay(50); + + return true; +} + +static bool SDL_DINPUT_JoystickInitRumble(SDL_Joystick *joystick, Sint16 magnitude) +{ + HRESULT result; + + // Reset and then enable actuators + result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET); + if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) { + result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + if (SUCCEEDED(result)) { + result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET); + } + } + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_RESET)", result); + } + + result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_SETACTUATORSON); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_SETACTUATORSON)", result); + } + + // Create the effect + joystick->hwdata->ffeffect = CreateRumbleEffectData(magnitude); + if (!joystick->hwdata->ffeffect) { + return false; + } + + result = IDirectInputDevice8_CreateEffect(joystick->hwdata->InputDevice, &GUID_Sine, + joystick->hwdata->ffeffect, &joystick->hwdata->ffeffect_ref, NULL); + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::CreateEffect", result); + } + return true; +} + +bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + HRESULT result; + + // Scale and average the two rumble strengths + Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2); + + if (!(joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK)) { + return SDL_Unsupported(); + } + + if (joystick->hwdata->ff_initialized) { + DIPERIODIC *periodic = ((DIPERIODIC *)joystick->hwdata->ffeffect->lpvTypeSpecificParams); + periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude); + + result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS)); + if (result == DIERR_INPUTLOST) { + result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + if (SUCCEEDED(result)) { + result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS)); + } + } + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::SetParameters", result); + } + } else { + if (!SDL_DINPUT_JoystickInitRumble(joystick, magnitude)) { + return false; + } + joystick->hwdata->ff_initialized = true; + } + + result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0); + if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) { + result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + if (SUCCEEDED(result)) { + result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0); + } + } + if (FAILED(result)) { + return SetDIerror("IDirectInputDevice8::Start", result); + } + return true; +} + +static Uint8 TranslatePOV(DWORD value) +{ + const Uint8 HAT_VALS[] = { + 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 + }; + + if (LOWORD(value) == 0xFFFF) { + return SDL_HAT_CENTERED; + } + + // Round the value up: + value += 4500 / 2; + value %= 36000; + value /= 4500; + + if (value >= 8) { + return SDL_HAT_CENTERED; // shouldn't happen + } + + return HAT_VALS[value]; +} + +/* Function to update the state of a joystick - called as a device poll. + * This function shouldn't update the joystick structure directly, + * but instead should call SDL_PrivateJoystick*() to deliver events + * and update joystick device state. + */ +static void UpdateDINPUTJoystickState_Polled(SDL_Joystick *joystick) +{ + DIJOYSTATE2 state; + HRESULT result; + int i; + Uint64 timestamp = SDL_GetTicksNS(); + + result = + IDirectInputDevice8_GetDeviceState(joystick->hwdata->InputDevice, + sizeof(DIJOYSTATE2), &state); + if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) { + IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + result = + IDirectInputDevice8_GetDeviceState(joystick->hwdata->InputDevice, + sizeof(DIJOYSTATE2), &state); + } + + if (result != DI_OK) { + return; + } + + // Set each known axis, button and POV. + for (i = 0; i < joystick->hwdata->NumInputs; ++i) { + const input_t *in = &joystick->hwdata->Inputs[i]; + + switch (in->type) { + case AXIS: + switch (in->ofs) { + case DIJOFS_X: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lX); + break; + case DIJOFS_Y: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lY); + break; + case DIJOFS_Z: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lZ); + break; + case DIJOFS_RX: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRx); + break; + case DIJOFS_RY: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRy); + break; + case DIJOFS_RZ: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRz); + break; + case DIJOFS_SLIDER(0): + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.rglSlider[0]); + break; + case DIJOFS_SLIDER(1): + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.rglSlider[1]); + break; + } + break; + + case BUTTON: + SDL_SendJoystickButton(timestamp, joystick, in->num, + (state.rgbButtons[in->ofs - DIJOFS_BUTTON0] != 0)); + break; + case HAT: + { + Uint8 pos = TranslatePOV(state.rgdwPOV[in->ofs - DIJOFS_POV(0)]); + SDL_SendJoystickHat(timestamp, joystick, in->num, pos); + break; + } + } + } +} + +static void UpdateDINPUTJoystickState_Buffered(SDL_Joystick *joystick) +{ + int i; + HRESULT result; + DWORD numevents; + DIDEVICEOBJECTDATA evtbuf[INPUT_QSIZE]; + Uint64 timestamp = SDL_GetTicksNS(); + + numevents = INPUT_QSIZE; + result = + IDirectInputDevice8_GetDeviceData(joystick->hwdata->InputDevice, + sizeof(DIDEVICEOBJECTDATA), evtbuf, + &numevents, 0); + if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) { + IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + result = + IDirectInputDevice8_GetDeviceData(joystick->hwdata->InputDevice, + sizeof(DIDEVICEOBJECTDATA), + evtbuf, &numevents, 0); + } + + // Handle the events or punt + if (FAILED(result)) { + return; + } + + for (i = 0; i < (int)numevents; ++i) { + int j; + + for (j = 0; j < joystick->hwdata->NumInputs; ++j) { + const input_t *in = &joystick->hwdata->Inputs[j]; + + if (evtbuf[i].dwOfs != in->ofs) { + continue; + } + + switch (in->type) { + case AXIS: + SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)evtbuf[i].dwData); + break; + case BUTTON: + SDL_SendJoystickButton(timestamp, joystick, in->num, + (evtbuf[i].dwData != 0)); + break; + case HAT: + { + Uint8 pos = TranslatePOV(evtbuf[i].dwData); + SDL_SendJoystickHat(timestamp, joystick, in->num, pos); + } break; + } + } + } + + if (result == DI_BUFFEROVERFLOW) { + /* Our buffer wasn't big enough to hold all the queued events, + * so poll the device to make sure we have the complete state. + */ + UpdateDINPUTJoystickState_Polled(joystick); + } +} + +void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ + HRESULT result; + + result = IDirectInputDevice8_Poll(joystick->hwdata->InputDevice); + if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) { + IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice); + IDirectInputDevice8_Poll(joystick->hwdata->InputDevice); + } + + if (joystick->hwdata->first_update) { + // Poll to get the initial state of the joystick + UpdateDINPUTJoystickState_Polled(joystick); + joystick->hwdata->first_update = false; + return; + } + + if (joystick->hwdata->buffered ) { + UpdateDINPUTJoystickState_Buffered(joystick); + } else { + UpdateDINPUTJoystickState_Polled(joystick); + } +} + +void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick) +{ + if (joystick->hwdata->ffeffect_ref) { + IDirectInputEffect_Unload(joystick->hwdata->ffeffect_ref); + joystick->hwdata->ffeffect_ref = NULL; + } + if (joystick->hwdata->ffeffect) { + FreeRumbleEffectData(joystick->hwdata->ffeffect); + joystick->hwdata->ffeffect = NULL; + } + IDirectInputDevice8_Unacquire(joystick->hwdata->InputDevice); + IDirectInputDevice8_Release(joystick->hwdata->InputDevice); + joystick->hwdata->ff_initialized = false; +} + +void SDL_DINPUT_JoystickQuit(void) +{ + if (dinput != NULL) { + IDirectInput8_Release(dinput); + dinput = NULL; + } + + if (coinitialized) { + WIN_CoUninitialize(); + coinitialized = false; + } +} + +#else // !SDL_JOYSTICK_DINPUT + +typedef struct JoyStick_DeviceData JoyStick_DeviceData; + +bool SDL_DINPUT_JoystickInit(void) +{ + return true; +} + +void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext) +{ +} + +bool SDL_DINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version) +{ + return false; +} + +bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) +{ + return SDL_Unsupported(); +} + +bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ +} + +void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick) +{ +} + +void SDL_DINPUT_JoystickQuit(void) +{ +} + +#endif // SDL_JOYSTICK_DINPUT diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h new file mode 100644 index 0000000..0643ce1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h @@ -0,0 +1,40 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-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. +*/ +#include "SDL_internal.h" + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +extern bool SDL_DINPUT_JoystickInit(void); +extern void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext); +extern bool SDL_DINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version); +extern bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice); +extern bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); +extern void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick); +extern void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick); +extern void SDL_DINPUT_JoystickQuit(void); + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif 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 new file mode 100644 index 0000000..d5166de --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c @@ -0,0 +1,2238 @@ +/* + 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 diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h new file mode 100644 index 0000000..b67544b --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h @@ -0,0 +1,32 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-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. +*/ +#include "SDL_internal.h" +#include "../../core/windows/SDL_windows.h" + +// Return true if the RawInput driver is enabled +extern bool RAWINPUT_IsEnabled(void); + +// Registers for input events +extern int RAWINPUT_RegisterNotifications(HWND hWnd); +extern int RAWINPUT_UnregisterNotifications(void); + +// Returns 0 if message was handled +extern LRESULT CALLBACK RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c new file mode 100644 index 0000000..dbc5658 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c @@ -0,0 +1,1039 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-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. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_WGI + +#include "../SDL_sysjoystick.h" +#include "../hidapi/SDL_hidapijoystick_c.h" +#include "SDL_rawinputjoystick_c.h" + +#include "../../core/windows/SDL_windows.h" +#define COBJMACROS +#include "windows.gaming.input.h" +#include +#include +#include +#include + +#ifdef ____FIReference_1_INT32_INTERFACE_DEFINED__ +// MinGW-64 uses __FIReference_1_INT32 instead of Microsoft's __FIReference_1_int +#define __FIReference_1_int __FIReference_1_INT32 +#define __FIReference_1_int_get_Value __FIReference_1_INT32_get_Value +#define __FIReference_1_int_Release __FIReference_1_INT32_Release +#endif + +struct joystick_hwdata +{ + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller; + __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller; + __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo *battery; + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; + __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; + UINT64 timestamp; +}; + +typedef struct WindowsGamingInputControllerState +{ + SDL_JoystickID instance_id; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller; + char *name; + SDL_GUID guid; + SDL_JoystickType type; + int steam_virtual_gamepad_slot; +} WindowsGamingInputControllerState; + +typedef HRESULT(WINAPI *CoIncrementMTAUsage_t)(CO_MTA_USAGE_COOKIE *pCookie); +typedef HRESULT(WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory); +typedef HRESULT(WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *string); +typedef HRESULT(WINAPI *WindowsDeleteString_t)(HSTRING string); +typedef PCWSTR(WINAPI *WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 *length); + +static struct +{ + CoIncrementMTAUsage_t CoIncrementMTAUsage; + RoGetActivationFactory_t RoGetActivationFactory; + WindowsCreateStringReference_t WindowsCreateStringReference; + WindowsDeleteString_t WindowsDeleteString; + WindowsGetStringRawBuffer_t WindowsGetStringRawBuffer; + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics *controller_statics; + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics *arcade_stick_statics; + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2 *arcade_stick_statics2; + __x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics *flight_stick_statics; + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics; + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2 *gamepad_statics2; + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics *racing_wheel_statics; + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2 *racing_wheel_statics2; + EventRegistrationToken controller_added_token; + EventRegistrationToken controller_removed_token; + int controller_count; + WindowsGamingInputControllerState *controllers; +} wgi; + +// WinRT headers in official Windows SDK contain only declarations, and we have to define these GUIDs ourselves. +// https://stackoverflow.com/a/55605485/1795050 +DEFINE_GUID(IID___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController, 0x00621c22, 0x42e8, 0x529f, 0x92, 0x70, 0x83, 0x6b, 0x32, 0x93, 0x1d, 0x72); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, 0x5c37b8c8, 0x37b1, 0x4ad8, 0x94, 0x58, 0x20, 0x0f, 0x1a, 0x30, 0x01, 0x8e); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, 0x52b5d744, 0xbb86, 0x445a, 0xb5, 0x9c, 0x59, 0x6f, 0x0e, 0x2a, 0x49, 0xdf); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, 0x5514924a, 0xfecc, 0x435e, 0x83, 0xdc, 0x5c, 0xec, 0x8a, 0x18, 0xa5, 0x20); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameController, 0x1baf6522, 0x5f64, 0x42c5, 0x82, 0x67, 0xb9, 0xfe, 0x22, 0x15, 0xbf, 0xbd); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, 0xdcecc681, 0x3963, 0x4da6, 0x95, 0x5d, 0x55, 0x3f, 0x3b, 0x6f, 0x61, 0x61); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, 0x8bbce529, 0xd49c, 0x39e9, 0x95, 0x60, 0xe4, 0x7d, 0xde, 0x96, 0xb7, 0xc8); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, 0x42676dc5, 0x0856, 0x47c4, 0x92, 0x13, 0xb3, 0x95, 0x50, 0x4c, 0x3a, 0x3c); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, 0x3ac12cd5, 0x581b, 0x4936, 0x9f, 0x94, 0x69, 0xf1, 0xe6, 0x51, 0x4c, 0x7d); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, 0xe666bcaa, 0xedfd, 0x4323, 0xa9, 0xf6, 0x3c, 0x38, 0x40, 0x48, 0xd1, 0xed); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, 0x7cad6d91, 0xa7e1, 0x4f71, 0x9a, 0x78, 0x33, 0xe9, 0xc5, 0xdf, 0xea, 0x62); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, 0x43c0c035, 0xbb73, 0x4756, 0xa7, 0x87, 0x3e, 0xd6, 0xbe, 0xa6, 0x17, 0xbd); +DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, 0xeb8d0792, 0xe95a, 0x4b19, 0xaf, 0xc7, 0x0a, 0x59, 0xf8, 0xbf, 0x75, 0x9e); + +extern bool SDL_XINPUT_Enabled(void); + + +static bool SDL_IsXInputDevice(Uint16 vendor, Uint16 product, const char *name) +{ +#if defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT) + PRAWINPUTDEVICELIST raw_devices = NULL; + UINT i, raw_device_count = 0; + LONG vidpid = MAKELONG(vendor, product); + + // XInput and RawInput backends will pick up XInput-compatible devices + if (!SDL_XINPUT_Enabled() +#ifdef SDL_JOYSTICK_RAWINPUT + && !RAWINPUT_IsEnabled() +#endif + ) { + return false; + } + + // Sometimes we'll get a Windows.Gaming.Input callback before the raw input device is even in the list, + // so try to do some checks up front to catch these cases. + if (SDL_IsJoystickXboxOne(vendor, product) || + (name && SDL_strncmp(name, "Xbox ", 5) == 0)) { + return true; + } + + // Go through RAWINPUT (WinXP and later) to find HID devices. + if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) { + return false; // oh well. + } + + raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count); + if (!raw_devices) { + return false; + } + + raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST)); + if (raw_device_count == (UINT)-1) { + SDL_free(raw_devices); + raw_devices = NULL; + return false; // oh well. + } + + for (i = 0; i < raw_device_count; i++) { + RID_DEVICE_INFO rdi; + char devName[MAX_PATH] = { 0 }; + UINT rdiSize = sizeof(rdi); + UINT nameSize = SDL_arraysize(devName); + DEVINST devNode; + char devVidPidString[32]; + int j; + + rdi.cbSize = sizeof(rdi); + + if ((raw_devices[i].dwType != RIM_TYPEHID) || + (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == ((UINT)-1)) || + (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) == ((UINT)-1)) || + (SDL_strstr(devName, "IG_") == NULL)) { + // Skip non-XInput devices + continue; + } + + // First check for a simple VID/PID match. This will work for Xbox 360 controllers. + if (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == vidpid) { + SDL_free(raw_devices); + return true; + } + + /* For Xbox One controllers, Microsoft doesn't propagate the VID/PID down to the HID stack. + * We'll have to walk the device tree upwards searching for a match for our VID/PID. */ + + // Make sure the device interface string is something we know how to parse + // Example: \\?\HID#VID_045E&PID_02FF&IG_00#9&2c203035&2&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} + if ((SDL_strstr(devName, "\\\\?\\") != devName) || (SDL_strstr(devName, "#{") == NULL)) { + continue; + } + + // Unescape the backslashes in the string and terminate before the GUID portion + for (j = 0; devName[j] != '\0'; j++) { + if (devName[j] == '#') { + if (devName[j + 1] == '{') { + devName[j] = '\0'; + break; + } else { + devName[j] = '\\'; + } + } + } + + /* We'll be left with a string like this: \\?\HID\VID_045E&PID_02FF&IG_00\9&2c203035&2&0000 + * Simply skip the \\?\ prefix and we'll have a properly formed device instance ID */ + if (CM_Locate_DevNodeA(&devNode, &devName[4], CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) { + continue; + } + + (void)SDL_snprintf(devVidPidString, sizeof(devVidPidString), "VID_%04X&PID_%04X", vendor, product); + + while (CM_Get_Parent(&devNode, devNode, 0) == CR_SUCCESS) { + char deviceId[MAX_DEVICE_ID_LEN]; + + if ((CM_Get_Device_IDA(devNode, deviceId, SDL_arraysize(deviceId), 0) == CR_SUCCESS) && + (SDL_strstr(deviceId, devVidPidString) != NULL)) { + // The VID/PID matched a parent device + SDL_free(raw_devices); + return true; + } + } + } + + SDL_free(raw_devices); +#endif // SDL_JOYSTICK_XINPUT || SDL_JOYSTICK_RAWINPUT + + return false; +} + +static void WGI_LoadRawGameControllerStatics(void) +{ + HRESULT hr; + HSTRING_HEADER class_name_header; + HSTRING class_name; + + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RawGameController, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RawGameController), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, (void **)&wgi.controller_statics); + if (!SUCCEEDED(hr)) { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRawGameControllerStatics", hr); + } + } +} + +static void WGI_LoadOtherControllerStatics(void) +{ + HRESULT hr; + HSTRING_HEADER class_name_header; + HSTRING class_name; + + if (!wgi.arcade_stick_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_ArcadeStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_ArcadeStick), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, (void **)&wgi.arcade_stick_statics); + if (SUCCEEDED(hr)) { + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_QueryInterface(wgi.arcade_stick_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, (void **)&wgi.arcade_stick_statics2); + } else { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IArcadeStickStatics", hr); + } + } + } + + if (!wgi.flight_stick_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_FlightStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_FlightStick), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, (void **)&wgi.flight_stick_statics); + if (!SUCCEEDED(hr)) { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IFlightStickStatics", hr); + } + } + } + + if (!wgi.gamepad_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_Gamepad), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, (void **)&wgi.gamepad_statics); + if (SUCCEEDED(hr)) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_QueryInterface(wgi.gamepad_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, (void **)&wgi.gamepad_statics2); + } else { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IGamepadStatics", hr); + } + } + } + + if (!wgi.racing_wheel_statics) { + hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RacingWheel, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RacingWheel), &class_name_header, &class_name); + if (SUCCEEDED(hr)) { + hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, (void **)&wgi.racing_wheel_statics); + if (SUCCEEDED(hr)) { + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_QueryInterface(wgi.racing_wheel_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, (void **)&wgi.racing_wheel_statics2); + } else { + WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRacingWheelStatics", hr); + } + } + } +} + +static SDL_JoystickType GetGameControllerType(__x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller) +{ + __x_ABI_CWindows_CGaming_CInput_CIArcadeStick *arcade_stick = NULL; + __x_ABI_CWindows_CGaming_CInput_CIFlightStick *flight_stick = NULL; + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad = NULL; + __x_ABI_CWindows_CGaming_CInput_CIRacingWheel *racing_wheel = NULL; + + /* Wait to initialize these interfaces until we need them. + * Initializing the gamepad interface will switch Bluetooth PS4 controllers into enhanced mode, breaking DirectInput + */ + WGI_LoadOtherControllerStatics(); + + if (wgi.gamepad_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, game_controller, &gamepad)) && gamepad) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad); + return SDL_JOYSTICK_TYPE_GAMEPAD; + } + + if (wgi.arcade_stick_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_FromGameController(wgi.arcade_stick_statics2, game_controller, &arcade_stick)) && arcade_stick) { + __x_ABI_CWindows_CGaming_CInput_CIArcadeStick_Release(arcade_stick); + return SDL_JOYSTICK_TYPE_ARCADE_STICK; + } + + if (wgi.flight_stick_statics && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_FromGameController(wgi.flight_stick_statics, game_controller, &flight_stick)) && flight_stick) { + __x_ABI_CWindows_CGaming_CInput_CIFlightStick_Release(flight_stick); + return SDL_JOYSTICK_TYPE_FLIGHT_STICK; + } + + if (wgi.racing_wheel_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_FromGameController(wgi.racing_wheel_statics2, game_controller, &racing_wheel)) && racing_wheel) { + __x_ABI_CWindows_CGaming_CInput_CIRacingWheel_Release(racing_wheel); + return SDL_JOYSTICK_TYPE_WHEEL; + } + + return SDL_JOYSTICK_TYPE_UNKNOWN; +} + +typedef struct RawGameControllerDelegate +{ + __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController iface; + SDL_AtomicInt refcount; +} RawGameControllerDelegate; + +static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *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___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController)) { + *ppvObject = This; + __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController_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_CRawGameControllerVtbl_AddRef(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This) +{ + RawGameControllerDelegate *self = (RawGameControllerDelegate *)This; + return SDL_AddAtomicInt(&self->refcount, 1) + 1UL; +} + +static ULONG STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_Release(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This) +{ + RawGameControllerDelegate *self = (RawGameControllerDelegate *)This; + int rc = SDL_AddAtomicInt(&self->refcount, -1) - 1; + // Should never free the static delegate objects + SDL_assert(rc > 0); + return rc; +} + +static int GetSteamVirtualGamepadSlot(__x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller, Uint16 vendor_id, Uint16 product_id) +{ + int slot = -1; + + if (vendor_id == USB_VENDOR_VALVE && + product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL; + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2); + if (SUCCEEDED(hr)) { + HSTRING hString; + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_NonRoamableId(controller2, &hString); + if (SUCCEEDED(hr)) { + PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL); + if (string) { + char *id = WIN_StringToUTF8W(string); + if (id) { + (void)SDL_sscanf(id, "{wgi/nrid/:steam-%*X&%*X&%*X#%d#%*u}", &slot); + SDL_free(id); + } + } + wgi.WindowsDeleteString(hString); + } + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2); + } + } + return slot; +} + +static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e) +{ + HRESULT hr; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL; + + SDL_LockJoysticks(); + + // We can get delayed calls to InvokeAdded() after WGI_JoystickQuit() + if (SDL_JoysticksQuitting() || !SDL_JoysticksInitialized()) { + SDL_UnlockJoysticks(); + return S_OK; + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller); + if (SUCCEEDED(hr)) { + char *name = NULL; + Uint16 bus = SDL_HARDWARE_BUS_USB; + Uint16 vendor = 0; + Uint16 product = 0; + Uint16 version = 0; + SDL_JoystickType type = SDL_JOYSTICK_TYPE_UNKNOWN; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL; + __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller = NULL; + bool ignore_joystick = false; + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareVendorId(controller, &vendor); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareProductId(controller, &product); + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&game_controller); + if (SUCCEEDED(hr)) { + boolean wireless = 0; + hr = __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(game_controller, &wireless); + if (SUCCEEDED(hr) && wireless) { + bus = SDL_HARDWARE_BUS_BLUETOOTH; + + // Fixup for Wireless Xbox 360 Controller + if (product == 0) { + vendor = USB_VENDOR_MICROSOFT; + product = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; + } + } + + __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(game_controller); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2); + if (SUCCEEDED(hr)) { + HSTRING hString; + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_DisplayName(controller2, &hString); + if (SUCCEEDED(hr)) { + PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL); + if (string) { + name = WIN_StringToUTF8W(string); + } + wgi.WindowsDeleteString(hString); + } + __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2); + } + if (!name) { + name = SDL_strdup(""); + } + + if (!ignore_joystick && SDL_ShouldIgnoreJoystick(vendor, product, version, name)) { + ignore_joystick = true; + } + + if (!ignore_joystick && SDL_JoystickHandledByAnotherDriver(&SDL_WGI_JoystickDriver, vendor, product, version, name)) { + ignore_joystick = true; + } + + if (!ignore_joystick && SDL_IsXInputDevice(vendor, product, name)) { + // This hasn't been detected by the RAWINPUT driver yet, but it will be picked up later. + ignore_joystick = true; + } + + if (!ignore_joystick) { + // New device, add it + WindowsGamingInputControllerState *controllers = SDL_realloc(wgi.controllers, sizeof(wgi.controllers[0]) * (wgi.controller_count + 1)); + if (controllers) { + WindowsGamingInputControllerState *state = &controllers[wgi.controller_count]; + SDL_JoystickID joystickID = SDL_GetNextObjectID(); + + if (game_controller) { + type = GetGameControllerType(game_controller); + } + + SDL_zerop(state); + state->instance_id = joystickID; + state->controller = controller; + state->name = name; + state->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, name, 'w', (Uint8)type); + state->type = type; + state->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(controller, vendor, product); + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(controller); + + ++wgi.controller_count; + wgi.controllers = controllers; + + SDL_PrivateJoystickAdded(joystickID); + } else { + SDL_free(name); + } + } else { + SDL_free(name); + } + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller); + } + + SDL_UnlockJoysticks(); + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeRemoved(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e) +{ + HRESULT hr; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL; + + SDL_LockJoysticks(); + + // Can we get delayed calls to InvokeRemoved() after WGI_JoystickQuit()? + if (!SDL_JoysticksInitialized()) { + SDL_UnlockJoysticks(); + return S_OK; + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller); + if (SUCCEEDED(hr)) { + int i; + + for (i = 0; i < wgi.controller_count; i++) { + if (wgi.controllers[i].controller == controller) { + WindowsGamingInputControllerState *state = &wgi.controllers[i]; + SDL_JoystickID joystickID = state->instance_id; + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(state->controller); + + SDL_free(state->name); + + --wgi.controller_count; + if (i < wgi.controller_count) { + SDL_memmove(&wgi.controllers[i], &wgi.controllers[i + 1], (wgi.controller_count - i) * sizeof(wgi.controllers[i])); + } + + SDL_PrivateJoystickRemoved(joystickID); + break; + } + } + + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller); + } + + SDL_UnlockJoysticks(); + + 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) // formal parameter 3 different from declaration (a more specific warning added in VS 2022), when using older buggy WGI headers +#endif + +static __FIEventHandler_1_Windows__CGaming__CInput__CRawGameControllerVtbl controller_added_vtbl = { + IEventHandler_CRawGameControllerVtbl_QueryInterface, + IEventHandler_CRawGameControllerVtbl_AddRef, + IEventHandler_CRawGameControllerVtbl_Release, + IEventHandler_CRawGameControllerVtbl_InvokeAdded +}; +static RawGameControllerDelegate controller_added = { + { &controller_added_vtbl }, + { 1 } +}; + +static __FIEventHandler_1_Windows__CGaming__CInput__CRawGameControllerVtbl controller_removed_vtbl = { + IEventHandler_CRawGameControllerVtbl_QueryInterface, + IEventHandler_CRawGameControllerVtbl_AddRef, + IEventHandler_CRawGameControllerVtbl_Release, + IEventHandler_CRawGameControllerVtbl_InvokeRemoved +}; +static RawGameControllerDelegate controller_removed = { + { &controller_removed_vtbl }, + { 1 } +}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static bool WGI_JoystickInit(void) +{ + HRESULT hr; + + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) { + return true; + } + + if (FAILED(WIN_RoInitialize())) { + return SDL_SetError("RoInitialize() failed"); + } + +#define RESOLVE(x) wgi.x = (x##_t)WIN_LoadComBaseFunction(#x); if (!wgi.x) return WIN_SetError("GetProcAddress failed for " #x); + RESOLVE(CoIncrementMTAUsage); + RESOLVE(RoGetActivationFactory); + RESOLVE(WindowsCreateStringReference); + RESOLVE(WindowsDeleteString); + RESOLVE(WindowsGetStringRawBuffer); +#undef RESOLVE + + { + /* There seems to be a bug in Windows where a dependency of WGI can be unloaded from memory prior to WGI itself. + * This results in Windows_Gaming_Input!GameController::~GameController() invoking an unloaded DLL and crashing. + * As a workaround, we will keep a reference to the MTA to prevent COM from unloading DLLs later. + * See https://github.com/libsdl-org/SDL/issues/5552 for more details. + */ + static CO_MTA_USAGE_COOKIE cookie = NULL; + if (!cookie) { + hr = wgi.CoIncrementMTAUsage(&cookie); + if (FAILED(hr)) { + return WIN_SetErrorFromHRESULT("CoIncrementMTAUsage() failed", hr); + } + } + } + + WGI_LoadRawGameControllerStatics(); + + if (wgi.controller_statics) { + __FIVectorView_1_Windows__CGaming__CInput__CRawGameController *controllers; + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerAdded(wgi.controller_statics, &controller_added.iface, &wgi.controller_added_token); + if (!SUCCEEDED(hr)) { + WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerAdded failed", hr); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerRemoved(wgi.controller_statics, &controller_removed.iface, &wgi.controller_removed_token); + if (!SUCCEEDED(hr)) { + WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerRemoved failed", hr); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_get_RawGameControllers(wgi.controller_statics, &controllers); + if (SUCCEEDED(hr)) { + unsigned i, count = 0; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_get_Size(controllers, &count); + if (SUCCEEDED(hr)) { + for (i = 0; i < count; ++i) { + __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_GetAt(controllers, i, &controller); + if (SUCCEEDED(hr) && controller) { + IEventHandler_CRawGameControllerVtbl_InvokeAdded(&controller_added.iface, NULL, controller); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller); + } + } + } + + __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_Release(controllers); + } + } + + return true; +} + +static int WGI_JoystickGetCount(void) +{ + return wgi.controller_count; +} + +static void WGI_JoystickDetect(void) +{ +} + +static bool WGI_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + // We don't override any other drivers + return false; +} + +static const char *WGI_JoystickGetDeviceName(int device_index) +{ + return wgi.controllers[device_index].name; +} + +static const char *WGI_JoystickGetDevicePath(int device_index) +{ + return NULL; +} + +static int WGI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return wgi.controllers[device_index].steam_virtual_gamepad_slot; +} + +static int WGI_JoystickGetDevicePlayerIndex(int device_index) +{ + return false; +} + +static void WGI_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID WGI_JoystickGetDeviceGUID(int device_index) +{ + return wgi.controllers[device_index].guid; +} + +static SDL_JoystickID WGI_JoystickGetDeviceInstanceID(int device_index) +{ + return wgi.controllers[device_index].instance_id; +} + +static bool WGI_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + WindowsGamingInputControllerState *state = &wgi.controllers[device_index]; + struct joystick_hwdata *hwdata; + boolean wireless = false; + + hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata)); + if (!hwdata) { + return false; + } + joystick->hwdata = hwdata; + + hwdata->controller = state->controller; + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(hwdata->controller); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&hwdata->game_controller); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, (void **)&hwdata->battery); + + if (wgi.gamepad_statics2) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, hwdata->game_controller, &hwdata->gamepad); + } + + if (hwdata->game_controller) { + __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(hwdata->game_controller, &wireless); + } + + // Initialize the joystick capabilities + if (wireless) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } else { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; + } + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_ButtonCount(hwdata->controller, &joystick->nbuttons); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_AxisCount(hwdata->controller, &joystick->naxes); + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_SwitchCount(hwdata->controller, &joystick->nhats); + + if (hwdata->gamepad) { + // FIXME: Can WGI even tell us if trigger rumble is supported? + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); + } + return true; +} + +static bool WGI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + + if (hwdata->gamepad) { + HRESULT hr; + + // Note: reusing partially filled vibration data struct + hwdata->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; + hwdata->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration); + if (SUCCEEDED(hr)) { + return true; + } else { + return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr); + } + } else { + return SDL_Unsupported(); + } +} + +static bool WGI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + + if (hwdata->gamepad) { + HRESULT hr; + + // Note: reusing partially filled vibration data struct + hwdata->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; + hwdata->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration); + if (SUCCEEDED(hr)) { + return true; + } else { + return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr); + } + } else { + return SDL_Unsupported(); + } +} + +static bool WGI_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool WGI_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool WGI_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static Uint8 ConvertHatValue(__x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition value) +{ + switch (value) { + case GameControllerSwitchPosition_Up: + return SDL_HAT_UP; + case GameControllerSwitchPosition_UpRight: + return SDL_HAT_RIGHTUP; + case GameControllerSwitchPosition_Right: + return SDL_HAT_RIGHT; + case GameControllerSwitchPosition_DownRight: + return SDL_HAT_RIGHTDOWN; + case GameControllerSwitchPosition_Down: + return SDL_HAT_DOWN; + case GameControllerSwitchPosition_DownLeft: + return SDL_HAT_LEFTDOWN; + case GameControllerSwitchPosition_Left: + return SDL_HAT_LEFT; + case GameControllerSwitchPosition_UpLeft: + return SDL_HAT_LEFTUP; + default: + return SDL_HAT_CENTERED; + } +} + +static void WGI_JoystickUpdate(SDL_Joystick *joystick) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + HRESULT hr; + UINT32 nbuttons = SDL_min(joystick->nbuttons, SDL_MAX_UINT8); + boolean *buttons = NULL; + UINT32 nhats = SDL_min(joystick->nhats, SDL_MAX_UINT8); + __x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition *hats = NULL; + UINT32 naxes = SDL_min(joystick->naxes, SDL_MAX_UINT8); + DOUBLE *axes = NULL; + UINT64 timestamp; + + if (nbuttons > 0) { + buttons = SDL_stack_alloc(boolean, nbuttons); + } + if (nhats > 0) { + hats = SDL_stack_alloc(__x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition, nhats); + } + if (naxes > 0) { + axes = SDL_stack_alloc(DOUBLE, naxes); + } + + hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_GetCurrentReading(hwdata->controller, nbuttons, buttons, nhats, hats, naxes, axes, ×tamp); + if (SUCCEEDED(hr) && (!timestamp || timestamp != hwdata->timestamp)) { + UINT32 i; + bool all_zero = false; + + hwdata->timestamp = timestamp; + + // The axes are all zero when the application loses focus + if (naxes > 0) { + all_zero = true; + for (i = 0; i < naxes; ++i) { + if (axes[i] != 0.0f) { + all_zero = false; + break; + } + } + } + if (all_zero) { + SDL_PrivateJoystickForceRecentering(joystick); + } else { + // FIXME: What units are the timestamp we get from GetCurrentReading()? + timestamp = SDL_GetTicksNS(); + for (i = 0; i < nbuttons; ++i) { + SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, buttons[i]); + } + for (i = 0; i < nhats; ++i) { + SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, ConvertHatValue(hats[i])); + } + for (i = 0; i < naxes; ++i) { + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, (Sint16)((int)(axes[i] * 65535) - 32768)); + } + } + } + + SDL_stack_free(buttons); + SDL_stack_free(hats); + SDL_stack_free(axes); + + if (hwdata->battery) { + __x_ABI_CWindows_CDevices_CPower_CIBatteryReport *report = NULL; + + hr = __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_TryGetBatteryReport(hwdata->battery, &report); + if (SUCCEEDED(hr) && report) { + SDL_PowerState state = SDL_POWERSTATE_UNKNOWN; + int percent = 0; + __x_ABI_CWindows_CSystem_CPower_CBatteryStatus status; + int full_capacity = 0, curr_capacity = 0; + __FIReference_1_int *full_capacityP, *curr_capacityP; + + hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_Status(report, &status); + if (SUCCEEDED(hr)) { + switch (status) { + case BatteryStatus_NotPresent: + state = SDL_POWERSTATE_NO_BATTERY; + break; + case BatteryStatus_Discharging: + state = SDL_POWERSTATE_ON_BATTERY; + break; + case BatteryStatus_Idle: + state = SDL_POWERSTATE_CHARGED; + break; + case BatteryStatus_Charging: + state = SDL_POWERSTATE_CHARGING; + break; + default: + state = SDL_POWERSTATE_UNKNOWN; + break; + } + } + + hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_FullChargeCapacityInMilliwattHours(report, &full_capacityP); + if (SUCCEEDED(hr)) { + __FIReference_1_int_get_Value(full_capacityP, &full_capacity); + __FIReference_1_int_Release(full_capacityP); + } + + hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_RemainingCapacityInMilliwattHours(report, &curr_capacityP); + if (SUCCEEDED(hr)) { + __FIReference_1_int_get_Value(curr_capacityP, &curr_capacity); + __FIReference_1_int_Release(curr_capacityP); + } + + if (full_capacity > 0) { + percent = (int)SDL_roundf(((float)curr_capacity / full_capacity) * 100.0f); + } + + SDL_SendJoystickPowerInfo(joystick, state, percent); + + __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_Release(report); + } + } +} + +static void WGI_JoystickClose(SDL_Joystick *joystick) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + + if (hwdata) { + if (hwdata->controller) { + __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(hwdata->controller); + } + if (hwdata->game_controller) { + __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(hwdata->game_controller); + } + if (hwdata->battery) { + __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_Release(hwdata->battery); + } + if (hwdata->gamepad) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(hwdata->gamepad); + } + SDL_free(hwdata); + } + joystick->hwdata = NULL; +} + +static void WGI_JoystickQuit(void) +{ + if (wgi.controller_statics) { + while (wgi.controller_count > 0) { + IEventHandler_CRawGameControllerVtbl_InvokeRemoved(&controller_removed.iface, NULL, wgi.controllers[wgi.controller_count - 1].controller); + } + if (wgi.controllers) { + SDL_free(wgi.controllers); + } + + if (wgi.arcade_stick_statics) { + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_Release(wgi.arcade_stick_statics); + } + if (wgi.arcade_stick_statics2) { + __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_Release(wgi.arcade_stick_statics2); + } + if (wgi.flight_stick_statics) { + __x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_Release(wgi.flight_stick_statics); + } + if (wgi.gamepad_statics) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi.gamepad_statics); + } + if (wgi.gamepad_statics2) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_Release(wgi.gamepad_statics2); + } + if (wgi.racing_wheel_statics) { + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_Release(wgi.racing_wheel_statics); + } + if (wgi.racing_wheel_statics2) { + __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_Release(wgi.racing_wheel_statics2); + } + + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerAdded(wgi.controller_statics, wgi.controller_added_token); + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerRemoved(wgi.controller_statics, wgi.controller_removed_token); + __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_Release(wgi.controller_statics); + } + + WIN_RoUninitialize(); + + SDL_zero(wgi); +} + +static bool WGI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_WGI_JoystickDriver = { + WGI_JoystickInit, + WGI_JoystickGetCount, + WGI_JoystickDetect, + WGI_JoystickIsDevicePresent, + WGI_JoystickGetDeviceName, + WGI_JoystickGetDevicePath, + WGI_JoystickGetDeviceSteamVirtualGamepadSlot, + WGI_JoystickGetDevicePlayerIndex, + WGI_JoystickSetDevicePlayerIndex, + WGI_JoystickGetDeviceGUID, + WGI_JoystickGetDeviceInstanceID, + WGI_JoystickOpen, + WGI_JoystickRumble, + WGI_JoystickRumbleTriggers, + WGI_JoystickSetLED, + WGI_JoystickSendEffect, + WGI_JoystickSetSensorsEnabled, + WGI_JoystickUpdate, + WGI_JoystickClose, + WGI_JoystickQuit, + WGI_JoystickGetGamepadMapping +}; + +#endif // SDL_JOYSTICK_WGI diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c new file mode 100644 index 0000000..e7fbfcb --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c @@ -0,0 +1,693 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-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. +*/ +#include "SDL_internal.h" + +#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT) + +/* DirectInput joystick driver; written by Glenn Maynard, based on Andrei de + * A. Formiga's WINMM driver. + * + * Hats and sliders are completely untested; the app I'm writing this for mostly + * doesn't use them and I don't own any joysticks with them. + * + * We don't bother to use event notification here. It doesn't seem to work + * with polled devices, and it's fine to call IDirectInputDevice8_GetDeviceData and + * let it return 0 events. */ + +#include "../SDL_sysjoystick.h" +#include "../../thread/SDL_systhread.h" +#include "../../core/windows/SDL_windows.h" +#include "../../core/windows/SDL_hid.h" +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +#include +#endif + +#define INITGUID // Only set here, if set twice will cause mingw32 to break. +#include "SDL_windowsjoystick_c.h" +#include "SDL_dinputjoystick_c.h" +#include "SDL_xinputjoystick_c.h" +#include "SDL_rawinputjoystick_c.h" + +#include "../../haptic/windows/SDL_dinputhaptic_c.h" // For haptic hot plugging + +#ifndef DEVICE_NOTIFY_WINDOW_HANDLE +#define DEVICE_NOTIFY_WINDOW_HANDLE 0x00000000 +#endif + +// local variables +static bool s_bJoystickThread = false; +static SDL_Condition *s_condJoystickThread = NULL; +static SDL_Mutex *s_mutexJoyStickEnum = NULL; +static SDL_Thread *s_joystickThread = NULL; +static bool s_bJoystickThreadQuit = false; +static Uint64 s_lastDeviceChange = 0; +static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } }; + +JoyStick_DeviceData *SYS_Joystick; // array to hold joystick ID values + + +static bool WindowsDeviceChanged(void) +{ + return (s_lastDeviceChange != WIN_GetLastDeviceNotification()); +} + +static void SetWindowsDeviceChanged(void) +{ + s_lastDeviceChange = 0; +} + +void WINDOWS_RAWINPUTEnabledChanged(void) +{ + SetWindowsDeviceChanged(); +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +typedef struct +{ + HRESULT coinitialized; + WNDCLASSEX wincl; + HWND messageWindow; + HDEVNOTIFY hNotify; +} SDL_DeviceNotificationData; + +#define IDT_SDL_DEVICE_CHANGE_TIMER_1 1200 +#define IDT_SDL_DEVICE_CHANGE_TIMER_2 1201 + +// windowproc for our joystick detect thread message only window, to detect any USB device addition/removal +static LRESULT CALLBACK SDL_PrivateJoystickDetectProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_DEVICECHANGE: + switch (wParam) { + case DBT_DEVICEARRIVAL: + case DBT_DEVICEREMOVECOMPLETE: + if (((DEV_BROADCAST_HDR *)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + // notify 300ms and 2 seconds later to ensure all APIs have updated status + SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_1, 300, NULL); + SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_2, 2000, NULL); + } + break; + } + return true; + case WM_TIMER: + if (wParam == IDT_SDL_DEVICE_CHANGE_TIMER_1 || + wParam == IDT_SDL_DEVICE_CHANGE_TIMER_2) { + KillTimer(hwnd, wParam); + SetWindowsDeviceChanged(); + return true; + } + break; + } + +#ifdef SDL_JOYSTICK_RAWINPUT + return CallWindowProc(RAWINPUT_WindowProc, hwnd, msg, wParam, lParam); +#else + return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam); +#endif +} + +static void SDL_CleanupDeviceNotification(SDL_DeviceNotificationData *data) +{ +#ifdef SDL_JOYSTICK_RAWINPUT + RAWINPUT_UnregisterNotifications(); +#endif + + if (data->hNotify) { + UnregisterDeviceNotification(data->hNotify); + } + + if (data->messageWindow) { + DestroyWindow(data->messageWindow); + } + + UnregisterClass(data->wincl.lpszClassName, data->wincl.hInstance); + + if (data->coinitialized == S_OK) { + WIN_CoUninitialize(); + } +} + +static bool SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data) +{ + DEV_BROADCAST_DEVICEINTERFACE dbh; + + SDL_zerop(data); + + data->coinitialized = WIN_CoInitialize(); + + data->wincl.hInstance = GetModuleHandle(NULL); + data->wincl.lpszClassName = TEXT("Message"); + data->wincl.lpfnWndProc = SDL_PrivateJoystickDetectProc; // This function is called by windows + data->wincl.cbSize = sizeof(WNDCLASSEX); + + if (!RegisterClassEx(&data->wincl)) { + WIN_SetError("Failed to create register class for joystick autodetect"); + SDL_CleanupDeviceNotification(data); + return false; + } + + data->messageWindow = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); + if (!data->messageWindow) { + WIN_SetError("Failed to create message window for joystick autodetect"); + SDL_CleanupDeviceNotification(data); + return false; + } + + SDL_zero(dbh); + dbh.dbcc_size = sizeof(dbh); + dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + dbh.dbcc_classguid = GUID_DEVINTERFACE_HID; + + data->hNotify = RegisterDeviceNotification(data->messageWindow, &dbh, DEVICE_NOTIFY_WINDOW_HANDLE); + if (!data->hNotify) { + WIN_SetError("Failed to create notify device for joystick autodetect"); + SDL_CleanupDeviceNotification(data); + return false; + } + +#ifdef SDL_JOYSTICK_RAWINPUT + RAWINPUT_RegisterNotifications(data->messageWindow); +#endif + return true; +} + +static bool SDL_WaitForDeviceNotification(SDL_DeviceNotificationData *data, SDL_Mutex *mutex) +{ + MSG msg; + int lastret = 1; + + if (!data->messageWindow) { + return false; // device notifications require a window + } + + SDL_UnlockMutex(mutex); + while (lastret > 0 && !WindowsDeviceChanged()) { + lastret = GetMessage(&msg, NULL, 0, 0); // WM_QUIT causes return value of 0 + if (lastret > 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + SDL_LockMutex(mutex); + return (lastret != -1); +} + +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +static SDL_DeviceNotificationData s_notification_data; +#endif + +// Function/thread to scan the system for joysticks. +static int SDLCALL SDL_JoystickThread(void *_data) +{ +#ifdef SDL_JOYSTICK_XINPUT + bool bOpenedXInputDevices[XUSER_MAX_COUNT]; + SDL_zeroa(bOpenedXInputDevices); +#endif + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (!SDL_CreateDeviceNotification(&s_notification_data)) { + return 0; + } +#endif + + SDL_LockMutex(s_mutexJoyStickEnum); + while (s_bJoystickThreadQuit == false) { +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (SDL_WaitForDeviceNotification(&s_notification_data, s_mutexJoyStickEnum) == false) { +#else + { +#endif +#ifdef SDL_JOYSTICK_XINPUT + // WM_DEVICECHANGE not working, poll for new XINPUT controllers + SDL_WaitConditionTimeout(s_condJoystickThread, s_mutexJoyStickEnum, 1000); + if (SDL_XINPUT_Enabled()) { + // scan for any change in XInput devices + Uint8 userId; + for (userId = 0; userId < XUSER_MAX_COUNT; userId++) { + XINPUT_CAPABILITIES capabilities; + const DWORD result = XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities); + const bool available = (result == ERROR_SUCCESS); + if (bOpenedXInputDevices[userId] != available) { + SetWindowsDeviceChanged(); + bOpenedXInputDevices[userId] = available; + } + } + } +#else + // WM_DEVICECHANGE not working, no XINPUT, no point in keeping thread alive + break; +#endif // SDL_JOYSTICK_XINPUT + } + } + + SDL_UnlockMutex(s_mutexJoyStickEnum); + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_CleanupDeviceNotification(&s_notification_data); +#endif + + return 1; +} + +// spin up the thread to detect hotplug of devices +static bool SDL_StartJoystickThread(void) +{ + s_mutexJoyStickEnum = SDL_CreateMutex(); + if (!s_mutexJoyStickEnum) { + return false; + } + + s_condJoystickThread = SDL_CreateCondition(); + if (!s_condJoystickThread) { + return false; + } + + s_bJoystickThreadQuit = false; + s_joystickThread = SDL_CreateThread(SDL_JoystickThread, "SDL_joystick", NULL); + if (!s_joystickThread) { + return false; + } + return true; +} + +static void SDL_StopJoystickThread(void) +{ + if (!s_joystickThread) { + return; + } + + SDL_LockMutex(s_mutexJoyStickEnum); + s_bJoystickThreadQuit = true; + SDL_BroadcastCondition(s_condJoystickThread); // signal the joystick thread to quit + SDL_UnlockMutex(s_mutexJoyStickEnum); + PostThreadMessage((DWORD)SDL_GetThreadID(s_joystickThread), WM_QUIT, 0, 0); + + // Unlock joysticks while the joystick thread finishes processing messages + SDL_AssertJoysticksLocked(); + SDL_UnlockJoysticks(); + SDL_WaitThread(s_joystickThread, NULL); // wait for it to bugger off + SDL_LockJoysticks(); + + SDL_DestroyCondition(s_condJoystickThread); + s_condJoystickThread = NULL; + + SDL_DestroyMutex(s_mutexJoyStickEnum); + s_mutexJoyStickEnum = NULL; + + s_joystickThread = NULL; +} + +void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device) +{ + device->send_add_event = true; + device->nInstanceID = SDL_GetNextObjectID(); + device->pNext = SYS_Joystick; + SYS_Joystick = device; +} + +void WINDOWS_JoystickDetect(void); +void WINDOWS_JoystickQuit(void); + +static bool WINDOWS_JoystickInit(void) +{ + if (!SDL_XINPUT_JoystickInit()) { + WINDOWS_JoystickQuit(); + return false; + } + + if (!SDL_DINPUT_JoystickInit()) { + WINDOWS_JoystickQuit(); + return false; + } + + WIN_InitDeviceNotification(); + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + s_bJoystickThread = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_THREAD, true); + if (s_bJoystickThread) { + if (!SDL_StartJoystickThread()) { + return false; + } + } else { + if (!SDL_CreateDeviceNotification(&s_notification_data)) { + return false; + } + } +#endif + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + // On Xbox, force create the joystick thread for device detection (since other methods don't work + s_bJoystickThread = true; + if (!SDL_StartJoystickThread()) { + return false; + } +#endif + + SetWindowsDeviceChanged(); // force a scan of the system for joysticks this first time + + WINDOWS_JoystickDetect(); + + return true; +} + +// return the number of joysticks that are connected right now +static int WINDOWS_JoystickGetCount(void) +{ + int nJoysticks = 0; + JoyStick_DeviceData *device = SYS_Joystick; + while (device) { + nJoysticks++; + device = device->pNext; + } + + return nJoysticks; +} + +// detect any new joysticks being inserted into the system +void WINDOWS_JoystickDetect(void) +{ + JoyStick_DeviceData *pCurList = NULL; + + // only enum the devices if the joystick thread told us something changed + if (!WindowsDeviceChanged()) { + return; // thread hasn't signaled, nothing to do right now. + } + + if (s_mutexJoyStickEnum) { + SDL_LockMutex(s_mutexJoyStickEnum); + } + + s_lastDeviceChange = WIN_GetLastDeviceNotification(); + + pCurList = SYS_Joystick; + SYS_Joystick = NULL; + + // Look for DirectInput joysticks, wheels, head trackers, gamepads, etc.. + SDL_DINPUT_JoystickDetect(&pCurList); + + // Look for XInput devices. Do this last, so they're first in the final list. + SDL_XINPUT_JoystickDetect(&pCurList); + + if (s_mutexJoyStickEnum) { + SDL_UnlockMutex(s_mutexJoyStickEnum); + } + + while (pCurList) { + JoyStick_DeviceData *pListNext = NULL; + + if (!pCurList->bXInputDevice) { +#ifdef SDL_HAPTIC_DINPUT + SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice); +#endif + } + + SDL_PrivateJoystickRemoved(pCurList->nInstanceID); + + pListNext = pCurList->pNext; + SDL_free(pCurList->joystickname); + SDL_free(pCurList); + pCurList = pListNext; + } + + for (pCurList = SYS_Joystick; pCurList; pCurList = pCurList->pNext) { + if (pCurList->send_add_event) { + if (!pCurList->bXInputDevice) { +#ifdef SDL_HAPTIC_DINPUT + SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice); +#endif + } + + SDL_PrivateJoystickAdded(pCurList->nInstanceID); + + pCurList->send_add_event = false; + } + } +} + +static bool WINDOWS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + if (SDL_DINPUT_JoystickPresent(vendor_id, product_id, version)) { + return true; + } + if (SDL_XINPUT_JoystickPresent(vendor_id, product_id, version)) { + return true; + } + return false; +} + +static const char *WINDOWS_JoystickGetDeviceName(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->joystickname; +} + +static const char *WINDOWS_JoystickGetDevicePath(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->path; +} + +static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + if (device->bXInputDevice) { + // The slot for XInput devices can change as controllers are seated + return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId); + } else { + return device->steam_virtual_gamepad_slot; + } +} + +static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->bXInputDevice ? (int)device->XInputUserId : -1; +} + +static void WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +// return the stable device guid for this device index +static SDL_GUID WINDOWS_JoystickGetDeviceGUID(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->guid; +} + +// Function to perform the mapping between current device instance and this joysticks instance id +static SDL_JoystickID WINDOWS_JoystickGetDeviceInstanceID(int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + return device->nInstanceID; +} + +/* Function to open a joystick for use. + The joystick to open is specified by the device index. + This should fill the nbuttons and naxes fields of the joystick structure. + It returns 0, or -1 if there is an error. + */ +static bool WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + JoyStick_DeviceData *device = SYS_Joystick; + int index; + + for (index = device_index; index > 0; index--) { + device = device->pNext; + } + + // allocate memory for system specific hardware data + joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(struct joystick_hwdata)); + if (!joystick->hwdata) { + return false; + } + joystick->hwdata->guid = device->guid; + + if (device->bXInputDevice) { + return SDL_XINPUT_JoystickOpen(joystick, device); + } else { + return SDL_DINPUT_JoystickOpen(joystick, device); + } +} + +static bool WINDOWS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + if (joystick->hwdata->bXInputDevice) { + return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); + } else { + return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble); + } +} + +static bool WINDOWS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static bool WINDOWS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool WINDOWS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool WINDOWS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static void WINDOWS_JoystickUpdate(SDL_Joystick *joystick) +{ + if (!joystick->hwdata) { + return; + } + + if (joystick->hwdata->bXInputDevice) { + SDL_XINPUT_JoystickUpdate(joystick); + } else { + SDL_DINPUT_JoystickUpdate(joystick); + } +} + +// Function to close a joystick after use +static void WINDOWS_JoystickClose(SDL_Joystick *joystick) +{ + if (joystick->hwdata->bXInputDevice) { + SDL_XINPUT_JoystickClose(joystick); + } else { + SDL_DINPUT_JoystickClose(joystick); + } + + SDL_free(joystick->hwdata); +} + +// Function to perform any system-specific joystick related cleanup +void WINDOWS_JoystickQuit(void) +{ + JoyStick_DeviceData *device = SYS_Joystick; + + while (device) { + JoyStick_DeviceData *device_next = device->pNext; + SDL_free(device->joystickname); + SDL_free(device); + device = device_next; + } + SYS_Joystick = NULL; + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (s_bJoystickThread) { + SDL_StopJoystickThread(); + } else { + SDL_CleanupDeviceNotification(&s_notification_data); + } +#endif + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + if (s_bJoystickThread) { + SDL_StopJoystickThread(); + } +#endif + + SDL_DINPUT_JoystickQuit(); + SDL_XINPUT_JoystickQuit(); + + WIN_QuitDeviceNotification(); +} + +static bool WINDOWS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + return false; +} + +SDL_JoystickDriver SDL_WINDOWS_JoystickDriver = { + WINDOWS_JoystickInit, + WINDOWS_JoystickGetCount, + WINDOWS_JoystickDetect, + WINDOWS_JoystickIsDevicePresent, + WINDOWS_JoystickGetDeviceName, + WINDOWS_JoystickGetDevicePath, + WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot, + WINDOWS_JoystickGetDevicePlayerIndex, + WINDOWS_JoystickSetDevicePlayerIndex, + WINDOWS_JoystickGetDeviceGUID, + WINDOWS_JoystickGetDeviceInstanceID, + WINDOWS_JoystickOpen, + WINDOWS_JoystickRumble, + WINDOWS_JoystickRumbleTriggers, + WINDOWS_JoystickSetLED, + WINDOWS_JoystickSendEffect, + WINDOWS_JoystickSetSensorsEnabled, + WINDOWS_JoystickUpdate, + WINDOWS_JoystickClose, + WINDOWS_JoystickQuit, + WINDOWS_JoystickGetGamepadMapping +}; + +#else + +#ifdef SDL_JOYSTICK_RAWINPUT +// The RAWINPUT driver needs the device notification setup above +#error SDL_JOYSTICK_RAWINPUT requires SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT +#endif + +#endif // SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h new file mode 100644 index 0000000..16b9184 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h @@ -0,0 +1,103 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-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. +*/ +#include "SDL_internal.h" + +#include "../SDL_sysjoystick.h" +#include "../../core/windows/SDL_windows.h" +#include "../../core/windows/SDL_directx.h" + +#define MAX_INPUTS 256 // each joystick can have up to 256 inputs + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct JoyStick_DeviceData +{ + SDL_GUID guid; + char *joystickname; + Uint8 send_add_event; + SDL_JoystickID nInstanceID; + bool bXInputDevice; + BYTE SubType; + Uint8 XInputUserId; + DIDEVICEINSTANCE dxdevice; + char path[MAX_PATH]; + int steam_virtual_gamepad_slot; + struct JoyStick_DeviceData *pNext; +} JoyStick_DeviceData; + +extern JoyStick_DeviceData *SYS_Joystick; // array to hold joystick ID values + +typedef enum Type +{ + BUTTON, + AXIS, + HAT +} Type; + +typedef struct input_t +{ + // DirectInput offset for this input type: + DWORD ofs; + + // Button, axis or hat: + Type type; + + // SDL input offset: + Uint8 num; +} input_t; + +// The private structure used to keep track of a joystick +struct joystick_hwdata +{ + SDL_GUID guid; + +#ifdef SDL_JOYSTICK_DINPUT + LPDIRECTINPUTDEVICE8 InputDevice; + DIDEVCAPS Capabilities; + bool buffered; + bool first_update; + input_t Inputs[MAX_INPUTS]; + int NumInputs; + int NumSliders; + bool ff_initialized; + DIEFFECT *ffeffect; + LPDIRECTINPUTEFFECT ffeffect_ref; +#endif + + bool bXInputDevice; // true if this device supports using the xinput API rather than DirectInput + bool bXInputHaptic; // Supports force feedback via XInput. + Uint8 userid; // XInput userid index for this joystick + DWORD dwPacketNumber; +}; + +#ifdef SDL_JOYSTICK_DINPUT +extern const DIDATAFORMAT SDL_c_dfDIJoystick2; +#endif + +extern void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device); + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c new file mode 100644 index 0000000..9f6ce10 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c @@ -0,0 +1,473 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-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. +*/ +#include "SDL_internal.h" + +#include "../SDL_sysjoystick.h" + +#ifdef SDL_JOYSTICK_XINPUT + +#include "SDL_windowsjoystick_c.h" +#include "SDL_xinputjoystick_c.h" +#include "SDL_rawinputjoystick_c.h" +#include "../hidapi/SDL_hidapijoystick_c.h" + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Internal stuff. + */ +static bool s_bXInputEnabled = false; + +bool SDL_XINPUT_Enabled(void) +{ + return s_bXInputEnabled; +} + +bool SDL_XINPUT_JoystickInit(void) +{ + bool enabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, true); + + if (enabled && !WIN_LoadXInputDLL()) { + enabled = false; // oh well. + } + s_bXInputEnabled = enabled; + + return true; +} + +static const char *GetXInputName(const Uint8 userid, BYTE SubType) +{ + static char name[32]; + + switch (SubType) { + case XINPUT_DEVSUBTYPE_GAMEPAD: + (void)SDL_snprintf(name, sizeof(name), "XInput Controller #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_WHEEL: + (void)SDL_snprintf(name, sizeof(name), "XInput Wheel #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_ARCADE_STICK: + (void)SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_FLIGHT_STICK: + (void)SDL_snprintf(name, sizeof(name), "XInput FlightStick #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_DANCE_PAD: + (void)SDL_snprintf(name, sizeof(name), "XInput DancePad #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_GUITAR: + case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE: + case XINPUT_DEVSUBTYPE_GUITAR_BASS: + (void)SDL_snprintf(name, sizeof(name), "XInput Guitar #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_DRUM_KIT: + (void)SDL_snprintf(name, sizeof(name), "XInput DrumKit #%d", 1 + userid); + break; + case XINPUT_DEVSUBTYPE_ARCADE_PAD: + (void)SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%d", 1 + userid); + break; + default: + (void)SDL_snprintf(name, sizeof(name), "XInput Device #%d", 1 + userid); + break; + } + return name; +} + +static bool GetXInputDeviceInfo(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion) +{ + SDL_XINPUT_CAPABILITIES_EX capabilities; + + if (!XINPUTGETCAPABILITIESEX || XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) != ERROR_SUCCESS) { + // Use a generic VID/PID representing an XInput controller + if (pVID) { + *pVID = USB_VENDOR_MICROSOFT; + } + if (pPID) { + *pPID = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; + } + return false; + } + + // Fixup for Wireless Xbox 360 Controller + if (capabilities.ProductId == 0 && capabilities.Capabilities.Flags & XINPUT_CAPS_WIRELESS) { + capabilities.VendorId = USB_VENDOR_MICROSOFT; + capabilities.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; + } + + if (pVID) { + *pVID = capabilities.VendorId; + } + if (pPID) { + *pPID = capabilities.ProductId; + } + if (pVersion) { + *pVersion = capabilities.ProductVersion; + } + return true; +} + +int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid) +{ + SDL_XINPUT_CAPABILITIES_EX capabilities; + + if (XINPUTGETCAPABILITIESEX && + XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) == ERROR_SUCCESS && + capabilities.VendorId == USB_VENDOR_VALVE && + capabilities.ProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + return (int)capabilities.unk2; + } + return -1; +} + +static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext) +{ + const char *name = NULL; + Uint16 vendor = 0; + Uint16 product = 0; + Uint16 version = 0; + JoyStick_DeviceData *pPrevJoystick = NULL; + JoyStick_DeviceData *pNewJoystick = *pContext; + +#ifdef SDL_JOYSTICK_RAWINPUT + if (RAWINPUT_IsEnabled()) { + // The raw input driver handles more than 4 controllers, so prefer that when available + /* We do this check here rather than at the top of SDL_XINPUT_JoystickDetect() because + we need to check XInput state before RAWINPUT gets a hold of the device, otherwise + when a controller is connected via the wireless adapter, it will shut down at the + first subsequent XInput call. This seems like a driver stack bug? + + Reference: https://github.com/libsdl-org/SDL/issues/3468 + */ + return; + } +#endif + + if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN) { + return; + } + + while (pNewJoystick) { + if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) { + // if we are replacing the front of the list then update it + if (pNewJoystick == *pContext) { + *pContext = pNewJoystick->pNext; + } else if (pPrevJoystick) { + pPrevJoystick->pNext = pNewJoystick->pNext; + } + + pNewJoystick->pNext = SYS_Joystick; + SYS_Joystick = pNewJoystick; + return; // already in the list. + } + + pPrevJoystick = pNewJoystick; + pNewJoystick = pNewJoystick->pNext; + } + + name = GetXInputName(userid, SubType); + GetXInputDeviceInfo(userid, &vendor, &product, &version); + if (SDL_ShouldIgnoreJoystick(vendor, product, version, name) || + SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, name)) { + return; + } + + pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData)); + if (!pNewJoystick) { + return; // better luck next time? + } + + pNewJoystick->bXInputDevice = true; + pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, name); + if (!pNewJoystick->joystickname) { + SDL_free(pNewJoystick); + return; // better luck next time? + } + (void)SDL_snprintf(pNewJoystick->path, sizeof(pNewJoystick->path), "XInput#%u", userid); + pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 'x', SubType); + pNewJoystick->SubType = SubType; + pNewJoystick->XInputUserId = userid; + + WINDOWS_AddJoystickDevice(pNewJoystick); +} + +void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) +{ + int iuserid; + + if (!s_bXInputEnabled) { + return; + } + + // iterate in reverse, so these are in the final list in ascending numeric order. + for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) { + const Uint8 userid = (Uint8)iuserid; + XINPUT_CAPABILITIES capabilities; + if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) { + AddXInputDevice(userid, capabilities.SubType, pContext); + } + } +} + +bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version) +{ + int iuserid; + + if (!s_bXInputEnabled) { + return false; + } + + // iterate in reverse, so these are in the final list in ascending numeric order. + for (iuserid = 0; iuserid < XUSER_MAX_COUNT; ++iuserid) { + const Uint8 userid = (Uint8)iuserid; + Uint16 slot_vendor; + Uint16 slot_product; + Uint16 slot_version; + if (GetXInputDeviceInfo(userid, &slot_vendor, &slot_product, &slot_version)) { + if (vendor == slot_vendor && product == slot_product && version == slot_version) { + return true; + } + } + } + return false; +} + +bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) +{ + const Uint8 userId = joystickdevice->XInputUserId; + XINPUT_CAPABILITIES capabilities; + XINPUT_VIBRATION state; + + SDL_assert(s_bXInputEnabled); + SDL_assert(XINPUTGETCAPABILITIES); + SDL_assert(XINPUTSETSTATE); + SDL_assert(userId < XUSER_MAX_COUNT); + + joystick->hwdata->bXInputDevice = true; + + if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) { + SDL_free(joystick->hwdata); + joystick->hwdata = NULL; + return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?"); + } + SDL_zero(state); + joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS); + joystick->hwdata->userid = userId; + + // The XInput API has a hard coded button/axis mapping, so we just match it + joystick->naxes = 6; + joystick->nbuttons = 11; + joystick->nhats = 1; + + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + + return true; +} + +static void UpdateXInputJoystickBatteryInformation(SDL_Joystick *joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) +{ + SDL_PowerState state; + int percent; + switch (pBatteryInformation->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 (pBatteryInformation->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); +} + +static void UpdateXInputJoystickState(SDL_Joystick *joystick, XINPUT_STATE *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) +{ + static WORD s_XInputButtons[] = { + XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, + XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START, + XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB, + XINPUT_GAMEPAD_GUIDE + }; + WORD wButtons = pXInputState->Gamepad.wButtons; + Uint8 button; + Uint8 hat = SDL_HAT_CENTERED; + Uint64 timestamp = SDL_GetTicksNS(); + + SDL_SendJoystickAxis(timestamp, joystick, 0, pXInputState->Gamepad.sThumbLX); + SDL_SendJoystickAxis(timestamp, joystick, 1, ~pXInputState->Gamepad.sThumbLY); + SDL_SendJoystickAxis(timestamp, joystick, 2, ((int)pXInputState->Gamepad.bLeftTrigger * 257) - 32768); + SDL_SendJoystickAxis(timestamp, joystick, 3, pXInputState->Gamepad.sThumbRX); + SDL_SendJoystickAxis(timestamp, joystick, 4, ~pXInputState->Gamepad.sThumbRY); + SDL_SendJoystickAxis(timestamp, joystick, 5, ((int)pXInputState->Gamepad.bRightTrigger * 257) - 32768); + + for (button = 0; button < (Uint8)SDL_arraysize(s_XInputButtons); ++button) { + bool down = ((wButtons & s_XInputButtons[button]) != 0); + SDL_SendJoystickButton(timestamp, joystick, button, down); + } + + if (wButtons & XINPUT_GAMEPAD_DPAD_UP) { + hat |= SDL_HAT_UP; + } + if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) { + hat |= SDL_HAT_DOWN; + } + if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) { + hat |= SDL_HAT_LEFT; + } + if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation); +} + +bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + XINPUT_VIBRATION XVibration; + + if (!XINPUTSETSTATE) { + return SDL_Unsupported(); + } + + XVibration.wLeftMotorSpeed = low_frequency_rumble; + XVibration.wRightMotorSpeed = high_frequency_rumble; + if (XINPUTSETSTATE(joystick->hwdata->userid, &XVibration) != ERROR_SUCCESS) { + return SDL_SetError("XInputSetState() failed"); + } + return true; +} + +void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ + DWORD result; + XINPUT_STATE XInputState; + XINPUT_BATTERY_INFORMATION_EX XBatteryInformation; + + if (!XINPUTGETSTATE) { + return; + } + + result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState); + if (result == ERROR_DEVICE_NOT_CONNECTED) { + return; + } + + SDL_zero(XBatteryInformation); + if (XINPUTGETBATTERYINFORMATION) { + result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation); + } + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + // XInputOnGameInput doesn't ever change dwPacketNumber, so have to just update every frame + UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation); +#else + // only fire events if the data changed from last time + if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) { + UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation); + joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber; + } +#endif +} + +void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick) +{ +} + +void SDL_XINPUT_JoystickQuit(void) +{ + if (s_bXInputEnabled) { + s_bXInputEnabled = false; + WIN_UnloadXInputDLL(); + } +} + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif + +#else // !SDL_JOYSTICK_XINPUT + +typedef struct JoyStick_DeviceData JoyStick_DeviceData; + +bool SDL_XINPUT_Enabled(void) +{ + return false; +} + +bool SDL_XINPUT_JoystickInit(void) +{ + return true; +} + +void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) +{ +} + +bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version) +{ + return false; +} + +bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) +{ + return SDL_Unsupported(); +} + +bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ +} + +void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick) +{ +} + +void SDL_XINPUT_JoystickQuit(void) +{ +} + +#endif // SDL_JOYSTICK_XINPUT diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h new file mode 100644 index 0000000..305b090 --- /dev/null +++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h @@ -0,0 +1,44 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-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. +*/ +#include "SDL_internal.h" + +#include "../../core/windows/SDL_xinput.h" + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +extern bool SDL_XINPUT_Enabled(void); +extern bool SDL_XINPUT_JoystickInit(void); +extern void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext); +extern bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version); +extern bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice); +extern bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); +extern void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick); +extern void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick); +extern void SDL_XINPUT_JoystickQuit(void); +extern int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid); + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif -- cgit v1.2.3