SDL: Disable lizard mode while steam deck HID device is opened.

From 67d44c1017ad3eb883b29083681c441bbc6f1bd0 Mon Sep 17 00:00:00 2001
From: Max Maisel <[EMAIL REDACTED]>
Date: Thu, 7 Sep 2023 17:20:24 +0200
Subject: [PATCH] Disable lizard mode while steam deck HID device is opened.

---
 src/joystick/hidapi/SDL_hidapi_steamdeck.c    | 74 +++++++++++++++++++
 .../hidapi/steam/controller_constants.h       |  9 +++
 .../hidapi/steam/controller_structs.h         | 14 ++++
 3 files changed, 97 insertions(+)

diff --git a/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/src/joystick/hidapi/SDL_hidapi_steamdeck.c
index a8bce66bca7b..a95483bd021b 100644
--- a/src/joystick/hidapi/SDL_hidapi_steamdeck.c
+++ b/src/joystick/hidapi/SDL_hidapi_steamdeck.c
@@ -46,8 +46,73 @@ typedef struct
     Uint32 update_rate_us;
     Uint32 sensor_timestamp_us;
     Uint64 last_button_state;
+    Uint8 watchdog_counter;
 } SDL_DriverSteamDeck_Context;
 
+static SDL_bool DisableDeckLizardMode(SDL_hid_device *dev)
+{
+    int rc;
+    Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
+    FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
+
+    msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;
+
+    rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
+    if (rc != sizeof(buffer))
+        return SDL_FALSE;
+
+    msg->header.type = ID_SET_SETTINGS_VALUES;
+    msg->header.length = 5 * sizeof(WriteDeckRegister);
+    msg->payload.wrDeckRegister.reg[0].addr = SETTING_DECK_RPAD_MARGIN; // disable margin
+    msg->payload.wrDeckRegister.reg[0].val = 0;
+    msg->payload.wrDeckRegister.reg[1].addr = SETTING_DECK_LPAD_MODE; // disable mouse
+    msg->payload.wrDeckRegister.reg[1].val = 7;
+    msg->payload.wrDeckRegister.reg[2].addr = SETTING_DECK_RPAD_MODE; // disable mouse
+    msg->payload.wrDeckRegister.reg[2].val = 7;
+    msg->payload.wrDeckRegister.reg[3].addr = SETTING_DECK_LPAD_CLICK_PRESSURE; // disable clicky pad
+    msg->payload.wrDeckRegister.reg[3].val = 0xFFFF;
+    msg->payload.wrDeckRegister.reg[4].addr = SETTING_DECK_RPAD_CLICK_PRESSURE; // disable clicky pad
+    msg->payload.wrDeckRegister.reg[4].val = 0xFFFF;
+
+    rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
+    if (rc != sizeof(buffer))
+        return SDL_FALSE;
+
+    // There may be a lingering report read back after changing settings.
+    // Discard it.
+    SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));
+
+    return SDL_TRUE;
+}
+
+static SDL_bool FeedDeckLizardWatchdog(SDL_hid_device *dev)
+{
+    int rc;
+    Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
+    FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
+
+    msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;
+
+    rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
+    if (rc != sizeof(buffer))
+        return SDL_FALSE;
+
+    msg->header.type = ID_SET_SETTINGS_VALUES;
+    msg->header.length = 1 * sizeof(WriteDeckRegister);
+    msg->payload.wrDeckRegister.reg[0].addr = SETTING_DECK_RPAD_MODE; // disable mouse
+    msg->payload.wrDeckRegister.reg[0].val = 7;
+
+    rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
+    if (rc != sizeof(buffer))
+        return SDL_FALSE;
+
+    // There may be a lingering report read back after changing settings.
+    // Discard it.
+    SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));
+
+    return SDL_TRUE;
+}
+
 /*****************************************************************************************************/
 
 static void HIDAPI_DriverSteamDeck_RegisterHints(SDL_HintCallback callback, void *userdata)
@@ -105,6 +170,9 @@ static SDL_bool HIDAPI_DriverSteamDeck_InitDevice(SDL_HIDAPI_Device *device)
     if (size == 0)
         return SDL_FALSE;
 
+    if (!DisableDeckLizardMode(device->dev))
+        return SDL_FALSE;
+
     HIDAPI_SetDeviceName(device, "Steam Deck");
 
     return HIDAPI_JoystickConnected(device, NULL);
@@ -137,6 +205,12 @@ static SDL_bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device)
         return SDL_FALSE;
     }
 
+    if (ctx->watchdog_counter++ > 200) {
+        ctx->watchdog_counter = 0;
+        if (!FeedDeckLizardWatchdog(device->dev))
+            return SDL_FALSE;
+    }
+
     SDL_memset(data, 0, sizeof(data));
     r = SDL_hid_read(device->dev, data, sizeof(data));
     if (r == 0) {
diff --git a/src/joystick/hidapi/steam/controller_constants.h b/src/joystick/hidapi/steam/controller_constants.h
index 42c84f247dd5..dc0c3f220a51 100644
--- a/src/joystick/hidapi/steam/controller_constants.h
+++ b/src/joystick/hidapi/steam/controller_constants.h
@@ -467,6 +467,15 @@ typedef enum
 	SETTING_ALL=0xFF
 } ControllerSettings;
 
+typedef enum
+{
+	SETTING_DECK_LPAD_MODE = 0x07,
+	SETTING_DECK_RPAD_MODE = 0x08,
+	SETTING_DECK_RPAD_MARGIN = 0x18,
+	SETTING_DECK_LPAD_CLICK_PRESSURE = 0x34,
+	SETTING_DECK_RPAD_CLICK_PRESSURE = 0x35
+} DeckSettings;
+
 typedef enum
 {
 	SETTING_DEFAULT,
diff --git a/src/joystick/hidapi/steam/controller_structs.h b/src/joystick/hidapi/steam/controller_structs.h
index 6e86426d7e73..d3de7338fc5e 100644
--- a/src/joystick/hidapi/steam/controller_structs.h
+++ b/src/joystick/hidapi/steam/controller_structs.h
@@ -45,6 +45,19 @@ typedef struct
 	ControllerAttribute attributes[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerAttribute ) ];
 } MsgGetAttributes;
 
+// 16bit Steam Deck register with address
+typedef struct
+{
+	uint8_t addr;
+	uint16_t val;
+} WriteDeckRegister;
+
+// Generic Steam Deck write register message
+typedef struct
+{
+	WriteDeckRegister reg[ (HID_FEATURE_REPORT_BYTES - sizeof ( FeatureReportHeader ) ) / sizeof (WriteDeckRegister ) ];
+} MsgWriteDeckRegister;
+
 
 // This is the only message struct that application code should use to interact with feature request messages. Any new
 // messages should be added to the union. The structures defined here should correspond to the ones defined in
@@ -56,6 +69,7 @@ typedef struct
 	union
 	{
 		MsgGetAttributes				getAttributes;
+		MsgWriteDeckRegister			wrDeckRegister;
 	} payload;
 
 } FeatureReportMsg;