diff options
Diffstat (limited to 'source/3rd-party/SDL2/src/joystick/hidapi')
6 files changed, 3727 insertions, 0 deletions
diff --git a/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_ps4.c b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_ps4.c new file mode 100644 index 0000000..cdd478a --- /dev/null +++ b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -0,0 +1,566 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +/* This driver supports both simplified reports and the extended input reports enabled by Steam. + Code and logic contributed by Valve Corporation under the SDL zlib license. +*/ +#include "../../SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_hints.h" +#include "SDL_log.h" +#include "SDL_events.h" +#include "SDL_timer.h" +#include "SDL_joystick.h" +#include "SDL_gamecontroller.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + + +#ifdef SDL_JOYSTICK_HIDAPI_PS4 + +#define SONY_USB_VID 0x054C +#define SONY_DS4_PID 0x05C4 +#define SONY_DS4_DONGLE_PID 0x0BA0 +#define SONY_DS4_SLIM_PID 0x09CC + +#define RAZER_USB_VID 0x1532 +#define RAZER_PANTHERA_PID 0X0401 +#define RAZER_PANTHERA_EVO_PID 0x1008 + +#define USB_PACKET_LENGTH 64 + +#define VOLUME_CHECK_INTERVAL_MS (10 * 1000) + +typedef enum +{ + k_EPS4ReportIdUsbState = 1, + k_EPS4ReportIdUsbEffects = 5, + k_EPS4ReportIdBluetoothState = 17, + k_EPS4ReportIdBluetoothEffects = 17, + k_EPS4ReportIdDisconnectMessage = 226, +} EPS4ReportId; + +typedef enum +{ + k_ePS4FeatureReportIdGyroCalibration_USB = 0x02, + k_ePS4FeatureReportIdGyroCalibration_BT = 0x05, + k_ePS4FeatureReportIdSerialNumber = 0x12, +} EPS4FeatureReportID; + +typedef struct +{ + Uint8 ucLeftJoystickX; + Uint8 ucLeftJoystickY; + Uint8 ucRightJoystickX; + Uint8 ucRightJoystickY; + Uint8 rgucButtonsHatAndCounter[ 3 ]; + Uint8 ucTriggerLeft; + Uint8 ucTriggerRight; + Uint8 _rgucPad0[ 3 ]; + Sint16 sGyroX; + Sint16 sGyroY; + Sint16 sGyroZ; + Sint16 sAccelX; + Sint16 sAccelY; + Sint16 sAccelZ; + Uint8 _rgucPad1[ 5 ]; + Uint8 ucBatteryLevel; + Uint8 _rgucPad2[ 4 ]; + Uint8 ucTrackpadCounter1; + Uint8 rgucTrackpadData1[ 3 ]; + Uint8 ucTrackpadCounter2; + Uint8 rgucTrackpadData2[ 3 ]; +} PS4StatePacket_t; + +typedef struct +{ + Uint8 ucRumbleRight; + Uint8 ucRumbleLeft; + Uint8 ucLedRed; + Uint8 ucLedGreen; + Uint8 ucLedBlue; + Uint8 ucLedDelayOn; + Uint8 ucLedDelayOff; + Uint8 _rgucPad0[ 8 ]; + Uint8 ucVolumeLeft; + Uint8 ucVolumeRight; + Uint8 ucVolumeMic; + Uint8 ucVolumeSpeaker; +} DS4EffectsState_t; + +typedef struct { + SDL_bool is_dongle; + SDL_bool is_bluetooth; + SDL_bool audio_supported; + SDL_bool rumble_supported; + Uint8 volume; + Uint32 last_volume_check; + Uint32 rumble_expiration; + PS4StatePacket_t last_state; +} SDL_DriverPS4_Context; + + +/* Public domain CRC implementation adapted from: + http://home.thep.lu.se/~bjorn/crc/crc32_simple.c +*/ +static Uint32 crc32_for_byte(Uint32 r) +{ + int i; + for(i = 0; i < 8; ++i) { + r = (r & 1? 0: (Uint32)0xEDB88320L) ^ r >> 1; + } + return r ^ (Uint32)0xFF000000L; +} + +static Uint32 crc32(Uint32 crc, const void *data, int count) +{ + int i; + for(i = 0; i < count; ++i) { + crc = crc32_for_byte((Uint8)crc ^ ((const Uint8*)data)[i]) ^ crc >> 8; + } + return crc; +} + +#if defined(__WIN32__) && defined(HAVE_ENDPOINTVOLUME_H) +#include "../../core/windows/SDL_windows.h" + +#ifndef NTDDI_VISTA +#define NTDDI_VISTA 0x06000000 +#endif +#ifndef _WIN32_WINNT_VISTA +#define _WIN32_WINNT_VISTA 0x0600 +#endif + +/* Define Vista for the Audio related includes below to work */ +#undef NTDDI_VERSION +#define NTDDI_VERSION NTDDI_VISTA +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_VISTA +#define COBJMACROS +#include <mmdeviceapi.h> +#include <audioclient.h> +#include <endpointvolume.h> + +#undef DEFINE_GUID +#define DEFINE_GUID(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) static const GUID n = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} +DEFINE_GUID(SDL_CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E); +DEFINE_GUID(SDL_IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6); +DEFINE_GUID(SDL_IID_IAudioEndpointVolume, 0x5CDF2C82, 0x841E, 0x4546, 0x97, 0x22, 0x0C, 0xF7, 0x40, 0x78, 0x22, 0x9A); +#endif + + + +static float GetSystemVolume(void) +{ + float volume = -1.0f; /* Return this if we can't get system volume */ + +#if defined(__WIN32__) && defined(HAVE_ENDPOINTVOLUME_H) + HRESULT hr = WIN_CoInitialize(); + if (SUCCEEDED(hr)) { + IMMDeviceEnumerator *pEnumerator; + + /* This should gracefully fail on XP and succeed on everything Vista and above */ + hr = CoCreateInstance(&SDL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &SDL_IID_IMMDeviceEnumerator, (LPVOID*)&pEnumerator); + if (SUCCEEDED(hr)) { + IMMDevice *pDevice; + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, eRender, eConsole, &pDevice); + if (SUCCEEDED(hr)) { + IAudioEndpointVolume *pEndpointVolume; + + hr = IMMDevice_Activate(pDevice, &SDL_IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (LPVOID*)&pEndpointVolume); + if (SUCCEEDED(hr)) { + IAudioEndpointVolume_GetMasterVolumeLevelScalar(pEndpointVolume, &volume); + IUnknown_Release(pEndpointVolume); + } + IUnknown_Release(pDevice); + } + IUnknown_Release(pEnumerator); + } + WIN_CoUninitialize(); + } +#endif /* __WIN32__ */ + + return volume; +} + +static uint8_t GetPlaystationVolumeFromFloat(float fVolume) +{ + const int k_nVolumeFitRatio = 15; + const int k_nVolumeFitOffset = 9; + float fVolLog; + + if (fVolume > 1.0f || fVolume < 0.0f) { + fVolume = 0.30f; + } + fVolLog = SDL_logf(fVolume * 100); + + return (Uint8)((fVolLog * k_nVolumeFitRatio) + k_nVolumeFitOffset); +} + +static SDL_bool +HIDAPI_DriverPS4_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number) +{ + return SDL_IsJoystickPS4(vendor_id, product_id); +} + +static const char * +HIDAPI_DriverPS4_GetDeviceName(Uint16 vendor_id, Uint16 product_id) +{ + if (vendor_id == SONY_USB_VID) { + return "PS4 Controller"; + } + return NULL; +} + +static SDL_bool ReadFeatureReport(hid_device *dev, Uint8 report_id, Uint8 *data, size_t size) +{ + Uint8 report[USB_PACKET_LENGTH + 1]; + + SDL_memset(report, 0, sizeof(report)); + report[0] = report_id; + if (hid_get_feature_report(dev, report, sizeof(report)) < 0) { + return SDL_FALSE; + } + SDL_memcpy(data, report, SDL_min(size, sizeof(report))); + return SDL_TRUE; +} + +static SDL_bool CheckUSBConnected(hid_device *dev) +{ + int i; + Uint8 data[16]; + + /* This will fail if we're on Bluetooth */ + if (ReadFeatureReport(dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data))) { + for (i = 0; i < sizeof(data); ++i) { + if (data[i] != 0x00) { + return SDL_TRUE; + } + } + /* Maybe the dongle without a connected controller? */ + } + return SDL_FALSE; +} + +static SDL_bool HIDAPI_DriverPS4_CanRumble(Uint16 vendor_id, Uint16 product_id) +{ + /* The Razer Panthera fight stick hangs when trying to rumble */ + if (vendor_id == RAZER_USB_VID && + (product_id == RAZER_PANTHERA_PID || product_id == RAZER_PANTHERA_EVO_PID)) { + return SDL_FALSE; + } + return SDL_TRUE; +} + +static int HIDAPI_DriverPS4_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); + +static SDL_bool +HIDAPI_DriverPS4_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context) +{ + SDL_DriverPS4_Context *ctx; + + ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + SDL_OutOfMemory(); + return SDL_FALSE; + } + *context = ctx; + + /* Check for type of connection */ + ctx->is_dongle = (vendor_id == SONY_USB_VID && product_id == SONY_DS4_DONGLE_PID); + if (ctx->is_dongle) { + ctx->is_bluetooth = SDL_FALSE; + } else if (vendor_id == SONY_USB_VID) { + ctx->is_bluetooth = !CheckUSBConnected(dev); + } else { + /* Third party controllers appear to all be wired */ + ctx->is_bluetooth = SDL_FALSE; + } +#ifdef DEBUG_PS4 + SDL_Log("PS4 dongle = %s, bluetooth = %s\n", ctx->is_dongle ? "TRUE" : "FALSE", ctx->is_bluetooth ? "TRUE" : "FALSE"); +#endif + + /* Check to see if audio is supported */ + if (vendor_id == SONY_USB_VID && + (product_id == SONY_DS4_SLIM_PID || product_id == SONY_DS4_DONGLE_PID )) { + ctx->audio_supported = SDL_TRUE; + } + + if (HIDAPI_DriverPS4_CanRumble(vendor_id, product_id)) { + if (ctx->is_bluetooth) { + ctx->rumble_supported = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, SDL_FALSE); + } else { + ctx->rumble_supported = SDL_TRUE; + } + } + + /* Initialize LED and effect state */ + HIDAPI_DriverPS4_Rumble(joystick, dev, ctx, 0, 0, 0); + + /* Initialize the joystick capabilities */ + joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX; + joystick->naxes = SDL_CONTROLLER_AXIS_MAX; + joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; + + return SDL_TRUE; +} + +static int +HIDAPI_DriverPS4_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context; + DS4EffectsState_t *effects; + Uint8 data[78]; + int report_size, offset; + + if (!ctx->rumble_supported) { + return SDL_Unsupported(); + } + + /* In order to send rumble, we have to send a complete effect packet */ + SDL_memset(data, 0, sizeof(data)); + + if (ctx->is_bluetooth) { + data[0] = k_EPS4ReportIdBluetoothEffects; + data[1] = 0xC0 | 0x04; /* Magic value HID + CRC, also sets interval to 4ms for samples */ + data[3] = 0x03; /* 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval */ + + report_size = 78; + offset = 6; + } else { + data[0] = k_EPS4ReportIdUsbEffects; + data[1] = 0x07; /* Magic value */ + + report_size = 32; + offset = 4; + } + effects = (DS4EffectsState_t *)&data[offset]; + + effects->ucRumbleLeft = (low_frequency_rumble >> 8); + effects->ucRumbleRight = (high_frequency_rumble >> 8); + + effects->ucLedRed = 0; + effects->ucLedGreen = 0; + effects->ucLedBlue = 80; + + if (ctx->audio_supported) { + Uint32 now = SDL_GetTicks(); + if (!ctx->last_volume_check || + SDL_TICKS_PASSED(now, ctx->last_volume_check + VOLUME_CHECK_INTERVAL_MS)) { + ctx->volume = GetPlaystationVolumeFromFloat(GetSystemVolume()); + ctx->last_volume_check = now; + } + + effects->ucVolumeRight = ctx->volume; + effects->ucVolumeLeft = ctx->volume; + effects->ucVolumeSpeaker = ctx->volume; + effects->ucVolumeMic = 0xFF; + } + + if (ctx->is_bluetooth) { + /* Bluetooth reports need a CRC at the end of the packet (at least on Linux) */ + Uint8 ubHdr = 0xA2; /* hidp header is part of the CRC calculation */ + Uint32 unCRC; + unCRC = crc32(0, &ubHdr, 1); + unCRC = crc32(unCRC, data, (Uint32)(report_size - sizeof(unCRC))); + SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC)); + } + + if (hid_write(dev, data, report_size) != report_size) { + return SDL_SetError("Couldn't send rumble packet"); + } + + if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) { + ctx->rumble_expiration = SDL_GetTicks() + duration_ms; + } else { + ctx->rumble_expiration = 0; + } + return 0; +} + +static void +HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverPS4_Context *ctx, PS4StatePacket_t *packet) +{ + Sint16 axis; + + if (ctx->last_state.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) { + { + Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4); + + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + } + { + Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F); + SDL_bool dpad_up = SDL_FALSE; + SDL_bool dpad_down = SDL_FALSE; + SDL_bool dpad_left = SDL_FALSE; + SDL_bool dpad_right = SDL_FALSE; + + switch (data) { + case 0: + dpad_up = SDL_TRUE; + break; + case 1: + dpad_up = SDL_TRUE; + dpad_right = SDL_TRUE; + break; + case 2: + dpad_right = SDL_TRUE; + break; + case 3: + dpad_right = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 4: + dpad_down = SDL_TRUE; + break; + case 5: + dpad_left = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 6: + dpad_left = SDL_TRUE; + break; + case 7: + dpad_up = SDL_TRUE; + dpad_left = SDL_TRUE; + break; + default: + break; + } + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, dpad_down); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, dpad_up); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, dpad_right); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, dpad_left); + } + } + + if (ctx->last_state.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) { + Uint8 data = packet->rgucButtonsHatAndCounter[1]; + + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x80) ? SDL_PRESSED : SDL_RELEASED); + } + + if (ctx->last_state.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) { + Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03); + + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + } + + axis = ((int)packet->ucTriggerLeft * 257) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + axis = ((int)packet->ucTriggerRight * 257) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + axis = ((int)packet->ucLeftJoystickX * 257) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + axis = ((int)packet->ucLeftJoystickY * 257) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis); + axis = ((int)packet->ucRightJoystickX * 257) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + axis = ((int)packet->ucRightJoystickY * 257) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); + + if (packet->ucBatteryLevel & 0x10) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; + } else { + /* Battery level ranges from 0 to 10 */ + int level = (packet->ucBatteryLevel & 0xF); + if (level == 0) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_EMPTY; + } else if (level <= 2) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_LOW; + } else if (level <= 7) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_MEDIUM; + } else { + joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL; + } + } + + SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); +} + +static SDL_bool +HIDAPI_DriverPS4_Update(SDL_Joystick *joystick, hid_device *dev, void *context) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context; + Uint8 data[USB_PACKET_LENGTH]; + int size; + + while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) { + switch (data[0]) { + case k_EPS4ReportIdUsbState: + HIDAPI_DriverPS4_HandleStatePacket(joystick, dev, ctx, (PS4StatePacket_t *)&data[1]); + break; + case k_EPS4ReportIdBluetoothState: + /* Bluetooth state packets have two additional bytes at the beginning */ + HIDAPI_DriverPS4_HandleStatePacket(joystick, dev, ctx, (PS4StatePacket_t *)&data[3]); + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown PS4 packet: 0x%.2x\n", data[0]); +#endif + break; + } + } + + if (ctx->rumble_expiration) { + Uint32 now = SDL_GetTicks(); + if (SDL_TICKS_PASSED(now, ctx->rumble_expiration)) { + HIDAPI_DriverPS4_Rumble(joystick, dev, context, 0, 0, 0); + } + } + + return (size >= 0); +} + +static void +HIDAPI_DriverPS4_Quit(SDL_Joystick *joystick, hid_device *dev, void *context) +{ + SDL_free(context); +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 = +{ + SDL_HINT_JOYSTICK_HIDAPI_PS4, + SDL_TRUE, + HIDAPI_DriverPS4_IsSupportedDevice, + HIDAPI_DriverPS4_GetDeviceName, + HIDAPI_DriverPS4_Init, + HIDAPI_DriverPS4_Rumble, + HIDAPI_DriverPS4_Update, + HIDAPI_DriverPS4_Quit +}; + +#endif /* SDL_JOYSTICK_HIDAPI_PS4 */ + +#endif /* SDL_JOYSTICK_HIDAPI */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_switch.c b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_switch.c new file mode 100644 index 0000000..16e4ea3 --- /dev/null +++ b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_switch.c @@ -0,0 +1,905 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +/* This driver supports the Nintendo Switch Pro controller. + Code and logic contributed by Valve Corporation under the SDL zlib license. +*/ +#include "../../SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_hints.h" +#include "SDL_log.h" +#include "SDL_events.h" +#include "SDL_timer.h" +#include "SDL_joystick.h" +#include "SDL_gamecontroller.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + + +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH + +typedef enum { + k_eSwitchInputReportIDs_SubcommandReply = 0x21, + k_eSwitchInputReportIDs_FullControllerState = 0x30, + k_eSwitchInputReportIDs_SimpleControllerState = 0x3F, + k_eSwitchInputReportIDs_CommandAck = 0x81, +} ESwitchInputReportIDs; + +typedef enum { + k_eSwitchOutputReportIDs_RumbleAndSubcommand = 0x01, + k_eSwitchOutputReportIDs_Rumble = 0x10, + k_eSwitchOutputReportIDs_Proprietary = 0x80, +} ESwitchOutputReportIDs; + +typedef enum { + k_eSwitchSubcommandIDs_BluetoothManualPair = 0x01, + k_eSwitchSubcommandIDs_RequestDeviceInfo = 0x02, + k_eSwitchSubcommandIDs_SetInputReportMode = 0x03, + k_eSwitchSubcommandIDs_SetHCIState = 0x06, + k_eSwitchSubcommandIDs_SPIFlashRead = 0x10, + k_eSwitchSubcommandIDs_SetPlayerLights = 0x30, + k_eSwitchSubcommandIDs_SetHomeLight = 0x38, + k_eSwitchSubcommandIDs_EnableIMU = 0x40, + k_eSwitchSubcommandIDs_SetIMUSensitivity = 0x41, + k_eSwitchSubcommandIDs_EnableVibration = 0x48, +} ESwitchSubcommandIDs; + +typedef enum { + k_eSwitchProprietaryCommandIDs_Handshake = 0x02, + k_eSwitchProprietaryCommandIDs_HighSpeed = 0x03, + k_eSwitchProprietaryCommandIDs_ForceUSB = 0x04, + k_eSwitchProprietaryCommandIDs_ClearUSB = 0x05, + k_eSwitchProprietaryCommandIDs_ResetMCU = 0x06, +} ESwitchProprietaryCommandIDs; + +typedef enum { + k_eSwitchDeviceInfoControllerType_JoyConLeft = 0x1, + k_eSwitchDeviceInfoControllerType_JoyConRight = 0x2, + k_eSwitchDeviceInfoControllerType_ProController = 0x3, +} ESwitchDeviceInfoControllerType; + +#define k_unSwitchOutputPacketDataLength 49 +#define k_unSwitchMaxOutputPacketLength 64 +#define k_unSwitchBluetoothPacketLength k_unSwitchOutputPacketDataLength +#define k_unSwitchUSBPacketLength k_unSwitchMaxOutputPacketLength + +#define k_unSPIStickCalibrationStartOffset 0x603D +#define k_unSPIStickCalibrationEndOffset 0x604E +#define k_unSPIStickCalibrationLength (k_unSPIStickCalibrationEndOffset - k_unSPIStickCalibrationStartOffset + 1) + +#pragma pack(1) +typedef struct +{ + Uint8 rgucButtons[2]; + Uint8 ucStickHat; + Sint16 sJoystickLeft[2]; + Sint16 sJoystickRight[2]; +} SwitchSimpleStatePacket_t; + +typedef struct +{ + Uint8 ucCounter; + Uint8 ucBatteryAndConnection; + Uint8 rgucButtons[3]; + Uint8 rgucJoystickLeft[3]; + Uint8 rgucJoystickRight[3]; + Uint8 ucVibrationCode; +} SwitchControllerStatePacket_t; + +typedef struct +{ + SwitchControllerStatePacket_t controllerState; + + struct { + Sint16 sAccelX; + Sint16 sAccelY; + Sint16 sAccelZ; + + Sint16 sGyroX; + Sint16 sGyroY; + Sint16 sGyroZ; + } imuState[3]; +} SwitchStatePacket_t; + +typedef struct +{ + Uint32 unAddress; + Uint8 ucLength; +} SwitchSPIOpData_t; + +typedef struct +{ + SwitchControllerStatePacket_t m_controllerState; + + Uint8 ucSubcommandAck; + Uint8 ucSubcommandID; + + #define k_unSubcommandDataBytes 35 + union { + Uint8 rgucSubcommandData[ k_unSubcommandDataBytes ]; + + struct { + SwitchSPIOpData_t opData; + Uint8 rgucReadData[ k_unSubcommandDataBytes - sizeof(SwitchSPIOpData_t) ]; + } spiReadData; + + struct { + Uint8 rgucFirmwareVersion[2]; + Uint8 ucDeviceType; + Uint8 ucFiller1; + Uint8 rgucMACAddress[6]; + Uint8 ucFiller2; + Uint8 ucColorLocation; + } deviceInfo; + }; +} SwitchSubcommandInputPacket_t; + +typedef struct +{ + Uint8 rgucData[4]; +} SwitchRumbleData_t; + +typedef struct +{ + Uint8 ucPacketType; + Uint8 ucPacketNumber; + SwitchRumbleData_t rumbleData[2]; +} SwitchCommonOutputPacket_t; + +typedef struct +{ + SwitchCommonOutputPacket_t commonData; + + Uint8 ucSubcommandID; + Uint8 rgucSubcommandData[ k_unSwitchOutputPacketDataLength - sizeof(SwitchCommonOutputPacket_t) - 1 ]; +} SwitchSubcommandOutputPacket_t; + +typedef struct +{ + Uint8 ucPacketType; + Uint8 ucProprietaryID; + + Uint8 rgucProprietaryData[ k_unSwitchOutputPacketDataLength - 1 - 1 ]; +} SwitchProprietaryOutputPacket_t; +#pragma pack() + +typedef struct { + hid_device *dev; + SDL_bool m_bIsUsingBluetooth; + Uint8 m_nCommandNumber; + SwitchCommonOutputPacket_t m_RumblePacket; + Uint32 m_nRumbleExpiration; + Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength]; + SwitchSimpleStatePacket_t m_lastSimpleState; + SwitchStatePacket_t m_lastFullState; + + struct StickCalibrationData { + struct { + Sint16 sCenter; + Sint16 sMin; + Sint16 sMax; + } axis[2]; + } m_StickCalData[2]; + + struct StickExtents { + struct { + Sint16 sMin; + Sint16 sMax; + } axis[2]; + } m_StickExtents[2]; +} SDL_DriverSwitch_Context; + + +static SDL_bool +HIDAPI_DriverSwitch_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number) +{ + return SDL_IsJoystickNintendoSwitchPro(vendor_id, product_id); +} + +static const char * +HIDAPI_DriverSwitch_GetDeviceName(Uint16 vendor_id, Uint16 product_id) +{ + /* Give a user friendly name for this controller */ + if (SDL_IsJoystickNintendoSwitchPro(vendor_id, product_id)) { + return "Nintendo Switch Pro Controller"; + } + return NULL; +} + +static int ReadInput(SDL_DriverSwitch_Context *ctx) +{ + return hid_read_timeout(ctx->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0); +} + +static int WriteOutput(SDL_DriverSwitch_Context *ctx, Uint8 *data, int size) +{ + return hid_write(ctx->dev, data, size); +} + +static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID) +{ + /* Average response time for messages is ~30ms */ + Uint32 TimeoutMs = 100; + Uint32 startTicks = SDL_GetTicks(); + + int nRead = 0; + while ((nRead = ReadInput(ctx)) != -1) { + if (nRead > 0) { + if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) { + SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[ 1 ]; + if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) { + return reply; + } + } + } else { + SDL_Delay(1); + } + + if (SDL_TICKS_PASSED(SDL_GetTicks(), startTicks + TimeoutMs)) { + break; + } + } + return NULL; +} + +static SDL_bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs expectedID) +{ + /* Average response time for messages is ~30ms */ + Uint32 TimeoutMs = 100; + Uint32 startTicks = SDL_GetTicks(); + + int nRead = 0; + while ((nRead = ReadInput(ctx)) != -1) { + if (nRead > 0) { + if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_CommandAck && ctx->m_rgucReadBuffer[ 1 ] == expectedID) { + return SDL_TRUE; + } + } else { + SDL_Delay(1); + } + + if (SDL_TICKS_PASSED(SDL_GetTicks(), startTicks + TimeoutMs)) { + break; + } + } + return SDL_FALSE; +} + +static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket) +{ + SDL_memset(outPacket, 0, sizeof(*outPacket)); + + outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand; + outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber; + + SDL_memcpy(&outPacket->commonData.rumbleData, &ctx->m_RumblePacket.rumbleData, sizeof(ctx->m_RumblePacket.rumbleData)); + + outPacket->ucSubcommandID = ucCommandID; + SDL_memcpy(outPacket->rgucSubcommandData, pBuf, ucLen); + + ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF; +} + +static SDL_bool WritePacket(SDL_DriverSwitch_Context *ctx, void *pBuf, Uint8 ucLen) +{ + Uint8 rgucBuf[k_unSwitchMaxOutputPacketLength]; + const size_t unWriteSize = ctx->m_bIsUsingBluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength; + + if (ucLen > k_unSwitchOutputPacketDataLength) { + return SDL_FALSE; + } + + if (ucLen < unWriteSize) { + SDL_memcpy(rgucBuf, pBuf, ucLen); + SDL_memset(rgucBuf+ucLen, 0, unWriteSize-ucLen); + pBuf = rgucBuf; + ucLen = (Uint8)unWriteSize; + } + return (WriteOutput(ctx, (Uint8 *)pBuf, ucLen) >= 0); +} + +static SDL_bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandInputPacket_t **ppReply) +{ + int nRetries = 5; + SwitchSubcommandInputPacket_t *reply = NULL; + + while (!reply && nRetries--) { + SwitchSubcommandOutputPacket_t commandPacket; + ConstructSubcommand(ctx, ucCommandID, pBuf, ucLen, &commandPacket); + + if (!WritePacket(ctx, &commandPacket, sizeof(commandPacket))) { + continue; + } + + reply = ReadSubcommandReply(ctx, ucCommandID); + } + + if (ppReply) { + *ppReply = reply; + } + return reply != NULL; +} + +static SDL_bool WriteProprietary(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs ucCommand, Uint8 *pBuf, Uint8 ucLen, SDL_bool waitForReply) +{ + int nRetries = 5; + + while (nRetries--) { + SwitchProprietaryOutputPacket_t packet; + + if ((!pBuf && ucLen > 0) || ucLen > sizeof(packet.rgucProprietaryData)) { + return SDL_FALSE; + } + + packet.ucPacketType = k_eSwitchOutputReportIDs_Proprietary; + packet.ucProprietaryID = ucCommand; + SDL_memcpy(packet.rgucProprietaryData, pBuf, ucLen); + + if (!WritePacket(ctx, &packet, sizeof(packet))) { + continue; + } + + if (!waitForReply || ReadProprietaryReply(ctx, ucCommand)) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +static void SetNeutralRumble(SwitchRumbleData_t *pRumble) +{ + pRumble->rgucData[0] = 0x00; + pRumble->rgucData[1] = 0x01; + pRumble->rgucData[2] = 0x40; + pRumble->rgucData[3] = 0x40; +} + +static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp) +{ + if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) { + // High-band frequency and low-band amplitude are actually nine-bits each so they + // take a bit from the high-band amplitude and low-band frequency bytes respectively + pRumble->rgucData[0] = usHighFreq & 0xFF; + pRumble->rgucData[1] = ucHighFreqAmp | ((usHighFreq >> 8) & 0x01); + + pRumble->rgucData[2] = ucLowFreq | ((usLowFreqAmp >> 8) & 0x80); + pRumble->rgucData[3] = usLowFreqAmp & 0xFF; + +#ifdef DEBUG_RUMBLE + SDL_Log("Freq: %.2X %.2X %.2X, Amp: %.2X %.2X %.2X\n", + usHighFreq & 0xFF, ((usHighFreq >> 8) & 0x01), ucLowFreq, + ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF); +#endif + } else { + SetNeutralRumble(pRumble); + } +} + +static SDL_bool WriteRumble(SDL_DriverSwitch_Context *ctx) +{ + /* Write into m_RumblePacket rather than a temporary buffer to allow the current rumble state + * to be retained for subsequent rumble or subcommand packets sent to the controller + */ + ctx->m_RumblePacket.ucPacketType = k_eSwitchOutputReportIDs_Rumble; + ctx->m_RumblePacket.ucPacketNumber = ctx->m_nCommandNumber; + ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF; + + return WritePacket(ctx, (Uint8 *)&ctx->m_RumblePacket, sizeof(ctx->m_RumblePacket)); +} + +static SDL_bool BTrySetupUSB(SDL_DriverSwitch_Context *ctx) +{ + /* We have to send a connection handshake to the controller when communicating over USB + * before we're able to send it other commands. Luckily this command is not supported + * over Bluetooth, so we can use the controller's lack of response as a way to + * determine if the connection is over USB or Bluetooth + */ + if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, SDL_TRUE)) { + return SDL_FALSE; + } + if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_HighSpeed, NULL, 0, SDL_TRUE)) { + return SDL_FALSE; + } + if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, SDL_TRUE)) { + return SDL_FALSE; + } + return SDL_TRUE; +} + +static SDL_bool SetVibrationEnabled(SDL_DriverSwitch_Context *ctx, Uint8 enabled) +{ + return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableVibration, &enabled, sizeof(enabled), NULL); + +} +static SDL_bool SetInputMode(SDL_DriverSwitch_Context *ctx, Uint8 input_mode) +{ + return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetInputReportMode, &input_mode, 1, NULL); +} + +static SDL_bool SetHomeLED(SDL_DriverSwitch_Context *ctx, Uint8 brightness) +{ + Uint8 ucLedIntensity = 0; + Uint8 rgucBuffer[4]; + + if (brightness > 0) { + if (brightness < 65) { + ucLedIntensity = (brightness + 5) / 10; + } else { + ucLedIntensity = (Uint8)SDL_ceilf(0xF * SDL_powf((float)brightness / 100.f, 2.13f)); + } + } + + rgucBuffer[0] = (0x0 << 4) | 0x1; /* 0 mini cycles (besides first), cycle duration 8ms */ + rgucBuffer[1] = ((ucLedIntensity & 0xF) << 4) | 0x0; /* LED start intensity (0x0-0xF), 0 cycles (LED stays on at start intensity after first cycle) */ + rgucBuffer[2] = ((ucLedIntensity & 0xF) << 4) | 0x0; /* First cycle LED intensity, 0x0 intensity for second cycle */ + rgucBuffer[3] = (0x0 << 4) | 0x0; /* 8ms fade transition to first cycle, 8ms first cycle LED duration */ + + return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetHomeLight, rgucBuffer, sizeof(rgucBuffer), NULL); +} + +static SDL_bool SetSlotLED(SDL_DriverSwitch_Context *ctx, Uint8 slot) +{ + Uint8 led_data = (1 << slot); + return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL); +} + +static SDL_bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) +{ + Uint8 *pStickCal; + size_t stick, axis; + SwitchSubcommandInputPacket_t *reply = NULL; + + /* Read Calibration Info */ + SwitchSPIOpData_t readParams; + readParams.unAddress = k_unSPIStickCalibrationStartOffset; + readParams.ucLength = k_unSPIStickCalibrationLength; + + if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) { + return SDL_FALSE; + } + + /* Stick calibration values are 12-bits each and are packed by bit + * For whatever reason the fields are in a different order for each stick + * Left: X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min + * Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max + */ + pStickCal = reply->spiReadData.rgucReadData; + + /* Left stick */ + ctx->m_StickCalData[0].axis[0].sMax = ((pStickCal[1] << 8) & 0xF00) | pStickCal[0]; /* X Axis max above center */ + ctx->m_StickCalData[0].axis[1].sMax = (pStickCal[2] << 4) | (pStickCal[1] >> 4); /* Y Axis max above center */ + ctx->m_StickCalData[0].axis[0].sCenter = ((pStickCal[4] << 8) & 0xF00) | pStickCal[3]; /* X Axis center */ + ctx->m_StickCalData[0].axis[1].sCenter = (pStickCal[5] << 4) | (pStickCal[4] >> 4); /* Y Axis center */ + ctx->m_StickCalData[0].axis[0].sMin = ((pStickCal[7] << 8) & 0xF00) | pStickCal[6]; /* X Axis min below center */ + ctx->m_StickCalData[0].axis[1].sMin = (pStickCal[8] << 4) | (pStickCal[7] >> 4); /* Y Axis min below center */ + + /* Right stick */ + ctx->m_StickCalData[1].axis[0].sCenter = ((pStickCal[10] << 8) & 0xF00) | pStickCal[9]; /* X Axis center */ + ctx->m_StickCalData[1].axis[1].sCenter = (pStickCal[11] << 4) | (pStickCal[10] >> 4); /* Y Axis center */ + ctx->m_StickCalData[1].axis[0].sMin = ((pStickCal[13] << 8) & 0xF00) | pStickCal[12]; /* X Axis min below center */ + ctx->m_StickCalData[1].axis[1].sMin = (pStickCal[14] << 4) | (pStickCal[13] >> 4); /* Y Axis min below center */ + ctx->m_StickCalData[1].axis[0].sMax = ((pStickCal[16] << 8) & 0xF00) | pStickCal[15]; /* X Axis max above center */ + ctx->m_StickCalData[1].axis[1].sMax = (pStickCal[17] << 4) | (pStickCal[16] >> 4); /* Y Axis max above center */ + + /* Filter out any values that were uninitialized (0xFFF) in the SPI read */ + for (stick = 0; stick < 2; ++stick) { + for (axis = 0; axis < 2; ++axis) { + if (ctx->m_StickCalData[stick].axis[axis].sCenter == 0xFFF) { + ctx->m_StickCalData[stick].axis[axis].sCenter = 0; + } + if (ctx->m_StickCalData[stick].axis[axis].sMax == 0xFFF) { + ctx->m_StickCalData[stick].axis[axis].sMax = 0; + } + if (ctx->m_StickCalData[stick].axis[axis].sMin == 0xFFF) { + ctx->m_StickCalData[stick].axis[axis].sMin = 0; + } + } + } + + if (ctx->m_bIsUsingBluetooth) { + for (stick = 0; stick < 2; ++stick) { + for(axis = 0; axis < 2; ++axis) { + ctx->m_StickExtents[stick].axis[axis].sMin = (Sint16)(SDL_MIN_SINT16 * 0.5f); + ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(SDL_MAX_SINT16 * 0.5f); + } + } + } else { + for (stick = 0; stick < 2; ++stick) { + for(axis = 0; axis < 2; ++axis) { + ctx->m_StickExtents[stick].axis[axis].sMin = -(Sint16)(ctx->m_StickCalData[stick].axis[axis].sMin * 0.7f); + ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sMax * 0.7f); + } + } + } + return SDL_TRUE; +} + +static float fsel(float fComparand, float fValGE, float fLT) +{ + return fComparand >= 0 ? fValGE : fLT; +} + +static float RemapVal(float val, float A, float B, float C, float D) +{ + if (A == B) { + return fsel(val - B , D , C); + } + return C + (D - C) * (val - A) / (B - A); +} + +static Sint16 ApplyStickCalibrationCentered(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue, Sint16 sCenter) +{ + sRawValue -= sCenter; + + if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) { + ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue; + } + if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) { + ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; + } + + if (sRawValue > 0) { + return (Sint16)(RemapVal(sRawValue, 0, ctx->m_StickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16)); + } else { + return (Sint16)(RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0)); + } +} + +static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue) +{ + return ApplyStickCalibrationCentered(ctx, nStick, nAxis, sRawValue, ctx->m_StickCalData[nStick].axis[nAxis].sCenter); +} + +static SDL_bool +HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context) +{ + SDL_DriverSwitch_Context *ctx; + Uint8 input_mode; + + ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + SDL_OutOfMemory(); + return SDL_FALSE; + } + ctx->dev = dev; + + *context = ctx; + + /* Initialize rumble data */ + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + + /* Try setting up USB mode, and if that fails we're using Bluetooth */ + if (!BTrySetupUSB(ctx)) { + ctx->m_bIsUsingBluetooth = SDL_TRUE; + } + + if (!LoadStickCalibration(ctx)) { + SDL_SetError("Couldn't load stick calibration"); + SDL_free(ctx); + return SDL_FALSE; + } + + if (!SetVibrationEnabled(ctx, 1)) { + SDL_SetError("Couldn't enable vibration"); + SDL_free(ctx); + return SDL_FALSE; + } + + /* Set the desired input mode */ + if (ctx->m_bIsUsingBluetooth) { + input_mode = k_eSwitchInputReportIDs_SimpleControllerState; + } else { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } + if (!SetInputMode(ctx, input_mode)) { + SDL_SetError("Couldn't set input mode"); + SDL_free(ctx); + return SDL_FALSE; + } + + /* Start sending USB reports */ + if (!ctx->m_bIsUsingBluetooth) { + /* ForceUSB doesn't generate an ACK, so don't wait for a reply */ + if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, SDL_FALSE)) { + SDL_SetError("Couldn't start USB reports"); + SDL_free(ctx); + return SDL_FALSE; + } + } + + /* Set the LED state */ + SetHomeLED(ctx, 100); + SetSlotLED(ctx, (joystick->instance_id % 4)); + + /* Initialize the joystick capabilities */ + joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX; + joystick->naxes = SDL_CONTROLLER_AXIS_MAX; + joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; + + return SDL_TRUE; +} + +static int +HIDAPI_DriverSwitch_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context; + + /* Experimentally determined rumble values. These will only matter on some controllers as tested ones + * seem to disregard these and just use any non-zero rumble values as a binary flag for constant rumble + * + * More information about these values can be found here: + * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md + */ + const Uint16 k_usHighFreq = 0x0074; + const Uint8 k_ucHighFreqAmp = 0xBE; + const Uint8 k_ucLowFreq = 0x3D; + const Uint16 k_usLowFreqAmp = 0x806F; + + if (low_frequency_rumble) { + EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); + } else { + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); + } + + if (high_frequency_rumble) { + EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp); + } else { + SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); + } + + if (!WriteRumble(ctx)) { + SDL_SetError("Couldn't send rumble packet"); + return -1; + } + + if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) { + ctx->m_nRumbleExpiration = SDL_GetTicks() + duration_ms; + } else { + ctx->m_nRumbleExpiration = 0; + } + return 0; +} + +static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) +{ + /* 0x8000 is the neutral value for all joystick axes */ + const Uint16 usJoystickCenter = 0x8000; + Sint16 axis; + + if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { + Uint8 data = packet->rgucButtons[0]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + + axis = (data & 0x40) ? 32767 : -32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + + axis = (data & 0x80) ? 32767 : -32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + } + + if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) { + Uint8 data = packet->rgucButtons[1]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + } + + if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) { + SDL_bool dpad_up = SDL_FALSE; + SDL_bool dpad_down = SDL_FALSE; + SDL_bool dpad_left = SDL_FALSE; + SDL_bool dpad_right = SDL_FALSE; + + switch (packet->ucStickHat) { + case 0: + dpad_up = SDL_TRUE; + break; + case 1: + dpad_up = SDL_TRUE; + dpad_right = SDL_TRUE; + break; + case 2: + dpad_right = SDL_TRUE; + break; + case 3: + dpad_right = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 4: + dpad_down = SDL_TRUE; + break; + case 5: + dpad_left = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 6: + dpad_left = SDL_TRUE; + break; + case 7: + dpad_up = SDL_TRUE; + dpad_left = SDL_TRUE; + break; + default: + break; + } + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, dpad_down); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, dpad_up); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, dpad_right); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, dpad_left); + } + + axis = ApplyStickCalibrationCentered(ctx, 0, 0, packet->sJoystickLeft[0], (Sint16)usJoystickCenter); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + + axis = ApplyStickCalibrationCentered(ctx, 0, 1, packet->sJoystickLeft[1], (Sint16)usJoystickCenter); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis); + + axis = ApplyStickCalibrationCentered(ctx, 1, 0, packet->sJoystickRight[0], (Sint16)usJoystickCenter); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + + axis = ApplyStickCalibrationCentered(ctx, 1, 1, packet->sJoystickRight[1], (Sint16)usJoystickCenter); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); + + ctx->m_lastSimpleState = *packet; +} + +static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +{ + Sint16 axis; + + if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { + Uint8 data = packet->controllerState.rgucButtons[0]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); + axis = (data & 0x80) ? 32767 : -32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + } + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + } + + if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { + Uint8 data = packet->controllerState.rgucButtons[2]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); + axis = (data & 0x80) ? 32767 : -32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + } + + axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 0, 0, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + + axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); + axis = ApplyStickCalibration(ctx, 0, 1, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, ~axis); + + axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 1, 0, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + + axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); + axis = ApplyStickCalibration(ctx, 1, 1, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, ~axis); + + /* High nibble of battery/connection byte is battery level, low nibble is connection status + * LSB of connection nibble is USB/Switch connection status + */ + if (packet->controllerState.ucBatteryAndConnection & 0x1) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; + } else { + /* LSB of the battery nibble is used to report charging. + * The battery level is reported from 0(empty)-8(full) + */ + int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4; + if (level == 0) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_EMPTY; + } else if (level <= 2) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_LOW; + } else if (level <= 6) { + joystick->epowerlevel = SDL_JOYSTICK_POWER_MEDIUM; + } else { + joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL; + } + } + + ctx->m_lastFullState = *packet; +} + +static SDL_bool +HIDAPI_DriverSwitch_Update(SDL_Joystick *joystick, hid_device *dev, void *context) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context; + int size; + + while ((size = ReadInput(ctx)) > 0) { + switch (ctx->m_rgucReadBuffer[0]) { + case k_eSwitchInputReportIDs_SimpleControllerState: + HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]); + break; + case k_eSwitchInputReportIDs_FullControllerState: + HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]); + break; + default: + break; + } + } + + if (ctx->m_nRumbleExpiration) { + Uint32 now = SDL_GetTicks(); + if (SDL_TICKS_PASSED(now, ctx->m_nRumbleExpiration)) { + HIDAPI_DriverSwitch_Rumble(joystick, dev, context, 0, 0, 0); + } + } + + return (size >= 0); +} + +static void +HIDAPI_DriverSwitch_Quit(SDL_Joystick *joystick, hid_device *dev, void *context) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context; + + /* Restore simple input mode for other applications */ + SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState); + + SDL_free(context); +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch = +{ + SDL_HINT_JOYSTICK_HIDAPI_SWITCH, + SDL_TRUE, + HIDAPI_DriverSwitch_IsSupportedDevice, + HIDAPI_DriverSwitch_GetDeviceName, + HIDAPI_DriverSwitch_Init, + HIDAPI_DriverSwitch_Rumble, + HIDAPI_DriverSwitch_Update, + HIDAPI_DriverSwitch_Quit +}; + +#endif /* SDL_JOYSTICK_HIDAPI_SWITCH */ + +#endif /* SDL_JOYSTICK_HIDAPI */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_xbox360.c b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_xbox360.c new file mode 100644 index 0000000..84c63c6 --- /dev/null +++ b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_xbox360.c @@ -0,0 +1,787 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_hints.h" +#include "SDL_log.h" +#include "SDL_events.h" +#include "SDL_timer.h" +#include "SDL_joystick.h" +#include "SDL_gamecontroller.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + + +#ifdef SDL_JOYSTICK_HIDAPI_XBOX360 + +#ifdef __WIN32__ +#define SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT +/* This requires the Windows 10 SDK to build */ +/*#define SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT*/ +#endif + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT +#include "../../core/windows/SDL_xinput.h" +#endif + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT +#include "../../core/windows/SDL_windows.h" +#define COBJMACROS +#include "windows.gaming.input.h" +#endif + +#define USB_PACKET_LENGTH 64 + + +typedef struct { + Uint8 last_state[USB_PACKET_LENGTH]; + Uint32 rumble_expiration; +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + SDL_bool xinput_enabled; + Uint8 xinput_slot; +#endif +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + SDL_bool coinitialized; + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics; + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; +#endif +} SDL_DriverXbox360_Context; + + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT +static Uint8 xinput_slots; + +static void +HIDAPI_DriverXbox360_MarkXInputSlotUsed(Uint8 xinput_slot) +{ + if (xinput_slot != XUSER_INDEX_ANY) { + xinput_slots |= (0x01 << xinput_slot); + } +} + +static void +HIDAPI_DriverXbox360_MarkXInputSlotFree(Uint8 xinput_slot) +{ + if (xinput_slot != XUSER_INDEX_ANY) { + xinput_slots &= ~(0x01 << xinput_slot); + } +} + +static SDL_bool +HIDAPI_DriverXbox360_MissingXInputSlot() +{ + return xinput_slots != 0x0F; +} + +static Uint8 +HIDAPI_DriverXbox360_GuessXInputSlot(WORD wButtons) +{ + DWORD user_index; + int match_count; + Uint8 match_slot; + + if (!XINPUTGETSTATE) { + return XUSER_INDEX_ANY; + } + + match_count = 0; + for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) { + XINPUT_STATE_EX xinput_state; + + if (XINPUTGETSTATE(user_index, &xinput_state) == ERROR_SUCCESS) { + if (xinput_state.Gamepad.wButtons == wButtons) { + ++match_count; + match_slot = (Uint8)user_index; + } + } + } + if (match_count == 1) { + return match_slot; + } + return XUSER_INDEX_ANY; +} + +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT */ + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + +static void +HIDAPI_DriverXbox360_InitWindowsGamingInput(SDL_DriverXbox360_Context *ctx) +{ + /* I think this takes care of RoInitialize() in a way that is compatible with the rest of SDL */ + if (FAILED(WIN_CoInitialize())) { + return; + } + ctx->coinitialized = SDL_TRUE; + + { + static const IID SDL_IID_IGamepadStatics = { 0x8BBCE529, 0xD49C, 0x39E9, { 0x95, 0x60, 0xE4, 0x7D, 0xDE, 0x96, 0xB7, 0xC8 } }; + HRESULT hr; + HMODULE hModule = LoadLibraryA("combase.dll"); + if (hModule != NULL) { + typedef HRESULT (WINAPI *WindowsCreateString_t)(PCNZWCH sourceString, UINT32 length, HSTRING* string); + typedef HRESULT (WINAPI *WindowsDeleteString_t)(HSTRING string); + typedef HRESULT (WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void** factory); + + WindowsCreateString_t WindowsCreateStringFunc = (WindowsCreateString_t)GetProcAddress(hModule, "WindowsCreateString"); + WindowsDeleteString_t WindowsDeleteStringFunc = (WindowsDeleteString_t)GetProcAddress(hModule, "WindowsDeleteString"); + RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)GetProcAddress(hModule, "RoGetActivationFactory"); + if (WindowsCreateStringFunc && WindowsDeleteStringFunc && RoGetActivationFactoryFunc) { + LPTSTR pNamespace = L"Windows.Gaming.Input.Gamepad"; + HSTRING hNamespaceString; + + hr = WindowsCreateStringFunc(pNamespace, SDL_wcslen(pNamespace), &hNamespaceString); + if (SUCCEEDED(hr)) { + RoGetActivationFactoryFunc(hNamespaceString, &SDL_IID_IGamepadStatics, &ctx->gamepad_statics); + WindowsDeleteStringFunc(hNamespaceString); + } + } + FreeLibrary(hModule); + } + } +} + +static Uint8 +HIDAPI_DriverXbox360_GetGamepadButtonsForMatch(__x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad) +{ + HRESULT hr; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state; + Uint8 buttons = 0; + + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(gamepad, &state); + if (SUCCEEDED(hr)) { + if (state.Buttons & GamepadButtons_A) { + buttons |= (1 << SDL_CONTROLLER_BUTTON_A); + } + if (state.Buttons & GamepadButtons_B) { + buttons |= (1 << SDL_CONTROLLER_BUTTON_B); + } + if (state.Buttons & GamepadButtons_X) { + buttons |= (1 << SDL_CONTROLLER_BUTTON_X); + } + if (state.Buttons & GamepadButtons_Y) { + buttons |= (1 << SDL_CONTROLLER_BUTTON_Y); + } + } + return buttons; +} + +static void +HIDAPI_DriverXbox360_GuessGamepad(SDL_DriverXbox360_Context *ctx, Uint8 buttons) +{ + HRESULT hr; + __FIVectorView_1_Windows__CGaming__CInput__CGamepad *gamepads; + + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_get_Gamepads(ctx->gamepad_statics, &gamepads); + if (SUCCEEDED(hr)) { + unsigned int i, num_gamepads; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_get_Size(gamepads, &num_gamepads); + if (SUCCEEDED(hr)) { + int match_count; + unsigned int match_slot; + + match_count = 0; + for (i = 0; i < num_gamepads; ++i) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad; + + hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_GetAt(gamepads, i, &gamepad); + if (SUCCEEDED(hr)) { + Uint8 gamepad_buttons = HIDAPI_DriverXbox360_GetGamepadButtonsForMatch(gamepad); + if (buttons == gamepad_buttons) { + ++match_count; + match_slot = i; + } + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad); + } + } + if (match_count == 1) { + hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_GetAt(gamepads, match_slot, &ctx->gamepad); + if (SUCCEEDED(hr)) { + } + } + } + __FIVectorView_1_Windows__CGaming__CInput__CGamepad_Release(gamepads); + } +} + +static void +HIDAPI_DriverXbox360_QuitWindowsGamingInput(SDL_DriverXbox360_Context *ctx) +{ + if (ctx->gamepad_statics) { + __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(ctx->gamepad_statics); + ctx->gamepad_statics = NULL; + } + if (ctx->gamepad) { + __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(ctx->gamepad); + ctx->gamepad = NULL; + } + + if (ctx->coinitialized) { + WIN_CoUninitialize(); + ctx->coinitialized = SDL_FALSE; + } +} + +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT */ + +static SDL_bool +HIDAPI_DriverXbox360_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number) +{ +#if defined(__MACOSX__) || defined(__WIN32__) + if (vendor_id == 0x045e && product_id == 0x028e && version == 1) { + /* This is the Steam Virtual Gamepad, which isn't supported by this driver */ + return SDL_FALSE; + } + return SDL_IsJoystickXbox360(vendor_id, product_id) || SDL_IsJoystickXboxOne(vendor_id, product_id); +#else + return SDL_IsJoystickXbox360(vendor_id, product_id); +#endif +} + +static const char * +HIDAPI_DriverXbox360_GetDeviceName(Uint16 vendor_id, Uint16 product_id) +{ + return HIDAPI_XboxControllerName(vendor_id, product_id); +} + +static SDL_bool SetSlotLED(hid_device *dev, Uint8 slot) +{ + const Uint8 led_packet[] = { 0x01, 0x03, (2 + slot) }; + + if (hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) { + return SDL_FALSE; + } + return SDL_TRUE; +} + +static SDL_bool +HIDAPI_DriverXbox360_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context) +{ + SDL_DriverXbox360_Context *ctx; + + ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + SDL_OutOfMemory(); + return SDL_FALSE; + } +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + ctx->xinput_enabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, SDL_TRUE); + if (ctx->xinput_enabled && WIN_LoadXInputDLL() < 0) { + ctx->xinput_enabled = SDL_FALSE; + } + ctx->xinput_slot = XUSER_INDEX_ANY; +#endif +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + HIDAPI_DriverXbox360_InitWindowsGamingInput(ctx); +#endif + *context = ctx; + + /* Set the controller LED */ + SetSlotLED(dev, (joystick->instance_id % 4)); + + /* Initialize the joystick capabilities */ + joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX; + joystick->naxes = SDL_CONTROLLER_AXIS_MAX; + joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; + + return SDL_TRUE; +} + +static int +HIDAPI_DriverXbox360_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context; + +#ifdef __WIN32__ + SDL_bool rumbled = SDL_FALSE; + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + if (!rumbled && ctx->gamepad) { + HRESULT hr; + + ctx->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; + ctx->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(ctx->gamepad, ctx->vibration); + if (SUCCEEDED(hr)) { + rumbled = SDL_TRUE; + } + } +#endif + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + if (!rumbled && ctx->xinput_slot != XUSER_INDEX_ANY) { + XINPUT_VIBRATION XVibration; + + if (!XINPUTSETSTATE) { + return SDL_Unsupported(); + } + + XVibration.wLeftMotorSpeed = low_frequency_rumble; + XVibration.wRightMotorSpeed = high_frequency_rumble; + if (XINPUTSETSTATE(ctx->xinput_slot, &XVibration) == ERROR_SUCCESS) { + rumbled = SDL_TRUE; + } else { + return SDL_SetError("XInputSetState() failed"); + } + } +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT */ + +#else /* !__WIN32__ */ + +#ifdef __MACOSX__ + /* On Mac OS X the 360Controller driver uses this short report, + and we need to prefix it with a magic token so hidapi passes it through untouched + */ + Uint8 rumble_packet[] = { 'M', 'A', 'G', 'I', 'C', '0', 0x00, 0x04, 0x00, 0x00 }; + + rumble_packet[6+2] = (low_frequency_rumble >> 8); + rumble_packet[6+3] = (high_frequency_rumble >> 8); +#else + Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + rumble_packet[3] = (low_frequency_rumble >> 8); + rumble_packet[4] = (high_frequency_rumble >> 8); +#endif + + if (hid_write(dev, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } +#endif /* __WIN32__ */ + + if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) { + ctx->rumble_expiration = SDL_GetTicks() + duration_ms; + } else { + ctx->rumble_expiration = 0; + } + return 0; +} + +#ifdef __WIN32__ + /* This is the packet format for Xbox 360 and Xbox One controllers on Windows, + however with this interface there is no rumble support, no guide button, + and the left and right triggers are tied together as a single axis. + + We use XInput and Windows.Gaming.Input to make up for these shortcomings. + */ +static void +HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + SDL_bool has_trigger_data = SDL_FALSE; + + if (ctx->last_state[10] != data[10]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data[10] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data[10] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data[10] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data[10] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data[10] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data[10] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data[10] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data[10] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + } + + if (ctx->last_state[11] != data[11]) { + SDL_bool dpad_up = SDL_FALSE; + SDL_bool dpad_down = SDL_FALSE; + SDL_bool dpad_left = SDL_FALSE; + SDL_bool dpad_right = SDL_FALSE; + + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data[11] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data[11] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + + switch (data[11] & 0x3C) { + case 4: + dpad_up = SDL_TRUE; + break; + case 8: + dpad_up = SDL_TRUE; + dpad_right = SDL_TRUE; + break; + case 12: + dpad_right = SDL_TRUE; + break; + case 16: + dpad_right = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 20: + dpad_down = SDL_TRUE; + break; + case 24: + dpad_left = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 28: + dpad_left = SDL_TRUE; + break; + case 32: + dpad_up = SDL_TRUE; + dpad_left = SDL_TRUE; + break; + default: + break; + } + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, dpad_down); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, dpad_up); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, dpad_right); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, dpad_left); + } + + axis = (int)*(Uint16*)(&data[0]) - 0x8000; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + axis = (int)*(Uint16*)(&data[2]) - 0x8000; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis); + axis = (int)*(Uint16*)(&data[4]) - 0x8000; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + axis = (int)*(Uint16*)(&data[6]) - 0x8000; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + if (ctx->gamepad_statics && !ctx->gamepad) { + Uint8 buttons = 0; + + if (data[10] & 0x01) { + buttons |= (1 << SDL_CONTROLLER_BUTTON_A); + } + if (data[10] & 0x02) { + buttons |= (1 << SDL_CONTROLLER_BUTTON_B); + } + if (data[10] & 0x04) { + buttons |= (1 << SDL_CONTROLLER_BUTTON_X); + } + if (data[10] & 0x08) { + buttons |= (1 << SDL_CONTROLLER_BUTTON_Y); + } + if (buttons != 0) { + HIDAPI_DriverXbox360_GuessGamepad(ctx, buttons); + } + } + + if (ctx->gamepad) { + HRESULT hr; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state; + + hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(ctx->gamepad, &state); + if (SUCCEEDED(hr)) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (state.Buttons & 0x40000000) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, ((int)(state.LeftTrigger * SDL_MAX_UINT16)) - 32768); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, ((int)(state.RightTrigger * SDL_MAX_UINT16)) - 32768); + has_trigger_data = SDL_TRUE; + } + } +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT */ + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + if (ctx->xinput_enabled) { + if (ctx->xinput_slot == XUSER_INDEX_ANY && HIDAPI_DriverXbox360_MissingXInputSlot()) { + WORD wButtons = 0; + + if (data[10] & 0x01) { + wButtons |= XINPUT_GAMEPAD_A; + } + if (data[10] & 0x02) { + wButtons |= XINPUT_GAMEPAD_B; + } + if (data[10] & 0x04) { + wButtons |= XINPUT_GAMEPAD_X; + } + if (data[10] & 0x08) { + wButtons |= XINPUT_GAMEPAD_Y; + } + if (wButtons != 0) { + Uint8 xinput_slot = HIDAPI_DriverXbox360_GuessXInputSlot(wButtons); + if (xinput_slot != XUSER_INDEX_ANY) { + HIDAPI_DriverXbox360_MarkXInputSlotUsed(xinput_slot); + ctx->xinput_slot = xinput_slot; + } + } + } + + if (!has_trigger_data && ctx->xinput_slot != XUSER_INDEX_ANY) { + XINPUT_STATE_EX xinput_state; + + if (XINPUTGETSTATE(ctx->xinput_slot, &xinput_state) == ERROR_SUCCESS) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, ((int)xinput_state.Gamepad.bLeftTrigger * 257) - 32768); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, ((int)xinput_state.Gamepad.bRightTrigger * 257) - 32768); + has_trigger_data = SDL_TRUE; + } + } + } +#endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT */ + + if (!has_trigger_data) { + axis = (data[9] * 257) - 32768; + if (data[9] < 0x80) { + axis = -axis * 2 - 32769; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + } else if (data[9] > 0x80) { + axis = axis * 2 - 32767; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + } else { + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_MIN_SINT16); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, SDL_MIN_SINT16); + } + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} +#else + +static void +HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; +#ifdef __MACOSX__ + const SDL_bool invert_y_axes = SDL_FALSE; +#else + const SDL_bool invert_y_axes = SDL_TRUE; +#endif + + if (ctx->last_state[2] != data[2]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + } + + if (ctx->last_state[3] != data[3]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + } + + axis = ((int)data[4] * 257) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + axis = ((int)data[5] * 257) - 32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + axis = *(Sint16*)(&data[6]); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + axis = *(Sint16*)(&data[8]); + if (invert_y_axes) { + axis = ~axis; + } + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis); + axis = *(Sint16*)(&data[10]); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + axis = *(Sint16*)(&data[12]); + if (invert_y_axes) { + axis = ~axis; + } + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} +#endif /* __WIN32__ */ + +#ifdef __MACOSX__ +static void +HIDAPI_DriverXboxOneS_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + + if (ctx->last_state[14] != data[14]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data[14] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data[14] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data[14] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data[14] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data[14] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data[14] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + } + + if (ctx->last_state[15] != data[15]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data[15] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data[15] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data[15] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + } + + if (ctx->last_state[16] != data[16]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data[16] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + } + + if (ctx->last_state[13] != data[13]) { + SDL_bool dpad_up = SDL_FALSE; + SDL_bool dpad_down = SDL_FALSE; + SDL_bool dpad_left = SDL_FALSE; + SDL_bool dpad_right = SDL_FALSE; + + switch (data[13]) { + case 1: + dpad_up = SDL_TRUE; + break; + case 2: + dpad_up = SDL_TRUE; + dpad_right = SDL_TRUE; + break; + case 3: + dpad_right = SDL_TRUE; + break; + case 4: + dpad_right = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 5: + dpad_down = SDL_TRUE; + break; + case 6: + dpad_left = SDL_TRUE; + dpad_down = SDL_TRUE; + break; + case 7: + dpad_left = SDL_TRUE; + break; + case 8: + dpad_up = SDL_TRUE; + dpad_left = SDL_TRUE; + break; + default: + break; + } + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, dpad_down); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, dpad_up); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, dpad_right); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, dpad_left); + } + + axis = (int)*(Uint16*)(&data[1]) - 0x8000; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + axis = (int)*(Uint16*)(&data[3]) - 0x8000; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis); + axis = (int)*(Uint16*)(&data[5]) - 0x8000; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + axis = (int)*(Uint16*)(&data[7]) - 0x8000; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); + + axis = ((int)*(Sint16*)(&data[9]) * 64) - 32768; + if (axis == 32704) { + axis = 32767; + } + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + + axis = ((int)*(Sint16*)(&data[11]) * 64) - 32768; + if (axis == 32704) { + axis = 32767; + } + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void +HIDAPI_DriverXboxOneS_HandleGuidePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size) +{ + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data[1] & 0x01) ? SDL_PRESSED : SDL_RELEASED); +} +#endif /* __MACOSX__ */ + +static SDL_bool +HIDAPI_DriverXbox360_Update(SDL_Joystick *joystick, hid_device *dev, void *context) +{ + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context; + Uint8 data[USB_PACKET_LENGTH]; + int size; + + while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) { +#ifdef __WIN32__ + HIDAPI_DriverXbox360_HandleStatePacket(joystick, dev, ctx, data, size); +#else + switch (data[0]) { + case 0x00: + HIDAPI_DriverXbox360_HandleStatePacket(joystick, dev, ctx, data, size); + break; +#ifdef __MACOSX__ + case 0x01: + HIDAPI_DriverXboxOneS_HandleStatePacket(joystick, dev, ctx, data, size); + break; + case 0x02: + HIDAPI_DriverXboxOneS_HandleGuidePacket(joystick, dev, ctx, data, size); + break; +#endif + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown Xbox 360 packet, size = %d\n", size); + SDL_Log("%.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x\n", + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16]); +#endif + break; + } +#endif /* __WIN32__ */ + } + + if (ctx->rumble_expiration) { + Uint32 now = SDL_GetTicks(); + if (SDL_TICKS_PASSED(now, ctx->rumble_expiration)) { + HIDAPI_DriverXbox360_Rumble(joystick, dev, context, 0, 0, 0); + } + } + + return (size >= 0); +} + +static void +HIDAPI_DriverXbox360_Quit(SDL_Joystick *joystick, hid_device *dev, void *context) +{ +#if defined(SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT) || defined(SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT) + SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context; +#endif + +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT + if (ctx->xinput_enabled) { + HIDAPI_DriverXbox360_MarkXInputSlotFree(ctx->xinput_slot); + WIN_UnloadXInputDLL(); + } +#endif +#ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT + HIDAPI_DriverXbox360_InitWindowsGamingInput(ctx); +#endif + SDL_free(context); +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = +{ + SDL_HINT_JOYSTICK_HIDAPI_XBOX, + SDL_TRUE, + HIDAPI_DriverXbox360_IsSupportedDevice, + HIDAPI_DriverXbox360_GetDeviceName, + HIDAPI_DriverXbox360_Init, + HIDAPI_DriverXbox360_Rumble, + HIDAPI_DriverXbox360_Update, + HIDAPI_DriverXbox360_Quit +}; + +#endif /* SDL_JOYSTICK_HIDAPI_XBOX360 */ + +#endif /* SDL_JOYSTICK_HIDAPI */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_xboxone.c b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_xboxone.c new file mode 100644 index 0000000..2cd593f --- /dev/null +++ b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -0,0 +1,324 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_hints.h" +#include "SDL_log.h" +#include "SDL_events.h" +#include "SDL_timer.h" +#include "SDL_joystick.h" +#include "SDL_gamecontroller.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + + +#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE + +#define USB_PACKET_LENGTH 64 + +/* + * This packet is required for all Xbox One pads with 2015 + * or later firmware installed (or present from the factory). + */ +static const Uint8 xboxone_fw2015_init[] = { + 0x05, 0x20, 0x00, 0x01, 0x00 +}; + +/* + * This packet is required for the Titanfall 2 Xbox One pads + * (0x0e6f:0x0165) to finish initialization and for Hori pads + * (0x0f0d:0x0067) to make the analog sticks work. + */ +static const Uint8 xboxone_hori_init[] = { + 0x01, 0x20, 0x00, 0x09, 0x00, 0x04, 0x20, 0x3a, + 0x00, 0x00, 0x00, 0x80, 0x00 +}; + +/* + * This packet is required for some of the PDP pads to start + * sending input reports. These pads include: (0x0e6f:0x02ab), + * (0x0e6f:0x02a4). + */ +static const Uint8 xboxone_pdp_init1[] = { + 0x0a, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14 +}; + +/* + * This packet is required for some of the PDP pads to start + * sending input reports. These pads include: (0x0e6f:0x02ab), + * (0x0e6f:0x02a4). + */ +static const Uint8 xboxone_pdp_init2[] = { + 0x06, 0x20, 0x00, 0x02, 0x01, 0x00 +}; + +/* + * A specific rumble packet is required for some PowerA pads to start + * sending input reports. One of those pads is (0x24c6:0x543a). + */ +static const Uint8 xboxone_rumblebegin_init[] = { + 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, + 0x1D, 0x1D, 0xFF, 0x00, 0x00 +}; + +/* + * A rumble packet with zero FF intensity will immediately + * terminate the rumbling required to init PowerA pads. + * This should happen fast enough that the motors don't + * spin up to enough speed to actually vibrate the gamepad. + */ +static const Uint8 xboxone_rumbleend_init[] = { + 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * This specifies the selection of init packets that a gamepad + * will be sent on init *and* the order in which they will be + * sent. The correct sequence number will be added when the + * packet is going to be sent. + */ +typedef struct { + Uint16 vendor_id; + Uint16 product_id; + const Uint8 *data; + int size; +} SDL_DriverXboxOne_InitPacket; + +static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = { + { 0x0e6f, 0x0165, xboxone_hori_init, sizeof(xboxone_hori_init) }, + { 0x0f0d, 0x0067, xboxone_hori_init, sizeof(xboxone_hori_init) }, + { 0x0000, 0x0000, xboxone_fw2015_init, sizeof(xboxone_fw2015_init) }, + { 0x0e6f, 0x0246, xboxone_pdp_init1, sizeof(xboxone_pdp_init1) }, + { 0x0e6f, 0x0246, xboxone_pdp_init2, sizeof(xboxone_pdp_init2) }, + { 0x0e6f, 0x02ab, xboxone_pdp_init1, sizeof(xboxone_pdp_init1) }, + { 0x0e6f, 0x02ab, xboxone_pdp_init2, sizeof(xboxone_pdp_init2) }, + { 0x0e6f, 0x02a4, xboxone_pdp_init1, sizeof(xboxone_pdp_init1) }, + { 0x0e6f, 0x02a4, xboxone_pdp_init2, sizeof(xboxone_pdp_init2) }, + { 0x24c6, 0x541a, xboxone_rumblebegin_init, sizeof(xboxone_rumblebegin_init) }, + { 0x24c6, 0x542a, xboxone_rumblebegin_init, sizeof(xboxone_rumblebegin_init) }, + { 0x24c6, 0x543a, xboxone_rumblebegin_init, sizeof(xboxone_rumblebegin_init) }, + { 0x24c6, 0x541a, xboxone_rumbleend_init, sizeof(xboxone_rumbleend_init) }, + { 0x24c6, 0x542a, xboxone_rumbleend_init, sizeof(xboxone_rumbleend_init) }, + { 0x24c6, 0x543a, xboxone_rumbleend_init, sizeof(xboxone_rumbleend_init) }, +}; + +typedef struct { + Uint8 sequence; + Uint8 last_state[USB_PACKET_LENGTH]; + Uint32 rumble_expiration; +} SDL_DriverXboxOne_Context; + + +static SDL_bool +HIDAPI_DriverXboxOne_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number) +{ + return SDL_IsJoystickXboxOne(vendor_id, product_id); +} + +static const char * +HIDAPI_DriverXboxOne_GetDeviceName(Uint16 vendor_id, Uint16 product_id) +{ + return HIDAPI_XboxControllerName(vendor_id, product_id); +} + +static SDL_bool +HIDAPI_DriverXboxOne_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context) +{ + SDL_DriverXboxOne_Context *ctx; + int i; + Uint8 init_packet[USB_PACKET_LENGTH]; + + ctx = (SDL_DriverXboxOne_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + SDL_OutOfMemory(); + return SDL_FALSE; + } + *context = ctx; + + /* Send the controller init data */ + for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) { + const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[i]; + if (!packet->vendor_id || (vendor_id == packet->vendor_id && product_id == packet->product_id)) { + SDL_memcpy(init_packet, packet->data, packet->size); + init_packet[2] = ctx->sequence++; + if (hid_write(dev, init_packet, packet->size) != packet->size) { + SDL_SetError("Couldn't write Xbox One initialization packet"); + SDL_free(ctx); + return SDL_FALSE; + } + } + } + + /* Initialize the joystick capabilities */ + joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX; + joystick->naxes = SDL_CONTROLLER_AXIS_MAX; + joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; + + return SDL_TRUE; +} + +static int +HIDAPI_DriverXboxOne_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context; + Uint8 rumble_packet[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF }; + + /* The Rock Candy Xbox One Controller limits the range of + low frequency rumble strength in the range of [0 - 0x99] + high frequency rumble strength in the range of [0 - 0x82] + + I think the valid range of rumble at the firmware level is [0 - 0x7F] + */ + rumble_packet[2] = ctx->sequence++; + rumble_packet[8] = (low_frequency_rumble >> 9); + rumble_packet[9] = (high_frequency_rumble >> 9); + + if (hid_write(dev, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + + if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) { + ctx->rumble_expiration = SDL_GetTicks() + duration_ms; + } else { + ctx->rumble_expiration = 0; + } + return 0; +} + +static void +HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + + if (ctx->last_state[4] != data[4]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data[4] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data[4] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data[4] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data[4] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data[4] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data[4] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + } + + if (ctx->last_state[5] != data[5]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, (data[5] & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, (data[5] & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, (data[5] & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, (data[5] & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data[5] & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data[5] & 0x20) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data[5] & 0x40) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data[5] & 0x80) ? SDL_PRESSED : SDL_RELEASED); + } + + axis = ((int)*(Sint16*)(&data[6]) * 64) - 32768; + if (axis == 32704) { + axis = 32767; + } + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + axis = ((int)*(Sint16*)(&data[8]) * 64) - 32768; + if (axis == 32704) { + axis = 32767; + } + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + axis = *(Sint16*)(&data[10]); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + axis = *(Sint16*)(&data[12]); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, ~axis); + axis = *(Sint16*)(&data[14]); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + axis = *(Sint16*)(&data[16]); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, ~axis); + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void +HIDAPI_DriverXboxOne_HandleModePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size) +{ + if (data[1] == 0x30) { + /* The Xbox One S controller needs acks for mode reports */ + const Uint8 seqnum = data[2]; + const Uint8 ack[] = { 0x01, 0x20, seqnum, 0x09, 0x00, 0x07, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 }; + hid_write(dev, ack, sizeof(ack)); + } + + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data[4] & 0x01) ? SDL_PRESSED : SDL_RELEASED); +} + +static SDL_bool +HIDAPI_DriverXboxOne_Update(SDL_Joystick *joystick, hid_device *dev, void *context) +{ + SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context; + Uint8 data[USB_PACKET_LENGTH]; + int size; + + while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) { + switch (data[0]) { + case 0x20: + HIDAPI_DriverXboxOne_HandleStatePacket(joystick, dev, ctx, data, size); + break; + case 0x07: + HIDAPI_DriverXboxOne_HandleModePacket(joystick, dev, ctx, data, size); + break; + default: +#ifdef DEBUG_JOYSTICK + SDL_Log("Unknown Xbox One packet: 0x%.2x\n", data[0]); +#endif + break; + } + } + + if (ctx->rumble_expiration) { + Uint32 now = SDL_GetTicks(); + if (SDL_TICKS_PASSED(now, ctx->rumble_expiration)) { + HIDAPI_DriverXboxOne_Rumble(joystick, dev, context, 0, 0, 0); + } + } + + return (size >= 0); +} + +static void +HIDAPI_DriverXboxOne_Quit(SDL_Joystick *joystick, hid_device *dev, void *context) +{ + SDL_free(context); +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne = +{ + SDL_HINT_JOYSTICK_HIDAPI_XBOX, + SDL_TRUE, + HIDAPI_DriverXboxOne_IsSupportedDevice, + HIDAPI_DriverXboxOne_GetDeviceName, + HIDAPI_DriverXboxOne_Init, + HIDAPI_DriverXboxOne_Rumble, + HIDAPI_DriverXboxOne_Update, + HIDAPI_DriverXboxOne_Quit +}; + +#endif /* SDL_JOYSTICK_HIDAPI_XBOXONE */ + +#endif /* SDL_JOYSTICK_HIDAPI */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapijoystick.c b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapijoystick.c new file mode 100644 index 0000000..064cb82 --- /dev/null +++ b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapijoystick.c @@ -0,0 +1,1071 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_endian.h" +#include "SDL_hints.h" +#include "SDL_log.h" +#include "SDL_mutex.h" +#include "SDL_thread.h" +#include "SDL_timer.h" +#include "SDL_joystick.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" + +#if defined(__WIN32__) +#include "../../core/windows/SDL_windows.h" +#endif + +#if defined(__MACOSX__) +#include <CoreFoundation/CoreFoundation.h> +#include <mach/mach.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/usb/USBSpec.h> +#endif + +#if defined(__LINUX__) +#include "../../core/linux/SDL_udev.h" +#ifdef SDL_USE_LIBUDEV +#include <poll.h> +#endif +#endif + +struct joystick_hwdata +{ + SDL_HIDAPI_DeviceDriver *driver; + void *context; + + SDL_mutex *mutex; + hid_device *dev; +}; + +typedef struct _SDL_HIDAPI_Device +{ + SDL_JoystickID instance_id; + char *name; + char *path; + Uint16 vendor_id; + Uint16 product_id; + Uint16 version; + SDL_JoystickGUID guid; + int interface_number; /* Available on Windows and Linux */ + Uint16 usage_page; /* Available on Windows and Mac OS X */ + Uint16 usage; /* Available on Windows and Mac OS X */ + SDL_HIDAPI_DeviceDriver *driver; + + /* Used during scanning for device changes */ + SDL_bool seen; + + struct _SDL_HIDAPI_Device *next; +} SDL_HIDAPI_Device; + +static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { +#ifdef SDL_JOYSTICK_HIDAPI_PS4 + &SDL_HIDAPI_DriverPS4, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_STEAM + &SDL_HIDAPI_DriverSteam, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_SWITCH + &SDL_HIDAPI_DriverSwitch, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_XBOX360 + &SDL_HIDAPI_DriverXbox360, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE + &SDL_HIDAPI_DriverXboxOne, +#endif +}; +static SDL_HIDAPI_Device *SDL_HIDAPI_devices; +static int SDL_HIDAPI_numjoysticks = 0; + +#if defined(SDL_USE_LIBUDEV) +static const SDL_UDEV_Symbols * usyms = NULL; +#endif + +static struct +{ + SDL_bool m_bHaveDevicesChanged; + SDL_bool m_bCanGetNotifications; + Uint32 m_unLastDetect; + +#if defined(__WIN32__) + SDL_threadID m_nThreadID; + WNDCLASSEXA m_wndClass; + HWND m_hwndMsg; + HDEVNOTIFY m_hNotify; + double m_flLastWin32MessageCheck; +#endif + +#if defined(__MACOSX__) + IONotificationPortRef m_notificationPort; + mach_port_t m_notificationMach; +#endif + +#if defined(SDL_USE_LIBUDEV) + struct udev *m_pUdev; + struct udev_monitor *m_pUdevMonitor; + int m_nUdevFd; +#endif +} SDL_HIDAPI_discovery; + + +#ifdef __WIN32__ +struct _DEV_BROADCAST_HDR +{ + DWORD dbch_size; + DWORD dbch_devicetype; + DWORD dbch_reserved; +}; + +typedef struct _DEV_BROADCAST_DEVICEINTERFACE_A +{ + DWORD dbcc_size; + DWORD dbcc_devicetype; + DWORD dbcc_reserved; + GUID dbcc_classguid; + char dbcc_name[ 1 ]; +} DEV_BROADCAST_DEVICEINTERFACE_A, *PDEV_BROADCAST_DEVICEINTERFACE_A; + +typedef struct _DEV_BROADCAST_HDR DEV_BROADCAST_HDR; +#define DBT_DEVICEARRIVAL 0x8000 /* system detected a new device */ +#define DBT_DEVICEREMOVECOMPLETE 0x8004 /* device was removed from the system */ +#define DBT_DEVTYP_DEVICEINTERFACE 0x00000005 /* device interface class */ +#define DBT_DEVNODES_CHANGED 0x0007 +#define DBT_CONFIGCHANGED 0x0018 +#define DBT_DEVICETYPESPECIFIC 0x8005 /* type specific event */ +#define DBT_DEVINSTSTARTED 0x8008 /* device installed and started */ + +#include <initguid.h> +DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED); + +static LRESULT CALLBACK ControllerWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_DEVICECHANGE: + switch (wParam) { + case DBT_DEVICEARRIVAL: + case DBT_DEVICEREMOVECOMPLETE: + if (((DEV_BROADCAST_HDR*)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + SDL_HIDAPI_discovery.m_bHaveDevicesChanged = SDL_TRUE; + } + break; + } + return TRUE; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} +#endif /* __WIN32__ */ + + +#if defined(__MACOSX__) +static void CallbackIOServiceFunc(void *context, io_iterator_t portIterator) +{ + /* Must drain the iterator, or we won't receive new notifications */ + io_object_t entry; + while ((entry = IOIteratorNext(portIterator)) != 0) { + IOObjectRelease(entry); + *(SDL_bool*)context = SDL_TRUE; + } +} +#endif /* __MACOSX__ */ + +static void +HIDAPI_InitializeDiscovery() +{ + SDL_HIDAPI_discovery.m_bHaveDevicesChanged = SDL_TRUE; + SDL_HIDAPI_discovery.m_bCanGetNotifications = SDL_FALSE; + SDL_HIDAPI_discovery.m_unLastDetect = 0; + +#if defined(__WIN32__) + SDL_HIDAPI_discovery.m_nThreadID = SDL_ThreadID(); + + SDL_memset(&SDL_HIDAPI_discovery.m_wndClass, 0x0, sizeof(SDL_HIDAPI_discovery.m_wndClass)); + SDL_HIDAPI_discovery.m_wndClass.hInstance = GetModuleHandle(NULL); + SDL_HIDAPI_discovery.m_wndClass.lpszClassName = "SDL_HIDAPI_DEVICE_DETECTION"; + SDL_HIDAPI_discovery.m_wndClass.lpfnWndProc = ControllerWndProc; /* This function is called by windows */ + SDL_HIDAPI_discovery.m_wndClass.cbSize = sizeof(WNDCLASSEX); + + RegisterClassExA(&SDL_HIDAPI_discovery.m_wndClass); + SDL_HIDAPI_discovery.m_hwndMsg = CreateWindowExA(0, "SDL_HIDAPI_DEVICE_DETECTION", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); + + { + DEV_BROADCAST_DEVICEINTERFACE_A devBroadcast; + SDL_memset( &devBroadcast, 0x0, sizeof( devBroadcast ) ); + + devBroadcast.dbcc_size = sizeof( devBroadcast ); + devBroadcast.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + devBroadcast.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE; + + /* DEVICE_NOTIFY_ALL_INTERFACE_CLASSES is important, makes GUID_DEVINTERFACE_USB_DEVICE ignored, + * but that seems to be necessary to get a notice after each individual usb input device actually + * installs, rather than just as the composite device is seen. + */ + SDL_HIDAPI_discovery.m_hNotify = RegisterDeviceNotification( SDL_HIDAPI_discovery.m_hwndMsg, &devBroadcast, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES ); + SDL_HIDAPI_discovery.m_bCanGetNotifications = ( SDL_HIDAPI_discovery.m_hNotify != 0 ); + } +#endif /* __WIN32__ */ + +#if defined(__MACOSX__) + SDL_HIDAPI_discovery.m_notificationPort = IONotificationPortCreate(kIOMasterPortDefault); + if (SDL_HIDAPI_discovery.m_notificationPort) { + { + CFMutableDictionaryRef matchingDict = IOServiceMatching("IOUSBDevice"); + + /* Note: IOServiceAddMatchingNotification consumes the reference to matchingDict */ + io_iterator_t portIterator = 0; + io_object_t entry; + if (IOServiceAddMatchingNotification(SDL_HIDAPI_discovery.m_notificationPort, kIOMatchedNotification, matchingDict, CallbackIOServiceFunc, &SDL_HIDAPI_discovery.m_bHaveDevicesChanged, &portIterator) == 0) { + /* Must drain the existing iterator, or we won't receive new notifications */ + while ((entry = IOIteratorNext(portIterator)) != 0) { + IOObjectRelease(entry); + } + } else { + IONotificationPortDestroy(SDL_HIDAPI_discovery.m_notificationPort); + SDL_HIDAPI_discovery.m_notificationPort = nil; + } + } + { + CFMutableDictionaryRef matchingDict = IOServiceMatching("IOBluetoothDevice"); + + /* Note: IOServiceAddMatchingNotification consumes the reference to matchingDict */ + io_iterator_t portIterator = 0; + io_object_t entry; + if (IOServiceAddMatchingNotification(SDL_HIDAPI_discovery.m_notificationPort, kIOMatchedNotification, matchingDict, CallbackIOServiceFunc, &SDL_HIDAPI_discovery.m_bHaveDevicesChanged, &portIterator) == 0) { + /* Must drain the existing iterator, or we won't receive new notifications */ + while ((entry = IOIteratorNext(portIterator)) != 0) { + IOObjectRelease(entry); + } + } else { + IONotificationPortDestroy(SDL_HIDAPI_discovery.m_notificationPort); + SDL_HIDAPI_discovery.m_notificationPort = nil; + } + } + } + + SDL_HIDAPI_discovery.m_notificationMach = MACH_PORT_NULL; + if (SDL_HIDAPI_discovery.m_notificationPort) { + SDL_HIDAPI_discovery.m_notificationMach = IONotificationPortGetMachPort(SDL_HIDAPI_discovery.m_notificationPort); + } + + SDL_HIDAPI_discovery.m_bCanGetNotifications = (SDL_HIDAPI_discovery.m_notificationMach != MACH_PORT_NULL); + +#endif // __MACOSX__ + +#if defined(SDL_USE_LIBUDEV) + SDL_HIDAPI_discovery.m_pUdev = NULL; + SDL_HIDAPI_discovery.m_pUdevMonitor = NULL; + SDL_HIDAPI_discovery.m_nUdevFd = -1; + + usyms = SDL_UDEV_GetUdevSyms(); + if (usyms) { + SDL_HIDAPI_discovery.m_pUdev = usyms->udev_new(); + } + if (SDL_HIDAPI_discovery.m_pUdev) { + SDL_HIDAPI_discovery.m_pUdevMonitor = usyms->udev_monitor_new_from_netlink(SDL_HIDAPI_discovery.m_pUdev, "udev"); + if (SDL_HIDAPI_discovery.m_pUdevMonitor) { + usyms->udev_monitor_enable_receiving(SDL_HIDAPI_discovery.m_pUdevMonitor); + SDL_HIDAPI_discovery.m_nUdevFd = usyms->udev_monitor_get_fd(SDL_HIDAPI_discovery.m_pUdevMonitor); + SDL_HIDAPI_discovery.m_bCanGetNotifications = SDL_TRUE; + } + } + +#endif /* SDL_USE_LIBUDEV */ +} + +static void +HIDAPI_UpdateDiscovery() +{ + if (!SDL_HIDAPI_discovery.m_bCanGetNotifications) { + const Uint32 SDL_HIDAPI_DETECT_INTERVAL_MS = 3000; /* Update every 3 seconds */ + Uint32 now = SDL_GetTicks(); + if (!SDL_HIDAPI_discovery.m_unLastDetect || SDL_TICKS_PASSED(now, SDL_HIDAPI_discovery.m_unLastDetect + SDL_HIDAPI_DETECT_INTERVAL_MS)) { + SDL_HIDAPI_discovery.m_bHaveDevicesChanged = SDL_TRUE; + SDL_HIDAPI_discovery.m_unLastDetect = now; + } + return; + } + +#if defined(__WIN32__) +#if 0 /* just let the usual SDL_PumpEvents loop dispatch these, fixing bug 4286. --ryan. */ + /* We'll only get messages on the same thread that created the window */ + if (SDL_ThreadID() == SDL_HIDAPI_discovery.m_nThreadID) { + MSG msg; + while (PeekMessage(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0, PM_NOREMOVE)) { + if (GetMessageA(&msg, SDL_HIDAPI_discovery.m_hwndMsg, 0, 0) != 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } +#endif +#endif /* __WIN32__ */ + +#if defined(__MACOSX__) + if (SDL_HIDAPI_discovery.m_notificationPort) { + struct { mach_msg_header_t hdr; char payload[ 4096 ]; } msg; + while (mach_msg(&msg.hdr, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(msg), SDL_HIDAPI_discovery.m_notificationMach, 0, MACH_PORT_NULL) == KERN_SUCCESS) { + IODispatchCalloutFromMessage(NULL, &msg.hdr, SDL_HIDAPI_discovery.m_notificationPort); + } + } +#endif + +#if defined(SDL_USE_LIBUDEV) + if (SDL_HIDAPI_discovery.m_nUdevFd >= 0) { + /* Drain all notification events. + * We don't expect a lot of device notifications so just + * do a new discovery on any kind or number of notifications. + * This could be made more restrictive if necessary. + */ + for (;;) { + struct pollfd PollUdev; + struct udev_device *pUdevDevice; + + PollUdev.fd = SDL_HIDAPI_discovery.m_nUdevFd; + PollUdev.events = POLLIN; + if (poll(&PollUdev, 1, 0) != 1) { + break; + } + + SDL_HIDAPI_discovery.m_bHaveDevicesChanged = SDL_TRUE; + + pUdevDevice = usyms->udev_monitor_receive_device(SDL_HIDAPI_discovery.m_pUdevMonitor); + if (pUdevDevice) { + usyms->udev_device_unref(pUdevDevice); + } + } + } +#endif +} + +static void +HIDAPI_ShutdownDiscovery() +{ +#if defined(__WIN32__) + if (SDL_HIDAPI_discovery.m_hNotify) + UnregisterDeviceNotification(SDL_HIDAPI_discovery.m_hNotify); + + if (SDL_HIDAPI_discovery.m_hwndMsg) { + DestroyWindow(SDL_HIDAPI_discovery.m_hwndMsg); + } + + UnregisterClassA(SDL_HIDAPI_discovery.m_wndClass.lpszClassName, SDL_HIDAPI_discovery.m_wndClass.hInstance); +#endif + +#if defined(__MACOSX__) + if (SDL_HIDAPI_discovery.m_notificationPort) { + IONotificationPortDestroy(SDL_HIDAPI_discovery.m_notificationPort); + } +#endif + +#if defined(SDL_USE_LIBUDEV) + if (usyms) { + if (SDL_HIDAPI_discovery.m_pUdevMonitor) { + usyms->udev_monitor_unref(SDL_HIDAPI_discovery.m_pUdevMonitor); + } + if (SDL_HIDAPI_discovery.m_pUdev) { + usyms->udev_unref(SDL_HIDAPI_discovery.m_pUdev); + } + SDL_UDEV_ReleaseUdevSyms(); + usyms = NULL; + } +#endif +} + + +const char * +HIDAPI_XboxControllerName(Uint16 vendor_id, Uint16 product_id) +{ + static struct + { + Uint32 vidpid; + const char *name; + } names[] = { + { MAKE_VIDPID(0x0079, 0x18d4), "GPD Win 2 X-Box Controller" }, + { MAKE_VIDPID(0x044f, 0xb326), "Thrustmaster Gamepad GP XID" }, + { MAKE_VIDPID(0x045e, 0x028e), "Microsoft X-Box 360 pad" }, + { MAKE_VIDPID(0x045e, 0x028f), "Microsoft X-Box 360 pad v2" }, + { MAKE_VIDPID(0x045e, 0x0291), "Xbox 360 Wireless Receiver (XBOX)" }, + { MAKE_VIDPID(0x045e, 0x02d1), "Microsoft X-Box One pad" }, + { MAKE_VIDPID(0x045e, 0x02dd), "Microsoft X-Box One pad (Firmware 2015)" }, + { MAKE_VIDPID(0x045e, 0x02e3), "Microsoft X-Box One Elite pad" }, + { MAKE_VIDPID(0x045e, 0x02ea), "Microsoft X-Box One S pad" }, + { MAKE_VIDPID(0x045e, 0x02ff), "Microsoft X-Box One pad" }, + { MAKE_VIDPID(0x045e, 0x0719), "Xbox 360 Wireless Receiver" }, + { MAKE_VIDPID(0x046d, 0xc21d), "Logitech Gamepad F310" }, + { MAKE_VIDPID(0x046d, 0xc21e), "Logitech Gamepad F510" }, + { MAKE_VIDPID(0x046d, 0xc21f), "Logitech Gamepad F710" }, + { MAKE_VIDPID(0x046d, 0xc242), "Logitech Chillstream Controller" }, + { MAKE_VIDPID(0x046d, 0xcaa3), "Logitech DriveFx Racing Wheel" }, + { MAKE_VIDPID(0x056e, 0x2004), "Elecom JC-U3613M" }, + { MAKE_VIDPID(0x06a3, 0xf51a), "Saitek P3600" }, + { MAKE_VIDPID(0x0738, 0x4716), "Mad Catz Wired Xbox 360 Controller" }, + { MAKE_VIDPID(0x0738, 0x4718), "Mad Catz Street Fighter IV FightStick SE" }, + { MAKE_VIDPID(0x0738, 0x4726), "Mad Catz Xbox 360 Controller" }, + { MAKE_VIDPID(0x0738, 0x4728), "Mad Catz Street Fighter IV FightPad" }, + { MAKE_VIDPID(0x0738, 0x4736), "Mad Catz MicroCon Gamepad" }, + { MAKE_VIDPID(0x0738, 0x4738), "Mad Catz Wired Xbox 360 Controller (SFIV)" }, + { MAKE_VIDPID(0x0738, 0x4740), "Mad Catz Beat Pad" }, + { MAKE_VIDPID(0x0738, 0x4758), "Mad Catz Arcade Game Stick" }, + { MAKE_VIDPID(0x0738, 0x4a01), "Mad Catz FightStick TE 2" }, + { MAKE_VIDPID(0x0738, 0x9871), "Mad Catz Portable Drum" }, + { MAKE_VIDPID(0x0738, 0xb726), "Mad Catz Xbox controller - MW2" }, + { MAKE_VIDPID(0x0738, 0xb738), "Mad Catz MVC2TE Stick 2" }, + { MAKE_VIDPID(0x0738, 0xbeef), "Mad Catz JOYTECH NEO SE Advanced GamePad" }, + { MAKE_VIDPID(0x0738, 0xcb02), "Saitek Cyborg Rumble Pad - PC/Xbox 360" }, + { MAKE_VIDPID(0x0738, 0xcb03), "Saitek P3200 Rumble Pad - PC/Xbox 360" }, + { MAKE_VIDPID(0x0738, 0xcb29), "Saitek Aviator Stick AV8R02" }, + { MAKE_VIDPID(0x0738, 0xf738), "Super SFIV FightStick TE S" }, + { MAKE_VIDPID(0x07ff, 0xffff), "Mad Catz GamePad" }, + { MAKE_VIDPID(0x0e6f, 0x0105), "HSM3 Xbox360 dancepad" }, + { MAKE_VIDPID(0x0e6f, 0x0113), "Afterglow AX.1 Gamepad for Xbox 360" }, + { MAKE_VIDPID(0x0e6f, 0x011f), "Rock Candy Gamepad Wired Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0131), "PDP EA Sports Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0133), "Xbox 360 Wired Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0139), "Afterglow Prismatic Wired Controller" }, + { MAKE_VIDPID(0x0e6f, 0x013a), "PDP Xbox One Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0146), "Rock Candy Wired Controller for Xbox One" }, + { MAKE_VIDPID(0x0e6f, 0x0147), "PDP Marvel Xbox One Controller" }, + { MAKE_VIDPID(0x0e6f, 0x015c), "PDP Xbox One Arcade Stick" }, + { MAKE_VIDPID(0x0e6f, 0x0161), "PDP Xbox One Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0162), "PDP Xbox One Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0163), "PDP Xbox One Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0164), "PDP Battlefield One" }, + { MAKE_VIDPID(0x0e6f, 0x0165), "PDP Titanfall 2" }, + { MAKE_VIDPID(0x0e6f, 0x0201), "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0213), "Afterglow Gamepad for Xbox 360" }, + { MAKE_VIDPID(0x0e6f, 0x021f), "Rock Candy Gamepad for Xbox 360" }, + { MAKE_VIDPID(0x0e6f, 0x0246), "Rock Candy Gamepad for Xbox One 2015" }, + { MAKE_VIDPID(0x0e6f, 0x02a4), "PDP Wired Controller for Xbox One - Stealth Series" }, + { MAKE_VIDPID(0x0e6f, 0x02ab), "PDP Controller for Xbox One" }, + { MAKE_VIDPID(0x0e6f, 0x0301), "Logic3 Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0346), "Rock Candy Gamepad for Xbox One 2016" }, + { MAKE_VIDPID(0x0e6f, 0x0401), "Logic3 Controller" }, + { MAKE_VIDPID(0x0e6f, 0x0413), "Afterglow AX.1 Gamepad for Xbox 360" }, + { MAKE_VIDPID(0x0e6f, 0x0501), "PDP Xbox 360 Controller" }, + { MAKE_VIDPID(0x0e6f, 0xf900), "PDP Afterglow AX.1" }, + { MAKE_VIDPID(0x0f0d, 0x000a), "Hori Co. DOA4 FightStick" }, + { MAKE_VIDPID(0x0f0d, 0x000c), "Hori PadEX Turbo" }, + { MAKE_VIDPID(0x0f0d, 0x000d), "Hori Fighting Stick EX2" }, + { MAKE_VIDPID(0x0f0d, 0x0016), "Hori Real Arcade Pro.EX" }, + { MAKE_VIDPID(0x0f0d, 0x001b), "Hori Real Arcade Pro VX" }, + { MAKE_VIDPID(0x0f0d, 0x0063), "Hori Real Arcade Pro Hayabusa (USA) Xbox One" }, + { MAKE_VIDPID(0x0f0d, 0x0067), "HORIPAD ONE" }, + { MAKE_VIDPID(0x0f0d, 0x0078), "Hori Real Arcade Pro V Kai Xbox One" }, + { MAKE_VIDPID(0x11c9, 0x55f0), "Nacon GC-100XF" }, + { MAKE_VIDPID(0x12ab, 0x0004), "Honey Bee Xbox360 dancepad" }, + { MAKE_VIDPID(0x12ab, 0x0301), "PDP AFTERGLOW AX.1" }, + { MAKE_VIDPID(0x12ab, 0x0303), "Mortal Kombat Klassic FightStick" }, + { MAKE_VIDPID(0x1430, 0x4748), "RedOctane Guitar Hero X-plorer" }, + { MAKE_VIDPID(0x1430, 0xf801), "RedOctane Controller" }, + { MAKE_VIDPID(0x146b, 0x0601), "BigBen Interactive XBOX 360 Controller" }, + { MAKE_VIDPID(0x1532, 0x0037), "Razer Sabertooth" }, + { MAKE_VIDPID(0x1532, 0x0a00), "Razer Atrox Arcade Stick" }, + { MAKE_VIDPID(0x1532, 0x0a03), "Razer Wildcat" }, + { MAKE_VIDPID(0x15e4, 0x3f00), "Power A Mini Pro Elite" }, + { MAKE_VIDPID(0x15e4, 0x3f0a), "Xbox Airflo wired controller" }, + { MAKE_VIDPID(0x15e4, 0x3f10), "Batarang Xbox 360 controller" }, + { MAKE_VIDPID(0x162e, 0xbeef), "Joytech Neo-Se Take2" }, + { MAKE_VIDPID(0x1689, 0xfd00), "Razer Onza Tournament Edition" }, + { MAKE_VIDPID(0x1689, 0xfd01), "Razer Onza Classic Edition" }, + { MAKE_VIDPID(0x1689, 0xfe00), "Razer Sabertooth" }, + { MAKE_VIDPID(0x1bad, 0x0002), "Harmonix Rock Band Guitar" }, + { MAKE_VIDPID(0x1bad, 0x0003), "Harmonix Rock Band Drumkit" }, + { MAKE_VIDPID(0x1bad, 0x0130), "Ion Drum Rocker" }, + { MAKE_VIDPID(0x1bad, 0xf016), "Mad Catz Xbox 360 Controller" }, + { MAKE_VIDPID(0x1bad, 0xf018), "Mad Catz Street Fighter IV SE Fighting Stick" }, + { MAKE_VIDPID(0x1bad, 0xf019), "Mad Catz Brawlstick for Xbox 360" }, + { MAKE_VIDPID(0x1bad, 0xf021), "Mad Cats Ghost Recon FS GamePad" }, + { MAKE_VIDPID(0x1bad, 0xf023), "MLG Pro Circuit Controller (Xbox)" }, + { MAKE_VIDPID(0x1bad, 0xf025), "Mad Catz Call Of Duty" }, + { MAKE_VIDPID(0x1bad, 0xf027), "Mad Catz FPS Pro" }, + { MAKE_VIDPID(0x1bad, 0xf028), "Street Fighter IV FightPad" }, + { MAKE_VIDPID(0x1bad, 0xf02e), "Mad Catz Fightpad" }, + { MAKE_VIDPID(0x1bad, 0xf030), "Mad Catz Xbox 360 MC2 MicroCon Racing Wheel" }, + { MAKE_VIDPID(0x1bad, 0xf036), "Mad Catz MicroCon GamePad Pro" }, + { MAKE_VIDPID(0x1bad, 0xf038), "Street Fighter IV FightStick TE" }, + { MAKE_VIDPID(0x1bad, 0xf039), "Mad Catz MvC2 TE" }, + { MAKE_VIDPID(0x1bad, 0xf03a), "Mad Catz SFxT Fightstick Pro" }, + { MAKE_VIDPID(0x1bad, 0xf03d), "Street Fighter IV Arcade Stick TE - Chun Li" }, + { MAKE_VIDPID(0x1bad, 0xf03e), "Mad Catz MLG FightStick TE" }, + { MAKE_VIDPID(0x1bad, 0xf03f), "Mad Catz FightStick SoulCaliber" }, + { MAKE_VIDPID(0x1bad, 0xf042), "Mad Catz FightStick TES+" }, + { MAKE_VIDPID(0x1bad, 0xf080), "Mad Catz FightStick TE2" }, + { MAKE_VIDPID(0x1bad, 0xf501), "HoriPad EX2 Turbo" }, + { MAKE_VIDPID(0x1bad, 0xf502), "Hori Real Arcade Pro.VX SA" }, + { MAKE_VIDPID(0x1bad, 0xf503), "Hori Fighting Stick VX" }, + { MAKE_VIDPID(0x1bad, 0xf504), "Hori Real Arcade Pro. EX" }, + { MAKE_VIDPID(0x1bad, 0xf505), "Hori Fighting Stick EX2B" }, + { MAKE_VIDPID(0x1bad, 0xf506), "Hori Real Arcade Pro.EX Premium VLX" }, + { MAKE_VIDPID(0x1bad, 0xf900), "Harmonix Xbox 360 Controller" }, + { MAKE_VIDPID(0x1bad, 0xf901), "Gamestop Xbox 360 Controller" }, + { MAKE_VIDPID(0x1bad, 0xf903), "Tron Xbox 360 controller" }, + { MAKE_VIDPID(0x1bad, 0xf904), "PDP Versus Fighting Pad" }, + { MAKE_VIDPID(0x1bad, 0xf906), "MortalKombat FightStick" }, + { MAKE_VIDPID(0x1bad, 0xfa01), "MadCatz GamePad" }, + { MAKE_VIDPID(0x1bad, 0xfd00), "Razer Onza TE" }, + { MAKE_VIDPID(0x1bad, 0xfd01), "Razer Onza" }, + { MAKE_VIDPID(0x24c6, 0x5000), "Razer Atrox Arcade Stick" }, + { MAKE_VIDPID(0x24c6, 0x5300), "PowerA MINI PROEX Controller" }, + { MAKE_VIDPID(0x24c6, 0x5303), "Xbox Airflo wired controller" }, + { MAKE_VIDPID(0x24c6, 0x530a), "Xbox 360 Pro EX Controller" }, + { MAKE_VIDPID(0x24c6, 0x531a), "PowerA Pro Ex" }, + { MAKE_VIDPID(0x24c6, 0x5397), "FUS1ON Tournament Controller" }, + { MAKE_VIDPID(0x24c6, 0x541a), "PowerA Xbox One Mini Wired Controller" }, + { MAKE_VIDPID(0x24c6, 0x542a), "Xbox ONE spectra" }, + { MAKE_VIDPID(0x24c6, 0x543a), "PowerA Xbox One wired controller" }, + { MAKE_VIDPID(0x24c6, 0x5500), "Hori XBOX 360 EX 2 with Turbo" }, + { MAKE_VIDPID(0x24c6, 0x5501), "Hori Real Arcade Pro VX-SA" }, + { MAKE_VIDPID(0x24c6, 0x5502), "Hori Fighting Stick VX Alt" }, + { MAKE_VIDPID(0x24c6, 0x5503), "Hori Fighting Edge" }, + { MAKE_VIDPID(0x24c6, 0x5506), "Hori SOULCALIBUR V Stick" }, + { MAKE_VIDPID(0x24c6, 0x550d), "Hori GEM Xbox controller" }, + { MAKE_VIDPID(0x24c6, 0x550e), "Hori Real Arcade Pro V Kai 360" }, + { MAKE_VIDPID(0x24c6, 0x551a), "PowerA FUSION Pro Controller" }, + { MAKE_VIDPID(0x24c6, 0x561a), "PowerA FUSION Controller" }, + { MAKE_VIDPID(0x24c6, 0x5b00), "ThrustMaster Ferrari 458 Racing Wheel" }, + { MAKE_VIDPID(0x24c6, 0x5b02), "Thrustmaster, Inc. GPX Controller" }, + { MAKE_VIDPID(0x24c6, 0x5b03), "Thrustmaster Ferrari 458 Racing Wheel" }, + { MAKE_VIDPID(0x24c6, 0x5d04), "Razer Sabertooth" }, + { MAKE_VIDPID(0x24c6, 0xfafe), "Rock Candy Gamepad for Xbox 360" }, + }; + int i; + Uint32 vidpid = MAKE_VIDPID(vendor_id, product_id); + + for (i = 0; i < SDL_arraysize(names); ++i) { + if (vidpid == names[i].vidpid) { + return names[i].name; + } + } + return NULL; +} + +static SDL_bool +HIDAPI_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version) +{ + int i; + + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + if (driver->enabled && driver->IsSupportedDevice(vendor_id, product_id, version, -1)) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +static SDL_HIDAPI_DeviceDriver * +HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device) +{ + const Uint16 USAGE_PAGE_GENERIC_DESKTOP = 0x0001; + const Uint16 USAGE_JOYSTICK = 0x0004; + const Uint16 USAGE_GAMEPAD = 0x0005; + const Uint16 USAGE_MULTIAXISCONTROLLER = 0x0008; + int i; + + if (SDL_ShouldIgnoreJoystick(device->name, device->guid)) { + return NULL; + } + + if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) { + return NULL; + } + if (device->usage && device->usage != USAGE_JOYSTICK && device->usage != USAGE_GAMEPAD && device->usage != USAGE_MULTIAXISCONTROLLER) { + return NULL; + } + + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + if (driver->enabled && driver->IsSupportedDevice(device->vendor_id, device->product_id, device->version, device->interface_number)) { + return driver; + } + } + return NULL; +} + +static SDL_HIDAPI_Device * +HIDAPI_GetJoystickByIndex(int device_index) +{ + SDL_HIDAPI_Device *device = SDL_HIDAPI_devices; + while (device) { + if (device->driver) { + if (device_index == 0) { + break; + } + --device_index; + } + device = device->next; + } + return device; +} + +static SDL_HIDAPI_Device * +HIDAPI_GetJoystickByInfo(const char *path, Uint16 vendor_id, Uint16 product_id) +{ + SDL_HIDAPI_Device *device = SDL_HIDAPI_devices; + while (device) { + if (device->vendor_id == vendor_id && device->product_id == product_id && + SDL_strcmp(device->path, path) == 0) { + break; + } + device = device->next; + } + return device; +} + +static void SDLCALL +SDL_HIDAPIDriverHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + int i; + SDL_HIDAPI_Device *device = SDL_HIDAPI_devices; + SDL_bool enabled = (!hint || !*hint || ((*hint != '0') && (SDL_strcasecmp(hint, "false") != 0))); + + if (SDL_strcmp(name, SDL_HINT_JOYSTICK_HIDAPI) == 0) { + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + driver->enabled = SDL_GetHintBoolean(driver->hint, enabled); + } + } else { + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + if (SDL_strcmp(name, driver->hint) == 0) { + driver->enabled = enabled; + break; + } + } + } + + /* Update device list if driver availability changes */ + while (device) { + if (device->driver) { + if (!device->driver->enabled) { + device->driver = NULL; + + --SDL_HIDAPI_numjoysticks; + + SDL_PrivateJoystickRemoved(device->instance_id); + } + } else { + device->driver = HIDAPI_GetDeviceDriver(device); + if (device->driver) { + device->instance_id = SDL_GetNextJoystickInstanceID(); + + ++SDL_HIDAPI_numjoysticks; + + SDL_PrivateJoystickAdded(device->instance_id); + } + } + device = device->next; + } +} + +static void HIDAPI_JoystickDetect(void); + +static int +HIDAPI_JoystickInit(void) +{ + int i; + + if (hid_init() < 0) { + SDL_SetError("Couldn't initialize hidapi"); + return -1; + } + + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + SDL_AddHintCallback(driver->hint, SDL_HIDAPIDriverHintChanged, NULL); + } + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI, + SDL_HIDAPIDriverHintChanged, NULL); + HIDAPI_InitializeDiscovery(); + HIDAPI_JoystickDetect(); + return 0; +} + +static int +HIDAPI_JoystickGetCount(void) +{ + return SDL_HIDAPI_numjoysticks; +} + +static void +HIDAPI_AddDevice(struct hid_device_info *info) +{ + SDL_HIDAPI_Device *device; + SDL_HIDAPI_Device *curr, *last = NULL; + + for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) { + continue; + } + + device = (SDL_HIDAPI_Device *)SDL_calloc(1, sizeof(*device)); + if (!device) { + return; + } + device->instance_id = -1; + device->seen = SDL_TRUE; + device->vendor_id = info->vendor_id; + device->product_id = info->product_id; + device->version = info->release_number; + device->interface_number = info->interface_number; + device->usage_page = info->usage_page; + device->usage = info->usage; + { + /* FIXME: Is there any way to tell whether this is a Bluetooth device? */ + const Uint16 vendor = device->vendor_id; + const Uint16 product = device->product_id; + const Uint16 version = device->version; + Uint16 *guid16 = (Uint16 *)device->guid.data; + + *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB); + *guid16++ = 0; + *guid16++ = SDL_SwapLE16(vendor); + *guid16++ = 0; + *guid16++ = SDL_SwapLE16(product); + *guid16++ = 0; + *guid16++ = SDL_SwapLE16(version); + *guid16++ = 0; + + /* Note that this is a HIDAPI device for special handling elsewhere */ + device->guid.data[14] = 'h'; + device->guid.data[15] = 0; + } + + /* Need the device name before getting the driver to know whether to ignore this device */ + if (!device->name && info->manufacturer_string && info->product_string) { + char *manufacturer_string = SDL_iconv_string("UTF-8", "WCHAR_T", (char*)info->manufacturer_string, (SDL_wcslen(info->manufacturer_string)+1)*sizeof(wchar_t)); + char *product_string = SDL_iconv_string("UTF-8", "WCHAR_T", (char*)info->product_string, (SDL_wcslen(info->product_string)+1)*sizeof(wchar_t)); + if (!manufacturer_string && !product_string) { + if (sizeof(wchar_t) == sizeof(Uint16)) { + manufacturer_string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char*)info->manufacturer_string, (SDL_wcslen(info->manufacturer_string)+1)*sizeof(wchar_t)); + product_string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char*)info->product_string, (SDL_wcslen(info->product_string)+1)*sizeof(wchar_t)); + } else if (sizeof(wchar_t) == sizeof(Uint32)) { + manufacturer_string = SDL_iconv_string("UTF-8", "UCS-4-INTERNAL", (char*)info->manufacturer_string, (SDL_wcslen(info->manufacturer_string)+1)*sizeof(wchar_t)); + product_string = SDL_iconv_string("UTF-8", "UCS-4-INTERNAL", (char*)info->product_string, (SDL_wcslen(info->product_string)+1)*sizeof(wchar_t)); + } + } + if (manufacturer_string && product_string) { + size_t name_size = (SDL_strlen(manufacturer_string) + 1 + SDL_strlen(product_string) + 1); + device->name = (char *)SDL_malloc(name_size); + if (device->name) { + SDL_snprintf(device->name, name_size, "%s %s", manufacturer_string, product_string); + } + } + if (manufacturer_string) { + SDL_free(manufacturer_string); + } + if (product_string) { + SDL_free(product_string); + } + } + if (!device->name) { + size_t name_size = (6 + 1 + 6 + 1); + device->name = (char *)SDL_malloc(name_size); + if (!device->name) { + SDL_free(device); + return; + } + SDL_snprintf(device->name, name_size, "0x%.4x/0x%.4x", info->vendor_id, info->product_id); + } + + device->driver = HIDAPI_GetDeviceDriver(device); + + if (device->driver) { + const char *name = device->driver->GetDeviceName(device->vendor_id, device->product_id); + if (name) { + SDL_free(device->name); + device->name = SDL_strdup(name); + } + } + + device->path = SDL_strdup(info->path); + if (!device->path) { + SDL_free(device->name); + SDL_free(device); + return; + } + +#ifdef DEBUG_HIDAPI + SDL_Log("Adding HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, version %d, interface %d, usage page 0x%.4x, usage 0x%.4x, driver = %s\n", device->name, device->vendor_id, device->product_id, device->version, device->interface_number, device->usage_page, device->usage, device->driver ? device->driver->hint : "NONE"); +#endif + + /* Add it to the list */ + if (last) { + last->next = device; + } else { + SDL_HIDAPI_devices = device; + } + + if (device->driver) { + /* It's a joystick! */ + device->instance_id = SDL_GetNextJoystickInstanceID(); + + ++SDL_HIDAPI_numjoysticks; + + SDL_PrivateJoystickAdded(device->instance_id); + } +} + + +static void +HIDAPI_DelDevice(SDL_HIDAPI_Device *device, SDL_bool send_event) +{ + SDL_HIDAPI_Device *curr, *last; + for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) { + if (curr == device) { + if (last) { + last->next = curr->next; + } else { + SDL_HIDAPI_devices = curr->next; + } + + if (device->driver && send_event) { + /* Need to decrement the joystick count before we post the event */ + --SDL_HIDAPI_numjoysticks; + + SDL_PrivateJoystickRemoved(device->instance_id); + } + + SDL_free(device->name); + SDL_free(device->path); + SDL_free(device); + return; + } + } +} + +static void +HIDAPI_UpdateDeviceList(void) +{ + SDL_HIDAPI_Device *device; + struct hid_device_info *devs, *info; + + /* Prepare the existing device list */ + device = SDL_HIDAPI_devices; + while (device) { + device->seen = SDL_FALSE; + device = device->next; + } + + /* Enumerate the devices */ + devs = hid_enumerate(0, 0); + if (devs) { + for (info = devs; info; info = info->next) { + device = HIDAPI_GetJoystickByInfo(info->path, info->vendor_id, info->product_id); + if (device) { + device->seen = SDL_TRUE; + } else { + HIDAPI_AddDevice(info); + } + } + hid_free_enumeration(devs); + } + + /* Remove any devices that weren't seen */ + device = SDL_HIDAPI_devices; + while (device) { + SDL_HIDAPI_Device *next = device->next; + + if (!device->seen) { + HIDAPI_DelDevice(device, SDL_TRUE); + } + device = next; + } +} + +SDL_bool +HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version) +{ + SDL_HIDAPI_Device *device; + + /* Don't update the device list for devices we know aren't supported */ + if (!HIDAPI_IsDeviceSupported(vendor_id, product_id, version)) { + return SDL_FALSE; + } + + /* Make sure the device list is completely up to date when we check for device presence */ + HIDAPI_UpdateDeviceList(); + + device = SDL_HIDAPI_devices; + while (device) { + if (device->vendor_id == vendor_id && device->product_id == product_id && device->driver) { + return SDL_TRUE; + } + device = device->next; + } + return SDL_FALSE; +} + +static void +HIDAPI_JoystickDetect(void) +{ + HIDAPI_UpdateDiscovery(); + if (SDL_HIDAPI_discovery.m_bHaveDevicesChanged) { + /* FIXME: We probably need to schedule an update in a few seconds as well */ + HIDAPI_UpdateDeviceList(); + SDL_HIDAPI_discovery.m_bHaveDevicesChanged = SDL_FALSE; + } +} + +static const char * +HIDAPI_JoystickGetDeviceName(int device_index) +{ + return HIDAPI_GetJoystickByIndex(device_index)->name; +} + +static int +HIDAPI_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + +static SDL_JoystickGUID +HIDAPI_JoystickGetDeviceGUID(int device_index) +{ + return HIDAPI_GetJoystickByIndex(device_index)->guid; +} + +static SDL_JoystickID +HIDAPI_JoystickGetDeviceInstanceID(int device_index) +{ + return HIDAPI_GetJoystickByIndex(device_index)->instance_id; +} + +static int +HIDAPI_JoystickOpen(SDL_Joystick * joystick, int device_index) +{ + SDL_HIDAPI_Device *device = HIDAPI_GetJoystickByIndex(device_index); + struct joystick_hwdata *hwdata; + + hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata)); + if (!hwdata) { + return SDL_OutOfMemory(); + } + + hwdata->driver = device->driver; + hwdata->dev = hid_open_path(device->path, 0); + if (!hwdata->dev) { + SDL_free(hwdata); + return SDL_SetError("Couldn't open HID device %s", device->path); + } + hwdata->mutex = SDL_CreateMutex(); + + if (!device->driver->Init(joystick, hwdata->dev, device->vendor_id, device->product_id, &hwdata->context)) { + hid_close(hwdata->dev); + SDL_free(hwdata); + return -1; + } + + joystick->hwdata = hwdata; + return 0; +} + +static int +HIDAPI_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + SDL_HIDAPI_DeviceDriver *driver = hwdata->driver; + int result; + + SDL_LockMutex(hwdata->mutex); + result = driver->Rumble(joystick, hwdata->dev, hwdata->context, low_frequency_rumble, high_frequency_rumble, duration_ms); + SDL_UnlockMutex(hwdata->mutex); + return result; +} + +static void +HIDAPI_JoystickUpdate(SDL_Joystick * joystick) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + SDL_HIDAPI_DeviceDriver *driver = hwdata->driver; + SDL_bool succeeded; + + SDL_LockMutex(hwdata->mutex); + succeeded = driver->Update(joystick, hwdata->dev, hwdata->context); + SDL_UnlockMutex(hwdata->mutex); + + if (!succeeded) { + SDL_HIDAPI_Device *device; + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->instance_id == joystick->instance_id) { + HIDAPI_DelDevice(device, SDL_TRUE); + break; + } + } + } +} + +static void +HIDAPI_JoystickClose(SDL_Joystick * joystick) +{ + struct joystick_hwdata *hwdata = joystick->hwdata; + SDL_HIDAPI_DeviceDriver *driver = hwdata->driver; + driver->Quit(joystick, hwdata->dev, hwdata->context); + + hid_close(hwdata->dev); + SDL_DestroyMutex(hwdata->mutex); + SDL_free(hwdata); + joystick->hwdata = NULL; +} + +static void +HIDAPI_JoystickQuit(void) +{ + int i; + + HIDAPI_ShutdownDiscovery(); + + while (SDL_HIDAPI_devices) { + HIDAPI_DelDevice(SDL_HIDAPI_devices, SDL_FALSE); + } + for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { + SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; + SDL_DelHintCallback(driver->hint, SDL_HIDAPIDriverHintChanged, NULL); + } + SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI, + SDL_HIDAPIDriverHintChanged, NULL); + SDL_HIDAPI_numjoysticks = 0; + + hid_exit(); +} + +SDL_JoystickDriver SDL_HIDAPI_JoystickDriver = +{ + HIDAPI_JoystickInit, + HIDAPI_JoystickGetCount, + HIDAPI_JoystickDetect, + HIDAPI_JoystickGetDeviceName, + HIDAPI_JoystickGetDevicePlayerIndex, + HIDAPI_JoystickGetDeviceGUID, + HIDAPI_JoystickGetDeviceInstanceID, + HIDAPI_JoystickOpen, + HIDAPI_JoystickRumble, + HIDAPI_JoystickUpdate, + HIDAPI_JoystickClose, + HIDAPI_JoystickQuit, +}; + +#endif /* SDL_JOYSTICK_HIDAPI */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapijoystick_c.h b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapijoystick_c.h new file mode 100644 index 0000000..18a4483 --- /dev/null +++ b/source/3rd-party/SDL2/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -0,0 +1,74 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#ifndef SDL_JOYSTICK_HIDAPI_H +#define SDL_JOYSTICK_HIDAPI_H + +#include "../../hidapi/hidapi/hidapi.h" + +/* This is the full set of HIDAPI drivers available */ +#define SDL_JOYSTICK_HIDAPI_PS4 +#define SDL_JOYSTICK_HIDAPI_SWITCH +#define SDL_JOYSTICK_HIDAPI_XBOX360 +#define SDL_JOYSTICK_HIDAPI_XBOXONE + +#ifdef __WINDOWS__ +/* On Windows, Xbox One controllers are handled by the Xbox 360 driver */ +#undef SDL_JOYSTICK_HIDAPI_XBOXONE +/* It turns out HIDAPI for Xbox controllers doesn't allow background input */ +#undef SDL_JOYSTICK_HIDAPI_XBOX360 +#endif + +#ifdef __MACOSX__ +/* On Mac OS X, Xbox One controllers are handled by the Xbox 360 driver */ +#undef SDL_JOYSTICK_HIDAPI_XBOXONE +#endif + +typedef struct _SDL_HIDAPI_DeviceDriver +{ + const char *hint; + SDL_bool enabled; + SDL_bool (*IsSupportedDevice)(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number); + const char *(*GetDeviceName)(Uint16 vendor_id, Uint16 product_id); + SDL_bool (*Init)(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context); + int (*Rumble)(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); + SDL_bool (*Update)(SDL_Joystick *joystick, hid_device *dev, void *context); + void (*Quit)(SDL_Joystick *joystick, hid_device *dev, void *context); + +} SDL_HIDAPI_DeviceDriver; + +/* HIDAPI device support */ +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne; + +/* Return true if a HID device is present and supported as a joystick */ +extern SDL_bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version); + +/* Return the name of an Xbox 360 or Xbox One controller */ +extern const char *HIDAPI_XboxControllerName(Uint16 vendor_id, Uint16 product_id); + +#endif /* SDL_JOYSTICK_HIDAPI_H */ + +/* vi: set ts=4 sw=4 expandtab: */ |