diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 2a2396071b45..4d1cdaeea88f 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -84,6 +84,18 @@ jobs: fi continue-on-error: true + - name: Download GameInput + shell: sh + id: gameinput + run: | + if python ./misc/scripts/install_gameinput_windows.py; then + echo "GAMEINPUT_ENABLED=yes" >> "$GITHUB_OUTPUT" + else + echo "::warning::Windows: GameInput installation failed, building without GameInput support and using XInput instead." + echo "GAMEINPUT_ENABLED=no" >> "$GITHUB_OUTPUT" + fi + continue-on-error: true + - name: Download pre-built ANGLE static libraries uses: dsaltares/fetch-gh-release-asset@1.1.2 with: @@ -109,7 +121,7 @@ jobs: - name: Compilation uses: ./.github/actions/godot-build with: - scons-flags: ${{ env.SCONS_FLAGS }} ${{ matrix.scons-flags }} d3d12=${{ steps.d3d12-sdk.outputs.D3D12_ENABLED }} + scons-flags: ${{ env.SCONS_FLAGS }} ${{ matrix.scons-flags }} d3d12=${{ steps.d3d12-sdk.outputs.D3D12_ENABLED }} gameinput=${{ steps.gameinput.outputs.GAMEINPUT_ENABLED }} platform: windows target: ${{ matrix.target }} diff --git a/SConstruct b/SConstruct index f2934251030b..e8d3031cc8ed 100644 --- a/SConstruct +++ b/SConstruct @@ -202,6 +202,7 @@ opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loade opts.Add(BoolVariable("accesskit", "Use AccessKit C SDK", True)) opts.Add(("accesskit_sdk_path", "Path to the AccessKit C SDK", "")) opts.Add(BoolVariable("sdl", "Enable the SDL3 input driver", True)) +opts.Add(BoolVariable("gameinput", "Enable the GameInput input driver on supported platforms", False)) opts.Add( EnumVariable( "profiler", "Specify the profiler to use", "none", ["none", "tracy", "perfetto", "instruments"], ignorecase=2 diff --git a/drivers/sdl/SCsub b/drivers/sdl/SCsub index 4666edb5bf3c..fcf7f396286c 100644 --- a/drivers/sdl/SCsub +++ b/drivers/sdl/SCsub @@ -1,6 +1,8 @@ #!/usr/bin/env python from misc.utility.scons_hints import * +import os + Import("env") env_sdl = env.Clone() @@ -163,7 +165,6 @@ if env["builtin_sdl"]: elif env["platform"] == "windows": env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_WINDOWS"]) thirdparty_sources += [ - "core/windows/SDL_gameinput.c", "core/windows/SDL_hid.c", "core/windows/SDL_immdevice.c", "core/windows/SDL_windows.c", @@ -172,10 +173,9 @@ if env["builtin_sdl"]: "haptic/windows/SDL_dinputhaptic.c", "haptic/windows/SDL_windowshaptic.c", "joystick/windows/SDL_dinputjoystick.c", - "joystick/windows/SDL_rawinputjoystick.c", - "joystick/windows/SDL_windows_gaming_input.c", "joystick/windows/SDL_windowsjoystick.c", "joystick/windows/SDL_xinputjoystick.c", + "loadso/windows/SDL_sysloadso.c", "thread/generic/SDL_syscond.c", "thread/generic/SDL_sysrwlock.c", "sensor/windows/SDL_windowssensor.c", @@ -188,6 +188,16 @@ if env["builtin_sdl"]: "timer/windows/SDL_systimer.c", ] + gameinput_path = env["gameinput_path"] + if env["gameinput"] and os.path.exists(gameinput_path): + thirdparty_sources += [ + "core/windows/SDL_gameinput.cpp", + "joystick/gdk/SDL_gameinputjoystick.cpp", + ] + + env_sdl.Prepend(CPPPATH=[os.path.join(gameinput_path, "native", "include", "v2")]) + env_sdl.Prepend(CPPDEFINES=["HAVE_GAMEINPUT_H"]) + elif env["platform"] in ["ios", "visionos"]: if env["platform"] == "ios": env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_IOS"]) diff --git a/drivers/sdl/SDL_build_config_private.h b/drivers/sdl/SDL_build_config_private.h index 1a86bb555727..ee29aa737f93 100644 --- a/drivers/sdl/SDL_build_config_private.h +++ b/drivers/sdl/SDL_build_config_private.h @@ -59,20 +59,24 @@ #define HAVE_LIBC 1 #define HAVE_DINPUT_H 1 #define HAVE_XINPUT_H 1 -#if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0A00 /* Windows 10 SDK */ -#define HAVE_WINDOWS_GAMING_INPUT_H 1 -#define SDL_JOYSTICK_WGI 1 + +#ifdef HAVE_GAMEINPUT_H // May be defined in "drivers/sdl/SCsub" +#define SDL_JOYSTICK_GAMEINPUT 1 #endif + #define SDL_JOYSTICK_DINPUT 1 -#define SDL_JOYSTICK_HIDAPI 1 -#define SDL_JOYSTICK_RAWINPUT 1 #define SDL_JOYSTICK_XINPUT 1 +#define SDL_JOYSTICK_HIDAPI 1 + #define SDL_HAPTIC_DINPUT 1 + #define SDL_THREAD_GENERIC_COND_SUFFIX 1 #define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1 #define SDL_THREAD_WINDOWS 1 + #define SDL_TIMER_WINDOWS 1 #define SDL_SENSOR_WINDOWS 1 +#define SDL_LOADSO_WINDOWS 1 // Linux defines #elif defined(SDL_PLATFORM_LINUX) diff --git a/drivers/sdl/joypad_sdl.cpp b/drivers/sdl/joypad_sdl.cpp index c68f5b4a22b6..91a689be2742 100644 --- a/drivers/sdl/joypad_sdl.cpp +++ b/drivers/sdl/joypad_sdl.cpp @@ -63,6 +63,7 @@ JoypadSDL::~JoypadSDL() { Error JoypadSDL::initialize() { SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_GAMEINPUT, "1"); SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); ERR_FAIL_COND_V_MSG(!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD), FAILED, SDL_GetError()); @@ -137,15 +138,15 @@ void JoypadSDL::process_events() { device_name = SDL_GetGamepadName(gamepad); joy = SDL_GetGamepadJoystick(gamepad); - print_verbose(vformat("SDL: Gamepad %s connected", SDL_GetGamepadName(gamepad))); + print_verbose(vformat("SDL: Gamepad %s connected", device_name)); } else { joy = SDL_OpenJoystick(sdl_event.jdevice.which); ERR_CONTINUE_MSG(!joy, vformat("Error opening joystick at index %d: %s", sdl_event.jdevice.which, SDL_GetError())); - device_name = SDL_GetJoystickName(joy); + device_name = String::utf8(SDL_GetJoystickName(joy)); - print_verbose(vformat("SDL: Joystick %s connected", SDL_GetJoystickName(joy))); + print_verbose(vformat("SDL: Joystick %s connected", device_name)); } const int MAX_GUID_SIZE = 64; diff --git a/misc/scripts/install_gameinput_windows.py b/misc/scripts/install_gameinput_windows.py new file mode 100755 index 000000000000..43d5224be050 --- /dev/null +++ b/misc/scripts/install_gameinput_windows.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +if __name__ != "__main__": + raise SystemExit(f'Utility script "{__file__}" should not be used as a module!') + +import argparse +import os +import shutil +import sys +import urllib.request + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) + +from misc.utility.color import Ansi, color_print + +parser = argparse.ArgumentParser(description="Install GameInput dependencies for Windows platforms.") +parser.add_argument( + "--mingw_prefix", + default=os.getenv("MINGW_PREFIX", ""), + help="Explicitly specify a path containing the MinGW bin folder.", +) +args = parser.parse_args() + +# Base Godot dependencies path +# If cross-compiling (no LOCALAPPDATA), we install in `bin` +deps_folder = os.getenv("LOCALAPPDATA") +if deps_folder: + deps_folder = os.path.join(deps_folder, "Godot", "build_deps") +else: + deps_folder = os.path.join("bin", "build_deps") + +# Check for latest version: https://www.nuget.org/api/v2/package/Microsoft.GameInput (check downloaded filename) +gameinput_version = "3.2.135" +gameinput_archive = os.path.join(deps_folder, f"microsoft.gameinput.{gameinput_version}.nupkg") +gameinput_folder = os.path.join(deps_folder, "gameinput") + +# Create dependencies folder +if not os.path.exists(deps_folder): + os.makedirs(deps_folder) + +if os.path.isfile(gameinput_archive): + os.remove(gameinput_archive) +print(f"Downloading GameInput {gameinput_version} ...") +urllib.request.urlretrieve( + f"https://www.nuget.org/api/v2/package/Microsoft.GameInput/{gameinput_version}", gameinput_archive +) +if os.path.exists(gameinput_folder): + print(f"Removing existing local GameInput installation in {gameinput_folder} ...") + shutil.rmtree(gameinput_folder) +print(f"Extracting GameInput {gameinput_version} to {gameinput_folder} ...") +shutil.unpack_archive(gameinput_archive, gameinput_folder, "zip") +os.remove(gameinput_archive) +print(f"GameInput {gameinput_version} installed successfully.\n") + +# Complete message +color_print(f'{Ansi.GREEN}All GameInput components were installed to "{deps_folder}" successfully!') +color_print(f'{Ansi.GREEN}You can now build Godot with GameInput support enabled by running "scons gameinput=yes".') diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 28861ffa1268..64f81c4710a2 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -166,10 +166,10 @@ def get_opts(): mingw = os.getenv("MINGW_PREFIX", "") - # Direct3D 12 SDK dependencies folder. - d3d12_deps_folder = os.getenv("LOCALAPPDATA") - if d3d12_deps_folder: - d3d12_deps_folder = os.path.join(d3d12_deps_folder, "Godot", "build_deps") + # Direct3D 12 SDK and GameInput dependencies folder. + deps_folder = os.getenv("LOCALAPPDATA") + if deps_folder: + deps_folder = os.path.join(deps_folder, "Godot", "build_deps") else: # Cross-compiling, the deps install script puts things in `bin`. # Getting an absolute path to it is a bit hacky in Python. @@ -178,9 +178,9 @@ def get_opts(): caller_frame = inspect.stack()[1] caller_script_dir = os.path.dirname(os.path.abspath(caller_frame[1])) - d3d12_deps_folder = os.path.join(caller_script_dir, "bin", "build_deps") + deps_folder = os.path.join(caller_script_dir, "bin", "build_deps") except Exception: # Give up. - d3d12_deps_folder = "" + deps_folder = "" return [ ("mingw_prefix", "MinGW prefix", mingw), @@ -199,12 +199,12 @@ def get_opts(): ( "mesa_libs", "Path to the MESA/NIR static libraries (required for D3D12)", - os.path.join(d3d12_deps_folder, "mesa"), + os.path.join(deps_folder, "mesa"), ), ( "agility_sdk_path", "Path to the Agility SDK distribution (optional for D3D12)", - os.path.join(d3d12_deps_folder, "agility_sdk"), + os.path.join(deps_folder, "agility_sdk"), ), BoolVariable( "agility_sdk_multiarch", @@ -215,7 +215,12 @@ def get_opts(): ( "pix_path", "Path to the PIX runtime distribution (optional for D3D12)", - os.path.join(d3d12_deps_folder, "pix"), + os.path.join(deps_folder, "pix"), + ), + ( + "gameinput_path", + "Path to the GameInput libraries", + os.path.join(deps_folder, "gameinput"), ), ] @@ -236,6 +241,7 @@ def get_flags(): return { "arch": arch, "d3d12": True, + "gameinput": True, "supported": ["d3d12", "dcomp", "library", "mono", "xaudio2"], } @@ -444,6 +450,14 @@ def spawn_capture(sh, escape, cmd, args, env): if env["sdl"]: env.Append(CPPDEFINES=["SDL_ENABLED"]) + if env["gameinput"]: + if env["sdl"]: + check_gameinput_installed(env) + # Do nothing else, GameInput is only used inside SDL, so the rest is handled in "drivers/sdl/SCsub". + else: + print("GameInput API is enabled, but SDL was explicitly disabled. Disabling GameInput API.") + env["gameinput"] = False + if env["d3d12"]: check_d3d12_installed(env, env["arch"] + "-msvc") @@ -831,6 +845,14 @@ def configure_mingw(env: "SConsEnvironment"): if env["sdl"]: env.Append(CPPDEFINES=["SDL_ENABLED"]) + if env["gameinput"]: + if env["sdl"]: + check_gameinput_installed(env) + # Do nothing else, GameInput is only used inside SDL, so the rest is handled in "drivers/sdl/SCsub". + else: + print("GameInput API is enabled, but SDL was explicitly disabled. Disabling GameInput API.") + env["gameinput"] = False + if env["d3d12"]: if env["use_llvm"]: check_d3d12_installed(env, env["arch"] + "-llvm") @@ -933,3 +955,13 @@ def check_d3d12_installed(env, suffix): "Alternatively, disable this driver by compiling with `d3d12=no` explicitly." ) sys.exit(255) + + +def check_gameinput_installed(env): + if not os.path.exists(env["gameinput_path"]): + print_error( + "The GameInput API dependencies are not installed.\n" + "You can install them by running `python misc\\scripts\\install_gameinput_windows.py`.\n" + "Alternatively, disable this driver by compiling with `gameinput=no` explicitly." + ) + sys.exit(255) diff --git a/thirdparty/README.md b/thirdparty/README.md index 03e267d0f8df..76b2d3f582b5 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -1015,6 +1015,7 @@ Patches: - `0005-fix-libudev-dbus.patch` ([GH-108373](https://github.com/godotengine/godot/pull/108373)) - `0006-fix-cs-environ.patch` ([GH-109283](https://github.com/godotengine/godot/pull/109283)) - `0007-shield-duplicate-macos.patch` ([GH-115510](https://github.com/godotengine/godot/pull/115510)) +- `0009-gameinput-fixes.patch` ([GH-116055](https://github.com/godotengine/godot/pull/116055)) ## spirv-cross diff --git a/thirdparty/sdl/core/windows/SDL_gameinput.c b/thirdparty/sdl/core/windows/SDL_gameinput.cpp similarity index 74% rename from thirdparty/sdl/core/windows/SDL_gameinput.c rename to thirdparty/sdl/core/windows/SDL_gameinput.cpp index 9ac5912db9d0..baf7ed216543 100644 --- a/thirdparty/sdl/core/windows/SDL_gameinput.c +++ b/thirdparty/sdl/core/windows/SDL_gameinput.cpp @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,16 +25,11 @@ #include "SDL_windows.h" #include "SDL_gameinput.h" -#ifdef SDL_PLATFORM_WIN32 -#include -// {11BE2A7E-4254-445A-9C09-FFC40F006918} -DEFINE_GUID(SDL_IID_GameInput, 0x11BE2A7E, 0x4254, 0x445A, 0x9C, 0x09, 0xFF, 0xC4, 0x0F, 0x00, 0x69, 0x18); -#endif - static SDL_SharedObject *g_hGameInputDLL; static IGameInput *g_pGameInput; static int g_nGameInputRefCount; + bool SDL_InitGameInput(IGameInput **ppGameInput) { if (g_nGameInputRefCount == 0) { @@ -43,30 +38,34 @@ bool SDL_InitGameInput(IGameInput **ppGameInput) return false; } - typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput); - GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); - if (!GameInputCreateFunc) { + typedef HRESULT (WINAPI *pfnGameInputCreate)(IGameInput **gameInput); + pfnGameInputCreate pGameInputCreate = (pfnGameInputCreate)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); + if (!pGameInputCreate) { SDL_UnloadObject(g_hGameInputDLL); return false; } IGameInput *pGameInput = NULL; - HRESULT hr = GameInputCreateFunc(&pGameInput); + HRESULT hr = pGameInputCreate(&pGameInput); if (FAILED(hr)) { SDL_UnloadObject(g_hGameInputDLL); return WIN_SetErrorFromHRESULT("GameInputCreate failed", hr); } #ifdef SDL_PLATFORM_WIN32 - hr = IGameInput_QueryInterface(pGameInput, &SDL_IID_GameInput, (void **)&g_pGameInput); - IGameInput_Release(pGameInput); +#if GAMEINPUT_API_VERSION >= 1 + hr = pGameInput->QueryInterface(IID_IGameInput, (void **)&g_pGameInput); +#else + // We require GameInput v1.1 or newer + hr = E_NOINTERFACE; +#endif + pGameInput->Release(); if (FAILED(hr)) { SDL_UnloadObject(g_hGameInputDLL); return WIN_SetErrorFromHRESULT("GameInput QueryInterface failed", hr); } #else // Assume that the version we get is compatible with the current SDK - // If that isn't the case, define the correct GUID for SDL_IID_GameInput above g_pGameInput = pGameInput; #endif } @@ -85,7 +84,7 @@ void SDL_QuitGameInput(void) --g_nGameInputRefCount; if (g_nGameInputRefCount == 0) { if (g_pGameInput) { - IGameInput_Release(g_pGameInput); + g_pGameInput->Release(); g_pGameInput = NULL; } if (g_hGameInputDLL) { diff --git a/thirdparty/sdl/core/windows/SDL_gameinput.h b/thirdparty/sdl/core/windows/SDL_gameinput.h index 0022c0bdde16..a0464899006d 100644 --- a/thirdparty/sdl/core/windows/SDL_gameinput.h +++ b/thirdparty/sdl/core/windows/SDL_gameinput.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga + Copyright (C) 1997-2026 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 @@ -25,9 +25,18 @@ #ifdef HAVE_GAMEINPUT_H -#define COBJMACROS #include +#ifndef GAMEINPUT_API_VERSION +#define GAMEINPUT_API_VERSION 0 +#endif + +#if GAMEINPUT_API_VERSION == 2 +using namespace GameInput::v2; +#elif GAMEINPUT_API_VERSION == 1 +using namespace GameInput::v1; +#endif + extern bool SDL_InitGameInput(IGameInput **ppGameInput); extern void SDL_QuitGameInput(void); diff --git a/thirdparty/sdl/joystick/SDL_joystick.c b/thirdparty/sdl/joystick/SDL_joystick.c index 090dc07303ab..b040be51763d 100644 --- a/thirdparty/sdl/joystick/SDL_joystick.c +++ b/thirdparty/sdl/joystick/SDL_joystick.c @@ -3115,6 +3115,11 @@ bool SDL_IsJoystickWGI(SDL_GUID guid) return (guid.data[14] == 'w') ? true : false; } +bool SDL_IsJoystickGameInputGamepad(SDL_GUID guid) +{ + return (guid.data[14] == 'g') ? true : false; +} + bool SDL_IsJoystickHIDAPI(SDL_GUID guid) { return (guid.data[14] == 'h') ? true : false; @@ -3208,6 +3213,10 @@ static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_GUID guid) return (SDL_JoystickType)guid.data[15]; } + if (SDL_IsJoystickGameInputGamepad(guid)) { + return (SDL_JoystickType)guid.data[15]; + } + if (SDL_IsJoystickVIRTUAL(guid)) { return (SDL_JoystickType)guid.data[15]; } diff --git a/thirdparty/sdl/joystick/gdk/SDL_gameinputjoystick.cpp b/thirdparty/sdl/joystick/gdk/SDL_gameinputjoystick.cpp new file mode 100644 index 000000000000..dcaa8bf5b094 --- /dev/null +++ b/thirdparty/sdl/joystick/gdk/SDL_gameinputjoystick.cpp @@ -0,0 +1,913 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_GAMEINPUT + +#include "../SDL_sysjoystick.h" +#include "../usb_ids.h" +#include "../../core/windows/SDL_windows.h" +#include "../../core/windows/SDL_gameinput.h" + +// Default value for SDL_HINT_JOYSTICK_GAMEINPUT +#if defined(SDL_PLATFORM_GDK) +#define SDL_GAMEINPUT_DEFAULT true +#else +#define SDL_GAMEINPUT_DEFAULT false +#endif + +// Enable sensor support in GameInput 2.0, once we have a device that can be used for testing +#if GAMEINPUT_API_VERSION >= 2 +//#define GAMEINPUT_SENSOR_SUPPORT +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11 +}; + +typedef struct GAMEINPUT_InternalDevice +{ + IGameInputDevice *device; + char path[(APP_LOCAL_DEVICE_ID_SIZE * 2) + 1]; + char *name; + SDL_GUID guid; // generated by SDL + SDL_JoystickID device_instance; // generated by SDL + const GameInputDeviceInfo *info; + int steam_virtual_gamepad_slot; + bool isAdded; + bool isDeleteRequested; +} GAMEINPUT_InternalDevice; + +typedef struct GAMEINPUT_InternalList +{ + GAMEINPUT_InternalDevice **devices; + int count; +} GAMEINPUT_InternalList; + +typedef struct joystick_hwdata +{ + GAMEINPUT_InternalDevice *devref; + bool report_sensors; + GameInputRumbleParams rumbleParams; + GameInputCallbackToken system_button_callback_token; +} GAMEINPUT_InternalJoystickHwdata; + +static GAMEINPUT_InternalList g_GameInputList = { NULL }; +static IGameInput *g_pGameInput = NULL; +static GameInputCallbackToken g_GameInputCallbackToken = 0; +static Uint64 g_GameInputTimestampOffset; + +static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) +{ + if (info->supportedInput & GameInputKindGamepad) { + return true; + } + return false; +} + +static Uint8 GAMEINPUT_GetDeviceSubtype(const GameInputDeviceInfo *info) { + GameInputKind supportedInput = info->supportedInput; + if (supportedInput & GameInputKindRacingWheel) { + return SDL_JOYSTICK_TYPE_WHEEL; + } + if (supportedInput & GameInputKindArcadeStick) { + return SDL_JOYSTICK_TYPE_ARCADE_STICK; + } + if (supportedInput & GameInputKindFlightStick) { + return SDL_JOYSTICK_TYPE_FLIGHT_STICK; + } + if (supportedInput & (GameInputKindGamepad | GameInputKindController)) { + return SDL_JOYSTICK_TYPE_GAMEPAD; + } + // Other device subtypes don't have their own GameInputKind enum entries. + return 0; +} + +#if GAMEINPUT_API_VERSION >= 1 +static int GetSteamVirtualGamepadSlot(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 + (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot); + return slot; +} +#endif // GAMEINPUT_API_VERSION >= 1 + +static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) +{ + GAMEINPUT_InternalDevice **devicelist = NULL; + GAMEINPUT_InternalDevice *elem = NULL; + const GameInputDeviceInfo *info = NULL; + Uint16 bus = SDL_HARDWARE_BUS_USB; + Uint16 vendor = 0; + Uint16 product = 0; + Uint16 version = 0; + const char *product_string = NULL; + Uint8 driver_signature = 'g'; + Uint8 subtype = 0; + char tmp[4]; + int idx = 0; + + SDL_AssertJoysticksLocked(); + +#if GAMEINPUT_API_VERSION >= 1 + HRESULT hr = pDevice->GetDeviceInfo(&info); + if (FAILED(hr)) { + return WIN_SetErrorFromHRESULT("IGameInputDevice::GetDeviceInfo", hr); + } +#else + info = pDevice->GetDeviceInfo(); +#endif + if (false /*info->capabilities & GameInputDeviceCapabilityWireless*/) { + bus = SDL_HARDWARE_BUS_BLUETOOTH; + } else { + bus = SDL_HARDWARE_BUS_USB; + } + vendor = info->vendorId; + product = info->productId; + //version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; + subtype = GAMEINPUT_GetDeviceSubtype(info); + +#if GAMEINPUT_API_VERSION >= 1 + if (info->displayName) { + product_string = info->displayName; + } +#else + if (info->displayName) { + product_string = info->displayName->data; + } +#endif + + if (SDL_ShouldIgnoreJoystick(vendor, product, version, product_string) + || SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, product_string)) { + return true; + } + +#if defined(SDL_JOYSTICK_DINPUT) && defined(SDL_HAPTIC_DINPUT) + // This joystick backend currently doesn't provide a haptic backend, + // so fallback to DirectInput for haptic-capable devices. + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true) && info->forceFeedbackMotorCount > 0 && pDevice->IsForceFeedbackMotorPoweredOn(0)) { + return true; + } +#endif + + if (!GAMEINPUT_InternalIsGamepad(info)) { + if (info->supportedInput & GameInputKindController) { + // Maintain GUID compatibility with DirectInput controller mappings. + driver_signature = 0; + subtype = 0; + } else { + // This joystick backend currently doesn't provide proper reading of other joystick types. + return true; + } + } + + for (idx = 0; idx < g_GameInputList.count; ++idx) { + elem = g_GameInputList.devices[idx]; + if (elem && elem->device == pDevice) { + // we're already added + elem->isDeleteRequested = false; + return true; + } + } + + elem = (GAMEINPUT_InternalDevice *)SDL_calloc(1, sizeof(*elem)); + if (!elem) { + return false; + } + + devicelist = (GAMEINPUT_InternalDevice **)SDL_realloc(g_GameInputList.devices, sizeof(elem) * (g_GameInputList.count + 1LL)); + if (!devicelist) { + SDL_free(elem); + return false; + } + + // Generate a device path + for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) { + SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]); + SDL_strlcat(elem->path, tmp, SDL_arraysize(elem->path)); + } + + pDevice->AddRef(); + elem->device = pDevice; + elem->name = SDL_CreateJoystickName(vendor, product, NULL, product_string); + elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, product_string, driver_signature, subtype); + elem->device_instance = SDL_GetNextObjectID(); + elem->info = info; +#if GAMEINPUT_API_VERSION >= 1 + elem->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(info->pnpPath); +#else + elem->steam_virtual_gamepad_slot = -1; +#endif + + g_GameInputList.devices = devicelist; + g_GameInputList.devices[g_GameInputList.count++] = elem; + + return true; +} + +static bool GAMEINPUT_InternalRemoveByIndex(int idx) +{ + GAMEINPUT_InternalDevice **devicelist = NULL; + GAMEINPUT_InternalDevice *elem; + int bytes = 0; + + SDL_AssertJoysticksLocked(); + + if (idx < 0 || idx >= g_GameInputList.count) { + return SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx); + } + + elem = g_GameInputList.devices[idx]; + if (elem) { + elem->device->Release(); + SDL_free(elem->name); + SDL_free(elem); + } + g_GameInputList.devices[idx] = NULL; + + if (g_GameInputList.count == 1) { + // last element in the list, free the entire list then + SDL_free(g_GameInputList.devices); + g_GameInputList.devices = NULL; + } else { + if (idx != g_GameInputList.count - 1) { + bytes = sizeof(*devicelist) * (g_GameInputList.count - idx); + SDL_memmove(&g_GameInputList.devices[idx], &g_GameInputList.devices[idx + 1], bytes); + } + } + + // decrement the count and return + --g_GameInputList.count; + return true; +} + +static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx) +{ + // We're guaranteed that the index is in range when this is called + SDL_AssertJoysticksLocked(); + return g_GameInputList.devices[idx]; +} + +static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback( + _In_ GameInputCallbackToken callbackToken, + _In_ void *context, + _In_ IGameInputDevice *device, + _In_ uint64_t timestamp, + _In_ GameInputDeviceStatus currentStatus, + _In_ GameInputDeviceStatus previousStatus) +{ + int idx = 0; + GAMEINPUT_InternalDevice *elem = NULL; + + if (!device) { + // This should never happen, but ignore it if it does + return; + } + + SDL_LockJoysticks(); + + if (currentStatus & GameInputDeviceConnected) { + GAMEINPUT_InternalAddOrFind(device); + } else { + for (idx = 0; idx < g_GameInputList.count; ++idx) { + elem = g_GameInputList.devices[idx]; + if (elem && elem->device == device) { + // will be deleted on the next Detect call + elem->isDeleteRequested = true; + break; + } + } + } + + SDL_UnlockJoysticks(); +} + +static void GAMEINPUT_JoystickDetect(void); +static void GAMEINPUT_JoystickQuit(void); + +static bool GAMEINPUT_JoystickInit(void) +{ + HRESULT hr; + + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) { + return true; + } + + if (!SDL_InitGameInput(&g_pGameInput)) { + return false; + } + +#if GAMEINPUT_API_VERSION >= 2 + // Allow background controller input + // SDL manages focus policy at a higher level, so we can set this unconditionally. + g_pGameInput->SetFocusPolicy(GameInputEnableBackgroundInput | GameInputEnableBackgroundGuideButton | GameInputEnableBackgroundShareButton); +#endif + + hr = g_pGameInput->RegisterDeviceCallback(NULL, + GameInputKindController, + GameInputDeviceConnected, + GameInputBlockingEnumeration, + NULL, + GAMEINPUT_InternalJoystickDeviceCallback, + &g_GameInputCallbackToken); + if (FAILED(hr)) { + GAMEINPUT_JoystickQuit(); + return WIN_SetErrorFromHRESULT("IGameInput::RegisterDeviceCallback", hr); + } + + // Calculate the relative offset between SDL timestamps and GameInput timestamps + Uint64 now = SDL_GetTicksNS(); + uint64_t timestampUS = g_pGameInput->GetCurrentTimestamp(); + g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS); + + GAMEINPUT_JoystickDetect(); + + return true; +} + +static int GAMEINPUT_JoystickGetCount(void) +{ + SDL_AssertJoysticksLocked(); + + return g_GameInputList.count; +} + +static void GAMEINPUT_JoystickDetect(void) +{ + int idx; + GAMEINPUT_InternalDevice *elem = NULL; + + SDL_AssertJoysticksLocked(); + + for (idx = 0; idx < g_GameInputList.count; ++idx) { + elem = g_GameInputList.devices[idx]; + if (!elem) { + continue; + } + + if (!elem->isAdded) { + SDL_PrivateJoystickAdded(elem->device_instance); + elem->isAdded = true; + } + + if (elem->isDeleteRequested || !(elem->device->GetDeviceStatus() & GameInputDeviceConnected)) { + SDL_PrivateJoystickRemoved(elem->device_instance); + GAMEINPUT_InternalRemoveByIndex(idx--); + } + } +} + +static bool GAMEINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + SDL_AssertJoysticksLocked(); + + if (g_pGameInput) { + if (vendor_id == USB_VENDOR_MICROSOFT && + product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) { + // The Xbox One controller shows up as a hardcoded raw input VID/PID, which we definitely handle + return true; + } + + for (int i = 0; i < g_GameInputList.count; ++i) { + GAMEINPUT_InternalDevice *elem = g_GameInputList.devices[i]; + if (elem && vendor_id == elem->info->vendorId && product_id == elem->info->productId) { + return true; + } + } + } + return false; +} + +static const char *GAMEINPUT_JoystickGetDeviceName(int device_index) +{ + return GAMEINPUT_InternalFindByIndex(device_index)->name; +} + +static const char *GAMEINPUT_JoystickGetDevicePath(int device_index) +{ + // APP_LOCAL_DEVICE_ID as a hex string, since it's required for some association callbacks + return GAMEINPUT_InternalFindByIndex(device_index)->path; +} + +static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return GAMEINPUT_InternalFindByIndex(device_index)->steam_virtual_gamepad_slot; +} + +static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static void GAMEINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + +static SDL_GUID GAMEINPUT_JoystickGetDeviceGUID(int device_index) +{ + return GAMEINPUT_InternalFindByIndex(device_index)->guid; +} + +static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index) +{ + return GAMEINPUT_InternalFindByIndex(device_index)->device_instance; +} + +static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device) +{ +#if 0 + GameInputBatteryState battery_state; + SDL_PowerState state; + int percent = 0; + + SDL_zero(battery_state); + IGameInputDevice_GetBatteryState(device, &battery_state); + + switch (battery_state.status) { + case GameInputBatteryNotPresent: + state = SDL_POWERSTATE_NO_BATTERY; + break; + case GameInputBatteryDischarging: + state = SDL_POWERSTATE_ON_BATTERY; + break; + case GameInputBatteryIdle: + state = SDL_POWERSTATE_CHARGED; + break; + case GameInputBatteryCharging: + state = SDL_POWERSTATE_CHARGING; + break; + default: + state = SDL_POWERSTATE_UNKNOWN; + break; + } + if (battery_state.fullChargeCapacity > 0.0f) { + percent = (int)SDL_roundf((battery_state.remainingCapacity / battery_state.fullChargeCapacity) * 100.0f); + } + SDL_SendJoystickPowerInfo(joystick, state, percent); +#endif +} + +#if GAMEINPUT_API_VERSION >= 1 +static void CALLBACK GAMEINPUT_InternalSystemButtonCallback( + _In_ GameInputCallbackToken callbackToken, + _In_ void * context, + _In_ IGameInputDevice * device, + _In_ uint64_t timestampUS, + _In_ GameInputSystemButtons currentButtons, + _In_ GameInputSystemButtons previousButtons) +{ + SDL_Joystick *joystick = (SDL_Joystick *)context; + + GameInputSystemButtons changedButtons = (previousButtons ^ currentButtons); + if (changedButtons) { + Uint64 timestamp = SDL_US_TO_NS(timestampUS + g_GameInputTimestampOffset); + + SDL_LockJoysticks(); + if (changedButtons & GameInputSystemButtonGuide) { + bool down = ((currentButtons & GameInputSystemButtonGuide) != 0); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, down); + } + if (changedButtons & GameInputSystemButtonShare) { + bool down = ((currentButtons & GameInputSystemButtonShare) != 0); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE, down); + } + SDL_UnlockJoysticks(); + } +} +#endif // GAMEINPUT_API_VERSION >= 1 + +static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index); + const GameInputDeviceInfo *info = elem->info; + GAMEINPUT_InternalJoystickHwdata *hwdata = NULL; + + if (!elem) { + return false; + } + + hwdata = (GAMEINPUT_InternalJoystickHwdata *)SDL_calloc(1, sizeof(*hwdata)); + if (!hwdata) { + return false; + } + + hwdata->devref = elem; + + joystick->hwdata = hwdata; + if (GAMEINPUT_InternalIsGamepad(info)) { + joystick->naxes = 6; + joystick->nbuttons = 11; + joystick->nhats = 1; + +#if GAMEINPUT_API_VERSION >= 1 + if (info->supportedSystemButtons != GameInputSystemButtonNone) { + if (info->supportedSystemButtons & GameInputSystemButtonShare) { + ++joystick->nbuttons; + } + + g_pGameInput->RegisterSystemButtonCallback(elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token); + } +#endif // GAMEINPUT_API_VERSION >= 1 + } else { + joystick->naxes = info->controllerAxisCount; + joystick->nbuttons = info->controllerButtonCount; + joystick->nhats = info->controllerSwitchCount; + } + + if (info->supportedRumbleMotors & (GameInputRumbleLowFrequency | GameInputRumbleHighFrequency)) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); + } + if (info->supportedRumbleMotors & (GameInputRumbleLeftTrigger | GameInputRumbleRightTrigger)) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); + } + +#ifdef GAMEINPUT_SENSOR_SUPPORT + if (info->supportedInput & GameInputKindSensors) { + // FIXME: What's the sensor update rate? + if (info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + } + if (info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + } + } +#endif + return true; +} + +static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it + GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; + GameInputRumbleParams *params = &hwdata->rumbleParams; + params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16; + params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16; + hwdata->devref->device->SetRumbleState(params); + return true; +} + +static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it + GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; + GameInputRumbleParams *params = &hwdata->rumbleParams; + params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16; + params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16; + hwdata->devref->device->SetRumbleState(params); + return true; +} + +static bool GAMEINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool GAMEINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + joystick->hwdata->report_sensors = enabled; + return true; +} + +static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) +{ + GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; + IGameInputDevice *device = hwdata->devref->device; + const GameInputDeviceInfo *info = hwdata->devref->info; + IGameInputReading *reading = NULL; + Uint64 timestamp; + GameInputGamepadState state; + HRESULT hr; + + hr = g_pGameInput->GetCurrentReading(info->supportedInput, device, &reading); + if (FAILED(hr)) { + // don't SetError here since there can be a legitimate case when there's no reading avail + return; + } + + timestamp = SDL_US_TO_NS(reading->GetTimestamp() + g_GameInputTimestampOffset); + + if (GAMEINPUT_InternalIsGamepad(info)) { + static WORD s_XInputButtons[] = { + GameInputGamepadA, // SDL_GAMEPAD_BUTTON_SOUTH + GameInputGamepadB, // SDL_GAMEPAD_BUTTON_EAST + GameInputGamepadX, // SDL_GAMEPAD_BUTTON_WEST + GameInputGamepadY, // SDL_GAMEPAD_BUTTON_NORTH + GameInputGamepadView, // SDL_GAMEPAD_BUTTON_BACK + 0, // The guide button is not available + GameInputGamepadMenu, // SDL_GAMEPAD_BUTTON_START + GameInputGamepadLeftThumbstick, // SDL_GAMEPAD_BUTTON_LEFT_STICK + GameInputGamepadRightThumbstick, // SDL_GAMEPAD_BUTTON_RIGHT_STICK + GameInputGamepadLeftShoulder, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER + GameInputGamepadRightShoulder, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER + }; + Uint8 btnidx = 0, hat = 0; + + if (reading->GetGamepadState(&state)) { + for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) { + WORD button_mask = s_XInputButtons[btnidx]; + if (!button_mask) { + continue; + } + bool down = ((state.buttons & button_mask) != 0); + SDL_SendJoystickButton(timestamp, joystick, btnidx, down); + } + + if (state.buttons & GameInputGamepadDPadUp) { + hat |= SDL_HAT_UP; + } + if (state.buttons & GameInputGamepadDPadDown) { + hat |= SDL_HAT_DOWN; + } + if (state.buttons & GameInputGamepadDPadLeft) { + hat |= SDL_HAT_LEFT; + } + if (state.buttons & GameInputGamepadDPadRight) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + +#define CONVERT_AXIS(v) (Sint16)(((v) < 0.0f) ? ((v)*32768.0f) : ((v)*32767.0f)) + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, CONVERT_AXIS(state.leftThumbstickX)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, CONVERT_AXIS(-state.leftThumbstickY)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, CONVERT_AXIS(state.rightThumbstickX)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, CONVERT_AXIS(-state.rightThumbstickY)); +#undef CONVERT_AXIS +#define CONVERT_TRIGGER(v) (Sint16)((v)*65535.0f - 32768.0f) + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, CONVERT_TRIGGER(state.leftTrigger)); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, CONVERT_TRIGGER(state.rightTrigger)); +#undef CONVERT_TRIGGER + } + } else { + bool *button_state = SDL_stack_alloc(bool, info->controllerButtonCount); + float *axis_state = SDL_stack_alloc(float, info->controllerAxisCount); + GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, info->controllerSwitchCount); + + if (button_state) { + uint32_t i; + uint32_t button_count = reading->GetControllerButtonState(info->controllerButtonCount, button_state); + for (i = 0; i < button_count; ++i) { + SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]); + } + SDL_stack_free(button_state); + } + +#define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f) + if (axis_state) { + uint32_t i; + uint32_t axis_count = reading->GetControllerAxisState(info->controllerAxisCount, axis_state); + for (i = 0; i < axis_count; ++i) { + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i])); + } + SDL_stack_free(axis_state); + } +#undef CONVERT_AXIS + + if (switch_state) { + uint32_t i; + uint32_t switch_count = reading->GetControllerSwitchState(info->controllerSwitchCount, switch_state); + for (i = 0; i < switch_count; ++i) { + Uint8 hat; + switch (switch_state[i]) { + case GameInputSwitchUp: + hat = SDL_HAT_UP; + break; + case GameInputSwitchUpRight: + hat = SDL_HAT_UP | SDL_HAT_RIGHT; + break; + case GameInputSwitchRight: + hat = SDL_HAT_RIGHT; + break; + case GameInputSwitchDownRight: + hat = SDL_HAT_DOWN | SDL_HAT_RIGHT; + break; + case GameInputSwitchDown: + hat = SDL_HAT_DOWN; + break; + case GameInputSwitchDownLeft: + hat = SDL_HAT_DOWN | SDL_HAT_LEFT; + break; + case GameInputSwitchLeft: + hat = SDL_HAT_LEFT; + break; + case GameInputSwitchUpLeft: + hat = SDL_HAT_UP | SDL_HAT_LEFT; + break; + case GameInputSwitchCenter: + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat); + } + SDL_stack_free(switch_state); + } + } + +#ifdef GAMEINPUT_SENSOR_SUPPORT + if (hwdata->report_sensors) { + GameInputSensorsState sensor_state; + + if (reading->GetSensorsState(&sensor_state)) { + if ((info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) != 0) { + float data[3] = { + sensor_state.angularVelocityInRadPerSecX, + sensor_state.angularVelocityInRadPerSecY, + sensor_state.angularVelocityInRadPerSecZ + }; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, SDL_arraysize(data)); + } + if ((info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) != 0) { + float data[3] = { + sensor_state.accelerationInGX * SDL_STANDARD_GRAVITY, + sensor_state.accelerationInGY * SDL_STANDARD_GRAVITY, + sensor_state.accelerationInGZ * SDL_STANDARD_GRAVITY + }; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, SDL_arraysize(data)); + } + } + } +#endif // GAMEINPUT_SENSOR_SUPPORT + + reading->Release(); + + // FIXME: We can poll this at a much lower rate + GAMEINPUT_UpdatePowerInfo(joystick, device); +} + +static void GAMEINPUT_JoystickClose(SDL_Joystick *joystick) +{ + GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; + + if (hwdata->system_button_callback_token) { +#if GAMEINPUT_API_VERSION >= 1 + g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token); +#else + g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token, 10000); +#endif + } + SDL_free(hwdata); + + joystick->hwdata = NULL; +} + +static void GAMEINPUT_JoystickQuit(void) +{ + if (g_pGameInput) { + // free the callback + if (g_GameInputCallbackToken) { +#if GAMEINPUT_API_VERSION >= 1 + g_pGameInput->UnregisterCallback(g_GameInputCallbackToken); +#else + g_pGameInput->UnregisterCallback(g_GameInputCallbackToken, 10000); +#endif + g_GameInputCallbackToken = 0; + } + + // free the list + while (g_GameInputList.count > 0) { + GAMEINPUT_InternalRemoveByIndex(0); + } + + SDL_QuitGameInput(); + g_pGameInput = NULL; + } +} + +static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index); + + if (!GAMEINPUT_InternalIsGamepad(elem->info)) { + return false; + } + + out->a.kind = EMappingKind_Button; + out->a.target = SDL_GAMEPAD_BUTTON_SOUTH; + + out->b.kind = EMappingKind_Button; + out->b.target = SDL_GAMEPAD_BUTTON_EAST; + + out->x.kind = EMappingKind_Button; + out->x.target = SDL_GAMEPAD_BUTTON_WEST; + + out->y.kind = EMappingKind_Button; + out->y.target = SDL_GAMEPAD_BUTTON_NORTH; + + out->back.kind = EMappingKind_Button; + out->back.target = SDL_GAMEPAD_BUTTON_BACK; + +#if GAMEINPUT_API_VERSION >= 1 + if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) { + out->guide.kind = EMappingKind_Button; + out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE; + } + + if (elem->info->supportedSystemButtons & GameInputSystemButtonShare) { + out->misc1.kind = EMappingKind_Button; + out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE; + } +#endif // GAMEINPUT_API_VERSION >= 1 + + out->start.kind = EMappingKind_Button; + out->start.target = SDL_GAMEPAD_BUTTON_START; + + out->leftstick.kind = EMappingKind_Button; + out->leftstick.target = SDL_GAMEPAD_BUTTON_LEFT_STICK; + + out->rightstick.kind = EMappingKind_Button; + out->rightstick.target = SDL_GAMEPAD_BUTTON_RIGHT_STICK; + + out->leftshoulder.kind = EMappingKind_Button; + out->leftshoulder.target = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; + + out->rightshoulder.kind = EMappingKind_Button; + out->rightshoulder.target = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; + + out->dpup.kind = EMappingKind_Hat; + out->dpup.target = SDL_HAT_UP; + + out->dpdown.kind = EMappingKind_Hat; + out->dpdown.target = SDL_HAT_DOWN; + + out->dpleft.kind = EMappingKind_Hat; + out->dpleft.target = SDL_HAT_LEFT; + + out->dpright.kind = EMappingKind_Hat; + out->dpright.target = SDL_HAT_RIGHT; + + out->leftx.kind = EMappingKind_Axis; + out->leftx.target = SDL_GAMEPAD_AXIS_LEFTX; + + out->lefty.kind = EMappingKind_Axis; + out->lefty.target = SDL_GAMEPAD_AXIS_LEFTY; + + out->rightx.kind = EMappingKind_Axis; + out->rightx.target = SDL_GAMEPAD_AXIS_RIGHTX; + + out->righty.kind = EMappingKind_Axis; + out->righty.target = SDL_GAMEPAD_AXIS_RIGHTY; + + out->lefttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; + + out->righttrigger.kind = EMappingKind_Axis; + out->righttrigger.target = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; + + return true; +} + + +SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver = +{ + GAMEINPUT_JoystickInit, + GAMEINPUT_JoystickGetCount, + GAMEINPUT_JoystickDetect, + GAMEINPUT_JoystickIsDevicePresent, + GAMEINPUT_JoystickGetDeviceName, + GAMEINPUT_JoystickGetDevicePath, + GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot, + GAMEINPUT_JoystickGetDevicePlayerIndex, + GAMEINPUT_JoystickSetDevicePlayerIndex, + GAMEINPUT_JoystickGetDeviceGUID, + GAMEINPUT_JoystickGetDeviceInstanceID, + GAMEINPUT_JoystickOpen, + GAMEINPUT_JoystickRumble, + GAMEINPUT_JoystickRumbleTriggers, + GAMEINPUT_JoystickSetLED, + GAMEINPUT_JoystickSendEffect, + GAMEINPUT_JoystickSetSensorsEnabled, + GAMEINPUT_JoystickUpdate, + GAMEINPUT_JoystickClose, + GAMEINPUT_JoystickQuit, + GAMEINPUT_JoystickGetGamepadMapping +}; + + +#endif // SDL_JOYSTICK_GAMEINPUT diff --git a/thirdparty/sdl/joystick/windows/SDL_dinputjoystick.c b/thirdparty/sdl/joystick/windows/SDL_dinputjoystick.c index 5b4b6fc0bb1e..9fd497a423a5 100644 --- a/thirdparty/sdl/joystick/windows/SDL_dinputjoystick.c +++ b/thirdparty/sdl/joystick/windows/SDL_dinputjoystick.c @@ -468,6 +468,7 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta char *manufacturer_string = NULL; char *product_string = NULL; LPDIRECTINPUTDEVICE8 device = NULL; + DIDEVCAPS capabilities; // We are only supporting HID devices. CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID); @@ -481,6 +482,17 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta CHECK(!SDL_ShouldIgnoreJoystick(vendor, product, version, product_string)); CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, product_string)); +#ifdef SDL_JOYSTICK_GAMEINPUT + // If GameInput is enabled, use DirectInput only for haptic-capable devices. + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, false)) { +#ifdef SDL_HAPTIC_DINPUT + CHECK(SUCCEEDED(IDirectInputDevice8_GetCapabilities(device, &capabilities)) && capabilities.dwFlags & (DIDC_ATTACHED | DIDC_FORCEFEEDBACK) != 0); +#else + CHECK(false); +#endif + } +#endif + pNewJoystick = *(JoyStick_DeviceData **)pContext; while (pNewJoystick) { // update GUIDs of joysticks with matching paths, in case they're not open yet diff --git a/thirdparty/sdl/joystick/windows/SDL_rawinputjoystick.c b/thirdparty/sdl/joystick/windows/SDL_rawinputjoystick.c deleted file mode 100644 index 8590d9a83615..000000000000 --- a/thirdparty/sdl/joystick/windows/SDL_rawinputjoystick.c +++ /dev/null @@ -1,2242 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - -*/ -/* - RAWINPUT Joystick API for better handling XInput-capable devices on Windows. - - XInput is limited to 4 devices. - Windows.Gaming.Input does not get inputs from XBox One controllers when not in the foreground. - DirectInput does not get inputs from XBox One controllers when not in the foreground, nor rumble or accurate triggers. - RawInput does not get rumble or accurate triggers. - - So, combine them as best we can! -*/ -#include "SDL_internal.h" - -#ifdef SDL_JOYSTICK_RAWINPUT - -#include "../usb_ids.h" -#include "../SDL_sysjoystick.h" -#include "../../core/windows/SDL_windows.h" -#include "../../core/windows/SDL_hid.h" -#include "../hidapi/SDL_hidapijoystick_c.h" - -/* SDL_JOYSTICK_RAWINPUT_XINPUT is disabled because using XInput at the same time as - raw input will turn off the Xbox Series X controller when it is connected via the - Xbox One Wireless Adapter. - */ -#ifdef HAVE_XINPUT_H -#define SDL_JOYSTICK_RAWINPUT_XINPUT -#endif -#ifdef HAVE_WINDOWS_GAMING_INPUT_H -#define SDL_JOYSTICK_RAWINPUT_WGI -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT -#include "../../core/windows/SDL_xinput.h" -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI -#include "../../core/windows/SDL_windows.h" -typedef struct WindowsGamingInputGamepadState WindowsGamingInputGamepadState; -#define GamepadButtons_GUIDE 0x40000000 -#define COBJMACROS -#include "windows.gaming.input.h" -#include -#endif - -#if defined(SDL_JOYSTICK_RAWINPUT_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT_WGI) -#define SDL_JOYSTICK_RAWINPUT_MATCHING -#define SDL_JOYSTICK_RAWINPUT_MATCH_AXES -#define SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS -#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS -#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 6 // stick + trigger axes -#else -#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 4 // stick axes -#endif -#endif - -#if 0 -#define DEBUG_RAWINPUT -#endif - -#ifndef RIDEV_EXINPUTSINK -#define RIDEV_EXINPUTSINK 0x00001000 -#define RIDEV_DEVNOTIFY 0x00002000 -#endif - -#ifndef WM_INPUT_DEVICE_CHANGE -#define WM_INPUT_DEVICE_CHANGE 0x00FE -#endif -#ifndef WM_INPUT -#define WM_INPUT 0x00FF -#endif -#ifndef GIDC_ARRIVAL -#define GIDC_ARRIVAL 1 -#define GIDC_REMOVAL 2 -#endif - -extern void WINDOWS_RAWINPUTEnabledChanged(void); -extern void WINDOWS_JoystickDetect(void); - -static bool SDL_RAWINPUT_inited = false; -static bool SDL_RAWINPUT_remote_desktop = false; -static int SDL_RAWINPUT_numjoysticks = 0; - -static void RAWINPUT_JoystickClose(SDL_Joystick *joystick); - -typedef struct SDL_RAWINPUT_Device -{ - SDL_AtomicInt refcount; - char *name; - char *path; - Uint16 vendor_id; - Uint16 product_id; - Uint16 version; - SDL_GUID guid; - bool is_xinput; - bool is_xboxone; - int steam_virtual_gamepad_slot; - PHIDP_PREPARSED_DATA preparsed_data; - - HANDLE hDevice; - SDL_Joystick *joystick; - SDL_JoystickID joystick_id; - - struct SDL_RAWINPUT_Device *next; -} SDL_RAWINPUT_Device; - -struct joystick_hwdata -{ - bool is_xinput; - bool is_xboxone; - PHIDP_PREPARSED_DATA preparsed_data; - ULONG max_data_length; - HIDP_DATA *data; - USHORT *button_indices; - USHORT *axis_indices; - USHORT *hat_indices; - bool guide_hack; - bool trigger_hack; - USHORT trigger_hack_index; - -#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING - Uint64 match_state; // Lowest 16 bits for button states, higher 24 for 6 4bit axes - Uint64 last_state_packet; -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT - bool xinput_enabled; - bool xinput_correlated; - Uint8 xinput_correlation_id; - Uint8 xinput_correlation_count; - Uint8 xinput_uncorrelate_count; - Uint8 xinput_slot; -#endif - -#ifdef SDL_JOYSTICK_RAWINPUT_WGI - bool wgi_correlated; - Uint8 wgi_correlation_id; - Uint8 wgi_correlation_count; - Uint8 wgi_uncorrelate_count; - WindowsGamingInputGamepadState *wgi_slot; - struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; -#endif - - bool triggers_rumbling; - - 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; -} 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]; - - string[0] = 0; - if (SDL_HidD_GetManufacturerString(hFile, string, sizeof(string))) { - manufacturer_string = WIN_StringToUTF8W(string); - } - string[0] = 0; - 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, false)) { - 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 && !ctx->triggers_rumbling) { - 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 - // Save off the motor state in case trigger rumble is started - ctx->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; - ctx->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; - if (!rumbled && ctx->wgi_correlated) { - WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->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; - - ctx->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; - ctx->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; - if (ctx->wgi_correlated) { - WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration); - if (!SUCCEEDED(hr)) { - return SDL_SetError("Setting vibration failed: 0x%lx", hr); - } - ctx->triggers_rumbling = (left_rumble > 0 || right_rumble > 0); - 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/thirdparty/sdl/joystick/windows/SDL_windows_gaming_input.c b/thirdparty/sdl/joystick/windows/SDL_windows_gaming_input.c deleted file mode 100644 index 5f9435e34a57..000000000000 --- a/thirdparty/sdl/joystick/windows/SDL_windows_gaming_input.c +++ /dev/null @@ -1,1043 +0,0 @@ -/* - 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 -{ - bool ro_initialized; - 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, false)) { - return true; - } - - if (FAILED(WIN_RoInitialize())) { - return SDL_SetError("RoInitialize() failed"); - } - wgi.ro_initialized = true; - -#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); - } - - if (wgi.ro_initialized) { - 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/thirdparty/sdl/joystick/windows/SDL_xinputjoystick.c b/thirdparty/sdl/joystick/windows/SDL_xinputjoystick.c index 9f6ce103d13f..44fe962a8871 100644 --- a/thirdparty/sdl/joystick/windows/SDL_xinputjoystick.c +++ b/thirdparty/sdl/joystick/windows/SDL_xinputjoystick.c @@ -439,6 +439,11 @@ bool SDL_XINPUT_JoystickInit(void) return true; } +int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid) +{ + return -1; +} + void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) { } diff --git a/thirdparty/sdl/loadso/windows/SDL_sysloadso.c b/thirdparty/sdl/loadso/windows/SDL_sysloadso.c new file mode 100644 index 000000000000..89e414560b56 --- /dev/null +++ b/thirdparty/sdl/loadso/windows/SDL_sysloadso.c @@ -0,0 +1,68 @@ +/* + 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_LOADSO_WINDOWS + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +// System dependent library loading routines + +#include "../../core/windows/SDL_windows.h" + +SDL_SharedObject *SDL_LoadObject(const char *sofile) +{ + if (!sofile) { + SDL_InvalidParamError("sofile"); + return NULL; + } + + LPWSTR wstr = WIN_UTF8ToStringW(sofile); + HMODULE handle = LoadLibraryW(wstr); + SDL_free(wstr); + + // Generate an error message if all loads failed + if (!handle) { + char errbuf[512]; + SDL_snprintf(errbuf, sizeof (errbuf), "Failed loading %s", sofile); + WIN_SetError(errbuf); + } + return (SDL_SharedObject *) handle; +} + +SDL_FunctionPointer SDL_LoadFunction(SDL_SharedObject *handle, const char *name) +{ + SDL_FunctionPointer symbol = (SDL_FunctionPointer)GetProcAddress((HMODULE)handle, name); + if (!symbol) { + char errbuf[512]; + SDL_snprintf(errbuf, sizeof (errbuf), "Failed loading %s", name); + WIN_SetError(errbuf); + } + return symbol; +} + +void SDL_UnloadObject(SDL_SharedObject *handle) +{ + if (handle) { + FreeLibrary((HMODULE)handle); + } +} + +#endif // SDL_LOADSO_WINDOWS diff --git a/thirdparty/sdl/patches/0009-gameinput-fixes.patch b/thirdparty/sdl/patches/0009-gameinput-fixes.patch new file mode 100644 index 000000000000..10619ce80696 --- /dev/null +++ b/thirdparty/sdl/patches/0009-gameinput-fixes.patch @@ -0,0 +1,160 @@ +diff --git a/thirdparty/sdl/joystick/SDL_joystick.c b/thirdparty/sdl/joystick/SDL_joystick.c +--- a/thirdparty/sdl/joystick/SDL_joystick.c ++++ b/thirdparty/sdl/joystick/SDL_joystick.c +@@ -3115,6 +3115,11 @@ bool SDL_IsJoystickWGI(SDL_GUID guid) + return (guid.data[14] == 'w') ? true : false; + } + ++bool SDL_IsJoystickGameInputGamepad(SDL_GUID guid) ++{ ++ return (guid.data[14] == 'g') ? true : false; ++} ++ + bool SDL_IsJoystickHIDAPI(SDL_GUID guid) + { + return (guid.data[14] == 'h') ? true : false; +@@ -3208,6 +3213,10 @@ static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_GUID guid) + return (SDL_JoystickType)guid.data[15]; + } + ++ if (SDL_IsJoystickGameInputGamepad(guid)) { ++ return (SDL_JoystickType)guid.data[15]; ++ } ++ + if (SDL_IsJoystickVIRTUAL(guid)) { + return (SDL_JoystickType)guid.data[15]; + } +diff --git a/thirdparty/sdl/joystick/gdk/SDL_gameinputjoystick.cpp b/thirdparty/sdl/joystick/gdk/SDL_gameinputjoystick.cpp +--- a/thirdparty/sdl/joystick/gdk/SDL_gameinputjoystick.cpp ++++ b/thirdparty/sdl/joystick/gdk/SDL_gameinputjoystick.cpp +@@ -84,6 +84,24 @@ static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) + return false; + } + ++static Uint8 GAMEINPUT_GetDeviceSubtype(const GameInputDeviceInfo *info) { ++ GameInputKind supportedInput = info->supportedInput; ++ if (supportedInput & GameInputKindRacingWheel) { ++ return SDL_JOYSTICK_TYPE_WHEEL; ++ } ++ if (supportedInput & GameInputKindArcadeStick) { ++ return SDL_JOYSTICK_TYPE_ARCADE_STICK; ++ } ++ if (supportedInput & GameInputKindFlightStick) { ++ return SDL_JOYSTICK_TYPE_FLIGHT_STICK; ++ } ++ if (supportedInput & (GameInputKindGamepad | GameInputKindController)) { ++ return SDL_JOYSTICK_TYPE_GAMEPAD; ++ } ++ // Other device subtypes don't have their own GameInputKind enum entries. ++ return 0; ++} ++ + #if GAMEINPUT_API_VERSION >= 1 + static int GetSteamVirtualGamepadSlot(const char *device_path) + { +@@ -105,8 +123,9 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) + Uint16 vendor = 0; + Uint16 product = 0; + Uint16 version = 0; +- const char *manufacturer_string = NULL; + const char *product_string = NULL; ++ Uint8 driver_signature = 'g'; ++ Uint8 subtype = 0; + char tmp[4]; + int idx = 0; + +@@ -128,10 +147,41 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) + vendor = info->vendorId; + product = info->productId; + //version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; ++ subtype = GAMEINPUT_GetDeviceSubtype(info); ++ ++#if GAMEINPUT_API_VERSION >= 1 ++ if (info->displayName) { ++ product_string = info->displayName; ++ } ++#else ++ if (info->displayName) { ++ product_string = info->displayName->data; ++ } ++#endif ++ ++ if (SDL_ShouldIgnoreJoystick(vendor, product, version, product_string) ++ || SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, product_string)) { ++ return true; ++ } + +- if (SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, "")) { ++#if defined(SDL_JOYSTICK_DINPUT) && defined(SDL_HAPTIC_DINPUT) ++ // This joystick backend currently doesn't provide a haptic backend, ++ // so fallback to DirectInput for haptic-capable devices. ++ if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true) && info->forceFeedbackMotorCount > 0 && pDevice->IsForceFeedbackMotorPoweredOn(0)) { + return true; + } ++#endif ++ ++ if (!GAMEINPUT_InternalIsGamepad(info)) { ++ if (info->supportedInput & GameInputKindController) { ++ // Maintain GUID compatibility with DirectInput controller mappings. ++ driver_signature = 0; ++ subtype = 0; ++ } else { ++ // This joystick backend currently doesn't provide proper reading of other joystick types. ++ return true; ++ } ++ } + + for (idx = 0; idx < g_GameInputList.count; ++idx) { + elem = g_GameInputList.devices[idx]; +@@ -159,20 +209,10 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) + SDL_strlcat(elem->path, tmp, SDL_arraysize(elem->path)); + } + +-#if GAMEINPUT_API_VERSION >= 1 +- if (info->displayName) { +- product_string = info->displayName; +- } +-#else +- if (info->displayName) { +- product_string = info->displayName->data; +- } +-#endif +- + pDevice->AddRef(); + elem->device = pDevice; +- elem->name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); +- elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, manufacturer_string, product_string, 'g', 0); ++ elem->name = SDL_CreateJoystickName(vendor, product, NULL, product_string); ++ elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, product_string, driver_signature, subtype); + elem->device_instance = SDL_GetNextObjectID(); + elem->info = info; + #if GAMEINPUT_API_VERSION >= 1 +diff --git a/thirdparty/sdl/joystick/windows/SDL_dinputjoystick.c b/thirdparty/sdl/joystick/windows/SDL_dinputjoystick.c +--- a/thirdparty/sdl/joystick/windows/SDL_dinputjoystick.c ++++ b/thirdparty/sdl/joystick/windows/SDL_dinputjoystick.c +@@ -468,6 +468,7 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta + char *manufacturer_string = NULL; + char *product_string = NULL; + LPDIRECTINPUTDEVICE8 device = NULL; ++ DIDEVCAPS capabilities; + + // We are only supporting HID devices. + CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID); +@@ -481,6 +482,17 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta + CHECK(!SDL_ShouldIgnoreJoystick(vendor, product, version, product_string)); + CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, product_string)); + ++#ifdef SDL_JOYSTICK_GAMEINPUT ++ // If GameInput is enabled, use DirectInput only for haptic-capable devices. ++ if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, false)) { ++#ifdef SDL_HAPTIC_DINPUT ++ CHECK(SUCCEEDED(IDirectInputDevice8_GetCapabilities(device, &capabilities)) && capabilities.dwFlags & (DIDC_ATTACHED | DIDC_FORCEFEEDBACK) != 0); ++#else ++ CHECK(false); ++#endif ++ } ++#endif ++ + pNewJoystick = *(JoyStick_DeviceData **)pContext; + while (pNewJoystick) { + // update GUIDs of joysticks with matching paths, in case they're not open yet diff --git a/thirdparty/sdl/update-sdl.sh b/thirdparty/sdl/update-sdl.sh index 08d0e32b9be0..9db1fd8de436 100755 --- a/thirdparty/sdl/update-sdl.sh +++ b/thirdparty/sdl/update-sdl.sh @@ -55,10 +55,12 @@ mkdir $target/haptic cp -rv haptic/{*.{c,h},darwin,dummy,linux,windows} $target/haptic mkdir $target/joystick -cp -rv joystick/{*.{c,h},apple,darwin,hidapi,linux,windows} $target/joystick +cp -rv joystick/{*.{c,h},apple,darwin,gdk,hidapi,linux} $target/joystick +mkdir $target/joystick/windows +cp -rv joystick/windows/SDL_{{dinput,windows,xinput}joystick*.{c,h},rawinputjoystick_c.h} $target/joystick/windows mkdir $target/loadso -cp -rv loadso/{dlopen,dummy} $target/loadso +cp -rv loadso/{dlopen,dummy,windows} $target/loadso mkdir $target/sensor cp -rv sensor/{*.{c,h},dummy,windows} $target/sensor