diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index e070c31ef7..cec3972f0f 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -438,6 +438,7 @@
+
@@ -576,9 +577,7 @@
-
-
@@ -691,6 +690,10 @@
CompileAsCpp
CompileAsCpp
+
+ $(IntDir)$(TargetName)_cpp.pch
+ $(IntDir)$(TargetName)_cpp.pch
+
diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h
index 2f25b597e5..d856d2323e 100644
--- a/include/build_config/SDL_build_config_wingdk.h
+++ b/include/build_config/SDL_build_config_wingdk.h
@@ -176,6 +176,16 @@
#define SDL_JOYSTICK_XINPUT 1
#define SDL_HAPTIC_DINPUT 1
+/* Native GameInput: */
+/*#define SDL_JOYSTICK_GAMEINPUT 1*/
+#if defined(SDL_JOYSTICK_GAMEINPUT) && (defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_DINPUT))
+#error "GameInput cannot co-exist, choose one."
+#endif /* defined(SDL_JOYSTICK_GAMEINPUT) && (defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_DINPUT)) */
+#if defined(SDL_JOYSTICK_GAMEINPUT) && SDL_JOYSTICK_GAMEINPUT
+/* TODO: Implement proper haptics for GameInput! */
+#define SDL_HAPTIC_DUMMY 1
+#endif /* defined(SDL_JOYSTICK_GAMEINPUT) && SDL_JOYSTICK_GAMEINPUT */
+
/* Enable the sensor driver */
#ifdef HAVE_SENSORSAPI_H
#define SDL_SENSOR_WINDOWS 1
diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h
index 2f0f090fe7..c5198aeec7 100644
--- a/include/build_config/SDL_build_config_xbox.h
+++ b/include/build_config/SDL_build_config_xbox.h
@@ -173,7 +173,17 @@
#ifdef HAVE_WINDOWS_GAMING_INPUT_H
#define SDL_JOYSTICK_WGI 1
#endif
-#define SDL_JOYSTICK_XINPUT 1
+/* This is XInputOnGameInput for GDK platforms: */
+/*#define SDL_JOYSTICK_XINPUT 1*/
+/* Native GameInput: */
+#define SDL_JOYSTICK_GAMEINPUT 1
+#if defined(SDL_JOYSTICK_GAMEINPUT) && (defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_DINPUT))
+#error "GameInput cannot co-exist, choose one."
+#endif /* defined(SDL_JOYSTICK_GAMEINPUT) && (defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_DINPUT)) */
+#if defined(SDL_JOYSTICK_GAMEINPUT) && SDL_JOYSTICK_GAMEINPUT
+/* TODO: Implement proper haptics for GameInput! */
+#define SDL_HAPTIC_DUMMY 1
+#endif /* defined(SDL_JOYSTICK_GAMEINPUT) && SDL_JOYSTICK_GAMEINPUT */
/*#define SDL_HAPTIC_DINPUT 1*/
/* Enable the sensor driver */
diff --git a/src/core/windows/SDL_xinput.c b/src/core/windows/SDL_xinput.c
index 13c06bde4d..fdeed70854 100644
--- a/src/core/windows/SDL_xinput.c
+++ b/src/core/windows/SDL_xinput.c
@@ -20,6 +20,8 @@
*/
#include "SDL_internal.h"
+#ifndef SDL_JOYSTICK_GAMEINPUT
+
#include "SDL_xinput.h"
/* Set up for C function definitions, even when using C++ */
@@ -142,3 +144,5 @@ void WIN_UnloadXInputDLL(void)
#ifdef __cplusplus
}
#endif
+
+#endif /* !SDL_JOYSTICK_GAMEINPUT */
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index bbc553b58b..c1c1a3843c 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -55,6 +55,9 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = {
#ifdef SDL_JOYSTICK_RAWINPUT /* Before WINDOWS_ driver, as WINDOWS wants to check if this driver is handling things */
&SDL_RAWINPUT_JoystickDriver,
#endif
+#ifdef SDL_JOYSTICK_GAMEINPUT /* Before WINDOWS_ driver, as GameInput takes priority over XInputOnGameInput for GDK platforms */
+ &SDL_GAMEINPUT_JoystickDriver,
+#endif
#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT) /* Before WGI driver, as WGI wants to check if this driver is handling things */
&SDL_WINDOWS_JoystickDriver,
#endif
diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index 95544118da..739d6973d1 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -245,6 +245,7 @@ extern SDL_JoystickDriver SDL_PS2_JoystickDriver;
extern SDL_JoystickDriver SDL_PSP_JoystickDriver;
extern SDL_JoystickDriver SDL_VITA_JoystickDriver;
extern SDL_JoystickDriver SDL_N3DS_JoystickDriver;
+extern SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver;
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
diff --git a/src/joystick/gdk/SDL_gameinputjoystick.cpp b/src/joystick/gdk/SDL_gameinputjoystick.cpp
new file mode 100644
index 0000000000..1b4223d6ea
--- /dev/null
+++ b/src/joystick/gdk/SDL_gameinputjoystick.cpp
@@ -0,0 +1,582 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 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_gameinputjoystick_c.h"
+
+#if defined(SDL_JOYSTICK_GAMEINPUT) && SDL_JOYSTICK_GAMEINPUT
+
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Public APIs: GAMEINPUT_Joystick... */
+/* Private APIs: GAMEINPUT_InternalJoystick... */
+
+#include "../usb_ids.h"
+
+typedef struct GAMEINPUT_InternalDevice
+{
+ IGameInputDevice *device;
+ const char *deviceName; /* this is a constant string literal */
+ SDL_JoystickGUID joystickGuid; /* generated by SDL. */
+ SDL_JoystickID instanceId; /* generated by SDL. */
+ int playerIndex;
+ Uint32 caps;
+ char devicePath[(APP_LOCAL_DEVICE_ID_SIZE * 2) + 1];
+ bool isAdded, isDeleteRequested;
+} GAMEINPUT_InternalDevice;
+
+typedef struct GAMEINPUT_InternalList
+{
+ GAMEINPUT_InternalDevice **devices;
+ int count;
+} GAMEINPUT_InternalList;
+
+typedef struct joystick_hwdata
+{
+ GAMEINPUT_InternalDevice *devref;
+ GameInputRumbleParams rumbleParams;
+ Uint64 lastTimestamp;
+} GAMEINPUT_InternalJoystickHwdata;
+
+
+static GAMEINPUT_InternalList g_GameInputList = { NULL };
+static IGameInput *g_pGameInput = NULL;
+static GameInputCallbackToken g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
+
+
+static int GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice)
+{
+ GAMEINPUT_InternalDevice **devicelist = NULL;
+ GAMEINPUT_InternalDevice *elem = NULL;
+ const GameInputDeviceInfo *devinfo = NULL;
+ char tmpbuff[4];
+ int idx = 0;
+
+ if (!pDevice) {
+ return SDL_SetError("GAMEINPUT_InternalAddOrFind argument pDevice cannot be NULL");
+ }
+
+ devinfo = pDevice->GetDeviceInfo();
+ if (!devinfo) {
+ return SDL_SetError("GAMEINPUT_InternalAddOrFind GetDeviceInfo returned NULL");
+ }
+
+ for (idx = 0; idx < g_GameInputList.count; ++idx) {
+ elem = g_GameInputList.devices[idx];
+ if (elem && elem->device == pDevice) {
+ /* we're already added */
+ return idx;
+ }
+ }
+
+ elem = (GAMEINPUT_InternalDevice *)SDL_calloc(1, sizeof(*elem));
+ if (!elem) {
+ return SDL_OutOfMemory();
+ }
+
+ /* generate a device name */
+ for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) {
+ (void)SDL_snprintf(tmpbuff, SDL_arraysize(tmpbuff), "%02hhX", devinfo->deviceId.value[idx]);
+ (void)strncat_s(elem->devicePath, tmpbuff, SDL_arraysize(tmpbuff));
+ }
+
+ devicelist = (GAMEINPUT_InternalDevice **)SDL_realloc(g_GameInputList.devices, sizeof(elem) * (g_GameInputList.count + 1LL));
+ if (!devicelist) {
+ SDL_free(elem);
+ return SDL_OutOfMemory();
+ }
+
+ g_GameInputList.devices = devicelist;
+ pDevice->AddRef();
+ elem->device = pDevice;
+ elem->deviceName = "GameInput Gamepad";
+ elem->caps = 0;
+ if (devinfo->supportedRumbleMotors & (GameInputRumbleLowFrequency | GameInputRumbleHighFrequency)) {
+ elem->caps |= SDL_JOYSTICK_CAP_RUMBLE;
+ }
+ if (devinfo->supportedRumbleMotors & (GameInputRumbleLeftTrigger | GameInputRumbleRightTrigger)) {
+ elem->caps |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE;
+ }
+ elem->joystickGuid = SDL_CreateJoystickGUID(
+ SDL_HARDWARE_BUS_BLUETOOTH,
+ USB_VENDOR_MICROSOFT,
+ USB_PRODUCT_XBOX_SERIES_X_BLE,
+ 1,
+ "GameInput",
+ "Gamepad",
+ 'g',
+ 0
+ );
+ elem->instanceId = SDL_GetNextObjectID();
+ g_GameInputList.devices[g_GameInputList.count] = elem;
+
+ /* finally increment the count and return */
+ return g_GameInputList.count++;
+}
+
+static int GAMEINPUT_InternalRemoveByIndex(int idx)
+{
+ GAMEINPUT_InternalDevice **devicelist = NULL;
+ int bytes = 0;
+
+ if (idx < 0 || idx >= g_GameInputList.count) {
+ return SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx);
+ }
+
+ g_GameInputList.devices[idx]->device->Release();
+
+ if (g_GameInputList.devices[idx]) {
+ SDL_free(g_GameInputList.devices[idx]);
+ 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);
+ }
+
+ devicelist = (GAMEINPUT_InternalDevice **)SDL_realloc(g_GameInputList.devices, sizeof(*devicelist) * (g_GameInputList.count - 1LL));
+ if (!devicelist) {
+ return SDL_OutOfMemory();
+ }
+
+ g_GameInputList.devices = devicelist;
+ }
+
+ /* decrement the count and return */
+ return g_GameInputList.count--;
+}
+
+static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx)
+{
+ if (idx < 0 || idx >= g_GameInputList.count) {
+ SDL_SetError("GAMEINPUT_InternalFindByIndex argument idx %d out of range", idx);
+ return NULL;
+ }
+
+ 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 (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;
+ }
+ }
+ }
+}
+
+static void GAMEINPUT_JoystickDetect(void);
+
+static int GAMEINPUT_JoystickInit(void)
+{
+ HRESULT hR;
+
+ if (!g_pGameInput) {
+ hR = GameInputCreate(&g_pGameInput);
+ if (FAILED(hR)) {
+ return SDL_SetError("GameInputCreate failure with HRESULT of %08X", hR);
+ }
+ }
+
+ hR = g_pGameInput->RegisterDeviceCallback(
+ nullptr,
+ GameInputKindGamepad,
+ GameInputDeviceConnected,
+ GameInputBlockingEnumeration,
+ nullptr,
+ GAMEINPUT_InternalJoystickDeviceCallback,
+ &g_GameInputCallbackToken
+ );
+ if (FAILED(hR)) {
+ return SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08X", hR);
+ }
+
+ GAMEINPUT_JoystickDetect();
+
+ /* no need to free IGameInput on failure. */
+ return 0;
+}
+
+static int GAMEINPUT_JoystickGetCount(void)
+{
+ return g_GameInputList.count;
+}
+
+static void GAMEINPUT_JoystickDetect(void)
+{
+ int idx = 0;
+ GAMEINPUT_InternalDevice *elem = NULL;
+
+ for (idx = 0; idx < g_GameInputList.count; ++idx) {
+ elem = g_GameInputList.devices[idx];
+ if (!elem) {
+ continue;
+ }
+
+ if (!elem->isAdded) {
+ SDL_PrivateJoystickAdded(elem->instanceId);
+ elem->isAdded = true;
+ }
+
+ if (elem->isDeleteRequested || !(elem->device->GetDeviceStatus() & GameInputDeviceConnected)) {
+ SDL_PrivateJoystickRemoved(elem->instanceId);
+ GAMEINPUT_InternalRemoveByIndex(idx--);
+ }
+ }
+}
+
+static const char *GAMEINPUT_JoystickGetDeviceName(int device_index)
+{
+ GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
+
+ if (!elem) {
+ return NULL;
+ }
+
+ return elem->deviceName;
+}
+
+static const char *GAMEINPUT_JoystickGetDevicePath(int device_index)
+{
+ GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
+
+ if (!elem) {
+ return NULL;
+ }
+
+ /* APP_LOCAL_DEVICE_ID as a hex string, since it's required for some association callbacks */
+ return elem->devicePath;
+}
+
+static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+ /* Steamworks API is not available in GDK */
+ return -1;
+}
+
+static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index)
+{
+ /*
+ * Okay, so, while XInput technically has player indicies,
+ * GameInput does not. It just dispatches a callback whenever a device is found.
+ * So if you're using true native GameInput (which this backend IS)
+ * you're meant to assign some index to a player yourself.
+ *
+ * GameMaker, for example, seems to do this in the order of plugging in.
+ *
+ * Sorry for the trouble!
+ */
+ GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
+
+ if (!elem) {
+ return -1;
+ }
+
+ return elem->playerIndex;
+}
+
+static void GAMEINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index)
+{
+ GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
+
+ if (!elem) {
+ return;
+ }
+
+ elem->playerIndex = player_index;
+}
+
+static SDL_JoystickGUID GAMEINPUT_JoystickGetDeviceGUID(int device_index)
+{
+ GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
+
+ if (!elem) {
+ /* empty guid */
+ return { { 0 } };
+ }
+
+ return elem->joystickGuid;
+}
+
+static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index)
+{
+ GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
+
+ if (!elem) {
+ return 0;
+ }
+
+ return elem->instanceId;
+}
+
+static int GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
+{
+ GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
+ GAMEINPUT_InternalJoystickHwdata *hwdata = NULL;
+
+ if (!elem) {
+ return -1;
+ }
+
+ hwdata = (GAMEINPUT_InternalJoystickHwdata *)SDL_calloc(1, sizeof(*hwdata));
+ if (!hwdata) {
+ return SDL_OutOfMemory();
+ }
+
+ hwdata->devref = elem;
+
+ joystick->hwdata = hwdata;
+ joystick->naxes = 6;
+ joystick->nbuttons = 11;
+ joystick->nhats = 1;
+
+ return 0;
+}
+
+static int 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 0;
+}
+
+static int 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 0;
+}
+
+static Uint32 GAMEINPUT_JoystickGetCapabilities(SDL_Joystick *joystick)
+{
+ return joystick->hwdata->devref->caps;
+}
+
+static int GAMEINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+ return SDL_Unsupported();
+}
+
+static int GAMEINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+ HRESULT hR = S_OK;
+ const GAMEINPUT_JoystickEffectData *effect = NULL;
+ GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
+
+ if (!data || size != sizeof(GAMEINPUT_JoystickEffectData)) {
+ return SDL_SetError("GAMEINPUT_JoystickSendEffect invalid data or size");
+ }
+
+ effect = (const GAMEINPUT_JoystickEffectData *)data;
+ if (effect->type == GAMEINPUT_JoystickEffectDataType_HapticFeedback) {
+ hR = hwdata->devref->device->SetHapticMotorState(
+ effect->hapticFeedbackMotorIndex,
+ &effect->hapticFeedbackParams
+ );
+ if (FAILED(hR)) {
+ return SDL_SetError("IGameInputDevice::SetHapticMotorState failure with HRESULT of %08X", hR);
+ }
+ }
+
+ return 0;
+}
+
+static int GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
+{
+ /* I am not sure what is this even supposed to do in case of GameInput... */
+ return 0;
+}
+
+static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
+{
+ static WORD s_XInputButtons[] = {
+ GameInputGamepadA, GameInputGamepadB, GameInputGamepadX, GameInputGamepadY,
+ GameInputGamepadLeftShoulder, GameInputGamepadRightShoulder, GameInputGamepadView, GameInputGamepadMenu,
+ GameInputGamepadLeftThumbstick, GameInputGamepadRightThumbstick,
+ 0 /* Guide button is not supported on Xbox so ignore that... */
+ };
+ Uint8 btnidx = 0, btnstate = 0, hat = 0;
+ GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
+ IGameInputDevice *device = hwdata->devref->device;
+ IGameInputReading *reading = NULL;
+ uint64_t ts = 0;
+ GameInputGamepadState state;
+ HRESULT hR = g_pGameInput->GetCurrentReading(
+ GameInputKindGamepad,
+ device,
+ &reading
+ );
+
+ if (FAILED(hR)) {
+ /* don't SetError here since there can be a legitimate case when there's no reading avail */
+ return;
+ }
+
+ /* GDKX private docs for GetTimestamp: "The microsecond timestamp describing when the input was made." */
+ /* SDL expects a nanosecond timestamp, so I guess US_TO_NS should be used here? */
+ ts = SDL_US_TO_NS(reading->GetTimestamp());
+
+ if (((!hwdata->lastTimestamp) || (ts != hwdata->lastTimestamp)) && reading->GetGamepadState(&state)) {
+ /* `state` is now valid */
+
+#define tosint16(_TheValue) ((Sint16)(((_TheValue) < 0.0f) ? ((_TheValue) * 32768.0f) : ((_TheValue) * 32767.0f)))
+ SDL_SendJoystickAxis(ts, joystick, 0, tosint16(state.leftThumbstickX));
+ SDL_SendJoystickAxis(ts, joystick, 1, tosint16(state.leftThumbstickY));
+ SDL_SendJoystickAxis(ts, joystick, 2, tosint16(state.leftTrigger));
+ SDL_SendJoystickAxis(ts, joystick, 3, tosint16(state.rightThumbstickX));
+ SDL_SendJoystickAxis(ts, joystick, 4, tosint16(state.rightThumbstickY));
+ SDL_SendJoystickAxis(ts, joystick, 5, tosint16(state.rightTrigger));
+#undef tosint16
+
+ for (btnidx = 0; btnidx < (Uint8)SDL_arraysize(s_XInputButtons); ++btnidx) {
+ if (s_XInputButtons[btnidx] == 0) {
+ btnstate = SDL_RELEASED;
+ } else {
+ btnstate = (state.buttons & s_XInputButtons[btnidx]) ? SDL_PRESSED : SDL_RELEASED;
+ }
+
+ SDL_SendJoystickButton(ts, joystick, btnidx, btnstate);
+ }
+
+ 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(ts, joystick, 0, hat);
+
+ /* Xbox doesn't let you obtain the power level, pretend we're always full */
+ SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL);
+
+ hwdata->lastTimestamp = ts;
+ }
+
+ reading->Release();
+}
+
+static void GAMEINPUT_JoystickClose(SDL_Joystick* joystick)
+{
+ SDL_free(joystick->hwdata);
+ joystick->hwdata = NULL;
+}
+
+static void GAMEINPUT_JoystickQuit(void)
+{
+ int idx;
+
+ if (!g_pGameInput) {
+ return;
+ }
+
+ /* free the callback */
+ g_pGameInput->UnregisterCallback(g_GameInputCallbackToken, /*timeoutInUs:*/ 10000);
+ g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
+
+ /* free the list */
+ for (idx = 0; idx < g_GameInputList.count; ++idx) {
+ g_GameInputList.devices[idx]->device->Release();
+ SDL_free(g_GameInputList.devices[idx]);
+ g_GameInputList.devices[idx] = NULL;
+ }
+ SDL_free(g_GameInputList.devices);
+ g_GameInputList.devices = NULL;
+ g_GameInputList.count = 0;
+
+ g_pGameInput->Release();
+ g_pGameInput = NULL;
+}
+
+static SDL_bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
+{
+ return SDL_FALSE;
+}
+
+
+SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver =
+{
+ GAMEINPUT_JoystickInit,
+ GAMEINPUT_JoystickGetCount,
+ GAMEINPUT_JoystickDetect,
+ GAMEINPUT_JoystickGetDeviceName,
+ GAMEINPUT_JoystickGetDevicePath,
+ GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot,
+ GAMEINPUT_JoystickGetDevicePlayerIndex,
+ GAMEINPUT_JoystickSetDevicePlayerIndex,
+ GAMEINPUT_JoystickGetDeviceGUID,
+ GAMEINPUT_JoystickGetDeviceInstanceID,
+ GAMEINPUT_JoystickOpen,
+ GAMEINPUT_JoystickRumble,
+ GAMEINPUT_JoystickRumbleTriggers,
+ GAMEINPUT_JoystickGetCapabilities,
+ GAMEINPUT_JoystickSetLED,
+ GAMEINPUT_JoystickSendEffect,
+ GAMEINPUT_JoystickSetSensorsEnabled,
+ GAMEINPUT_JoystickUpdate,
+ GAMEINPUT_JoystickClose,
+ GAMEINPUT_JoystickQuit,
+ GAMEINPUT_JoystickGetGamepadMapping
+};
+
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* defined(SDL_JOYSTICK_GAMEINPUT) && SDL_JOYSTICK_GAMEINPUT */
diff --git a/src/joystick/gdk/SDL_gameinputjoystick_c.h b/src/joystick/gdk/SDL_gameinputjoystick_c.h
new file mode 100644
index 0000000000..03bc1c0599
--- /dev/null
+++ b/src/joystick/gdk/SDL_gameinputjoystick_c.h
@@ -0,0 +1,58 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+#include "../SDL_sysjoystick.h"
+
+#if defined(SDL_JOYSTICK_GAMEINPUT) && SDL_JOYSTICK_GAMEINPUT
+
+/* include this file in C++ */
+#include
+
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum GAMEINPUT_JoystickEffectDataType
+{
+ GAMEINPUT_JoystickEffectDataType_HapticFeedback
+} GAMEINPUT_JoystickEffectDataType;
+
+typedef struct GAMEINPUT_JoystickEffectData
+{
+ GAMEINPUT_JoystickEffectDataType type;
+
+ union
+ {
+ struct /* type == GAMEINPUT_JoystickEffectDataType_HapticFeedback */
+ {
+ uint32_t hapticFeedbackMotorIndex;
+ GameInputHapticFeedbackParams hapticFeedbackParams;
+ };
+ };
+} GAMEINPUT_JoystickEffectData;
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* defined(SDL_JOYSTICK_GAMEINPUT) && SDL_JOYSTICK_GAMEINPUT */