SDL: Added left and right sensors for Nintendo Joy-Con and Wii controllers

From 4018f35ef2787a3a177f28ec5100510e41691e8a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 7 Sep 2022 00:00:27 -0700
Subject: [PATCH] Added left and right sensors for Nintendo Joy-Con and Wii
 controllers

---
 include/SDL_sensor.h                    |  6 ++-
 src/joystick/hidapi/SDL_hidapi_switch.c | 54 +++++++++++++++++++------
 src/joystick/hidapi/SDL_hidapi_wii.c    |  7 ++--
 test/testgamecontroller.c               | 49 ++++++++++++++++------
 4 files changed, 87 insertions(+), 29 deletions(-)

diff --git a/include/SDL_sensor.h b/include/SDL_sensor.h
index a2f30e0f8f6..6b07183eadf 100644
--- a/include/SDL_sensor.h
+++ b/include/SDL_sensor.h
@@ -71,7 +71,11 @@ typedef enum
     SDL_SENSOR_INVALID = -1,    /**< Returned for an invalid sensor */
     SDL_SENSOR_UNKNOWN,         /**< Unknown sensor type */
     SDL_SENSOR_ACCEL,           /**< Accelerometer */
-    SDL_SENSOR_GYRO             /**< Gyroscope */
+    SDL_SENSOR_GYRO,            /**< Gyroscope */
+    SDL_SENSOR_ACCEL_L,         /**< Accelerometer for left Joy-Con controller and Wii nunchuk */
+    SDL_SENSOR_GYRO_L,          /**< Gyroscope for left Joy-Con controller */
+    SDL_SENSOR_ACCEL_R,         /**< Accelerometer for right Joy-Con controller */
+    SDL_SENSOR_GYRO_R           /**< Gyroscope for right Joy-Con controller */
 } SDL_SensorType;
 
 /**
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index 799dbb1cf50..727b59ce1a1 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -254,7 +254,6 @@ typedef struct {
     SDL_bool m_bRumblePending;
     SDL_bool m_bRumbleZeroPending;
     Uint32 m_unRumblePending;
-    SDL_bool m_bHasSensors;
     SDL_bool m_bReportSensors;
     SDL_bool m_bHasSensorData;
     Uint32 m_unLastInput;
@@ -1351,7 +1350,16 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti
                 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
                 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 200.0f);
                 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 200.0f);
-                ctx->m_bHasSensors = SDL_TRUE;
+            }
+            if (device->parent &&
+                ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_L, 200.0f);
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 200.0f);
+            }
+            if (device->parent &&
+                ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_R, 200.0f);
+                SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_R, 200.0f);
             }
         }
 
@@ -1610,10 +1618,6 @@ HIDAPI_DriverSwitch_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joy
 {
     SDL_DriverSwitch_Context* ctx = (SDL_DriverSwitch_Context*)device->context;
     
-    if (!ctx->m_bHasSensors) {
-        return SDL_Unsupported();
-    }
-
     SetIMUEnabled(ctx, enabled);
     ctx->m_bReportSensors = enabled;
 
@@ -1816,7 +1820,7 @@ static void SendSensorUpdate(SDL_Joystick *joystick, SDL_DriverSwitch_Context *c
      * since that's our de facto standard from already supporting those controllers, and
      * users will want consistent axis mappings across devices.
      */
-    if (type == SDL_SENSOR_GYRO) {
+    if (type == SDL_SENSOR_GYRO || type == SDL_SENSOR_GYRO_L || type == SDL_SENSOR_GYRO_R ) {
         data[0] = -(ctx->m_IMUScaleData.fGyroScaleY * (float)values[1]);
         data[1] = ctx->m_IMUScaleData.fGyroScaleZ * (float)values[2];
         data[2] = -(ctx->m_IMUScaleData.fGyroScaleX * (float)values[0]);
@@ -2068,13 +2072,37 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C
         if (bHasSensorData) {
             ctx->m_bHasSensorData = SDL_TRUE;
 
-            SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO, &packet->imuState[2].sGyroX);
-            SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO, &packet->imuState[1].sGyroX);
-            SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO, &packet->imuState[0].sGyroX);
+            if (!ctx->device->parent ||
+                ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO, &packet->imuState[2].sGyroX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO, &packet->imuState[1].sGyroX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO, &packet->imuState[0].sGyroX);
+
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL, &packet->imuState[2].sAccelX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL, &packet->imuState[1].sAccelX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL, &packet->imuState[0].sAccelX);
+            }
+
+            if (ctx->device->parent &&
+                ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO_L, &packet->imuState[2].sGyroX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO_L, &packet->imuState[1].sGyroX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO_L, &packet->imuState[0].sGyroX);
 
-            SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL, &packet->imuState[2].sAccelX);
-            SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL, &packet->imuState[1].sAccelX);
-            SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL, &packet->imuState[0].sAccelX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL_L, &packet->imuState[2].sAccelX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL_L, &packet->imuState[1].sAccelX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL_L, &packet->imuState[0].sAccelX);
+            }
+            if (ctx->device->parent &&
+                ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO_R, &packet->imuState[2].sGyroX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO_R, &packet->imuState[1].sGyroX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_GYRO_R, &packet->imuState[0].sGyroX);
+
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL_R, &packet->imuState[2].sAccelX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL_R, &packet->imuState[1].sAccelX);
+                SendSensorUpdate(joystick, ctx, SDL_SENSOR_ACCEL_R, &packet->imuState[0].sAccelX);
+            }
 
         } else if (ctx->m_bHasSensorData) {
             /* Uh oh, someone turned off the IMU? */
diff --git a/src/joystick/hidapi/SDL_hidapi_wii.c b/src/joystick/hidapi/SDL_hidapi_wii.c
index b000a036f96..0b9922fad67 100644
--- a/src/joystick/hidapi/SDL_hidapi_wii.c
+++ b/src/joystick/hidapi/SDL_hidapi_wii.c
@@ -809,6 +809,9 @@ HIDAPI_DriverWii_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
     if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None ||
         ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {
         SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f);
+        if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {
+            SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 100.0f);
+        }
 
         if (ctx->m_bMotionPlusPresent) {
             SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 100.0f);
