SDL: Add steam deck detection and HIDAPI driver scaffold.

From c1a7d0f96ed4123c63600b1f97cb4c51eb9bdb62 Mon Sep 17 00:00:00 2001
From: Max Maisel <[EMAIL REDACTED]>
Date: Thu, 7 Sep 2023 17:04:01 +0200
Subject: [PATCH] Add steam deck detection and HIDAPI driver scaffold.

---
 include/SDL3/SDL_hints.h                   |  11 ++
 src/joystick/SDL_joystick.c                |   6 +
 src/joystick/SDL_joystick_c.h              |   3 +
 src/joystick/controller_list.h             |   1 +
 src/joystick/controller_type.h             |   1 +
 src/joystick/hidapi/SDL_hidapi_steamdeck.c | 156 +++++++++++++++++++++
 src/joystick/hidapi/SDL_hidapijoystick.c   |   3 +
 src/joystick/hidapi/SDL_hidapijoystick_c.h |   2 +
 8 files changed, 183 insertions(+)
 create mode 100644 src/joystick/hidapi/SDL_hidapi_steamdeck.c

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 10c24e1151bf..baf43e54a78d 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -943,6 +943,17 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM"
 
+/**
+ *  A variable controlling whether the HIDAPI driver for the Steam Deck builtin controller should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK "SDL_JOYSTICK_HIDAPI_STEAMDECK"
+
 /**
  *  A variable controlling whether the HIDAPI driver for Nintendo Switch controllers should be used.
  *
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index 1405800cd763..28fcdda3af76 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -2912,6 +2912,12 @@ SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id)
     return eType == k_eControllerType_SteamController || eType == k_eControllerType_SteamControllerV2;
 }
 
+SDL_bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id)
+{
+    EControllerType eType = GuessControllerType(vendor_id, product_id);
+    return eType == k_eControllerType_SteamDeck;
+}
+
 SDL_bool SDL_IsJoystickXInput(SDL_JoystickGUID guid)
 {
     return (guid.data[14] == 'x') ? SDL_TRUE : SDL_FALSE;
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index 0d14caec9e6b..53360bb5d13a 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -126,6 +126,9 @@ extern SDL_bool SDL_IsJoystickNVIDIASHIELDController(Uint16 vendor_id, Uint16 pr
 /* Function to return whether a joystick is a Steam Controller */
 extern SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id);
 
+/* Function to return whether a joystick is a Steam Deck */
+extern SDL_bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id);
+
 /* Function to return whether a joystick guid comes from the XInput driver */
 extern SDL_bool SDL_IsJoystickXInput(SDL_JoystickGUID guid);
 
diff --git a/src/joystick/controller_list.h b/src/joystick/controller_list.h
index d8342f680907..38f638b2a3ce 100644
--- a/src/joystick/controller_list.h
+++ b/src/joystick/controller_list.h
@@ -597,4 +597,5 @@ static const ControllerDescription_t arrControllers[] = {
 	{ MAKE_CONTROLLER_ID( 0x28de, 0x1142 ), k_eControllerType_SteamController, NULL },	// Valve wireless Steam Controller
 	{ MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL },	// Valve wired Steam Controller (HEADCRAB)
 	{ MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL },	// Valve Bluetooth Steam Controller (HEADCRAB)
+	{ MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamDeck, NULL },	// Valve Steam Deck Builtin Controller
 };
diff --git a/src/joystick/controller_type.h b/src/joystick/controller_type.h
index cf8923956254..78c595586301 100644
--- a/src/joystick/controller_type.h
+++ b/src/joystick/controller_type.h
@@ -37,6 +37,7 @@ typedef enum
 	k_eControllerType_UnknownSteamController = 1,
 	k_eControllerType_SteamController = 2,
 	k_eControllerType_SteamControllerV2 = 3,
+	k_eControllerType_SteamDeck = 4,
 
 	// Other Controllers
 	k_eControllerType_UnknownNonSteamController = 30,
