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

extract capabilities for 360 controllers over libusb (#15183)

Read capabilities when using xinput controllers via the libusb backend

This gives us access to the subtype on linux and macOS, and gives us a lot of data we can use for handling more detailed device types when I look into a unified api for exposing instrument data later.
Sanjay Govind 3 дней назад
Родитель
Сommit
101273f429

+ 103 - 1
src/joystick/hidapi/SDL_hidapi_xbox360.c

@@ -23,9 +23,11 @@
 #ifdef SDL_JOYSTICK_HIDAPI
 
 #include "../../SDL_hints_c.h"
+#include "../../misc/SDL_libusb.h"
 #include "../SDL_sysjoystick.h"
 #include "SDL_hidapijoystick_c.h"
 #include "SDL_hidapi_rumble.h"
+#include "SDL_hidapi_xbox360.h"
 
 #ifdef SDL_JOYSTICK_HIDAPI_XBOX360
 
@@ -42,6 +44,7 @@ typedef struct
     SDL_Joystick *joystick;
     int player_index;
     bool player_lights;
+    SDL_xinput_capabilities capabilities;
     Uint8 last_state[USB_PACKET_LENGTH];
 #ifdef SDL_PLATFORM_MACOS
     bool controlled_by_360controller;
@@ -82,6 +85,103 @@ static bool IsControlledBy360ControllerDriverMacOS(SDL_HIDAPI_Device *device)
 }
 #endif
 
+#ifdef HAVE_LIBUSB
+static void FetchXInputCapabilities(SDL_HIDAPI_Device *device)
+{
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
+    SDL_LibUSBContext *libusb_ctx;
+    if (SDL_InitLibUSB(&libusb_ctx)) {
+        libusb_device_handle *handle = (libusb_device_handle *)SDL_GetPointerProperty(SDL_hid_get_properties(device->dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, NULL);
+        if (handle == NULL) {
+            SDL_QuitLibUSB();
+            return;
+        }
+        libusb_device *dev = libusb_ctx->get_device(handle);
+        if (dev == NULL) {
+            SDL_QuitLibUSB();
+            return;
+        }
+        struct libusb_config_descriptor *conf_desc = NULL;
+        const struct libusb_interface_descriptor *intf_desc;
+        libusb_ctx->get_active_config_descriptor(dev, &conf_desc);
+        if (conf_desc == NULL || conf_desc->bNumInterfaces < device->interface_number) {
+            SDL_QuitLibUSB();
+            return;
+        }
+        const struct libusb_interface *intf = &conf_desc->interface[device->interface_number];
+        intf_desc = &intf->altsetting[0];
+        if (intf_desc->extra_length == 17 && intf_desc->extra[1] == 0x21) {
+			ctx->capabilities.type = intf_desc->extra[3];
+			ctx->capabilities.subType = intf_desc->extra[4];
+            switch (ctx->capabilities.subType) {
+                case 0x01: // XINPUT_DEVSUBTYPE_GAMEPAD
+                    device->joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
+                    break;
+                case 0x02: // XINPUT_DEVSUBTYPE_WHEEL
+                    device->joystick_type = SDL_JOYSTICK_TYPE_WHEEL;
+                    break;
+                case 0x03: // XINPUT_DEVSUBTYPE_ARCADE_STICK
+                    device->joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
+                    break;
+                case 0x04: // XINPUT_DEVSUBTYPE_FLIGHT_STICK
+                    device->joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;
+                    break;
+                case 0x05: // XINPUT_DEVSUBTYPE_DANCE_PAD
+                    device->joystick_type = SDL_JOYSTICK_TYPE_DANCE_PAD;
+                    break;
+                case 0x06: // XINPUT_DEVSUBTYPE_GUITAR
+                case 0x07: // XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE
+                case 0x0B: // XINPUT_DEVSUBTYPE_GUITAR_BASS
+                    device->joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
+                    break;
+                case 0x08: // XINPUT_DEVSUBTYPE_DRUM_KIT
+                    device->joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;
+                    break;
+                case 0x13: // XINPUT_DEVSUBTYPE_ARCADE_PAD
+                    device->joystick_type = SDL_JOYSTICK_TYPE_ARCADE_PAD;
+                    break;
+                default:
+                    break;
+            }
+            device->guid.data[15] = ctx->capabilities.subType;
+            unsigned char buf[20];
+            int ret = libusb_ctx->control_transfer(handle, 0xC1, 0x01, 0x100, 0x0, buf, sizeof(buf), 100);
+            if (ret == sizeof(buf)) {
+                ctx->capabilities.flags = LOAD16(buf[18], buf[19]);
+                ctx->capabilities.gamepad.wButtons = LOAD16(buf[2], buf[3]);
+                ctx->capabilities.gamepad.bLeftTrigger = buf[4];
+                ctx->capabilities.gamepad.bRightTrigger = buf[5];
+                ctx->capabilities.gamepad.sThumbLX = LOAD16(buf[6], buf[7]);
+                ctx->capabilities.gamepad.sThumbLY = LOAD16(buf[8], buf[9]);
+                ctx->capabilities.gamepad.sThumbRX = LOAD16(buf[10], buf[11]);
+                ctx->capabilities.gamepad.sThumbRY = LOAD16(buf[12], buf[13]);
+            }
+            ret = libusb_ctx->control_transfer(handle, 0xC1, 0x01, 0x00, 0x0, buf, 8, 100);
+            if (ret == 8) {
+                ctx->capabilities.vibration.wLeftMotorSpeed = buf[3] << 8;
+                ctx->capabilities.vibration.wRightMotorSpeed = buf[4] << 8;
+            }
+#ifdef DEBUG_XBOX_PROTOCOL
+            SDL_Log("Xbox 360 capabilities:");
+            SDL_Log("   type: %02x", ctx->capabilities.type);
+            SDL_Log("   subType: %02x", ctx->capabilities.subType);
+            SDL_Log("   flags: %04x", ctx->capabilities.flags);
+            SDL_Log("   wButtons: %02x", ctx->capabilities.gamepad.wButtons);
+            SDL_Log("   bLeftTrigger: %02x", ctx->capabilities.gamepad.bLeftTrigger);
+            SDL_Log("   bRightTrigger: %02x", ctx->capabilities.gamepad.bRightTrigger);
+            SDL_Log("   sThumbLX: %02x", ctx->capabilities.gamepad.sThumbLX);
+            SDL_Log("   sThumbLY: %02x", ctx->capabilities.gamepad.sThumbLY);
+            SDL_Log("   sThumbRX: %02x", ctx->capabilities.gamepad.sThumbRX);
+            SDL_Log("   sThumbRY: %02x", ctx->capabilities.gamepad.sThumbRY);
+            SDL_Log("   wLeftMotorSpeed: %02x", ctx->capabilities.vibration.wLeftMotorSpeed);
+            SDL_Log("   wRightMotorSpeed: %02x", ctx->capabilities.vibration.wRightMotorSpeed);
+#endif
+		}
+        SDL_QuitLibUSB();
+    }
+}
+#endif
+
 static bool HIDAPI_DriverXbox360_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)
 {
     const int XB360W_IFACE_PROTOCOL = 129; // Wireless
@@ -227,7 +327,9 @@ static bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
     joystick->nbuttons = 11;
     joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
     joystick->nhats = 1;
-
+#ifdef HAVE_LIBUSB
+    FetchXInputCapabilities(device);
+#endif
     return true;
 }
 

