SDL: Fixed interactions with the Linux Wiimote driver

From b6d23d21db888c119e669a966ddc4ee3f947bc28 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 2 Sep 2022 11:21:51 -0700
Subject: [PATCH] Fixed interactions with the Linux Wiimote driver

---
 src/joystick/hidapi/SDL_hidapi_wii.c | 71 ++++++++++++++++++++--------
 1 file changed, 50 insertions(+), 21 deletions(-)

diff --git a/src/joystick/hidapi/SDL_hidapi_wii.c b/src/joystick/hidapi/SDL_hidapi_wii.c
index 446ece2bbac..37a69f77b7f 100644
--- a/src/joystick/hidapi/SDL_hidapi_wii.c
+++ b/src/joystick/hidapi/SDL_hidapi_wii.c
@@ -174,24 +174,37 @@ HIDAPI_DriverWii_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 produc
 
 static int ReadInput(SDL_DriverWii_Context *ctx)
 {
+    int size;
+
     /* Make sure we don't try to read at the same time a write is happening */
     if (SDL_AtomicGet(&ctx->device->rumble_pending) > 0) {
         return 0;
     }
 
-    return SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0);
+    size = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0);
+#ifdef DEBUG_WII_PROTOCOL
+    if (size > 0) {
+        HIDAPI_DumpPacket("Wii packet: size = %d", ctx->m_rgucReadBuffer, size);
+    }
+#endif
+    return size;
 }
 
-static int WriteOutput(SDL_DriverWii_Context *ctx, const Uint8 *data, int size, SDL_bool sync)
+static SDL_bool WriteOutput(SDL_DriverWii_Context *ctx, const Uint8 *data, int size, SDL_bool sync)
 {
+#ifdef DEBUG_WII_PROTOCOL
+    if (size > 0) {
+        HIDAPI_DumpPacket("Wii write packet: size = %d", data, size);
+    }
+#endif
     if (sync) {
-        return SDL_hid_write(ctx->device->dev, data, size);
+        return (SDL_hid_write(ctx->device->dev, data, size) >= 0);
     } else {
         /* Use the rumble thread for general asynchronous writes */
         if (SDL_HIDAPI_LockRumble() < 0) {
-            return -1;
+            return SDL_FALSE;
         }
-        return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size);
+        return (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) >= 0);
     }
 }
 
@@ -226,6 +239,7 @@ static SDL_bool WriteRegister(SDL_DriverWii_Context *ctx, Uint32 address, const
 {
     Uint8 writeRequest[k_unWiiPacketDataLength];
 
+    SDL_zeroa(writeRequest);
     writeRequest[0] = k_eWiiOutputReportIDs_WriteMemory;
     writeRequest[1] = 0x04 | ctx->m_bRumbleActive;
     writeRequest[2] = (address >> 16) & 0xff;
@@ -512,19 +526,27 @@ ReadExtensionControllerType(SDL_HIDAPI_Device *device)
 
         device->dev = SDL_hid_open_path(device->path, 0);
         if (device->dev) {
-            const Uint8 statusRequest[2] = { k_eWiiOutputReportIDs_StatusRequest, 0 };
-            WriteOutput(ctx, statusRequest, sizeof(statusRequest), SDL_TRUE);
-            if (ReadInputSync(ctx, k_eWiiInputReportIDs_Status, NULL)) {
-                SDL_bool hasExtension = (ctx->m_rgucReadBuffer[3] & 2) ? SDL_TRUE : SDL_FALSE;
-                if (hasExtension) {
-                    /* http://wiibrew.org/wiki/Wiimote/Extension_Controllers#The_New_Way */
-                    if (SendExtensionIdentify1(ctx, SDL_TRUE) &&
-                        SendExtensionIdentify2(ctx, SDL_TRUE) &&
-                        SendExtensionIdentify3(ctx, SDL_TRUE)) {
-                        ParseExtensionResponse(ctx, &eExtensionControllerType);
+            const int MAX_ATTEMPTS = 20;
+            int attempts = 0;
+            for (attempts = 0; attempts < MAX_ATTEMPTS; ++attempts) {
+                const Uint8 statusRequest[2] = { k_eWiiOutputReportIDs_StatusRequest, 0 };
+                WriteOutput(ctx, statusRequest, sizeof(statusRequest), SDL_TRUE);
+                if (ReadInputSync(ctx, k_eWiiInputReportIDs_Status, NULL)) {
+                    SDL_bool hasExtension = (ctx->m_rgucReadBuffer[3] & 2) ? SDL_TRUE : SDL_FALSE;
+                    if (hasExtension) {
+                        /* http://wiibrew.org/wiki/Wiimote/Extension_Controllers#The_New_Way */
+                        if (SendExtensionIdentify1(ctx, SDL_TRUE) &&
+                            SendExtensionIdentify2(ctx, SDL_TRUE) &&
+                            SendExtensionIdentify3(ctx, SDL_TRUE)) {
+                            ParseExtensionResponse(ctx, &eExtensionControllerType);
+                        }
+                    } else {
+                        eExtensionControllerType = k_eWiiExtensionControllerType_None;
                     }
-                } else {
-                    eExtensionControllerType = k_eWiiExtensionControllerType_None;
+                }
+                if (eExtensionControllerType != k_eWiiExtensionControllerType_Unknown) {
+                    /* Got it! */
+                    break;
                 }
             }
             SDL_hid_close(device->dev);
@@ -1048,6 +1070,7 @@ static void HandleResponse(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
             break;
     }
 
+#if 0 /* This happens with the Linux Wiimote driver */
     if (type == k_eWiiInputReportIDs_Acknowledge) {
         SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI WII: A mysterious ack has arrived!  Type: %02x, Code: %d",
                      ctx->m_rgucReadBuffer[3], ctx->m_rgucReadBuffer[4]);
@@ -1068,13 +1091,21 @@ static void HandleResponse(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
                      ctx->m_rgucReadBuffer[3] & 0xF,
                      str);
     }
+#endif /* 0 */
 }
 
 static void HandleButtonPacket(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
 {
+    EWiiInputReportIDs eExpectedReport = GetButtonPacketType(ctx);
     WiiButtonData data;
-    SDL_zero(data);
+
+    if (eExpectedReport != ctx->m_rgucReadBuffer[0]) {
+        SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI WII: Resetting report mode to %d\n", eExpectedReport);
+        RequestButtonPacketType(ctx, eExpectedReport);
+    }
+
     /* IR camera data is not supported */
+    SDL_zero(data);
     switch (ctx->m_rgucReadBuffer[0]) {
         case k_eWiiInputReportIDs_ButtonData0: /* 30 BB BB */
             GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
@@ -1149,9 +1180,6 @@ HIDAPI_DriverWii_UpdateDevice(SDL_HIDAPI_Device *device)
     now = SDL_GetTicks();
 
     while ((size = ReadInput(ctx)) > 0) {
-#ifdef DEBUG_WII_PROTOCOL
-        HIDAPI_DumpPacket("Wii packet: size = %d", ctx->m_rgucReadBuffer, size);
-#endif
         HandleInput(ctx, joystick);
 
         ctx->m_unLastInput = now;
@@ -1178,6 +1206,7 @@ HIDAPI_DriverWii_UpdateDevice(SDL_HIDAPI_Device *device)
             data[1] = ctx->m_bRumbleActive;
             WriteOutput(ctx, data, sizeof(data), SDL_FALSE);
 
+            ctx->m_eCommState = k_eWiiCommunicationState_None;
             ctx->m_unLastStatus = now;
         }
     }