SDL: Refactored GameInput initialization

From 1b35ca9c32e2eac091378838ada01c24575e0c14 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 12 Feb 2025 12:10:36 -0800
Subject: [PATCH] Refactored GameInput initialization

---
 VisualC-GDK/SDL/SDL.vcxproj              |  2 +
 VisualC-GDK/SDL/SDL.vcxproj.filters      |  2 +
 VisualC/SDL/SDL.vcxproj                  |  2 +
 VisualC/SDL/SDL.vcxproj.filters          |  9 +++
 src/core/windows/SDL_gameinput.c         | 98 ++++++++++++++++++++++++
 src/core/windows/SDL_gameinput.h         | 36 +++++++++
 src/joystick/gdk/SDL_gameinputjoystick.c | 33 +-------
 src/video/windows/SDL_windowsgameinput.c | 26 ++-----
 8 files changed, 158 insertions(+), 50 deletions(-)
 create mode 100644 src/core/windows/SDL_gameinput.c
 create mode 100644 src/core/windows/SDL_gameinput.h

diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 7376967129430..ce0c92a28210a 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -425,6 +425,7 @@
     <ClInclude Include="..\..\src\camera\SDL_syscamera.h" />
     <ClInclude Include="..\..\src\core\gdk\SDL_gdk.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_directx.h" />
+    <ClInclude Include="..\..\src\core\windows\SDL_gameinput.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_hid.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_immdevice.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_windows.h" />
@@ -640,6 +641,7 @@
     <ClCompile Include="..\..\src\audio\SDL_wave.c" />
     <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />
     <ClCompile Include="..\..\src\core\SDL_core_unsupported.c" />
+    <ClCompile Include="..\..\src\core\windows\SDL_gameinput.c"/>
     <ClCompile Include="..\..\src\core\windows\SDL_hid.c" />
     <ClCompile Include="..\..\src\core\windows\SDL_immdevice.c" />
     <ClCompile Include="..\..\src\core\windows\SDL_windows.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 247bbdc0b034a..1753f24768b49 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -27,6 +27,7 @@
     <ClCompile Include="..\..\src\audio\SDL_wave.c" />
     <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />
     <ClCompile Include="..\..\src\core\SDL_core_unsupported.c" />
+    <ClCompile Include="..\..\src\core\windows\SDL_gameinput.c" />
     <ClCompile Include="..\..\src\core\windows\SDL_hid.c" />
     <ClCompile Include="..\..\src\core\windows\SDL_immdevice.c" />
     <ClCompile Include="..\..\src\core\windows\SDL_windows.c" />
@@ -314,6 +315,7 @@
     <ClInclude Include="..\..\src\audio\wasapi\SDL_wasapi.h" />
     <ClInclude Include="..\..\src\core\gdk\SDL_gdk.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_directx.h" />
+    <ClInclude Include="..\..\src\core\windows\SDL_gameinput.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_hid.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_immdevice.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_windows.h" />
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 79d5c4681311e..02fa7d6097ec4 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -336,6 +336,7 @@
     <ClInclude Include="..\..\src\camera\SDL_camera_c.h" />
     <ClInclude Include="..\..\src\camera\SDL_syscamera.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_directx.h" />
+    <ClInclude Include="..\..\src\core\windows\SDL_gameinput.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_hid.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_immdevice.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_windows.h" />
@@ -538,6 +539,7 @@
     <ClCompile Include="..\..\src\audio\SDL_wave.c" />
     <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />
     <ClCompile Include="..\..\src\core\SDL_core_unsupported.c" />
+    <ClCompile Include="..\..\src\core\windows\SDL_gameinput.c" />
     <ClCompile Include="..\..\src\core\windows\SDL_hid.c" />
     <ClCompile Include="..\..\src\core\windows\SDL_immdevice.c" />
     <ClCompile Include="..\..\src\core\windows\SDL_windows.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 176bea64c9c7d..e021390875b02 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -486,6 +486,12 @@
     <ClInclude Include="..\..\src\audio\SDL_audioresample.h">
       <Filter>audio</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\core\windows\SDL_directx.h">
