Просмотр исходного кода

switch2: Preliminary rumble support

Fused controller support is somewhat lacking, and the scaling and frequency
on rumble is somewhat arbitrary, but otherwise it works fine.
Vicki Pfau 10 месяцев назад
Родитель
Сommit
c89fed4eae
1 измененных файлов с 100 добавлено и 2 удалено
  1. 100 2
      src/joystick/hidapi/SDL_hidapi_switch2.c

+ 100 - 2
src/joystick/hidapi/SDL_hidapi_switch2.c

@@ -29,9 +29,13 @@
 #include "../../misc/SDL_libusb.h"
 #include "../SDL_sysjoystick.h"
 #include "SDL_hidapijoystick_c.h"
+#include "SDL_hidapi_rumble.h"
 
 #ifdef SDL_JOYSTICK_HIDAPI_SWITCH2
 
+#define RUMBLE_INTERVAL 12
+#define RUMBLE_MAX 29000
+
 // Define this if you want to log all packets from the controller
 #if 0
 #define DEBUG_SWITCH2_PROTOCOL
@@ -95,6 +99,13 @@ typedef struct
     Uint8 out_endpoint;
     Uint8 in_endpoint;
 
+    Uint64 rumble_timestamp;
+    Uint32 rumble_seq;
+    Uint16 rumble_hi_amp;
+    Uint16 rumble_lo_amp;
+    Uint32 rumble_error;
+    bool rumble_updated;
+
     Switch2_StickCalibration left_stick;
     Switch2_StickCalibration right_stick;
     Uint8 left_trigger_zero;
@@ -552,7 +563,15 @@ static bool HIDAPI_DriverSwitch2_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
 
 static bool HIDAPI_DriverSwitch2_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
-    return SDL_Unsupported();
+    SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context;
+
+    if (low_frequency_rumble != ctx->rumble_lo_amp || high_frequency_rumble != ctx->rumble_hi_amp) {
+        ctx->rumble_lo_amp = low_frequency_rumble;
+        ctx->rumble_hi_amp = high_frequency_rumble;
+        ctx->rumble_updated = true;
+    }
+
+    return true;
 }
 
 static bool HIDAPI_DriverSwitch2_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
@@ -563,7 +582,7 @@ static bool HIDAPI_DriverSwitch2_RumbleJoystickTriggers(SDL_HIDAPI_Device *devic
 static Uint32 HIDAPI_DriverSwitch2_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
     SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context;
-    Uint32 result = 0;
+    Uint32 result = SDL_JOYSTICK_CAP_RUMBLE;
 
     if (ctx->player_lights) {
         result |= SDL_JOYSTICK_CAP_PLAYER_LED;
@@ -924,6 +943,83 @@ static void HandleSwitchProState(Uint64 timestamp, SDL_Joystick *joystick, SDL_D
     );
 }
 
+static bool UpdateRumble(SDL_DriverSwitch2_Context *ctx)
+{
+    if (!ctx->rumble_updated && !ctx->rumble_lo_amp && !ctx->rumble_hi_amp) {
+        return true;
+    }
+
+    Uint64 timestamp = SDL_GetTicks();
+    Uint64 interval = RUMBLE_INTERVAL;
+
+    if (timestamp < ctx->rumble_timestamp) {
+        return true;
+    }
+
+    if (!SDL_HIDAPI_LockRumble()) {
+        return false;
+    }
+
+    unsigned char rumble_data[64] = {};
+    if (ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER) {
+        Uint16 rumble_max = SDL_max(ctx->rumble_lo_amp, ctx->rumble_hi_amp);
+        rumble_data[0x00] = 0x3;
+        rumble_data[1] = 0x50 | (ctx->rumble_seq & 0xf);
+        if (rumble_max == 0) {
+            rumble_data[2] = 2;
+            ctx->rumble_error = 0;
+        } else {
+            if (ctx->rumble_error < rumble_max) {
+                rumble_data[2] = 1;
+                ctx->rumble_error += UINT16_MAX - rumble_max;
+            } else {
+                rumble_data[2] = 0;
+                ctx->rumble_error -= rumble_max;
+            }
+        }
+    } else {
+        // Rumble can get so strong that it might be dangerous to the controller...
+        // This is a game controller, not a massage device, so let's clamp it somewhat
+        int low_amp = ctx->rumble_lo_amp * RUMBLE_MAX / UINT16_MAX;
+        int high_amp = ctx->rumble_hi_amp * RUMBLE_MAX / UINT16_MAX;
+        rumble_data[0x01] = 0x50 | (ctx->rumble_seq & 0xf);
+        rumble_data[0x02] = 0x87;
+        rumble_data[0x03] = ((high_amp >> 4) & 0xfc) | 1;
+        rumble_data[0x04] = (high_amp >> 12) | 0x20;
+        rumble_data[0x05] = (low_amp & 0xc0) | 0x11;
+        rumble_data[0x06] = low_amp >> 8;
+        switch (ctx->device->product_id) {
+        case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT:
+        case USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT:
+            if (ctx->device->parent) {
+                // FIXME: This shouldn't be necessary, but the rumble thread appears to back up if we don't do this
+                interval *= 2;
+            }
+            rumble_data[0] = 0x1;
+            break;
+        case USB_PRODUCT_NINTENDO_SWITCH2_PRO:
+            rumble_data[0] = 0x2;
+            SDL_memcpy(&rumble_data[0x11], &rumble_data[0x01], 6);
+            break;
+        }
+    }
+    ctx->rumble_seq++;
+    ctx->rumble_updated = false;
+    if (!ctx->rumble_lo_amp && !ctx->rumble_hi_amp) {
+        ctx->rumble_timestamp = 0;
+    } else {
+        if (!ctx->rumble_timestamp) {
+            ctx->rumble_timestamp = timestamp;
+        }
+        ctx->rumble_timestamp += interval;
+    }
+
+    if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, rumble_data, sizeof(rumble_data)) != sizeof(rumble_data)) {
+        return SDL_SetError("Couldn't send rumble packet");
+    }
+    return true;
+}
+
 static void HIDAPI_DriverSwitch2_HandleStatePacket(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_DriverSwitch2_Context *ctx, Uint8 *data, int size)
 {
     Uint64 timestamp = SDL_GetTicksNS();
@@ -990,6 +1086,8 @@ static bool HIDAPI_DriverSwitch2_UpdateDevice(SDL_HIDAPI_Device *device)
         }
 
         HIDAPI_DriverSwitch2_HandleStatePacket(device, joystick, ctx, data, size);
+
+        UpdateRumble(ctx);
     }
 
     if (size < 0) {