Initial commit

This commit is contained in:
InoriRus
2021-12-01 19:29:27 +10:00
parent b1e7dcdc5d
commit 43f49c8763
1843 changed files with 1111694 additions and 0 deletions
@@ -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: */
@@ -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: */
@@ -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: */
@@ -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: */
File diff suppressed because it is too large Load Diff
@@ -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: */