+ 27 - 0
src/joystick/hidapi/SDL_hidapi_xbox360.h

@@ -0,0 +1,27 @@
+#include "SDL_internal.h"
+
+#define FLAG_FORCE_FEEDBACK	0x01
+#define FLAG_WIRELESS		0x02
+#define FLAG_VOICE			0x04
+#define FLAG_PLUGIN_MODULES	0x08
+#define FLAG_NO_NAVIGATION	0x10
+
+typedef struct {
+  uint8_t type;
+  uint8_t subType;
+  uint16_t flags;
+  struct {
+	uint16_t  wButtons;
+	uint8_t  bLeftTrigger;
+	uint8_t  bRightTrigger;
+	int16_t sThumbLX;
+	int16_t sThumbLY;
+	int16_t sThumbRX;
+	int16_t sThumbRY;
+  } gamepad;
+
+  struct {
+	uint16_t wLeftMotorSpeed;
+	uint16_t wRightMotorSpeed;
+  } vibration;
+} SDL_xinput_capabilities;

+ 78 - 0
src/joystick/hidapi/SDL_hidapi_xbox360w.c

@@ -26,6 +26,7 @@
 #include "../SDL_sysjoystick.h"
 #include "SDL_hidapijoystick_c.h"
 #include "SDL_hidapi_rumble.h"
+#include "SDL_hidapi_xbox360.h"
 
 #ifdef SDL_JOYSTICK_HIDAPI_XBOX360
 
@@ -38,6 +39,7 @@ typedef struct
     bool connected;
     int player_index;
     bool player_lights;
+    SDL_xinput_capabilities capabilities;
     Uint8 last_state[USB_PACKET_LENGTH];
 } SDL_DriverXbox360W_Context;
 
@@ -179,6 +181,18 @@ static bool HIDAPI_DriverXbox360W_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo
     joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
     joystick->nhats = 1;
     joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
+    ctx->capabilities.type = 1;
+    ctx->capabilities.flags = FLAG_WIRELESS;
+    ctx->capabilities.subType = SDL_JOYSTICK_TYPE_GAMEPAD;
+    ctx->capabilities.gamepad.wButtons = 0xFFFF;
+    ctx->capabilities.gamepad.bLeftTrigger = 0xFF;
+    ctx->capabilities.gamepad.bRightTrigger = 0xFF;
+    ctx->capabilities.gamepad.sThumbLX = 0xFFC0;
+    ctx->capabilities.gamepad.sThumbLY = 0xFFC0;
+    ctx->capabilities.gamepad.sThumbRX = 0xFFC0;
+    ctx->capabilities.gamepad.sThumbRY = 0xFFC0;
+    ctx->capabilities.vibration.wLeftMotorSpeed = 0xFFFF;
+    ctx->capabilities.vibration.wRightMotorSpeed = 0xFFFF;
 
     return true;
 }