+      <Filter>core\windows</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\core\windows\SDL_gameinput.h">
+      <Filter>core\windows</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\core\windows\SDL_hid.h">
       <Filter>core\windows</Filter>
     </ClInclude>
@@ -1028,6 +1034,9 @@
     <ClCompile Include="..\..\src\core\SDL_core_unsupported.c">
       <Filter>core</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\core\windows\SDL_gameinput.c">
+      <Filter>core\windows</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\core\windows\SDL_hid.c">
       <Filter>core\windows</Filter>
     </ClCompile>
diff --git a/src/core/windows/SDL_gameinput.c b/src/core/windows/SDL_gameinput.c
new file mode 100644
index 0000000000000..9ac5912db9d06
--- /dev/null
+++ b/src/core/windows/SDL_gameinput.c
@@ -0,0 +1,98 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
+
+  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 HAVE_GAMEINPUT_H
+
+#include "SDL_windows.h"
+#include "SDL_gameinput.h"
+
+#ifdef SDL_PLATFORM_WIN32
+#include <initguid.h>
+// {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) {
+        g_hGameInputDLL = SDL_LoadObject("gameinput.dll");
+        if (!g_hGameInputDLL) {
+            return false;
+        }
+
+        typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput);
+        GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate");
+        if (!GameInputCreateFunc) {
+            SDL_UnloadObject(g_hGameInputDLL);
+            return false;
+        }
+
+        IGameInput *pGameInput = NULL;
+        HRESULT hr = GameInputCreateFunc(&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 (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
+    }
+    ++g_nGameInputRefCount;
+
+    if (ppGameInput) {
+        *ppGameInput = g_pGameInput;
+    }
+    return true;
+}
+
+void SDL_QuitGameInput(void)
+{
+    SDL_assert(g_nGameInputRefCount > 0);
+
+    --g_nGameInputRefCount;
+    if (g_nGameInputRefCount == 0) {
+        if (g_pGameInput) {
+            IGameInput_Release(g_pGameInput);
+            g_pGameInput = NULL;
+        }
+        if (g_hGameInputDLL) {
+            SDL_UnloadObject(g_hGameInputDLL);
+            g_hGameInputDLL = NULL;
+        }
+    }
+}
+
+#endif // HAVE_GAMEINPUT_H
diff --git a/src/core/windows/SDL_gameinput.h b/src/core/windows/SDL_gameinput.h
new file mode 100644
index 0000000000000..0022c0bdde163
--- /dev/null
+++ b/src/core/windows/SDL_gameinput.h
@@ -0,0 +1,36 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
+
+  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"
+
+#ifndef SDL_gameinput_h_
+#define SDL_gameinput_h_
+
+#ifdef HAVE_GAMEINPUT_H
+
+#define COBJMACROS
+#include <gameinput.h>
+
+extern bool SDL_InitGameInput(IGameInput **ppGameInput);
+extern void SDL_QuitGameInput(void);
+
+#endif // HAVE_GAMEINPUT_H
+
+#endif // SDL_gameinput_h_
diff --git a/src/joystick/gdk/SDL_gameinputjoystick.c b/src/joystick/gdk/SDL_gameinputjoystick.c
index 27e36e00c5ab5..8674ca0d4a15e 100644
--- a/src/joystick/gdk/SDL_gameinputjoystick.c
+++ b/src/joystick/gdk/SDL_gameinputjoystick.c
@@ -24,9 +24,7 @@
 
 #include "../SDL_sysjoystick.h"
 #include "../usb_ids.h"
-
-#define COBJMACROS
-#include <gameinput.h>
+#include "../../core/windows/SDL_gameinput.h"
 
 // Default value for SDL_HINT_JOYSTICK_GAMEINPUT
 #if defined(SDL_PLATFORM_GDK)
