Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Added support for detecting previously unknown Xbox 360 and Xbox One …
…controllers using the HIDAPI driver with libusb and Android
  • Loading branch information
slouken committed Jan 18, 2020
1 parent 2703542 commit 43aa1fa
Show file tree
Hide file tree
Showing 18 changed files with 248 additions and 145 deletions.
Expand Up @@ -470,7 +470,7 @@ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristi
// Only register controller with the native side once it has been fully configured
if (!isRegistered()) {
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0);
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
setRegistered();
}
}
Expand Down
Expand Up @@ -241,17 +241,7 @@ private void shutdownUSB() {
}
}

private boolean isHIDDeviceUSB(UsbDevice usbDevice) {
for (int interface_number = 0; interface_number < usbDevice.getInterfaceCount(); ++interface_number) {
if (isHIDDeviceInterface(usbDevice, interface_number)) {
return true;
}
}
return false;
}

private boolean isHIDDeviceInterface(UsbDevice usbDevice, int interface_number) {
UsbInterface usbInterface = usbDevice.getInterface(interface_number);
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
return true;
}
Expand Down Expand Up @@ -331,9 +321,7 @@ private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterfa
}

private void handleUsbDeviceAttached(UsbDevice usbDevice) {
if (isHIDDeviceUSB(usbDevice)) {
connectHIDDeviceUSB(usbDevice);
}
connectHIDDeviceUSB(usbDevice);
}

private void handleUsbDeviceDetached(UsbDevice usbDevice) {
Expand Down Expand Up @@ -366,11 +354,12 @@ private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_g
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
synchronized (this) {
for (int interface_number = 0; interface_number < usbDevice.getInterfaceCount(); interface_number++) {
if (isHIDDeviceInterface(usbDevice, interface_number)) {
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_number);
UsbInterface usbInterface = usbDevice.getInterface(interface_number);
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, usbInterface.getId());
int id = device.getId();
mDevicesById.put(id, device);
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), interface_number);
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
}
}
}
Expand Down Expand Up @@ -670,7 +659,7 @@ public void closeDevice(int deviceID) {
private native void HIDDeviceRegisterCallback();
private native void HIDDeviceReleaseCallback();

native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number);
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
native void HIDDeviceOpenPending(int deviceID);
native void HIDDeviceOpenResult(int deviceID, boolean opened);
native void HIDDeviceDisconnected(int deviceID);
Expand Down
6 changes: 6 additions & 0 deletions src/hidapi/SDL_hidapi.c
Expand Up @@ -419,6 +419,9 @@ LIBUSB_CopyHIDDeviceInfo(struct LIBUSB_hid_device_info *pSrc,
pDst->usage_page = pSrc->usage_page;
pDst->usage = pSrc->usage;
pDst->interface_number = pSrc->interface_number;
pDst->interface_class = pSrc->interface_class;
pDst->interface_subclass = pSrc->interface_subclass;
pDst->interface_protocol = pSrc->interface_protocol;
pDst->next = NULL;
}
#endif /* SDL_LIBUSB_DYNAMIC */
Expand All @@ -438,6 +441,9 @@ PLATFORM_CopyHIDDeviceInfo(struct PLATFORM_hid_device_info *pSrc,
pDst->usage_page = pSrc->usage_page;
pDst->usage = pSrc->usage;
pDst->interface_number = pSrc->interface_number;
pDst->interface_class = pSrc->interface_class;
pDst->interface_subclass = pSrc->interface_subclass;
pDst->interface_protocol = pSrc->interface_protocol;
pDst->next = NULL;
}
#endif /* HAVE_PLATFORM_BACKEND */
Expand Down
7 changes: 5 additions & 2 deletions src/hidapi/android/hid.cpp
Expand Up @@ -739,7 +739,7 @@ extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz);

extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface );
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol );

extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID);
Expand Down Expand Up @@ -828,7 +828,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallbac
}

extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface )
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface, int nInterfaceClass, int nInterfaceSubclass, int nInterfaceProtocol )
{
LOGV( "HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface );

Expand All @@ -842,6 +842,9 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNI
pInfo->manufacturer_string = CreateWStringFromJString( env, sManufacturer );
pInfo->product_string = CreateWStringFromJString( env, sProduct );
pInfo->interface_number = nInterface;
pInfo->interface_class = nInterfaceClass;
pInfo->interface_subclass = nInterfaceSubclass;
pInfo->interface_protocol = nInterfaceProtocol;

hid_device_ref<CHIDDevice> pDevice( new CHIDDevice( nDeviceID, pInfo ) );

Expand Down
6 changes: 6 additions & 0 deletions src/hidapi/hidapi/hidapi.h
Expand Up @@ -78,6 +78,12 @@ namespace NAMESPACE {
only if the device contains more than one interface. */
int interface_number;

/** Additional information about the USB interface.
Valid on libusb and Android implementations. */
int interface_class;
int interface_subclass;
int interface_protocol;

/** Pointer to the next device */
struct hid_device_info *next;
};
Expand Down
3 changes: 3 additions & 0 deletions src/hidapi/libusb/hid.c
Expand Up @@ -714,6 +714,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,

/* Interface Number */
cur_dev->interface_number = interface_num;
cur_dev->interface_class = intf_desc->bInterfaceClass;
cur_dev->interface_subclass = intf_desc->bInterfaceSubClass;
cur_dev->interface_protocol = intf_desc->bInterfaceProtocol;
}
}
} /* altsettings */
Expand Down
47 changes: 24 additions & 23 deletions src/hidapi/mac/hid.c
Expand Up @@ -41,10 +41,10 @@
StackOverflow. It is used with his permission. */
typedef int pthread_barrierattr_t;
typedef struct pthread_barrier {
pthread_mutex_t mutex;
pthread_cond_t cond;
int count;
int trip_count;
pthread_mutex_t mutex;
pthread_cond_t cond;
int count;
int trip_count;
} pthread_barrier_t;