@@ -1162,7 +1165,6 @@ static void HandleNunchuckButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *j
     PostStickCalibrated(joystick, &ctx->m_StickCalibrationData[0], SDL_CONTROLLER_AXIS_LEFTX, data->rgucExtension[0]);
     PostStickCalibrated(joystick, &ctx->m_StickCalibrationData[1], SDL_CONTROLLER_AXIS_LEFTY, data->rgucExtension[1]);
 
-#if 0 /* We'll report this when we have the concept of right/left sensors */
     if (ctx->m_bReportSensors) {
         const float ACCEL_RES_PER_G = 200.0f;
         Sint16 x, y, z;
@@ -1190,9 +1192,8 @@ static void HandleNunchuckButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *j
         values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
         values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
         values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
-        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, values, 3);
+        SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL_L, values, 3);
     }
-#endif
 }
 
 static void HandleMotionPlusData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
diff --git a/test/testgamecontroller.c b/test/testgamecontroller.c
index 7e51bcbfcef..2dc32d60e17 100644
--- a/test/testgamecontroller.c
+++ b/test/testgamecontroller.c
@@ -127,6 +127,26 @@ static void UpdateWindowTitle()
     }
 }
 
+static const char *GetSensorName(SDL_SensorType sensor)
+{
+    switch (sensor) {
+    case SDL_SENSOR_ACCEL:
+        return "accelerometer";
+    case SDL_SENSOR_GYRO:
+        return "gyro";
+    case SDL_SENSOR_ACCEL_L:
+        return "accelerometer (L)";
+    case SDL_SENSOR_GYRO_L:
+        return "gyro (L)";
+    case SDL_SENSOR_ACCEL_R:
+        return "accelerometer (R)";
+    case SDL_SENSOR_GYRO_R:
+        return "gyro (R)";
+    default:
+        return "UNKNOWN";
+    }
+}
+
 static int FindController(SDL_JoystickID controller_id)
 {
     int i;
@@ -145,6 +165,15 @@ static void AddController(int device_index, SDL_bool verbose)
     SDL_GameController *controller;
     SDL_GameController **controllers;
     Uint16 firmware_version;
+    SDL_SensorType sensors[] = {
+        SDL_SENSOR_ACCEL,
+        SDL_SENSOR_GYRO,
+        SDL_SENSOR_ACCEL_L,
+        SDL_SENSOR_GYRO_L,
+        SDL_SENSOR_ACCEL_R,
+        SDL_SENSOR_GYRO_R
+    };
+    unsigned int i;
 
     controller_id = SDL_JoystickGetDeviceInstanceID(device_index);
     if (controller_id < 0) {
@@ -187,18 +216,15 @@ static void AddController(int device_index, SDL_bool verbose)
         }
     }
 
-    if (SDL_GameControllerHasSensor(gamecontroller, SDL_SENSOR_ACCEL)) {
-        if (verbose) {
-            SDL_Log("Enabling accelerometer at %.2f Hz\n", SDL_GameControllerGetSensorDataRate(gamecontroller, SDL_SENSOR_ACCEL));
-        }
-        SDL_GameControllerSetSensorEnabled(gamecontroller, SDL_SENSOR_ACCEL, SDL_TRUE);
-    }
+    for (i = 0; i < SDL_arraysize(sensors); ++i) {
+        SDL_SensorType sensor = sensors[i];
 
-    if (SDL_GameControllerHasSensor(gamecontroller, SDL_SENSOR_GYRO)) {
-        if (verbose) {
-            SDL_Log("Enabling gyro at %.2f Hz\n", SDL_GameControllerGetSensorDataRate(gamecontroller, SDL_SENSOR_GYRO));
+        if (SDL_GameControllerHasSensor(gamecontroller, sensor)) {
+            if (verbose) {
+                SDL_Log("Enabling %s at %.2f Hz\n", GetSensorName(sensor), SDL_GameControllerGetSensorDataRate(gamecontroller, sensor));
+            }
+            SDL_GameControllerSetSensorEnabled(gamecontroller, sensor, SDL_TRUE);
         }
-        SDL_GameControllerSetSensorEnabled(gamecontroller, SDL_SENSOR_GYRO, SDL_TRUE);
     }
 
     if (SDL_GameControllerHasRumble(gamecontroller)) {
@@ -565,8 +591,7 @@ loop(void *arg)
         case SDL_CONTROLLERSENSORUPDATE:
             SDL_Log("Controller %d sensor %s: %.2f, %.2f, %.2f\n",
                 event.csensor.which,
-                event.csensor.sensor == SDL_SENSOR_ACCEL ? "accelerometer" :
-                event.csensor.sensor == SDL_SENSOR_GYRO ? "gyro" : "unknown",
+                GetSensorName((SDL_SensorType)event.csensor.sensor),
                 event.csensor.data[0],
                 event.csensor.data[1],
                 event.csensor.data[2]);