@@ -67,12 +65,10 @@ typedef struct joystick_hwdata
 } GAMEINPUT_InternalJoystickHwdata;
 
 static GAMEINPUT_InternalList g_GameInputList = { NULL };
-static SDL_SharedObject *g_hGameInputDLL = NULL;
 static IGameInput *g_pGameInput = NULL;
 static GameInputCallbackToken g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
 static Uint64 g_GameInputTimestampOffset;
 
-
 static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info)
 {
     if (info->supportedInput & GameInputKindGamepad) {
@@ -245,24 +241,8 @@ static bool GAMEINPUT_JoystickInit(void)
         return true;
     }
 
-    if (!g_hGameInputDLL) {
-        g_hGameInputDLL = SDL_LoadObject("gameinput.dll");
-        if (!g_hGameInputDLL) {
-            return false;
-        }
-    }
-
-    if (!g_pGameInput) {
-        typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput);
-        GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate");
-        if (!GameInputCreateFunc) {
-            return false;
-        }
-
-        hR = GameInputCreateFunc(&g_pGameInput);
-        if (FAILED(hR)) {
-            return SDL_SetError("GameInputCreate failure with HRESULT of %08lX", hR);
-        }
+    if (!SDL_InitGameInput(&g_pGameInput)) {
+        return false;
     }
 
     hR = IGameInput_RegisterDeviceCallback(g_pGameInput,
@@ -689,14 +669,9 @@ static void GAMEINPUT_JoystickQuit(void)
             GAMEINPUT_InternalRemoveByIndex(0);
         }
 
-        IGameInput_Release(g_pGameInput);
+        SDL_QuitGameInput();
         g_pGameInput = NULL;
     }
-
-    if (g_hGameInputDLL) {
-        SDL_UnloadObject(g_hGameInputDLL);
-        g_hGameInputDLL = NULL;
-    }
 }
 
 static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
diff --git a/src/video/windows/SDL_windowsgameinput.c b/src/video/windows/SDL_windowsgameinput.c
index ddc116af3525a..79b3d98ab2d05 100644
--- a/src/video/windows/SDL_windowsgameinput.c
+++ b/src/video/windows/SDL_windowsgameinput.c
@@ -27,9 +27,7 @@
 
 #ifdef HAVE_GAMEINPUT_H
 
-#define COBJMACROS
-#include <gameinput.h>
-
+#include "../../core/windows/SDL_gameinput.h"
 #include "../../events/SDL_mouse_c.h"
 #include "../../events/SDL_keyboard_c.h"
 #include "../../events/scancodes_windows.h"
@@ -61,7 +59,6 @@ typedef struct GAMEINPUT_Device
 
 struct WIN_GameInputData
 {
-    void *hGameInputDLL;
     IGameInput *pGameInput;
     GameInputCallbackToken gameinput_callback_token;
     int num_devices;
@@ -237,20 +234,7 @@ bool WIN_InitGameInput(SDL_VideoDevice *_this)
         goto done;
     }
 
-    data->hGameInputDLL = SDL_LoadObject("gameinput.dll");
-    if (!data->hGameInputDLL) {
-        goto done;
-    }
-
-    typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput);
-    GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(data->hGameInputDLL, "GameInputCreate");
-    if (!GameInputCreateFunc) {
-        goto done;
-    }
-
-    hr = GameInputCreateFunc(&data->pGameInput);
-    if (FAILED(hr)) {
-        SDL_SetError("GameInputCreate failure with HRESULT of %08X", hr);
+    if (!SDL_InitGameInput(&data->pGameInput)) {
         goto done;
     }
 
@@ -587,9 +571,9 @@ void WIN_QuitGameInput(SDL_VideoDevice *_this)
         data->pGameInput = NULL;
     }
 
-    if (data->hGameInputDLL) {
-        SDL_UnloadObject(data->hGameInputDLL);
-        data->hGameInputDLL = NULL;
+    if (data->pGameInput) {
+        SDL_QuitGameInput();
+        data->pGameInput = NULL;
     }
 
     if (data->lock) {