#include "SDL_internal.h"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../SDL_hints_c.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#ifdef SDL_JOYSTICK_HIDAPI_STEAM
#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED "SDL_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED"
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT false
#else
#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)
#endif
#define PAIRING_STATE_DURATION_SECONDS 60
#include "steam/controller_constants.h"
#include "steam/controller_structs.h"
enum
{
SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE = 11,
SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE,
SDL_GAMEPAD_NUM_STEAM_BUTTONS,
};
typedef struct SteamControllerStateInternal_t
{
Uint32 eControllerType;
Uint32 unPacketNum;
Uint64 ulButtons;
short sLeftPadX;
short sLeftPadY;
short sRightPadX;
short sRightPadY;
short sCenterPadX;
short sCenterPadY;
short sLeftStickX;
short sLeftStickY;
short sRightStickX;
short sRightStickY;
unsigned short sTriggerL;
unsigned short sTriggerR;
short sAccelX;
short sAccelY;
short sAccelZ;
short sGyroX;
short sGyroY;
short sGyroZ;
float sGyroQuatW;
float sGyroQuatX;
float sGyroQuatY;
float sGyroQuatZ;
short sGyroSteeringAngle;
unsigned short sBatteryLevel;
unsigned short sPressurePadLeft;
unsigned short sPressurePadRight;
unsigned short sPressureBumperLeft;
unsigned short sPressureBumperRight;
short sPrevLeftPad[2];
short sPrevLeftStick[2];
} SteamControllerStateInternal_t;
#define STEAM_RIGHT_TRIGGER_MASK 0x00000001
#define STEAM_LEFT_TRIGGER_MASK 0x00000002
#define STEAM_RIGHT_BUMPER_MASK 0x00000004
#define STEAM_LEFT_BUMPER_MASK 0x00000008
#define STEAM_BUTTON_NORTH_MASK 0x00000010
#define STEAM_BUTTON_EAST_MASK 0x00000020
#define STEAM_BUTTON_WEST_MASK 0x00000040
#define STEAM_BUTTON_SOUTH_MASK 0x00000080
#define STEAM_DPAD_UP_MASK 0x00000100
#define STEAM_DPAD_RIGHT_MASK 0x00000200
#define STEAM_DPAD_LEFT_MASK 0x00000400
#define STEAM_DPAD_DOWN_MASK 0x00000800
#define STEAM_BUTTON_MENU_MASK 0x00001000
#define STEAM_BUTTON_STEAM_MASK 0x00002000
#define STEAM_BUTTON_ESCAPE_MASK 0x00004000
#define STEAM_BUTTON_BACK_LEFT_MASK 0x00008000
#define STEAM_BUTTON_BACK_RIGHT_MASK 0x00010000
#define STEAM_BUTTON_LEFTPAD_CLICKED_MASK 0x00020000
#define STEAM_BUTTON_RIGHTPAD_CLICKED_MASK 0x00040000
#define STEAM_LEFTPAD_FINGERDOWN_MASK 0x00080000
#define STEAM_RIGHTPAD_FINGERDOWN_MASK 0x00100000
#define STEAM_JOYSTICK_BUTTON_MASK 0x00400000
#define STEAM_LEFTPAD_AND_JOYSTICK_MASK 0x00800000
#define D0G_IS_VALID_WIRELESS_EVENT(data, len) ((len) >= 5 && (data)[0] == 1 && (data)[1] == 0 && (data)[2] == 3 && (data)[3] >= 1)
#define D0G_GET_WIRELESS_EVENT_TYPE(data) ((data)[4])
#define D0G_WIRELESS_DISCONNECTED 1
#define D0G_WIRELESS_ESTABLISHED 2
#define D0G_WIRELESS_NEWLYPAIRED 3
#define D0G_IS_WIRELESS_DISCONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) == D0G_WIRELESS_DISCONNECTED)
#define D0G_IS_WIRELESS_CONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) != D0G_WIRELESS_DISCONNECTED)
#define MAX_REPORT_SEGMENT_PAYLOAD_SIZE 18
typedef struct
{
uint8_t uBuffer[MAX_REPORT_SEGMENT_PAYLOAD_SIZE * 8 + 1];
int nExpectedSegmentNumber;
bool bIsBle;
} SteamControllerPacketAssembler;
#undef clamp
#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
#undef offsetof
#define offsetof(s, m) (size_t) & (((s *)0)->m)
#ifdef DEBUG_STEAM_CONTROLLER
#define DPRINTF(format, ...) printf(format, ##__VA_ARGS__)
#define HEXDUMP(ptr, len) hexdump(ptr, len)
#else
#define DPRINTF(format, ...)
#define HEXDUMP(ptr, len)
#endif
#define printf SDL_Log
#define MAX_REPORT_SEGMENT_SIZE (MAX_REPORT_SEGMENT_PAYLOAD_SIZE + 2)
#define CALC_REPORT_SEGMENT_NUM(index) ((index / MAX_REPORT_SEGMENT_PAYLOAD_SIZE) & 0x07)
#define REPORT_SEGMENT_DATA_FLAG 0x80
#define REPORT_SEGMENT_LAST_FLAG 0x40
#define BLE_REPORT_NUMBER 0x03
#define STEAMCONTROLLER_TRIGGER_MAX_ANALOG 26000
#undef ENABLE_MOUSE_MODE
#define RADIO_WORKAROUND_SLEEP_ATTEMPTS 50
#define RADIO_WORKAROUND_SLEEP_DURATION_US 500
#define CONTROLLER_CONFIGURATION_DELAY_US 3000
static uint8_t GetSegmentHeader(int nSegmentNumber, bool bLastPacket)
{
uint8_t header = REPORT_SEGMENT_DATA_FLAG;
header |= nSegmentNumber;
if (bLastPacket) {
header |= REPORT_SEGMENT_LAST_FLAG;
}
return header;
}
static void hexdump(const uint8_t *ptr, int len)
{
HIDAPI_DumpPacket("Data", ptr, len);
}
static void ResetSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler)
{
SDL_zeroa(pAssembler->uBuffer);
pAssembler->nExpectedSegmentNumber = 0;
}
static void InitializeSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, bool bIsBle)
{
pAssembler->bIsBle = bIsBle;
ResetSteamControllerPacketAssembler(pAssembler);
}
static int WriteSegmentToSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, const uint8_t *pSegment, int nSegmentLength)
{
if (pAssembler->bIsBle) {
uint8_t uSegmentHeader = pSegment[1];
int nSegmentNumber = uSegmentHeader & 0x07;
HEXDUMP(pSegment, nSegmentLength);
if (pSegment[0] != BLE_REPORT_NUMBER) {
return 0;
}
if (nSegmentLength != MAX_REPORT_SEGMENT_SIZE) {
printf("Bad segment size! %d\n", nSegmentLength);
hexdump(pSegment, nSegmentLength);
ResetSteamControllerPacketAssembler(pAssembler);
return -1;
}
DPRINTF("GOT PACKET HEADER = 0x%x\n", uSegmentHeader);
if (!(uSegmentHeader & REPORT_SEGMENT_DATA_FLAG)) {
return 0;
}
if (nSegmentNumber != pAssembler->nExpectedSegmentNumber) {
ResetSteamControllerPacketAssembler(pAssembler);
if (nSegmentNumber) {
DPRINTF("Bad segment number, got %d, expected %d\n",
nSegmentNumber, pAssembler->nExpectedSegmentNumber);
return -1;
}
}
SDL_memcpy(pAssembler->uBuffer + nSegmentNumber * MAX_REPORT_SEGMENT_PAYLOAD_SIZE,
pSegment + 2, MAX_REPORT_SEGMENT_PAYLOAD_SIZE);
if (uSegmentHeader & REPORT_SEGMENT_LAST_FLAG) {
pAssembler->nExpectedSegmentNumber = 0;
return (nSegmentNumber + 1) * MAX_REPORT_SEGMENT_PAYLOAD_SIZE;
}
pAssembler->nExpectedSegmentNumber++;
} else {
SDL_memcpy(pAssembler->uBuffer,
pSegment,
nSegmentLength);
return nSegmentLength;
}
return 0;
}
#define BLE_MAX_READ_RETRIES 8
static int SetFeatureReport(SDL_HIDAPI_Device *dev, const unsigned char uBuffer[65], int nActualDataLen)
{
int nRet = -1;
DPRINTF("SetFeatureReport %p %p %d\n", dev, uBuffer, nActualDataLen);
if (dev->is_bluetooth) {
int nSegmentNumber = 0;
uint8_t uPacketBuffer[MAX_REPORT_SEGMENT_SIZE];
const unsigned char *pBufferPtr = uBuffer + 1;
if (nActualDataLen < 1) {
return -1;
}
nActualDataLen--;
while (nActualDataLen > 0) {
int nBytesInPacket = nActualDataLen > MAX_REPORT_SEGMENT_PAYLOAD_SIZE ? MAX_REPORT_SEGMENT_PAYLOAD_SIZE : nActualDataLen;
nActualDataLen -= nBytesInPacket;
SDL_zeroa(uPacketBuffer);
uPacketBuffer[0] = BLE_REPORT_NUMBER;
uPacketBuffer[1] = GetSegmentHeader(nSegmentNumber, nActualDataLen == 0);
SDL_memcpy(&uPacketBuffer[2], pBufferPtr, nBytesInPacket);
pBufferPtr += nBytesInPacket;
nSegmentNumber++;
nRet = SDL_hid_send_feature_report(dev->dev, uPacketBuffer, sizeof(uPacketBuffer));
}
} else {
for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {
nRet = SDL_hid_send_feature_report(dev->dev, uBuffer, 65);
if (nRet >= 0) {
break;
}
SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);
}
}
DPRINTF("SetFeatureReport() ret = %d\n", nRet);
return nRet;
}
static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65])
{
int nRet = -1;
DPRINTF("GetFeatureReport( %p %p )\n", dev, uBuffer);
if (dev->is_bluetooth) {
int nRetries = 0;
uint8_t uSegmentBuffer[MAX_REPORT_SEGMENT_SIZE + 1];
uint8_t ucBytesToRead = MAX_REPORT_SEGMENT_SIZE;
uint8_t ucDataStartOffset = 0;
SteamControllerPacketAssembler assembler;
InitializeSteamControllerPacketAssembler(&assembler, dev->is_bluetooth);
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_MACOS)
++ucBytesToRead;
++ucDataStartOffset;
#endif
while (nRetries < BLE_MAX_READ_RETRIES) {
SDL_zeroa(uSegmentBuffer);
uSegmentBuffer[0] = BLE_REPORT_NUMBER;
nRet = SDL_hid_get_feature_report(dev->dev, uSegmentBuffer, ucBytesToRead);
DPRINTF("GetFeatureReport ble ret=%d\n", nRet);
HEXDUMP(uSegmentBuffer, nRet);
if (nRet > 2 && (uSegmentBuffer[ucDataStartOffset + 1] & REPORT_SEGMENT_DATA_FLAG)) {
nRetries = 0;
} else {
nRetries++;
}
if (nRet > 0) {
int nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&assembler,
uSegmentBuffer + ucDataStartOffset,
nRet - ucDataStartOffset);
if (nPacketLength > 0 && nPacketLength < 65) {
uBuffer[0] = 0;
SDL_memcpy(uBuffer + 1, assembler.uBuffer, nPacketLength);
return nPacketLength;
}
}
}
printf("Could not get a full ble packet after %d retries\n", nRetries);
return -1;
} else {
SDL_memset(uBuffer, 0, 65);
for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {
nRet = SDL_hid_get_feature_report(dev->dev, uBuffer, 65);
if (nRet >= 0) {
break;
}
SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);
}
DPRINTF("GetFeatureReport USB ret=%d\n", nRet);
HEXDUMP(uBuffer, nRet);
}
return nRet;
}
static int ReadResponse(SDL_HIDAPI_Device *dev, uint8_t uBuffer[65], int nExpectedResponse)
{
for (int nRetries = 0; nRetries < 10; nRetries++) {
int nRet = GetFeatureReport(dev, uBuffer);
DPRINTF("ReadResponse( %p %p 0x%x )\n", dev, uBuffer, nExpectedResponse);
if (nRet < 0) {
continue;
}
DPRINTF("ReadResponse got %d bytes of data: ", nRet);
HEXDUMP(uBuffer, nRet);
if (uBuffer[1] != nExpectedResponse) {
continue;
}
return nRet;
}
return -1;
}
static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew, uint32_t *punUpdateRateUS)
{
unsigned char buf[65];
unsigned int i;
int res = -1;
int nSettings = 0;
int nAttributesLength;
FeatureReportMsg *msg;
uint32_t unUpdateRateUS = 9000;
DPRINTF("ResetSteamController hid=%p\n", dev);
SDL_zero(buf);
buf[0] = 0;
buf[1] = ID_GET_ATTRIBUTES_VALUES;
res = SetFeatureReport(dev, buf, 2);
if (res < 0) {
if (!bSuppressErrorSpew) {
printf("GET_ATTRIBUTES_VALUES failed for controller %p\n", dev);
}
return false;
}
res = ReadResponse(dev, buf, ID_GET_ATTRIBUTES_VALUES);
if (res < 0 || buf[1] != ID_GET_ATTRIBUTES_VALUES) {
HEXDUMP(buf, res);
if (!bSuppressErrorSpew) {
printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev);
}
return false;
}
nAttributesLength = buf[2];
if (nAttributesLength > res) {
if (!bSuppressErrorSpew) {
printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev);
}
return false;
}
msg = (FeatureReportMsg *)&buf[1];
for (i = 0; i < (int)msg->header.length / sizeof(ControllerAttribute); ++i) {
uint8_t unAttribute = msg->payload.getAttributes.attributes[i].attributeTag;
uint32_t unValue = msg->payload.getAttributes.attributes[i].attributeValue;
switch (unAttribute) {
case ATTRIB_UNIQUE_ID:
break;
case ATTRIB_PRODUCT_ID:
break;
case ATTRIB_CAPABILITIES:
break;
case ATTRIB_CONNECTION_INTERVAL_IN_US:
unUpdateRateUS = unValue;
break;
default:
break;
}
}
if (punUpdateRateUS) {
*punUpdateRateUS = unUpdateRateUS;
}
SDL_zero(buf);
buf[0] = 0;
buf[1] = ID_CLEAR_DIGITAL_MAPPINGS;
res = SetFeatureReport(dev, buf, 2);
if (res < 0) {
if (!bSuppressErrorSpew) {
printf("CLEAR_DIGITAL_MAPPINGS failed for controller %p\n", dev);
}
return false;
}
SDL_zero(buf);
buf[1] = ID_LOAD_DEFAULT_SETTINGS;
buf[2] = 0;
res = SetFeatureReport(dev, buf, 3);
if (res < 0) {
if (!bSuppressErrorSpew) {
printf("LOAD_DEFAULT_SETTINGS failed for controller %p\n", dev);
}
return false;
}
#define ADD_SETTING(SETTING, VALUE) \
buf[3 + nSettings * 3] = SETTING; \
buf[3 + nSettings * 3 + 1] = ((uint16_t)VALUE) & 0xFF; \
buf[3 + nSettings * 3 + 2] = ((uint16_t)VALUE) >> 8; \
++nSettings;
SDL_zero(buf);
buf[1] = ID_SET_SETTINGS_VALUES;
ADD_SETTING(SETTING_WIRELESS_PACKET_VERSION, 2);
ADD_SETTING(SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE);
#ifdef ENABLE_MOUSE_MODE
ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE);
ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 1);
ADD_SETTING(SETTING_MOMENTUM_MAXIMUM_VELOCITY, 20000); ADD_SETTING(SETTING_MOMENTUM_DECAY_AMOUNT, 50); #else
ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE);
ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 0);
#endif
buf[2] = (unsigned char)(nSettings * 3);
res = SetFeatureReport(dev, buf, 3 + nSettings * 3);
if (res < 0) {
if (!bSuppressErrorSpew) {
printf("SET_SETTINGS failed for controller %p\n", dev);
}
return false;
}
#ifdef ENABLE_MOUSE_MODE
bool bMappingsCleared = false;
int iRetry;
for (iRetry = 0; iRetry < 2; ++iRetry) {
SDL_zero(buf);
buf[1] = ID_GET_DIGITAL_MAPPINGS;
buf[2] = 1; buf[3] = 0;
res = SetFeatureReport(dev, buf, 4);
if (res < 0) {
printf("GET_DIGITAL_MAPPINGS failed for controller %p\n", dev);
return false;
}
res = ReadResponse(dev, buf, ID_GET_DIGITAL_MAPPINGS);
if (res < 0 || buf[1] != ID_GET_DIGITAL_MAPPINGS) {
printf("Bad GET_DIGITAL_MAPPINGS response for controller %p\n", dev);
return false;
}
if (buf[2] == 1 && buf[3] == 0xFF) {
bMappingsCleared = true;
break;
}
usleep(CONTROLLER_CONFIGURATION_DELAY_US);
}
if (!bMappingsCleared && !bSuppressErrorSpew) {
printf("Warning: CLEAR_DIGITAL_MAPPINGS never completed for controller %p\n", dev);
}
SDL_zero(buf);
buf[1] = ID_SET_DIGITAL_MAPPINGS;
buf[2] = 6; buf[3] = IO_DIGITAL_BUTTON_RIGHT_TRIGGER;
buf[4] = DEVICE_MOUSE;
buf[5] = MOUSE_BTN_LEFT;
buf[6] = IO_DIGITAL_BUTTON_LEFT_TRIGGER;
buf[7] = DEVICE_MOUSE;
buf[8] = MOUSE_BTN_RIGHT;
res = SetFeatureReport(dev, buf, 9);
if (res < 0) {
if (!bSuppressErrorSpew) {
printf("SET_DIGITAL_MAPPINGS failed for controller %p\n", dev);
}
return false;
}
#endif
return true;
}
static int ReadSteamController(SDL_hid_device *dev, uint8_t *pData, int nDataSize)
{
SDL_memset(pData, 0, nDataSize);
pData[0] = BLE_REPORT_NUMBER; return SDL_hid_read(dev, pData, nDataSize);
}
static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing)
{
unsigned char buf[65];
SDL_zero(buf);
buf[1] = ID_ENABLE_PAIRING;
buf[2] = 2; buf[3] = bEnablePairing ? 1 : 0;
buf[4] = bEnablePairing ? PAIRING_STATE_DURATION_SECONDS : 0;
SetFeatureReport(dev, buf, 5);
}
static void CommitPairing(SDL_HIDAPI_Device *dev)
{
unsigned char buf[65];
SDL_zero(buf);
buf[1] = ID_DONGLE_COMMIT_DEVICE;
SetFeatureReport(dev, buf, 2);
}
static void CloseSteamController(SDL_HIDAPI_Device *dev)
{
unsigned char buf[65];
int nSettings = 0;
SDL_zero(buf);
buf[1] = ID_SET_DEFAULT_DIGITAL_MAPPINGS;
SetFeatureReport(dev, buf, 2);
SDL_zero(buf);
buf[1] = ID_LOAD_DEFAULT_SETTINGS;
buf[2] = 0;
SetFeatureReport(dev, buf, 3);
SDL_zero(buf);
buf[1] = ID_SET_SETTINGS_VALUES;
ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE);
buf[2] = (unsigned char)(nSettings * 3);
SetFeatureReport(dev, buf, 3 + nSettings * 3);
}
static float RemapValClamped(float val, float A, float B, float C, float D)
{
if (A == B) {
return (val - B) >= 0.0f ? D : C;
} else {
float cVal = (val - A) / (B - A);
cVal = clamp(cVal, 0.0f, 1.0f);
return C + (D - C) * cVal;
}
}
static void RotatePad(int *pX, int *pY, float flAngleInRad)
{
int origX = *pX, origY = *pY;
*pX = (int)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY);
*pY = (int)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY);
}
static void FormatStatePacketUntilGyro(SteamControllerStateInternal_t *pState, ValveControllerStatePacket_t *pStatePacket)
{
int nLeftPadX;
int nLeftPadY;
int nRightPadX;
int nRightPadY;
int nPadOffset;
const float flRotationAngle = 0.261799f;
SDL_memset(pState, 0, offsetof(SteamControllerStateInternal_t, sBatteryLevel));
pState->eControllerType = 2; pState->unPacketNum = pStatePacket->unPacketNum;
SDL_memcpy(&pState->ulButtons, &pStatePacket->ButtonTriggerData.ulButtons, 8);
pState->ulButtons &= ~0xFFFF000000LL;
if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
pState->sLeftPadX = pState->sPrevLeftPad[0] = pStatePacket->sLeftPadX;
pState->sLeftPadY = pState->sPrevLeftPad[1] = pStatePacket->sLeftPadY;
if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {
pState->sLeftStickX = pState->sPrevLeftStick[0];
pState->sLeftStickY = pState->sPrevLeftStick[1];
} else {
pState->sPrevLeftStick[0] = 0;
pState->sPrevLeftStick[1] = 0;
}
} else {
{
pState->sPrevLeftStick[0] = pState->sLeftStickX = pStatePacket->sLeftPadX;
pState->sPrevLeftStick[1] = pState->sLeftStickY = pStatePacket->sLeftPadY;
}
if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {
pState->sLeftPadX = pState->sPrevLeftPad[0];
pState->sLeftPadY = pState->sPrevLeftPad[1];
} else {
pState->sPrevLeftPad[0] = 0;
pState->sPrevLeftPad[1] = 0;
if (pState->ulButtons & STEAM_BUTTON_LEFTPAD_CLICKED_MASK) {
pState->ulButtons &= ~STEAM_BUTTON_LEFTPAD_CLICKED_MASK;
pState->ulButtons |= STEAM_JOYSTICK_BUTTON_MASK;
}
}
}
if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {
pState->ulButtons |= STEAM_LEFTPAD_FINGERDOWN_MASK;
}
pState->sRightPadX = pStatePacket->sRightPadX;
pState->sRightPadY = pStatePacket->sRightPadY;
nLeftPadX = pState->sLeftPadX;
nLeftPadY = pState->sLeftPadY;
nRightPadX = pState->sRightPadX;
nRightPadY = pState->sRightPadY;
RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle);
RotatePad(&nRightPadX, &nRightPadY, flRotationAngle);
if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
nPadOffset = 1000;
} else {
nPadOffset = 0;
}
pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
nPadOffset = 0;
if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) {
nPadOffset = 1000;
} else {
nPadOffset = 0;
}
pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pState->sTriggerL = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nLeft << 7) | pStatePacket->ButtonTriggerData.Triggers.nLeft), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
pState->sTriggerR = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nRight << 7) | pStatePacket->ButtonTriggerData.Triggers.nRight), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
}
static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState)
{
int nLeftPadX;
int nLeftPadY;
int nRightPadX;
int nRightPadY;
int nPadOffset;
uint32_t ucOptionDataMask;
const float flRotationAngle = 0.261799f;
pState->unPacketNum++;
ucOptionDataMask = (*pData++ & 0xF0);
ucOptionDataMask |= (uint32_t)(*pData++) << 8;
if (ucOptionDataMask & k_EBLEButtonChunk1) {
SDL_memcpy(&pState->ulButtons, pData, 3);
pData += 3;
}
if (ucOptionDataMask & k_EBLEButtonChunk2) {
pState->sTriggerL = (unsigned short)RemapValClamped((float)((pData[0] << 7) | pData[0]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
pState->sTriggerR = (unsigned short)RemapValClamped((float)((pData[1] << 7) | pData[1]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
pData += 2;
}
if (ucOptionDataMask & k_EBLEButtonChunk3) {
uint8_t *pButtonByte = (uint8_t *)&pState->ulButtons;
pButtonByte[5] = *pData++;
pButtonByte[6] = *pData++;
pButtonByte[7] = *pData++;
}
if (ucOptionDataMask & k_EBLELeftJoystickChunk) {
int nLength = sizeof(pState->sLeftStickX) + sizeof(pState->sLeftStickY);
SDL_memcpy(&pState->sLeftStickX, pData, nLength);
pData += nLength;
}
if (ucOptionDataMask & k_EBLELeftTrackpadChunk) {
int nLength = sizeof(pState->sLeftPadX) + sizeof(pState->sLeftPadY);
SDL_memcpy(&pState->sLeftPadX, pData, nLength);
if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
nPadOffset = 1000;
} else {
nPadOffset = 0;
}
nLeftPadX = pState->sLeftPadX;
nLeftPadY = pState->sLeftPadY;
RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle);
pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pData += nLength;
}
if (ucOptionDataMask & k_EBLERightTrackpadChunk) {
int nLength = sizeof(pState->sRightPadX) + sizeof(pState->sRightPadY);
SDL_memcpy(&pState->sRightPadX, pData, nLength);
if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) {
nPadOffset = 1000;
} else {
nPadOffset = 0;
}
nRightPadX = pState->sRightPadX;
nRightPadY = pState->sRightPadY;
RotatePad(&nRightPadX, &nRightPadY, flRotationAngle);
pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pData += nLength;
}
if (ucOptionDataMask & k_EBLEIMUAccelChunk) {
int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ);
SDL_memcpy(&pState->sAccelX, pData, nLength);
pData += nLength;
}
if (ucOptionDataMask & k_EBLEIMUGyroChunk) {
int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ);
SDL_memcpy(&pState->sGyroX, pData, nLength);
pData += nLength;
}
if (ucOptionDataMask & k_EBLEIMUQuatChunk) {
int nLength = sizeof(pState->sGyroQuatW) + sizeof(pState->sGyroQuatX) + sizeof(pState->sGyroQuatY) + sizeof(pState->sGyroQuatZ);
SDL_memcpy(&pState->sGyroQuatW, pData, nLength);
pData += nLength;
}
return true;
}
static bool UpdateSteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState)
{
ValveInReport_t *pInReport = (ValveInReport_t *)pData;
if (pInReport->header.unReportVersion != k_ValveInReportMsgVersion) {
if ((pData[0] & 0x0F) == k_EBLEReportState) {
return UpdateBLESteamControllerState(pData, nDataSize, pState);
}
return false;
}
if ((pInReport->header.ucType != ID_CONTROLLER_STATE) &&
(pInReport->header.ucType != ID_CONTROLLER_BLE_STATE)) {
return false;
}
if (pInReport->header.ucType == ID_CONTROLLER_STATE) {
ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState;
if (pState->unPacketNum == pStatePacket->unPacketNum) {
return true;
}
FormatStatePacketUntilGyro(pState, pStatePacket);
pState->sAccelX = pStatePacket->sAccelX;
pState->sAccelY = pStatePacket->sAccelY;
pState->sAccelZ = pStatePacket->sAccelZ;
pState->sGyroQuatW = pStatePacket->sGyroQuatW;
pState->sGyroQuatX = pStatePacket->sGyroQuatX;
pState->sGyroQuatY = pStatePacket->sGyroQuatY;
pState->sGyroQuatZ = pStatePacket->sGyroQuatZ;
pState->sGyroX = pStatePacket->sGyroX;
pState->sGyroY = pStatePacket->sGyroY;
pState->sGyroZ = pStatePacket->sGyroZ;
} else if (pInReport->header.ucType == ID_CONTROLLER_BLE_STATE) {
ValveControllerBLEStatePacket_t *pBLEStatePacket = &pInReport->payload.controllerBLEState;
ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState;
if (pState->unPacketNum == pStatePacket->unPacketNum) {
return true;
}
FormatStatePacketUntilGyro(pState, pStatePacket);
switch (pBLEStatePacket->ucGyroDataType) {
case 1:
pState->sGyroQuatW = ((float)pBLEStatePacket->sGyro[0]);
pState->sGyroQuatX = ((float)pBLEStatePacket->sGyro[1]);
pState->sGyroQuatY = ((float)pBLEStatePacket->sGyro[2]);
pState->sGyroQuatZ = ((float)pBLEStatePacket->sGyro[3]);
break;
case 2:
pState->sAccelX = pBLEStatePacket->sGyro[0];
pState->sAccelY = pBLEStatePacket->sGyro[1];
pState->sAccelZ = pBLEStatePacket->sGyro[2];
break;
case 3:
pState->sGyroX = pBLEStatePacket->sGyro[0];
pState->sGyroY = pBLEStatePacket->sGyro[1];
pState->sGyroZ = pBLEStatePacket->sGyro[2];
break;
default:
break;
}
}
return true;
}
typedef struct
{
SDL_HIDAPI_Device *device;
bool connected;
bool report_sensors;
uint32_t update_rate_in_us;
Uint64 sensor_timestamp;
Uint64 pairing_time;
SteamControllerPacketAssembler m_assembler;
SteamControllerStateInternal_t m_state;
SteamControllerStateInternal_t m_last_state;
} SDL_DriverSteam_Context;
static bool IsDongle(Uint16 product_id)
{
return (product_id == USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE);
}
static void HIDAPI_DriverSteam_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
}
static void HIDAPI_DriverSteam_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
}
static bool HIDAPI_DriverSteam_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT);
}
static bool HIDAPI_DriverSteam_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
if (!SDL_IsJoystickSteamController(vendor_id, product_id)) {
return false;
}
if (!device) {
return true;
}
if (device->is_bluetooth) {
return true;
}
if (IsDongle(product_id)) {
if (interface_number >= 1 && interface_number <= 4) {
return true;
}
} else {
if (interface_number == 2) {
return true;
}
}
return false;
}
static void HIDAPI_DriverSteam_SetPairingState(SDL_DriverSteam_Context *ctx, bool enabled)
{
static SDL_DriverSteam_Context *s_PairingContext = NULL;
if (enabled && s_PairingContext != NULL) {
return;
}
if (!enabled && s_PairingContext != ctx) {
return;
}
if (ctx->connected) {
return;
}
SetPairingState(ctx->device, enabled);
if (enabled) {
ctx->pairing_time = SDL_GetTicks();
s_PairingContext = ctx;
} else {
ctx->pairing_time = 0;
s_PairingContext = NULL;
}
}
static void HIDAPI_DriverSteam_RenewPairingState(SDL_DriverSteam_Context *ctx)
{
Uint64 now = SDL_GetTicks();
if (now >= ctx->pairing_time + PAIRING_STATE_DURATION_SECONDS * 1000) {
SetPairingState(ctx->device, true);
ctx->pairing_time = now;
}
}
static void HIDAPI_DriverSteam_CommitPairing(SDL_DriverSteam_Context *ctx)
{
CommitPairing(ctx->device);
}
static void SDLCALL SDL_PairingEnabledHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata;
bool enabled = SDL_GetStringBoolean(hint, false);
HIDAPI_DriverSteam_SetPairingState(ctx, enabled);
}
static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverSteam_Context *ctx;
ctx = (SDL_DriverSteam_Context *)SDL_calloc(1, sizeof(*ctx));
if (!ctx) {
return false;
}
ctx->device = device;
device->context = ctx;
#ifdef SDL_PLATFORM_WIN32
if (device->serial) {
SDL_free(device->serial);
device->serial = NULL;
}
#endif
HIDAPI_SetDeviceName(device, "Steam Controller");
if (IsDongle(device->product_id)) {
unsigned char buf[65];
int res;
SDL_zero(buf);
buf[0] = 0;
buf[1] = ID_DONGLE_GET_WIRELESS_STATE;
res = SetFeatureReport(device, buf, 2);
if (res < 0) {
return SDL_SetError("Failed to send ID_DONGLE_GET_WIRELESS_STATE request");
}
for (int attempt = 0; attempt < 5; ++attempt) {
uint8_t data[128];
res = ReadSteamController(device->dev, data, sizeof(data));
if (res == 0) {
SDL_Delay(1);
continue;
}
if (res < 0) {
break;
}
#ifdef DEBUG_STEAM_PROTOCOL
HIDAPI_DumpPacket("Initial dongle packet: size = %d", data, res);
#endif
if (D0G_IS_WIRELESS_CONNECT(data, res)) {
ctx->connected = true;
break;
} else if (D0G_IS_WIRELESS_DISCONNECT(data, res)) {
ctx->connected = false;
break;
}
}
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED,
SDL_PairingEnabledHintChanged, ctx);
} else {
ctx->connected = true;
}
if (ctx->connected) {
return HIDAPI_JoystickConnected(device, NULL);
} else {
return true;
}
}
static int HIDAPI_DriverSteam_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverSteam_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static bool SetHomeLED(SDL_HIDAPI_Device *device, Uint8 value)
{
unsigned char buf[65];
int nSettings = 0;
SDL_zero(buf);
buf[1] = ID_SET_SETTINGS_VALUES;
ADD_SETTING(SETTING_LED_USER_BRIGHTNESS, value);
buf[2] = (unsigned char)(nSettings * 3);
if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) {
return SDL_SetError("Couldn't write feature report");
}
return true;
}
static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata;
if (hint && *hint) {
int value;
if (SDL_strchr(hint, '.') != NULL) {
value = (int)(100.0f * SDL_atof(hint));
if (value > 255) {
value = 255;
}
} else if (SDL_GetStringBoolean(hint, true)) {
value = 100;
} else {
value = 0;
}
SetHomeLED(ctx->device, (Uint8)value);
}
}
static bool HIDAPI_DriverSteam_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
float update_rate_in_hz = 0.0f;
SDL_AssertJoysticksLocked();
ctx->report_sensors = false;
SDL_zero(ctx->m_assembler);
SDL_zero(ctx->m_state);
SDL_zero(ctx->m_last_state);
if (!ResetSteamController(device, false, &ctx->update_rate_in_us)) {
SDL_SetError("Couldn't reset controller");
return false;
}
if (ctx->update_rate_in_us > 0) {
update_rate_in_hz = 1000000.0f / ctx->update_rate_in_us;
}
InitializeSteamControllerPacketAssembler(&ctx->m_assembler, device->is_bluetooth);
joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_BUTTONS;
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
if (IsDongle(device->product_id)) {
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
}
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED,
SDL_HomeLEDHintChanged, ctx);
return true;
}
static bool HIDAPI_DriverSteam_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverSteam_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverSteam_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
return 0;
}
static bool HIDAPI_DriverSteam_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverSteam_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
if (size == 65) {
if (SetFeatureReport(device, data, size) < 0) {
return SDL_SetError("Couldn't write feature report");
}
return true;
}
return SDL_Unsupported();
}
static bool HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
{
SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
unsigned char buf[65];
int nSettings = 0;
SDL_zero(buf);
buf[1] = ID_SET_SETTINGS_VALUES;
if (enabled) {
ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO);
} else {
ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_OFF);
}
buf[2] = (unsigned char)(nSettings * 3);
if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) {
return SDL_SetError("Couldn't write feature report");
}
ctx->report_sensors = enabled;
return true;
}
static bool ControllerConnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick)
{
SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
if (!HIDAPI_JoystickConnected(device, NULL)) {
return false;
}
HIDAPI_DriverSteam_CommitPairing(ctx);
*joystick = SDL_GetJoystickFromID(device->joysticks[0]);
ctx->connected = true;
return true;
}
static void ControllerDisconnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick)
{
SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
if (device->joysticks) {
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
ctx->connected = false;
*joystick = NULL;
}
static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
SDL_Joystick *joystick = NULL;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
}
if (ctx->pairing_time) {
HIDAPI_DriverSteam_RenewPairingState(ctx);
}
for (;;) {
uint8_t data[128];
int r, nPacketLength;
const Uint8 *pPacket;
r = ReadSteamController(device->dev, data, sizeof(data));
if (r == 0) {
break;
}
if (r < 0) {
ControllerDisconnected(device, &joystick);
return false;
}
#ifdef DEBUG_STEAM_PROTOCOL
HIDAPI_DumpPacket("Steam Controller packet: size = %d", data, r);
#endif
nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&ctx->m_assembler, data, r);
pPacket = ctx->m_assembler.uBuffer;
if (nPacketLength > 0 && UpdateSteamControllerState(pPacket, nPacketLength, &ctx->m_state)) {
Uint64 timestamp = SDL_GetTicksNS();
if (!ctx->connected) {
ControllerConnected(device, &joystick);
}
if (!joystick) {
continue;
}
if (ctx->m_state.ulButtons != ctx->m_last_state.ulButtons) {
Uint8 hat = 0;
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,
((ctx->m_state.ulButtons & STEAM_BUTTON_SOUTH_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,
((ctx->m_state.ulButtons & STEAM_BUTTON_EAST_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,
((ctx->m_state.ulButtons & STEAM_BUTTON_WEST_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,
((ctx->m_state.ulButtons & STEAM_BUTTON_NORTH_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
((ctx->m_state.ulButtons & STEAM_LEFT_BUMPER_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
((ctx->m_state.ulButtons & STEAM_RIGHT_BUMPER_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,
((ctx->m_state.ulButtons & STEAM_BUTTON_MENU_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,
((ctx->m_state.ulButtons & STEAM_BUTTON_ESCAPE_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,
((ctx->m_state.ulButtons & STEAM_BUTTON_STEAM_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,
((ctx->m_state.ulButtons & STEAM_JOYSTICK_BUTTON_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE,
((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_LEFT_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE,
((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_RIGHT_MASK) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,
((ctx->m_state.ulButtons & STEAM_BUTTON_RIGHTPAD_CLICKED_MASK) != 0));
if (ctx->m_state.ulButtons & STEAM_DPAD_UP_MASK) {
hat |= SDL_HAT_UP;
}
if (ctx->m_state.ulButtons & STEAM_DPAD_DOWN_MASK) {
hat |= SDL_HAT_DOWN;
}
if (ctx->m_state.ulButtons & STEAM_DPAD_LEFT_MASK) {
hat |= SDL_HAT_LEFT;
}
if (ctx->m_state.ulButtons & STEAM_DPAD_RIGHT_MASK) {
hat |= SDL_HAT_RIGHT;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (int)ctx->m_state.sTriggerL * 2 - 32768);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (int)ctx->m_state.sTriggerR * 2 - 32768);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ctx->m_state.sLeftStickX);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~ctx->m_state.sLeftStickY);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, ctx->m_state.sRightPadX);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~ctx->m_state.sRightPadY);
if (ctx->report_sensors) {
float values[3];
ctx->sensor_timestamp += SDL_US_TO_NS(ctx->update_rate_in_us);
values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp, values, 3);
values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[1] = (ctx->m_state.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[2] = (-ctx->m_state.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp, values, 3);
}
ctx->m_last_state = ctx->m_state;
} else if (!ctx->connected && D0G_IS_WIRELESS_CONNECT(pPacket, nPacketLength)) {
ControllerConnected(device, &joystick);
} else if (ctx->connected && D0G_IS_WIRELESS_DISCONNECT(pPacket, nPacketLength)) {
ControllerDisconnected(device, &joystick);
}
}
return true;
}
static void HIDAPI_DriverSteam_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED,
SDL_HomeLEDHintChanged, ctx);
CloseSteamController(device);
}
static void HIDAPI_DriverSteam_FreeDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
if (IsDongle(device->product_id)) {
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED,
SDL_PairingEnabledHintChanged, ctx);
HIDAPI_DriverSteam_SetPairingState(ctx, false);
}
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam = {
SDL_HINT_JOYSTICK_HIDAPI_STEAM,
true,
HIDAPI_DriverSteam_RegisterHints,
HIDAPI_DriverSteam_UnregisterHints,
HIDAPI_DriverSteam_IsEnabled,
HIDAPI_DriverSteam_IsSupportedDevice,
HIDAPI_DriverSteam_InitDevice,
HIDAPI_DriverSteam_GetDevicePlayerIndex,
HIDAPI_DriverSteam_SetDevicePlayerIndex,
HIDAPI_DriverSteam_UpdateDevice,
HIDAPI_DriverSteam_OpenJoystick,
HIDAPI_DriverSteam_RumbleJoystick,
HIDAPI_DriverSteam_RumbleJoystickTriggers,
HIDAPI_DriverSteam_GetJoystickCapabilities,
HIDAPI_DriverSteam_SetJoystickLED,
HIDAPI_DriverSteam_SendJoystickEffect,
HIDAPI_DriverSteam_SetSensorsEnabled,
HIDAPI_DriverSteam_CloseJoystick,
HIDAPI_DriverSteam_FreeDevice,
};
#endif
#endif