static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count)
Expand Down Expand Up @@ -178,22 +178,22 @@ static void free_hid_device(hid_device *dev)
free(dev->input_report_buf);

if (device_list) {
if (device_list->dev == dev) {
device_list = device_list->next;
}
else {
struct hid_device_list_node *node = device_list;
while (node) {
if (node->next && node->next->dev == dev) {
struct hid_device_list_node *new_next = node->next->next;
free(node->next);
node->next = new_next;
break;
}

node = node->next;
}
}
if (device_list->dev == dev) {
device_list = device_list->next;
}
else {
struct hid_device_list_node *node = device_list;
while (node) {
if (node->next && node->next->dev == dev) {
struct hid_device_list_node *new_next = node->next->next;
free(node->next);
node->next = new_next;
break;
}

node = node->next;
}
}
}

/* Clean up the thread objects */
Expand Down Expand Up @@ -478,9 +478,10 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,

IOHIDDeviceRef dev = device_array[i];

if (!dev) {
continue;
}
if (!dev) {
continue;
}

dev_vid = get_vendor_id(dev);
dev_pid = get_product_id(dev);

Expand Down
139 changes: 111 additions & 28 deletions src/joystick/SDL_joystick.c
Expand Up @@ -751,17 +751,17 @@ SDL_JoystickSetPlayerIndex(SDL_Joystick * joystick, int player_index)
int
SDL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
{
int result;
int result;

if (!SDL_PrivateJoystickValid(joystick)) {
return -1;
}

SDL_LockJoysticks();
SDL_LockJoysticks();
result = joystick->driver->Rumble(joystick, low_frequency_rumble, high_frequency_rumble, duration_ms);
SDL_UnlockJoysticks();
SDL_UnlockJoysticks();

return result;
return result;
}

/*
Expand Down Expand Up @@ -1352,7 +1352,7 @@ SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGUID guid, const char *nam
Uint16 vendor, product;

SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL);
type = SDL_GetJoystickGameControllerType(vendor, product, name);
type = SDL_GetJoystickGameControllerType(name, vendor, product, -1, 0, 0, 0);
if (type == SDL_CONTROLLER_TYPE_UNKNOWN) {
if (SDL_IsJoystickXInput(guid)) {
/* This is probably an Xbox One controller */
Expand All @@ -1363,37 +1363,120 @@ SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGUID guid, const char *nam
}