@@ -328,6 +342,44 @@ static bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device)
             if (joystick) {
                 UpdatePowerLevel(joystick, data[17]);
             }
+            ctx->capabilities.type = 1;
+            ctx->capabilities.subType = data[25] & 0x7f;
+            if ((data[25] & 0x80) != 0) {
+                ctx->capabilities.flags |= FLAG_FORCE_FEEDBACK;
+            }
+            switch (data[25] & 0x7f) {
+                case 0x01: // XINPUT_DEVSUBTYPE_GAMEPAD
+                    device->joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
+                    break;
+                case 0x02: // XINPUT_DEVSUBTYPE_WHEEL
+                    device->joystick_type = SDL_JOYSTICK_TYPE_WHEEL;
+                    break;
+                case 0x03: // XINPUT_DEVSUBTYPE_ARCADE_STICK
+                    device->joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
+                    break;
+                case 0x04: // XINPUT_DEVSUBTYPE_FLIGHT_STICK
+                    device->joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;
+                    break;
+                case 0x05: // XINPUT_DEVSUBTYPE_DANCE_PAD
+                    device->joystick_type = SDL_JOYSTICK_TYPE_DANCE_PAD;
+                    break;
+                case 0x06: // XINPUT_DEVSUBTYPE_GUITAR
+                case 0x07: // XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE
+                case 0x0B: // XINPUT_DEVSUBTYPE_GUITAR_BASS
+                    device->joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
+                    break;
+                case 0x08: // XINPUT_DEVSUBTYPE_DRUM_KIT
+                    device->joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;
+                    break;
+                case 0x13: // XINPUT_DEVSUBTYPE_ARCADE_PAD
+                    device->joystick_type = SDL_JOYSTICK_TYPE_ARCADE_PAD;
+                    break;
+            }
+            device->guid.data[15] = ctx->capabilities.subType;
+            const Uint8 capabilities_packet[] = { 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+            if (SDL_hid_write(device->dev, capabilities_packet, sizeof(capabilities_packet)) != sizeof(capabilities_packet)) {
+                SDL_SetError("Couldn't write capabilities_packet packet");
+            }
         } else if (size == 29 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x13) {
 #ifdef DEBUG_JOYSTICK
             SDL_Log("Battery status: %d", data[4]);
@@ -335,6 +387,32 @@ static bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device)
             if (joystick) {
                 UpdatePowerLevel(joystick, data[4]);
             }
+        } else if (data[0] == 0x00 && data[1] == 0x05 && data[5] == 0x12) {
+            ctx->capabilities.gamepad.wButtons = LOAD16(data[6], data[7]);
+            ctx->capabilities.gamepad.bLeftTrigger = data[8];
+            ctx->capabilities.gamepad.bRightTrigger = data[9];
+            ctx->capabilities.gamepad.sThumbLX = LOAD16(data[10], data[11]);
+            ctx->capabilities.gamepad.sThumbLY = LOAD16(data[12], data[13]);
+            ctx->capabilities.gamepad.sThumbRX = LOAD16(data[14], data[15]);
+            ctx->capabilities.gamepad.sThumbRY = LOAD16(data[16], data[17]);
+            ctx->capabilities.flags |= data[20];
+            ctx->capabilities.vibration.wLeftMotorSpeed = data[18] << 8;
+            ctx->capabilities.vibration.wRightMotorSpeed = data[19] << 8;
+#ifdef DEBUG_XBOX_PROTOCOL
+            SDL_Log("Xbox 360 capabilities:");
+            SDL_Log("   type: %02x", ctx->capabilities.type);
+            SDL_Log("   subType: %02x", ctx->capabilities.subType);
+            SDL_Log("   flags: %02x", ctx->capabilities.flags);
+            SDL_Log("   wButtons: %02x", ctx->capabilities.gamepad.wButtons);
+            SDL_Log("   bLeftTrigger: %02x", ctx->capabilities.gamepad.bLeftTrigger);
+            SDL_Log("   bRightTrigger: %02x", ctx->capabilities.gamepad.bRightTrigger);
+            SDL_Log("   sThumbLX: %02x", ctx->capabilities.gamepad.sThumbLX);
+            SDL_Log("   sThumbLY: %02x", ctx->capabilities.gamepad.sThumbLY);
+            SDL_Log("   sThumbRX: %02x", ctx->capabilities.gamepad.sThumbRX);
+            SDL_Log("   sThumbRY: %02x", ctx->capabilities.gamepad.sThumbRY);
+            SDL_Log("   wLeftMotorSpeed: %02x", ctx->capabilities.vibration.wLeftMotorSpeed);
+            SDL_Log("   wRightMotorSpeed: %02x", ctx->capabilities.vibration.wRightMotorSpeed);
+#endif
         } else if (size == 29 && data[0] == 0x00 && (data[1] & 0x01) == 0x01) {
             if (joystick) {
                 HIDAPI_DriverXbox360W_HandleStatePacket(joystick, device->dev, ctx, data + 4, size - 4);