diff --git a/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/src/joystick/hidapi/SDL_hidapi_steamdeck.c
new file mode 100644
index 000000000000..95f8ea9a918a
--- /dev/null
+++ b/src/joystick/hidapi/SDL_hidapi_steamdeck.c
@@ -0,0 +1,156 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 2023 Max Maisel <max.maisel@posteo.de>
+
+  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_HIDAPI
+
+#include "../SDL_sysjoystick.h"
+#include "SDL_hidapijoystick_c.h"
+
+#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
+
+/*****************************************************************************************************/
+
+#include <stdint.h>
+
+/*****************************************************************************************************/
+
+static void HIDAPI_DriverSteamDeck_RegisterHints(SDL_HintCallback callback, void *userdata)
+{
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);
+}
+
+static void HIDAPI_DriverSteamDeck_UnregisterHints(SDL_HintCallback callback, void *userdata)
+{
+    SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);
+}
+
+static SDL_bool HIDAPI_DriverSteamDeck_IsEnabled(void)
+{
+    return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,
+                              SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
+}
+
+static SDL_bool HIDAPI_DriverSteamDeck_IsSupportedDevice(
+    SDL_HIDAPI_Device *device,
+    const char *name,
+    SDL_GamepadType type,
+    Uint16 vendor_id,
+    Uint16 product_id,
+    Uint16 version,
+    int interface_number,
+    int interface_class,
+    int interface_subclass,
+    int interface_protocol)
+{
+    return SDL_IsJoystickSteamDeck(vendor_id, product_id);
+}
+
+static SDL_bool HIDAPI_DriverSteamDeck_InitDevice(SDL_HIDAPI_Device *device)
+{
+    return SDL_FALSE;
+}
+
+static int HIDAPI_DriverSteamDeck_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
+{
+    return -1;
+}
+
+static void HIDAPI_DriverSteamDeck_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
+{
+}
+
+static SDL_bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device)
+{
+    return SDL_FALSE;
+}
+
+static SDL_bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    return SDL_FALSE;
+}
+
+static int HIDAPI_DriverSteamDeck_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+{
+    /* You should use the full Steam Input API for rumble support */
+    return SDL_Unsupported();
+}
+
+static int HIDAPI_DriverSteamDeck_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
+{
+    return SDL_Unsupported();
+}
+
+static Uint32 HIDAPI_DriverSteamDeck_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    return 0;
+}
+
+static int HIDAPI_DriverSteamDeck_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+    return SDL_Unsupported();
+}
+
+static int HIDAPI_DriverSteamDeck_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
+static int HIDAPI_DriverSteamDeck_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
+{
+    // On steam deck, sensors are enabled by default. Nothing to do here.
+    return 0;
+}
+
+static void HIDAPI_DriverSteamDeck_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    // Lizard mode id automatically re-enabled by watchdog. Nothing to do here.
+}
+
+static void HIDAPI_DriverSteamDeck_FreeDevice(SDL_HIDAPI_Device *device)
+{
+}
+
+SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck = {
+    SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,
+    SDL_TRUE,
+    HIDAPI_DriverSteamDeck_RegisterHints,
+    HIDAPI_DriverSteamDeck_UnregisterHints,
+    HIDAPI_DriverSteamDeck_IsEnabled,
+    HIDAPI_DriverSteamDeck_IsSupportedDevice,
+    HIDAPI_DriverSteamDeck_InitDevice,
+    HIDAPI_DriverSteamDeck_GetDevicePlayerIndex,
+    HIDAPI_DriverSteamDeck_SetDevicePlayerIndex,
+    HIDAPI_DriverSteamDeck_UpdateDevice,
+    HIDAPI_DriverSteamDeck_OpenJoystick,
+    HIDAPI_DriverSteamDeck_RumbleJoystick,
+    HIDAPI_DriverSteamDeck_RumbleJoystickTriggers,
+    HIDAPI_DriverSteamDeck_GetJoystickCapabilities,
+    HIDAPI_DriverSteamDeck_SetJoystickLED,
+    HIDAPI_DriverSteamDeck_SendJoystickEffect,
+    HIDAPI_DriverSteamDeck_SetSensorsEnabled,
+    HIDAPI_DriverSteamDeck_CloseJoystick,
+    HIDAPI_DriverSteamDeck_FreeDevice,
+};
+
+#endif /* SDL_JOYSTICK_HIDAPI_STEAMDECK */
+
+#endif /* SDL_JOYSTICK_HIDAPI */
diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index db17fd4096dc..5152828f8d0d 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -66,6 +66,9 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = {
 #ifdef SDL_JOYSTICK_HIDAPI_STEAM
     &SDL_HIDAPI_DriverSteam,
 #endif
+#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
+    &SDL_HIDAPI_DriverSteamDeck,
+#endif
 #ifdef SDL_JOYSTICK_HIDAPI_SWITCH
     &SDL_HIDAPI_DriverNintendoClassic,
     &SDL_HIDAPI_DriverJoyCons,
diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h
index 04961b2ecd4f..acca9a5c85ba 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick_c.h
+++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h
@@ -33,6 +33,7 @@
 #define SDL_JOYSTICK_HIDAPI_PS5
 #define SDL_JOYSTICK_HIDAPI_STADIA
 #define SDL_JOYSTICK_HIDAPI_STEAM /* Simple support for BLE Steam Controller, hint is disabled by default */
+#define SDL_JOYSTICK_HIDAPI_STEAMDECK
 #define SDL_JOYSTICK_HIDAPI_SWITCH
 #define SDL_JOYSTICK_HIDAPI_WII
 #define SDL_JOYSTICK_HIDAPI_XBOX360
@@ -126,6 +127,7 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam;
+extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360;