SDL_GameControllerType
SDL_GetJoystickGameControllerType(Uint16 vendor, Uint16 product, const char *name)
SDL_GetJoystickGameControllerType(const char *name, Uint16 vendor, Uint16 product, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
SDL_GameControllerType type = SDL_CONTROLLER_TYPE_UNKNOWN;

if (vendor == 0x0000 && product == 0x0000) {
/* Some devices are only identifiable by their name */
if (SDL_strcmp(name, "Lic Pro Controller") == 0 ||
SDL_strcmp(name, "Nintendo Wireless Gamepad") == 0 ||
SDL_strcmp(name, "Wireless Gamepad") == 0) {
/* HORI or PowerA Switch Pro Controller clone */
return SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO;
type = SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO;
} else {
type = SDL_CONTROLLER_TYPE_UNKNOWN;
}

} else if (vendor == 0x0001 && product == 0x0001) {
type = SDL_CONTROLLER_TYPE_UNKNOWN;

} else {
switch (GuessControllerType(vendor, product)) {
case k_eControllerType_XBox360Controller:
type = SDL_CONTROLLER_TYPE_XBOX360;
break;
case k_eControllerType_XBoxOneController:
type = SDL_CONTROLLER_TYPE_XBOXONE;
break;
case k_eControllerType_PS3Controller:
type = SDL_CONTROLLER_TYPE_PS3;
break;
case k_eControllerType_PS4Controller:
type = SDL_CONTROLLER_TYPE_PS4;
break;
case k_eControllerType_SwitchProController:
case k_eControllerType_SwitchInputOnlyController:
type = SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO;
break;
default:
type = SDL_CONTROLLER_TYPE_UNKNOWN;
break;
}
return SDL_CONTROLLER_TYPE_UNKNOWN;
}
if (vendor == 0x0001 && product == 0x0001) {
return SDL_CONTROLLER_TYPE_UNKNOWN;
}

switch (GuessControllerType(vendor, product)) {
case k_eControllerType_XBox360Controller:
return SDL_CONTROLLER_TYPE_XBOX360;
case k_eControllerType_XBoxOneController:
return SDL_CONTROLLER_TYPE_XBOXONE;
case k_eControllerType_PS3Controller:
return SDL_CONTROLLER_TYPE_PS3;
case k_eControllerType_PS4Controller:
return SDL_CONTROLLER_TYPE_PS4;
case k_eControllerType_SwitchProController:
case k_eControllerType_SwitchInputOnlyController:
return SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO;
default:
return SDL_CONTROLLER_TYPE_UNKNOWN;
}

if (type == SDL_CONTROLLER_TYPE_UNKNOWN) {
/* This code should match the checks in libusb/hid.c and HIDDeviceManager.java */
static const int LIBUSB_CLASS_VENDOR_SPEC = 0xFF;
static const int XB360_IFACE_SUBCLASS = 93;
static const int XB360_IFACE_PROTOCOL = 1; /* Wired */
static const int XB360W_IFACE_PROTOCOL = 129; /* Wireless */
static const int XBONE_IFACE_SUBCLASS = 71;
static const int XBONE_IFACE_PROTOCOL = 208;

if (interface_class == LIBUSB_CLASS_VENDOR_SPEC &&
interface_subclass == XB360_IFACE_SUBCLASS &&
(interface_protocol == XB360_IFACE_PROTOCOL ||
interface_protocol == XB360W_IFACE_PROTOCOL)) {

static const int SUPPORTED_VENDORS[] = {
0x0079, /* GPD Win 2 */
0x044f, /* Thrustmaster */
0x045e, /* Microsoft */
0x046d, /* Logitech */
0x056e, /* Elecom */
0x06a3, /* Saitek */
0x0738, /* Mad Catz */
0x07ff, /* Mad Catz */
0x0e6f, /* PDP */
0x0f0d, /* Hori */
0x1038, /* SteelSeries */
0x11c9, /* Nacon */
0x12ab, /* Unknown */
0x1430, /* RedOctane */
0x146b, /* BigBen */
0x1532, /* Razer Sabertooth */
0x15e4, /* Numark */
0x162e, /* Joytech */
0x1689, /* Razer Onza */
0x1bad, /* Harmonix */
0x24c6, /* PowerA */
};

int i;
for (i = 0; i < SDL_arraysize(SUPPORTED_VENDORS); ++i) {
if (vendor == SUPPORTED_VENDORS[i]) {
type = SDL_CONTROLLER_TYPE_XBOX360;
break;
}
}
}

if (interface_number == 0 &&
interface_class == LIBUSB_CLASS_VENDOR_SPEC &&
interface_subclass == XBONE_IFACE_SUBCLASS &&
interface_protocol == XBONE_IFACE_PROTOCOL) {

static const int SUPPORTED_VENDORS[] = {
0x045e, /* Microsoft */
0x0738, /* Mad Catz */
0x0e6f, /* PDP */
0x0f0d, /* Hori */
0x1532, /* Razer Wildcat */
0x24c6, /* PowerA */
0x2e24, /* Hyperkin */
};

int i;
for (i = 0; i < SDL_arraysize(SUPPORTED_VENDORS); ++i) {
if (vendor == SUPPORTED_VENDORS[i]) {
type = SDL_CONTROLLER_TYPE_XBOXONE;
break;
}
}
}
}
return type;
}

SDL_bool
Expand Down Expand Up @@ -1686,7 +1769,7 @@ SDL_bool SDL_ShouldIgnoreJoystick(const char *name, SDL_JoystickGUID guid)
}
}

if (SDL_GetJoystickGameControllerType(vendor, product, name) == SDL_CONTROLLER_TYPE_PS4 && SDL_IsPS4RemapperRunning()) {
if (SDL_GetJoystickGameControllerType(name, vendor, product, -1, 0, 0, 0) == SDL_CONTROLLER_TYPE_PS4 && SDL_IsPS4RemapperRunning()) {
return SDL_TRUE;
}

Expand Down
2 changes: 1 addition & 1 deletion src/joystick/SDL_joystick_c.h
Expand Up @@ -60,7 +60,7 @@ extern const char *SDL_GetCustomJoystickName(Uint16 vendor, Uint16 product);

/* Function to return the type of a controller */
extern SDL_GameControllerType SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGUID guid, const char *name);
extern SDL_GameControllerType SDL_GetJoystickGameControllerType(Uint16 vendor, Uint16 product, const char *name);
extern SDL_GameControllerType SDL_GetJoystickGameControllerType(const char *name, Uint16 vendor, Uint16 product, int interface_number, int interface_class, int interface_subclass, int interface_protocol);

/* Function to return whether a joystick is a Nintendo Switch Pro controller */
extern SDL_bool SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor_id, Uint16 product_id);
Expand Down

0 comments on commit 43aa1fa

Please sign in to comment.