old data
This commit is contained in:
@@ -0,0 +1,705 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 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_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_8BITDO
|
||||
|
||||
// Define this if you want to log all packets from the controller
|
||||
#if 0
|
||||
#define DEBUG_8BITDO_PROTOCOL
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
SDL_GAMEPAD_BUTTON_8BITDO_L4 = 11,
|
||||
SDL_GAMEPAD_BUTTON_8BITDO_R4,
|
||||
SDL_GAMEPAD_BUTTON_8BITDO_PL,
|
||||
SDL_GAMEPAD_BUTTON_8BITDO_PR,
|
||||
SDL_GAMEPAD_NUM_8BITDO_BUTTONS,
|
||||
};
|
||||
|
||||
#define SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID 0x06
|
||||
#define SDL_8BITDO_REPORTID_SDL_REPORTID 0x04
|
||||
#define SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID 0x03
|
||||
#define SDL_8BITDO_BT_REPORTID_SDL_REPORTID 0x01
|
||||
|
||||
#define SDL_8BITDO_SENSOR_TIMESTAMP_ENABLE 0xAA
|
||||
#define ABITDO_ACCEL_SCALE 4096.f
|
||||
#define ABITDO_GYRO_MAX_DEGREES_PER_SECOND 2000.f
|
||||
|
||||
|
||||
#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \
|
||||
(((Uint32)(B)) << 8) | \
|
||||
(((Uint32)(C)) << 16) | \
|
||||
(((Uint32)(D)) << 24))
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool sensors_supported;
|
||||
bool sensors_enabled;
|
||||
bool touchpad_01_supported;
|
||||
bool touchpad_02_supported;
|
||||
bool rumble_supported;
|
||||
bool rumble_type;
|
||||
bool rgb_supported;
|
||||
bool player_led_supported;
|
||||
bool powerstate_supported;
|
||||
bool sensor_timestamp_supported;
|
||||
Uint8 serial[6];
|
||||
Uint16 version;
|
||||
Uint16 version_beta;
|
||||
float accelScale;
|
||||
float gyroScale;
|
||||
Uint8 last_state[USB_PACKET_LENGTH];
|
||||
Uint64 sensor_timestamp; // Nanoseconds. Simulate onboard clock. Different models have different rates vs different connection styles.
|
||||
Uint64 sensor_timestamp_interval;
|
||||
Uint32 last_tick;
|
||||
} SDL_Driver8BitDo_Context;
|
||||
|
||||
#pragma pack(push,1)
|
||||
typedef struct
|
||||
{
|
||||
bool sensors_supported;
|
||||
bool touchpad_01_supported;
|
||||
bool touchpad_02_supported;
|
||||
bool rumble_supported;
|
||||
bool rumble_type;
|
||||
bool rgb_supported;
|
||||
Uint8 device_type;
|
||||
Uint8 serial[6];
|
||||
Uint16 version;
|
||||
Uint16 version_beta;
|
||||
Uint16 pid;
|
||||
} ABITDO_DEVICE_INFO;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// Accelerometer values
|
||||
short sAccelX;
|
||||
short sAccelY;
|
||||
short sAccelZ;
|
||||
|
||||
// Gyroscope values
|
||||
short sGyroX;
|
||||
short sGyroY;
|
||||
short sGyroZ;
|
||||
} ABITDO_SENSORS;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
static void HIDAPI_Driver8BitDo_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_Driver8BitDo_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
|
||||
}
|
||||
|
||||
static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
|
||||
{
|
||||
SDL_memset(report, 0, length);
|
||||
report[0] = report_id;
|
||||
return SDL_hid_get_feature_report(dev, report, length);
|
||||
}
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_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 (vendor_id == USB_VENDOR_8BITDO) {
|
||||
switch (product_id) {
|
||||
case USB_PRODUCT_8BITDO_SF30_PRO:
|
||||
case USB_PRODUCT_8BITDO_SF30_PRO_BT:
|
||||
case USB_PRODUCT_8BITDO_SN30_PRO:
|
||||
case USB_PRODUCT_8BITDO_SN30_PRO_BT:
|
||||
case USB_PRODUCT_8BITDO_PRO_2:
|
||||
case USB_PRODUCT_8BITDO_PRO_2_BT:
|
||||
case USB_PRODUCT_8BITDO_PRO_3:
|
||||
case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
device->context = ctx;
|
||||
|
||||
if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) {
|
||||
// The Ultimate 2 Wireless v1.02 firmware has 12 byte reports, v1.03 firmware has 34 byte reports
|
||||
const int ULTIMATE2_WIRELESS_V103_REPORT_SIZE = 34;
|
||||
const int MAX_ATTEMPTS = 3;
|
||||
|
||||
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80);
|
||||
if (size == 0) {
|
||||
// Try again
|
||||
continue;
|
||||
}
|
||||
if (size >= ULTIMATE2_WIRELESS_V103_REPORT_SIZE) {
|
||||
ctx->sensors_supported = true;
|
||||
ctx->rumble_supported = true;
|
||||
ctx->powerstate_supported = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
const int MAX_ATTEMPTS = 5;
|
||||
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
|
||||
int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID, data, sizeof(data));
|
||||
if (size > 0) {
|
||||
#ifdef DEBUG_8BITDO_PROTOCOL
|
||||
HIDAPI_DumpPacket("8BitDo features packet: size = %d", data, size);
|
||||
#endif
|
||||
ctx->sensors_supported = true;
|
||||
ctx->rumble_supported = true;
|
||||
ctx->powerstate_supported = true;
|
||||
|
||||
if (size >= 14 && data[13] == SDL_8BITDO_SENSOR_TIMESTAMP_ENABLE) {
|
||||
ctx->sensor_timestamp_supported = true;
|
||||
}
|
||||
|
||||
// Set the serial number to the Bluetooth MAC address
|
||||
if (size >= 12 && data[10] != 0) {
|
||||
char serial[18];
|
||||
(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
|
||||
data[10], data[9], data[8], data[7], data[6], data[5]);
|
||||
HIDAPI_SetDeviceSerial(device, serial);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Try again
|
||||
SDL_Delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
if (device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_SF30_PRO_BT) {
|
||||
HIDAPI_SetDeviceName(device, "8BitDo SF30 Pro");
|
||||
} else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT) {
|
||||
HIDAPI_SetDeviceName(device, "8BitDo SN30 Pro");
|
||||
} else if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) {
|
||||
HIDAPI_SetDeviceName(device, "8BitDo Pro 2");
|
||||
} else if (device->product_id == USB_PRODUCT_8BITDO_PRO_3) {
|
||||
HIDAPI_SetDeviceName(device, "8BitDo Pro 3");
|
||||
}
|
||||
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_Driver8BitDo_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_Driver8BitDo_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
static Uint64 HIDAPI_Driver8BitDo_GetIMURateForProductID(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
|
||||
|
||||
// TODO: If sensor time stamp is sent, these fixed settings from observation can be replaced
|
||||
switch (device->product_id) {
|
||||
case USB_PRODUCT_8BITDO_SF30_PRO:
|
||||
case USB_PRODUCT_8BITDO_SF30_PRO_BT:
|
||||
case USB_PRODUCT_8BITDO_SN30_PRO:
|
||||
case USB_PRODUCT_8BITDO_SN30_PRO_BT:
|
||||
if (device->is_bluetooth) {
|
||||
// Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool
|
||||
return 70; // Observed to be anywhere between 60-90 hz. Possibly lossy in current state
|
||||
} else if (ctx->sensor_timestamp_supported) {
|
||||
// This firmware appears to update at 200 Hz over USB
|
||||
return 200;
|
||||
} else {
|
||||
// This firmware appears to update at 100 Hz over USB
|
||||
return 100;
|
||||
}
|
||||
case USB_PRODUCT_8BITDO_PRO_2:
|
||||
case USB_PRODUCT_8BITDO_PRO_2_BT: // Note, labeled as "BT" but appears this way when wired.
|
||||
case USB_PRODUCT_8BITDO_PRO_3:
|
||||
if (device->is_bluetooth) {
|
||||
// Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool
|
||||
return 85; // Observed Bluetooth packet rate seems to be 80-90hz
|
||||
} else if (ctx->sensor_timestamp_supported) {
|
||||
// This firmware appears to update at 200 Hz over USB
|
||||
return 200;
|
||||
} else {
|
||||
// This firmware appears to update at 100 Hz over USB
|
||||
return 100;
|
||||
}
|
||||
case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS:
|
||||
default:
|
||||
return 120;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef DEG2RAD
|
||||
#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
|
||||
#endif
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
SDL_zeroa(ctx->last_state);
|
||||
|
||||
// Initialize the joystick capabilities
|
||||
if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 ||
|
||||
device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT ||
|
||||
device->product_id == USB_PRODUCT_8BITDO_PRO_3 ||
|
||||
device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) {
|
||||
// This controller has additional buttons
|
||||
joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS;
|
||||
} else {
|
||||
joystick->nbuttons = 11;
|
||||
}
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
|
||||
if (ctx->sensors_supported) {
|
||||
|
||||
// Different 8Bitdo controllers in different connection modes have different polling rates.
|
||||
const Uint64 imu_polling_rate = HIDAPI_Driver8BitDo_GetIMURateForProductID(device);
|
||||
ctx->sensor_timestamp_interval = SDL_NS_PER_SECOND / imu_polling_rate;
|
||||
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, (float)imu_polling_rate);
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, (float)imu_polling_rate);
|
||||
|
||||
ctx->accelScale = SDL_STANDARD_GRAVITY / ABITDO_ACCEL_SCALE;
|
||||
// Hardware senses +/- N Degrees per second mapped to +/- INT16_MAX
|
||||
ctx->gyroScale = DEG2RAD(ABITDO_GYRO_MAX_DEGREES_PER_SECOND) / INT16_MAX;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
|
||||
if (ctx->rumble_supported) {
|
||||
Uint8 rumble_packet[5] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
|
||||
rumble_packet[1] = low_frequency_rumble >> 8;
|
||||
rumble_packet[2] = high_frequency_rumble >> 8;
|
||||
|
||||
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
|
||||
return SDL_SetError("Couldn't send rumble packet");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
}
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_Driver8BitDo_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
|
||||
Uint32 caps = 0;
|
||||
if (ctx->rumble_supported) {
|
||||
caps |= SDL_JOYSTICK_CAP_RUMBLE;
|
||||
}
|
||||
if (ctx->rgb_supported) {
|
||||
caps |= SDL_JOYSTICK_CAP_RGB_LED;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
|
||||
if (ctx->sensors_supported) {
|
||||
ctx->sensors_enabled = enabled;
|
||||
return true;
|
||||
}
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void HIDAPI_Driver8BitDo_HandleOldStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Sint16 axis;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
if (ctx->last_state[2] != data[2]) {
|
||||
Uint8 hat;
|
||||
|
||||
switch (data[2]) {
|
||||
case 0:
|
||||
hat = SDL_HAT_UP;
|
||||
break;
|
||||
case 1:
|
||||
hat = SDL_HAT_RIGHTUP;
|
||||
break;
|
||||
case 2:
|
||||
hat = SDL_HAT_RIGHT;
|
||||
break;
|
||||
case 3:
|
||||
hat = SDL_HAT_RIGHTDOWN;
|
||||
break;
|
||||
case 4:
|
||||
hat = SDL_HAT_DOWN;
|
||||
break;
|
||||
case 5:
|
||||
hat = SDL_HAT_LEFTDOWN;
|
||||
break;
|
||||
case 6:
|
||||
hat = SDL_HAT_LEFT;
|
||||
break;
|
||||
case 7:
|
||||
hat = SDL_HAT_LEFTUP;
|
||||
break;
|
||||
default:
|
||||
hat = SDL_HAT_CENTERED;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
}
|
||||
|
||||
if (ctx->last_state[0] != data[0]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x80) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[1] != data[1]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x40) != 0));
|
||||
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[1] & 0x01) ? SDL_MAX_SINT16 : SDL_MIN_SINT16);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[1] & 0x02) ? SDL_MAX_SINT16 : SDL_MIN_SINT16);
|
||||
}
|
||||
|
||||
#define READ_STICK_AXIS(offset) \
|
||||
(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16))
|
||||
{
|
||||
axis = READ_STICK_AXIS(3);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
|
||||
axis = READ_STICK_AXIS(4);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
|
||||
axis = READ_STICK_AXIS(5);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
|
||||
axis = READ_STICK_AXIS(6);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
|
||||
}
|
||||
#undef READ_STICK_AXIS
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Sint16 axis;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
switch (data[0]) {
|
||||
case SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID: // Firmware without enhanced mode
|
||||
case SDL_8BITDO_REPORTID_SDL_REPORTID: // Enhanced mode USB report
|
||||
case SDL_8BITDO_BT_REPORTID_SDL_REPORTID: // Enhanced mode Bluetooth report
|
||||
break;
|
||||
default:
|
||||
// We don't know how to handle this report
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->last_state[1] != data[1]) {
|
||||
Uint8 hat;
|
||||
|
||||
switch (data[1]) {
|
||||
case 0:
|
||||
hat = SDL_HAT_UP;
|
||||
break;
|
||||
case 1:
|
||||
hat = SDL_HAT_RIGHTUP;
|
||||
break;
|
||||
case 2:
|
||||
hat = SDL_HAT_RIGHT;
|
||||
break;
|
||||
case 3:
|
||||
hat = SDL_HAT_RIGHTDOWN;
|
||||
break;
|
||||
case 4:
|
||||
hat = SDL_HAT_DOWN;
|
||||
break;
|
||||
case 5:
|
||||
hat = SDL_HAT_LEFTDOWN;
|
||||
break;
|
||||
case 6:
|
||||
hat = SDL_HAT_LEFT;
|
||||
break;
|
||||
case 7:
|
||||
hat = SDL_HAT_LEFTUP;
|
||||
break;
|
||||
default:
|
||||
hat = SDL_HAT_CENTERED;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
}
|
||||
|
||||
|
||||
if (ctx->last_state[8] != data[8]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[8] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[8] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[8] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[8] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[8] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[8] & 0x80) != 0));
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PL, ((data[8] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PR, ((data[8] & 0x04) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[9] != data[9]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[9] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[9] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[9] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[9] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0));
|
||||
}
|
||||
|
||||
if (size > 10 && ctx->last_state[10] != data[10]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_L4, ((data[10] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_R4, ((data[10] & 0x02) != 0));
|
||||
}
|
||||
|
||||
#define READ_STICK_AXIS(offset) \
|
||||
(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16))
|
||||
{
|
||||
axis = READ_STICK_AXIS(2);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
|
||||
axis = READ_STICK_AXIS(3);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
|
||||
axis = READ_STICK_AXIS(4);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
|
||||
axis = READ_STICK_AXIS(5);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
|
||||
}
|
||||
#undef READ_STICK_AXIS
|
||||
|
||||
#define READ_TRIGGER_AXIS(offset) \
|
||||
(Sint16)(((int)data[offset] * 257) - 32768)
|
||||
{
|
||||
axis = READ_TRIGGER_AXIS(7);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
|
||||
axis = READ_TRIGGER_AXIS(6);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
|
||||
}
|
||||
#undef READ_TRIGGER_AXIS
|
||||
|
||||
if (ctx->powerstate_supported) {
|
||||
SDL_PowerState state;
|
||||
int percent;
|
||||
Uint8 status = data[14] >> 7;
|
||||
Uint8 level = (data[14] & 0x7f);
|
||||
if (level == 100) {
|
||||
status = 2;
|
||||
}
|
||||
switch (status) {
|
||||
case 0:
|
||||
state = SDL_POWERSTATE_ON_BATTERY;
|
||||
percent = level;
|
||||
break;
|
||||
case 1:
|
||||
state = SDL_POWERSTATE_CHARGING;
|
||||
percent = level;
|
||||
break;
|
||||
case 2:
|
||||
state = SDL_POWERSTATE_CHARGED;
|
||||
percent = 100;
|
||||
break;
|
||||
default:
|
||||
state = SDL_POWERSTATE_UNKNOWN;
|
||||
percent = 0;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickPowerInfo(joystick, state, percent);
|
||||
}
|
||||
|
||||
if (ctx->sensors_enabled) {
|
||||
Uint64 sensor_timestamp;
|
||||
float values[3];
|
||||
ABITDO_SENSORS *sensors = (ABITDO_SENSORS *)&data[15];
|
||||
|
||||
if (ctx->sensor_timestamp_supported) {
|
||||
Uint32 delta;
|
||||
Uint32 tick = LOAD32(data[27], data[28], data[29], data[30]);
|
||||
|
||||
if (ctx->last_tick) {
|
||||
if (ctx->last_tick < tick) {
|
||||
delta = (tick - ctx->last_tick);
|
||||
} else {
|
||||
delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1);
|
||||
}
|
||||
// Sanity check the delta value
|
||||
if (delta < 100000) {
|
||||
ctx->sensor_timestamp_interval = SDL_US_TO_NS(delta);
|
||||
}
|
||||
}
|
||||
ctx->last_tick = tick;
|
||||
}
|
||||
|
||||
// Note: we cannot use the time stamp of the receiving computer due to packet delay creating "spiky" timings.
|
||||
// The imu time stamp is intended to be the sample time of the on-board hardware.
|
||||
// In the absence of time stamp data from the data[], we can simulate that by
|
||||
// advancing a time stamp by the observed/known imu clock rate. This is 8ms = 125 Hz
|
||||
sensor_timestamp = ctx->sensor_timestamp;
|
||||
ctx->sensor_timestamp += ctx->sensor_timestamp_interval;
|
||||
|
||||
// This device's IMU values are reported differently from SDL
|
||||
// Thus we perform a rotation of the coordinate system to match the SDL standard.
|
||||
|
||||
// By observation of this device:
|
||||
// Hardware x is reporting roll (rotation about the power jack's axis)
|
||||
// Hardware y is reporting pitch (rotation about the horizontal axis)
|
||||
// Hardware z is reporting yaw (rotation about the joysticks' center axis)
|
||||
values[0] = -sensors->sGyroY * ctx->gyroScale; // Rotation around pitch axis
|
||||
values[1] = sensors->sGyroZ * ctx->gyroScale; // Rotation around yaw axis
|
||||
values[2] = -sensors->sGyroX * ctx->gyroScale; // Rotation around roll axis
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3);
|
||||
|
||||
// By observation of this device:
|
||||
// Accelerometer X is positive when front of the controller points toward the sky.
|
||||
// Accelerometer y is positive when left side of the controller points toward the sky.
|
||||
// Accelerometer Z is positive when sticks point toward the sky.
|
||||
values[0] = -sensors->sAccelY * ctx->accelScale; // Acceleration along pitch axis
|
||||
values[1] = sensors->sAccelZ * ctx->accelScale; // Acceleration along yaw axis
|
||||
values[2] = -sensors->sAccelX * ctx->accelScale; // Acceleration along roll axis
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3);
|
||||
}
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static bool HIDAPI_Driver8BitDo_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = 0;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
|
||||
#ifdef DEBUG_8BITDO_PROTOCOL
|
||||
HIDAPI_DumpPacket("8BitDo packet: size = %d", data, size);
|
||||
#endif
|
||||
if (!joystick) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (size == 9) {
|
||||
// Old firmware USB report for the SF30 Pro and SN30 Pro controllers
|
||||
HIDAPI_Driver8BitDo_HandleOldStatePacket(joystick, ctx, data, size);
|
||||
} else {
|
||||
HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size);
|
||||
}
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
// Read error, device is disconnected
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
return (size >= 0);
|
||||
}
|
||||
|
||||
static void HIDAPI_Driver8BitDo_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
}
|
||||
|
||||
static void HIDAPI_Driver8BitDo_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_8BITDO,
|
||||
true,
|
||||
HIDAPI_Driver8BitDo_RegisterHints,
|
||||
HIDAPI_Driver8BitDo_UnregisterHints,
|
||||
HIDAPI_Driver8BitDo_IsEnabled,
|
||||
HIDAPI_Driver8BitDo_IsSupportedDevice,
|
||||
HIDAPI_Driver8BitDo_InitDevice,
|
||||
HIDAPI_Driver8BitDo_GetDevicePlayerIndex,
|
||||
HIDAPI_Driver8BitDo_SetDevicePlayerIndex,
|
||||
HIDAPI_Driver8BitDo_UpdateDevice,
|
||||
HIDAPI_Driver8BitDo_OpenJoystick,
|
||||
HIDAPI_Driver8BitDo_RumbleJoystick,
|
||||
HIDAPI_Driver8BitDo_RumbleJoystickTriggers,
|
||||
HIDAPI_Driver8BitDo_GetJoystickCapabilities,
|
||||
HIDAPI_Driver8BitDo_SetJoystickLED,
|
||||
HIDAPI_Driver8BitDo_SendJoystickEffect,
|
||||
HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled,
|
||||
HIDAPI_Driver8BitDo_CloseJoystick,
|
||||
HIDAPI_Driver8BitDo_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_8BITDO
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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 Joy-Cons pair controllers
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI
|
||||
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "../SDL_sysjoystick.h"
|
||||
|
||||
static void HIDAPI_DriverCombined_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverCombined_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_IsEnabled(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_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)
|
||||
{
|
||||
// This is always explicitly created for combined devices
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverCombined_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverCombined_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
int i;
|
||||
char *serial = NULL, *new_serial;
|
||||
size_t serial_length = 0, new_length;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
for (i = 0; i < device->num_children; ++i) {
|
||||
SDL_HIDAPI_Device *child = device->children[i];
|
||||
if (!child->driver->OpenJoystick(child, joystick)) {
|
||||
child->broken = true;
|
||||
|
||||
while (i-- > 0) {
|
||||
child = device->children[i];
|
||||
child->driver->CloseJoystick(child, joystick);
|
||||
}
|
||||
if (serial) {
|
||||
SDL_free(serial);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extend the serial number with the child serial number
|
||||
if (joystick->serial) {
|
||||
new_length = serial_length + 1 + SDL_strlen(joystick->serial);
|
||||
new_serial = (char *)SDL_realloc(serial, new_length);
|
||||
if (new_serial) {
|
||||
if (serial) {
|
||||
SDL_strlcat(new_serial, ",", new_length);
|
||||
SDL_strlcat(new_serial, joystick->serial, new_length);
|
||||
} else {
|
||||
SDL_strlcpy(new_serial, joystick->serial, new_length);
|
||||
}
|
||||
serial = new_serial;
|
||||
serial_length = new_length;
|
||||
}
|
||||
SDL_free(joystick->serial);
|
||||
joystick->serial = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the joystick with the combined serial numbers
|
||||
if (joystick->serial) {
|
||||
SDL_free(joystick->serial);
|
||||
}
|
||||
joystick->serial = serial;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
int i;
|
||||
bool result = false;
|
||||
|
||||
for (i = 0; i < device->num_children; ++i) {
|
||||
SDL_HIDAPI_Device *child = device->children[i];
|
||||
if (child->driver->RumbleJoystick(child, joystick, low_frequency_rumble, high_frequency_rumble)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
int i;
|
||||
bool result = false;
|
||||
|
||||
for (i = 0; i < device->num_children; ++i) {
|
||||
SDL_HIDAPI_Device *child = device->children[i];
|
||||
if (child->driver->RumbleJoystickTriggers(child, joystick, left_rumble, right_rumble)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverCombined_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
int i;
|
||||
Uint32 caps = 0;
|
||||
|
||||
for (i = 0; i < device->num_children; ++i) {
|
||||
SDL_HIDAPI_Device *child = device->children[i];
|
||||
caps |= child->driver->GetJoystickCapabilities(child, joystick);
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
int i;
|
||||
bool result = false;
|
||||
|
||||
for (i = 0; i < device->num_children; ++i) {
|
||||
SDL_HIDAPI_Device *child = device->children[i];
|
||||
if (child->driver->SetJoystickLED(child, joystick, red, green, blue)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
int i;
|
||||
bool result = false;
|
||||
|
||||
for (i = 0; i < device->num_children; ++i) {
|
||||
SDL_HIDAPI_Device *child = device->children[i];
|
||||
if (child->driver->SetJoystickSensorsEnabled(child, joystick, enabled)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverCombined_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
int i;
|
||||
int result = true;
|
||||
|
||||
for (i = 0; i < device->num_children; ++i) {
|
||||
SDL_HIDAPI_Device *child = device->children[i];
|
||||
if (!child->driver->UpdateDevice(child)) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverCombined_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < device->num_children; ++i) {
|
||||
SDL_HIDAPI_Device *child = device->children[i];
|
||||
child->driver->CloseJoystick(child, joystick);
|
||||
}
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverCombined_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined = {
|
||||
"SDL_JOYSTICK_HIDAPI_COMBINED",
|
||||
true,
|
||||
HIDAPI_DriverCombined_RegisterHints,
|
||||
HIDAPI_DriverCombined_UnregisterHints,
|
||||
HIDAPI_DriverCombined_IsEnabled,
|
||||
HIDAPI_DriverCombined_IsSupportedDevice,
|
||||
HIDAPI_DriverCombined_InitDevice,
|
||||
HIDAPI_DriverCombined_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverCombined_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverCombined_UpdateDevice,
|
||||
HIDAPI_DriverCombined_OpenJoystick,
|
||||
HIDAPI_DriverCombined_RumbleJoystick,
|
||||
HIDAPI_DriverCombined_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverCombined_GetJoystickCapabilities,
|
||||
HIDAPI_DriverCombined_SetJoystickLED,
|
||||
HIDAPI_DriverCombined_SendJoystickEffect,
|
||||
HIDAPI_DriverCombined_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverCombined_CloseJoystick,
|
||||
HIDAPI_DriverCombined_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
@@ -0,0 +1,518 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 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_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI
|
||||
|
||||
// Define this if you want to log all packets from the controller
|
||||
#if 0
|
||||
#define DEBUG_FLYDIGI_PROTOCOL
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
SDL_GAMEPAD_BUTTON_FLYDIGI_M1 = 11,
|
||||
SDL_GAMEPAD_BUTTON_FLYDIGI_M2,
|
||||
SDL_GAMEPAD_BUTTON_FLYDIGI_M3,
|
||||
SDL_GAMEPAD_BUTTON_FLYDIGI_M4,
|
||||
SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS
|
||||
};
|
||||
|
||||
/* Rate of IMU Sensor Packets over wireless Dongle observed in testcontroller tool at 1000hz */
|
||||
#define SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ 1000
|
||||
#define SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ)
|
||||
/* Rate of IMU Sensor Packets over wired observed in testcontroller tool connection at 500hz */
|
||||
#define SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ 500
|
||||
#define SENSOR_INTERVAL_VADER_PRO4_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ)
|
||||
|
||||
#define FLYDIGI_CMD_REPORT_ID 0x05
|
||||
#define FLYDIGI_HAPTIC_COMMAND 0x0F
|
||||
#define FLYDIGI_GET_CONFIG_COMMAND 0xEB
|
||||
#define FLYDIGI_GET_INFO_COMMAND 0xEC
|
||||
|
||||
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Uint8 deviceID;
|
||||
bool has_cz;
|
||||
bool wireless;
|
||||
bool sensors_supported;
|
||||
bool sensors_enabled;
|
||||
Uint16 firmware_version;
|
||||
Uint64 sensor_timestamp_ns; // Simulate onboard clock. Advance by known time step. Nanoseconds.
|
||||
Uint64 sensor_timestamp_step_ns; // Based on observed rate of receipt of IMU sensor packets.
|
||||
float accelScale;
|
||||
Uint8 last_state[USB_PACKET_LENGTH];
|
||||
} SDL_DriverFlydigi_Context;
|
||||
|
||||
|
||||
static void HIDAPI_DriverFlydigi_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverFlydigi_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_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)
|
||||
{
|
||||
return SDL_IsJoystickFlydigiController(vendor_id, product_id) && interface_number == 2;
|
||||
}
|
||||
|
||||
static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
|
||||
|
||||
// Detecting the Vader 2 can take over 1000 read retries, so be generous here
|
||||
for (int attempt = 0; ctx->deviceID == 0 && attempt < 30; ++attempt) {
|
||||
const Uint8 request[] = { FLYDIGI_CMD_REPORT_ID, FLYDIGI_GET_INFO_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
// This write will occasionally return -1, so ignore failure here and try again
|
||||
(void)SDL_hid_write(device->dev, request, sizeof(request));
|
||||
|
||||
// Read the reply
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
SDL_Delay(1);
|
||||
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0);
|
||||
if (size < 0) {
|
||||
break;
|
||||
}
|
||||
if (size == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_FLYDIGI_PROTOCOL
|
||||
HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size);
|
||||
#endif
|
||||
if (size == 32 && data[15] == 236) {
|
||||
ctx->deviceID = data[3];
|
||||
ctx->firmware_version = data[9] | (data[10] << 8);
|
||||
|
||||
char serial[9];
|
||||
(void)SDL_snprintf(serial, sizeof(serial), "%.2x%.2x%.2x%.2x", data[5], data[6], data[7], data[8]);
|
||||
HIDAPI_SetDeviceSerial(device, serial);
|
||||
|
||||
// The Vader 2 with firmware 6.0.4.9 doesn't report the connection state
|
||||
if (ctx->firmware_version >= 0x6400) {
|
||||
switch (data[13]) {
|
||||
case 0:
|
||||
// Wireless connection
|
||||
ctx->wireless = true;
|
||||
break;
|
||||
case 1:
|
||||
// Wired connection
|
||||
ctx->wireless = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Done!
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->deviceID == 0) {
|
||||
// Try to guess from the name of the controller
|
||||
if (SDL_strstr(device->name, "VADER") != NULL) {
|
||||
if (SDL_strstr(device->name, "VADER2") != NULL) {
|
||||
ctx->deviceID = 20;
|
||||
} else if (SDL_strstr(device->name, "VADER3") != NULL) {
|
||||
ctx->deviceID = 28;
|
||||
} else if (SDL_strstr(device->name, "VADER4") != NULL) {
|
||||
ctx->deviceID = 85;
|
||||
}
|
||||
} else if (SDL_strstr(device->name, "APEX") != NULL) {
|
||||
if (SDL_strstr(device->name, "APEX2") != NULL) {
|
||||
ctx->deviceID = 19;
|
||||
} else if (SDL_strstr(device->name, "APEX3") != NULL) {
|
||||
ctx->deviceID = 24;
|
||||
} else if (SDL_strstr(device->name, "APEX4") != NULL) {
|
||||
ctx->deviceID = 84;
|
||||
}
|
||||
}
|
||||
}
|
||||
device->guid.data[15] = ctx->deviceID;
|
||||
|
||||
// This is the previous sensor default of 125hz.
|
||||
// Override this in the switch statement below based on observed sensor packet rate.
|
||||
ctx->sensor_timestamp_step_ns = SDL_NS_PER_SECOND / 125;
|
||||
|
||||
switch (ctx->deviceID) {
|
||||
case 19:
|
||||
HIDAPI_SetDeviceName(device, "Flydigi Apex 2");
|
||||
break;
|
||||
case 24:
|
||||
case 26:
|
||||
case 29:
|
||||
HIDAPI_SetDeviceName(device, "Flydigi Apex 3");
|
||||
break;
|
||||
case 84:
|
||||
// The Apex 4 controller has sensors, but they're only reported when gyro mouse is enabled
|
||||
HIDAPI_SetDeviceName(device, "Flydigi Apex 4");
|
||||
break;
|
||||
case 20:
|
||||
case 21:
|
||||
case 23:
|
||||
// The Vader 2 controller has sensors, but they're only reported when gyro mouse is enabled
|
||||
HIDAPI_SetDeviceName(device, "Flydigi Vader 2");
|
||||
ctx->has_cz = true;
|
||||
break;
|
||||
case 22:
|
||||
HIDAPI_SetDeviceName(device, "Flydigi Vader 2 Pro");
|
||||
ctx->has_cz = true;
|
||||
break;
|
||||
case 28:
|
||||
HIDAPI_SetDeviceName(device, "Flydigi Vader 3");
|
||||
ctx->has_cz = true;
|
||||
break;
|
||||
case 80:
|
||||
case 81:
|
||||
HIDAPI_SetDeviceName(device, "Flydigi Vader 3 Pro");
|
||||
ctx->has_cz = true;
|
||||
ctx->sensors_supported = true;
|
||||
ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f;
|
||||
ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER_PRO4_WIRED_NS;
|
||||
break;
|
||||
case 85:
|
||||
case 105:
|
||||
HIDAPI_SetDeviceName(device, "Flydigi Vader 4 Pro");
|
||||
ctx->has_cz = true;
|
||||
ctx->sensors_supported = true;
|
||||
ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f;
|
||||
ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER_PRO4_WIRED_NS;
|
||||
break;
|
||||
default:
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Unknown FlyDigi controller with ID %d, name '%s'", ctx->deviceID, device->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
device->context = ctx;
|
||||
|
||||
UpdateDeviceIdentity(device);
|
||||
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverFlydigi_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverFlydigi_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
#ifndef DEG2RAD
|
||||
#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
|
||||
#endif
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
SDL_zeroa(ctx->last_state);
|
||||
|
||||
// Initialize the joystick capabilities
|
||||
joystick->nbuttons = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS;
|
||||
if (ctx->has_cz) {
|
||||
joystick->nbuttons += 2;
|
||||
}
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
|
||||
if (ctx->wireless) {
|
||||
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
|
||||
}
|
||||
|
||||
if (ctx->sensors_supported) {
|
||||
|
||||
const float flSensorRate = ctx->wireless ? (float)SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ : (float)SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ;
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, flSensorRate);
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, flSensorRate);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
Uint8 rumble_packet[4] = { FLYDIGI_CMD_REPORT_ID, FLYDIGI_HAPTIC_COMMAND, 0x00, 0x00 };
|
||||
rumble_packet[2] = low_frequency_rumble >> 8;
|
||||
rumble_packet[3] = high_frequency_rumble >> 8;
|
||||
|
||||
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
|
||||
return SDL_SetError("Couldn't send rumble packet");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverFlydigi_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
return SDL_JOYSTICK_CAP_RUMBLE;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
|
||||
if (ctx->sensors_supported) {
|
||||
ctx->sensors_enabled = enabled;
|
||||
return true;
|
||||
}
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Sint16 axis;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
if (data[0] != 0x04 && data[0] != 0xFE) {
|
||||
// We don't know how to handle this report
|
||||
return;
|
||||
}
|
||||
|
||||
Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS;
|
||||
|
||||
if (ctx->last_state[9] != data[9]) {
|
||||
Uint8 hat;
|
||||
|
||||
switch (data[9] & 0x0F) {
|
||||
case 0b0001:
|
||||
hat = SDL_HAT_UP;
|
||||
break;
|
||||
case 0b0011:
|
||||
hat = SDL_HAT_RIGHTUP;
|
||||
break;
|
||||
case 0b0010:
|
||||
hat = SDL_HAT_RIGHT;
|
||||
break;
|
||||
case 0b0110:
|
||||
hat = SDL_HAT_RIGHTDOWN;
|
||||
break;
|
||||
case 0b0100:
|
||||
hat = SDL_HAT_DOWN;
|
||||
break;
|
||||
case 0b1100:
|
||||
hat = SDL_HAT_LEFTDOWN;
|
||||
break;
|
||||
case 0b1000:
|
||||
hat = SDL_HAT_LEFT;
|
||||
break;
|
||||
case 0b1001:
|
||||
hat = SDL_HAT_LEFTUP;
|
||||
break;
|
||||
default:
|
||||
hat = SDL_HAT_CENTERED;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[9] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[9] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[9] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[9] & 0x80) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[10] != data[10]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[10] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[10] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[10] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[10] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[10] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[10] & 0x80) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[7] != data[7]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M1, ((data[7] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M2, ((data[7] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M3, ((data[7] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M4, ((data[7] & 0x20) != 0));
|
||||
if (ctx->has_cz) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x02) != 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->last_state[8] != data[8]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[8] & 0x08) != 0));
|
||||
// The '+' button is used to toggle gyro mouse mode, so don't pass that to the application
|
||||
//SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x01) != 0));
|
||||
// The '-' button is only available on the Vader 2, for simplicity let's ignore that
|
||||
//SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x10) != 0));
|
||||
}
|
||||
|
||||
#define READ_STICK_AXIS(offset) \
|
||||
(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16))
|
||||
{
|
||||
axis = READ_STICK_AXIS(17);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
|
||||
axis = READ_STICK_AXIS(19);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
|
||||
axis = READ_STICK_AXIS(21);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
|
||||
axis = READ_STICK_AXIS(22);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
|
||||
}
|
||||
#undef READ_STICK_AXIS
|
||||
|
||||
#define READ_TRIGGER_AXIS(offset) \
|
||||
(Sint16)(((int)data[offset] * 257) - 32768)
|
||||
{
|
||||
axis = READ_TRIGGER_AXIS(23);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
|
||||
axis = READ_TRIGGER_AXIS(24);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
|
||||
}
|
||||
#undef READ_TRIGGER_AXIS
|
||||
|
||||
if (ctx->sensors_enabled) {
|
||||
Uint64 sensor_timestamp;
|
||||
float values[3];
|
||||
|
||||
// Advance the imu sensor time stamp based on the observed rate of receipt of packets in the testcontroller app.
|
||||
// This varies between Product ID and connection type.
|
||||
sensor_timestamp = ctx->sensor_timestamp_ns;
|
||||
ctx->sensor_timestamp_ns += ctx->sensor_timestamp_step_ns;
|
||||
|
||||
// Pitch and yaw scales may be receiving extra filtering for the sake of bespoke direct mouse output.
|
||||
// As result, roll has a different scaling factor than pitch and yaw.
|
||||
// These values were estimated using the testcontroller tool in lieux of hard data sheet references.
|
||||
const float flPitchAndYawScale = DEG2RAD(72000.0f);
|
||||
const float flRollScale = DEG2RAD(1200.0f);
|
||||
values[0] = HIDAPI_RemapVal(-1.0f * LOAD16(data[26], data[27]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale);
|
||||
values[1] = HIDAPI_RemapVal(-1.0f * LOAD16(data[18], data[20]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale);
|
||||
values[2] = HIDAPI_RemapVal(-1.0f * LOAD16(data[29], data[30]), INT16_MIN, INT16_MAX, -flRollScale, flRollScale);
|
||||
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3);
|
||||
|
||||
values[0] = -LOAD16(data[11], data[12]) * ctx->accelScale; // Acceleration along pitch axis
|
||||
values[1] = LOAD16(data[15], data[16]) * ctx->accelScale; // Acceleration along yaw axis
|
||||
values[2] = LOAD16(data[13], data[14]) * ctx->accelScale; // Acceleration along roll axis
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3);
|
||||
}
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = 0;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
|
||||
#ifdef DEBUG_FLYDIGI_PROTOCOL
|
||||
HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size);
|
||||
#endif
|
||||
if (!joystick) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HIDAPI_DriverFlydigi_HandleStatePacket(joystick, ctx, data, size);
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
// Read error, device is disconnected
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
return (size >= 0);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverFlydigi_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverFlydigi_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI,
|
||||
true,
|
||||
HIDAPI_DriverFlydigi_RegisterHints,
|
||||
HIDAPI_DriverFlydigi_UnregisterHints,
|
||||
HIDAPI_DriverFlydigi_IsEnabled,
|
||||
HIDAPI_DriverFlydigi_IsSupportedDevice,
|
||||
HIDAPI_DriverFlydigi_InitDevice,
|
||||
HIDAPI_DriverFlydigi_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverFlydigi_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverFlydigi_UpdateDevice,
|
||||
HIDAPI_DriverFlydigi_OpenJoystick,
|
||||
HIDAPI_DriverFlydigi_RumbleJoystick,
|
||||
HIDAPI_DriverFlydigi_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverFlydigi_GetJoystickCapabilities,
|
||||
HIDAPI_DriverFlydigi_SetJoystickLED,
|
||||
HIDAPI_DriverFlydigi_SendJoystickEffect,
|
||||
HIDAPI_DriverFlydigi_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverFlydigi_CloseJoystick,
|
||||
HIDAPI_DriverFlydigi_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_FLYDIGI
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
@@ -0,0 +1,533 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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_c.h"
|
||||
#include "../SDL_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
#include "../../hidapi/SDL_hidapi_c.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
|
||||
|
||||
// Define this if you want to log all packets from the controller
|
||||
#if 0
|
||||
#define DEBUG_GAMECUBE_PROTOCOL
|
||||
#endif
|
||||
|
||||
#define MAX_CONTROLLERS 4
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool pc_mode;
|
||||
SDL_JoystickID joysticks[MAX_CONTROLLERS];
|
||||
Uint8 wireless[MAX_CONTROLLERS];
|
||||
Uint8 min_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT];
|
||||
Uint8 max_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT];
|
||||
Uint8 rumbleAllowed[MAX_CONTROLLERS];
|
||||
Uint8 rumble[1 + MAX_CONTROLLERS];
|
||||
// Without this variable, hid_write starts to lag a TON
|
||||
bool rumbleUpdate;
|
||||
bool useRumbleBrake;
|
||||
} SDL_DriverGameCube_Context;
|
||||
|
||||
static void HIDAPI_DriverGameCube_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverGameCube_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
|
||||
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
|
||||
SDL_HIDAPI_DEFAULT));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_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 (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
|
||||
// Nintendo Co., Ltd. Wii U GameCube Controller Adapter
|
||||
return true;
|
||||
}
|
||||
if (vendor_id == USB_VENDOR_DRAGONRISE &&
|
||||
(product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 ||
|
||||
product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 ||
|
||||
product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3)) {
|
||||
// EVORETRO GameCube Controller Adapter
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index)
|
||||
{
|
||||
SDL_memset(&ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 - 88, SDL_GAMEPAD_AXIS_COUNT);
|
||||
SDL_memset(&ctx->max_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 + 88, SDL_GAMEPAD_AXIS_COUNT);
|
||||
|
||||
// Trigger axes may have a higher resting value
|
||||
ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_LEFT_TRIGGER] = 40;
|
||||
ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = 40;
|
||||
}
|
||||
|
||||
static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
|
||||
{
|
||||
if (hint) {
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
|
||||
ctx->useRumbleBrake = SDL_GetStringBoolean(hint, false);
|
||||
}
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx;
|
||||
Uint8 packet[37];
|
||||
Uint8 *curSlot;
|
||||
Uint8 i;
|
||||
int size;
|
||||
Uint8 initMagic = 0x13;
|
||||
Uint8 rumbleMagic = 0x11;
|
||||
|
||||
#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS
|
||||
SDL_EnableGameCubeAdaptors();
|
||||
#endif
|
||||
|
||||
ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
device->context = ctx;
|
||||
|
||||
ctx->rumble[0] = rumbleMagic;
|
||||
|
||||
if (device->vendor_id != USB_VENDOR_NINTENDO) {
|
||||
ctx->pc_mode = true;
|
||||
}
|
||||
|
||||
if (ctx->pc_mode) {
|
||||
ResetAxisRange(ctx, 0);
|
||||
HIDAPI_JoystickConnected(device, &ctx->joysticks[0]);
|
||||
} else {
|
||||
// This is all that's needed to initialize the device. Really!
|
||||
if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
|
||||
"HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for the adapter to initialize
|
||||
SDL_Delay(10);
|
||||
|
||||
// Add all the applicable joysticks
|
||||
while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
|
||||
#ifdef DEBUG_GAMECUBE_PROTOCOL
|
||||
HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
|
||||
#endif
|
||||
if (size < 37 || packet[0] != 0x21) {
|
||||
continue; // Nothing to do yet...?
|
||||
}
|
||||
|
||||
// Go through all 4 slots
|
||||
curSlot = packet + 1;
|
||||
for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
|
||||
ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
|
||||
|
||||
// Only allow rumble if the adapter's second USB cable is connected
|
||||
ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
|
||||
|
||||
if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless
|
||||
if (ctx->joysticks[i] == 0) {
|
||||
ResetAxisRange(ctx, i);
|
||||
HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
|
||||
}
|
||||
} else {
|
||||
if (ctx->joysticks[i] != 0) {
|
||||
HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
|
||||
ctx->joysticks[i] = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE,
|
||||
SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
|
||||
|
||||
HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
|
||||
Uint8 i;
|
||||
|
||||
for (i = 0; i < 4; ++i) {
|
||||
if (instance_id == ctx->joysticks[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, bool invert_c_stick)
|
||||
{
|
||||
SDL_Joystick *joystick;
|
||||
const Uint8 i = 0; // We have a separate context for each connected controller in PC mode, just use the first index
|
||||
Uint8 v;
|
||||
Sint16 axis_value;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
joystick = SDL_GetJoystickFromID(ctx->joysticks[i]);
|
||||
if (!joystick) {
|
||||
// Hasn't been opened yet, skip
|
||||
return;
|
||||
}
|
||||
|
||||
#define READ_BUTTON(off, flag, button) \
|
||||
SDL_SendJoystickButton( \
|
||||
timestamp, \
|
||||
joystick, \
|
||||
button, \
|
||||
((packet[off] & flag) != 0));
|
||||
READ_BUTTON(0, 0x02, 0) // A
|
||||
READ_BUTTON(0, 0x04, 1) // B
|
||||
READ_BUTTON(0, 0x08, 3) // Y
|
||||
READ_BUTTON(0, 0x01, 2) // X
|
||||
READ_BUTTON(1, 0x80, 4) // DPAD_LEFT
|
||||
READ_BUTTON(1, 0x20, 5) // DPAD_RIGHT
|
||||
READ_BUTTON(1, 0x40, 6) // DPAD_DOWN
|
||||
READ_BUTTON(1, 0x10, 7) // DPAD_UP
|
||||
READ_BUTTON(1, 0x02, 8) // START
|
||||
READ_BUTTON(0, 0x80, 9) // RIGHTSHOULDER
|
||||
/* These two buttons are for the bottoms of the analog triggers.
|
||||
* More than likely, you're going to want to read the axes instead!
|
||||
* -flibit
|
||||
*/
|
||||
READ_BUTTON(0, 0x20, 10) // TRIGGERRIGHT
|
||||
READ_BUTTON(0, 0x10, 11) // TRIGGERLEFT
|
||||
#undef READ_BUTTON
|
||||
|
||||
#define READ_AXIS(off, axis, invert) \
|
||||
v = (invert) ? (0xff - packet[off]) : packet[off]; \
|
||||
if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
|
||||
ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \
|
||||
if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
|
||||
ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \
|
||||
axis_value = (Sint16)HIDAPI_RemapVal(v, ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
|
||||
SDL_SendJoystickAxis( \
|
||||
timestamp, \
|
||||
joystick, \
|
||||
axis, axis_value);
|
||||
READ_AXIS(2, SDL_GAMEPAD_AXIS_LEFTX, 0)
|
||||
READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTY, 1)
|
||||
READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX, invert_c_stick ? 1 : 0)
|
||||
READ_AXIS(4, SDL_GAMEPAD_AXIS_RIGHTY, invert_c_stick ? 0 : 1)
|
||||
READ_AXIS(6, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0)
|
||||
READ_AXIS(7, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0)
|
||||
#undef READ_AXIS
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 *packet, int size)
|
||||
{
|
||||
SDL_Joystick *joystick;
|
||||
Uint8 *curSlot;
|
||||
Uint8 i;
|
||||
Sint16 axis_value;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
if (size < 37 || packet[0] != 0x21) {
|
||||
return; // Nothing to do right now...?
|
||||
}
|
||||
|
||||
// Go through all 4 slots
|
||||
curSlot = packet + 1;
|
||||
for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
|
||||
ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
|
||||
|
||||
// Only allow rumble if the adapter's second USB cable is connected
|
||||
ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
|
||||
|
||||
if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless
|
||||
if (ctx->joysticks[i] == 0) {
|
||||
ResetAxisRange(ctx, i);
|
||||
HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
|
||||
}
|
||||
joystick = SDL_GetJoystickFromID(ctx->joysticks[i]);
|
||||
|
||||
// Hasn't been opened yet, skip
|
||||
if (!joystick) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (ctx->joysticks[i] != 0) {
|
||||
HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
|
||||
ctx->joysticks[i] = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
#define READ_BUTTON(off, flag, button) \
|
||||
SDL_SendJoystickButton( \
|
||||
timestamp, \
|
||||
joystick, \
|
||||
button, \
|
||||
((curSlot[off] & flag) != 0));
|
||||
READ_BUTTON(1, 0x01, 0) // A
|
||||
READ_BUTTON(1, 0x02, 1) // B
|
||||
READ_BUTTON(1, 0x04, 2) // X
|
||||
READ_BUTTON(1, 0x08, 3) // Y
|
||||
READ_BUTTON(1, 0x10, 4) // DPAD_LEFT
|
||||
READ_BUTTON(1, 0x20, 5) // DPAD_RIGHT
|
||||
READ_BUTTON(1, 0x40, 6) // DPAD_DOWN
|
||||
READ_BUTTON(1, 0x80, 7) // DPAD_UP
|
||||
READ_BUTTON(2, 0x01, 8) // START
|
||||
READ_BUTTON(2, 0x02, 9) // RIGHTSHOULDER
|
||||
/* These two buttons are for the bottoms of the analog triggers.
|
||||
* More than likely, you're going to want to read the axes instead!
|
||||
* -flibit
|
||||
*/
|
||||
READ_BUTTON(2, 0x04, 10) // TRIGGERRIGHT
|
||||
READ_BUTTON(2, 0x08, 11) // TRIGGERLEFT
|
||||
#undef READ_BUTTON
|
||||
|
||||
#define READ_AXIS(off, axis) \
|
||||
if (curSlot[off] < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
|
||||
ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \
|
||||
if (curSlot[off] > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
|
||||
ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \
|
||||
axis_value = (Sint16)HIDAPI_RemapVal(curSlot[off], ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
|
||||
SDL_SendJoystickAxis( \
|
||||
timestamp, \
|
||||
joystick, \
|
||||
axis, axis_value);
|
||||
READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX)
|
||||
READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY)
|
||||
READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX)
|
||||
READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTY)
|
||||
READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER)
|
||||
READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)
|
||||
#undef READ_AXIS
|
||||
}
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
|
||||
Uint8 packet[USB_PACKET_LENGTH];
|
||||
int size;
|
||||
|
||||
// Read input packet
|
||||
while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
|
||||
#ifdef DEBUG_GAMECUBE_PROTOCOL
|
||||
HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
|
||||
#endif
|
||||
if (ctx->pc_mode) {
|
||||
if (size == 10) {
|
||||
// This is the older firmware
|
||||
// The first byte is the index of the connected controller
|
||||
// The C stick has an inverted value range compared to the left stick
|
||||
HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, &packet[1], true);
|
||||
} else if (size == 9) {
|
||||
// This is the newer firmware (version 0x7)
|
||||
// The C stick has the same value range compared to the left stick
|
||||
HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, false);
|
||||
} else {
|
||||
// How do we handle this packet?
|
||||
}
|
||||
} else {
|
||||
HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size);
|
||||
}
|
||||
}
|
||||
|
||||
// Write rumble packet
|
||||
if (ctx->rumbleUpdate) {
|
||||
SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
|
||||
ctx->rumbleUpdate = false;
|
||||
}
|
||||
|
||||
// If we got here, nothing bad happened!
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
|
||||
Uint8 i;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
for (i = 0; i < MAX_CONTROLLERS; i += 1) {
|
||||
if (joystick->instance_id == ctx->joysticks[i]) {
|
||||
joystick->nbuttons = 12;
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
if (ctx->wireless[i]) {
|
||||
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
|
||||
} else {
|
||||
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // Should never get here!
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
|
||||
Uint8 i, val;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
if (ctx->pc_mode) {
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_CONTROLLERS; i += 1) {
|
||||
if (joystick->instance_id == ctx->joysticks[i]) {
|
||||
if (ctx->wireless[i]) {
|
||||
return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble");
|
||||
}
|
||||
if (!ctx->rumbleAllowed[i]) {
|
||||
return SDL_SetError("Second USB cable for WUP-028 not connected");
|
||||
}
|
||||
if (ctx->useRumbleBrake) {
|
||||
if (low_frequency_rumble == 0 && high_frequency_rumble > 0) {
|
||||
val = 0; // if only low is 0 we want to do a regular stop
|
||||
} else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) {
|
||||
val = 2; // if both frequencies are 0 we want to do a hard stop
|
||||
} else {
|
||||
val = 1; // normal rumble
|
||||
}
|
||||
} else {
|
||||
val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
|
||||
}
|
||||
if (val != ctx->rumble[i + 1]) {
|
||||
ctx->rumble[i + 1] = val;
|
||||
ctx->rumbleUpdate = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Should never get here!
|
||||
return SDL_SetError("Couldn't find joystick");
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverGameCube_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
|
||||
Uint32 result = 0;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
if (!ctx->pc_mode) {
|
||||
Uint8 i;
|
||||
|
||||
for (i = 0; i < MAX_CONTROLLERS; i += 1) {
|
||||
if (joystick->instance_id == ctx->joysticks[i]) {
|
||||
if (!ctx->wireless[i] && ctx->rumbleAllowed[i]) {
|
||||
result |= SDL_JOYSTICK_CAP_RUMBLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverGameCube_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
|
||||
|
||||
// Stop rumble activity
|
||||
if (ctx->rumbleUpdate) {
|
||||
SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
|
||||
ctx->rumbleUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
|
||||
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE,
|
||||
SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
|
||||
true,
|
||||
HIDAPI_DriverGameCube_RegisterHints,
|
||||
HIDAPI_DriverGameCube_UnregisterHints,
|
||||
HIDAPI_DriverGameCube_IsEnabled,
|
||||
HIDAPI_DriverGameCube_IsSupportedDevice,
|
||||
HIDAPI_DriverGameCube_InitDevice,
|
||||
HIDAPI_DriverGameCube_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverGameCube_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverGameCube_UpdateDevice,
|
||||
HIDAPI_DriverGameCube_OpenJoystick,
|
||||
HIDAPI_DriverGameCube_RumbleJoystick,
|
||||
HIDAPI_DriverGameCube_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverGameCube_GetJoystickCapabilities,
|
||||
HIDAPI_DriverGameCube_SetJoystickLED,
|
||||
HIDAPI_DriverGameCube_SendJoystickEffect,
|
||||
HIDAPI_DriverGameCube_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverGameCube_CloseJoystick,
|
||||
HIDAPI_DriverGameCube_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_GAMECUBE
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_LUNA
|
||||
|
||||
// Define this if you want to log all packets from the controller
|
||||
// #define DEBUG_LUNA_PROTOCOL
|
||||
|
||||
// Sending rumble on macOS blocks for a long time and eventually fails
|
||||
#ifndef SDL_PLATFORM_MACOS
|
||||
#define ENABLE_LUNA_BLUETOOTH_RUMBLE
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE = 11,
|
||||
SDL_GAMEPAD_NUM_LUNA_BUTTONS,
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Uint8 last_state[USB_PACKET_LENGTH];
|
||||
} SDL_DriverLuna_Context;
|
||||
|
||||
static void HIDAPI_DriverLuna_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverLuna_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LUNA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_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)
|
||||
{
|
||||
return SDL_IsJoystickAmazonLunaController(vendor_id, product_id);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverLuna_Context *ctx;
|
||||
|
||||
ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
device->context = ctx;
|
||||
|
||||
HIDAPI_SetDeviceName(device, "Amazon Luna Controller");
|
||||
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverLuna_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverLuna_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
SDL_zeroa(ctx->last_state);
|
||||
|
||||
// Initialize the joystick capabilities
|
||||
joystick->nbuttons = SDL_GAMEPAD_NUM_LUNA_BUTTONS;
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE
|
||||
if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {
|
||||
// Same packet as on Xbox One controllers connected via Bluetooth
|
||||
Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };
|
||||
|
||||
// Magnitude is 1..100 so scale the 16-bit input here
|
||||
rumble_packet[4] = (Uint8)(low_frequency_rumble / 655);
|
||||
rumble_packet[5] = (Uint8)(high_frequency_rumble / 655);
|
||||
|
||||
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
|
||||
return SDL_SetError("Couldn't send rumble packet");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // ENABLE_LUNA_BLUETOOTH_RUMBLE
|
||||
|
||||
// There is currently no rumble packet over USB
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverLuna_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
Uint32 result = 0;
|
||||
|
||||
#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE
|
||||
if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {
|
||||
result |= SDL_JOYSTICK_CAP_RUMBLE;
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverLuna_HandleUSBStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
if (ctx->last_state[1] != data[1]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x80) != 0));
|
||||
}
|
||||
if (ctx->last_state[2] != data[2]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[2] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x08) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[3] != data[3]) {
|
||||
Uint8 hat;
|
||||
|
||||
switch (data[3] & 0x0f) {
|
||||
case 0:
|
||||
hat = SDL_HAT_UP;
|
||||
break;
|
||||
case 1:
|
||||
hat = SDL_HAT_RIGHTUP;
|
||||
break;
|
||||
case 2:
|
||||
hat = SDL_HAT_RIGHT;
|
||||
break;
|
||||
case 3:
|
||||
hat = SDL_HAT_RIGHTDOWN;
|
||||
break;
|
||||
case 4:
|
||||
hat = SDL_HAT_DOWN;
|
||||
break;
|
||||
case 5:
|
||||
hat = SDL_HAT_LEFTDOWN;
|
||||
break;
|
||||
case 6:
|
||||
hat = SDL_HAT_LEFT;
|
||||
break;
|
||||
case 7:
|
||||
hat = SDL_HAT_LEFTUP;
|
||||
break;
|
||||
default:
|
||||
hat = SDL_HAT_CENTERED;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
}
|
||||
|
||||
#define READ_STICK_AXIS(offset) \
|
||||
(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))
|
||||
{
|
||||
Sint16 axis = READ_STICK_AXIS(4);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
|
||||
axis = READ_STICK_AXIS(5);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
|
||||
axis = READ_STICK_AXIS(6);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
|
||||
axis = READ_STICK_AXIS(7);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
|
||||
}
|
||||
#undef READ_STICK_AXIS
|
||||
|
||||
#define READ_TRIGGER_AXIS(offset) \
|
||||
(Sint16) HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16)
|
||||
{
|
||||
Sint16 axis = READ_TRIGGER_AXIS(8);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
|
||||
axis = READ_TRIGGER_AXIS(9);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
|
||||
}
|
||||
#undef READ_TRIGGER_AXIS
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverLuna_HandleBluetoothStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
if (size >= 2 && data[0] == 0x02) {
|
||||
// Home button has dedicated report
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x1) != 0));
|
||||
return;
|
||||
}
|
||||
|
||||
if (size >= 2 && data[0] == 0x04) {
|
||||
// Battery level report
|
||||
int percent = (int)SDL_roundf((data[1] / 255.0f) * 100.0f);
|
||||
SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (size < 17 || data[0] != 0x01) {
|
||||
// We don't know how to handle this report
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->last_state[13] != data[13]) {
|
||||
Uint8 hat;
|
||||
|
||||
switch (data[13] & 0x0f) {
|
||||
case 1:
|
||||
hat = SDL_HAT_UP;
|
||||
break;
|
||||
case 2:
|
||||
hat = SDL_HAT_RIGHTUP;
|
||||
break;
|
||||
case 3:
|
||||
hat = SDL_HAT_RIGHT;
|
||||
break;
|
||||
case 4:
|
||||
hat = SDL_HAT_RIGHTDOWN;
|
||||
break;
|
||||
case 5:
|
||||
hat = SDL_HAT_DOWN;
|
||||
break;
|
||||
case 6:
|
||||
hat = SDL_HAT_LEFTDOWN;
|
||||
break;
|
||||
case 7:
|
||||
hat = SDL_HAT_LEFT;
|
||||
break;
|
||||
case 8:
|
||||
hat = SDL_HAT_LEFTUP;
|
||||
break;
|
||||
default:
|
||||
hat = SDL_HAT_CENTERED;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
}
|
||||
|
||||
if (ctx->last_state[14] != data[14]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0));
|
||||
}
|
||||
if (ctx->last_state[15] != data[15]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0));
|
||||
}
|
||||
if (ctx->last_state[16] != data[16]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[16] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[16] & 0x02) != 0));
|
||||
}
|
||||
|
||||
#define READ_STICK_AXIS(offset) \
|
||||
(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))
|
||||
{
|
||||
Sint16 axis = READ_STICK_AXIS(2);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
|
||||
axis = READ_STICK_AXIS(4);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
|
||||
axis = READ_STICK_AXIS(6);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
|
||||
axis = READ_STICK_AXIS(8);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
|
||||
}
|
||||
#undef READ_STICK_AXIS
|
||||
|
||||
#define READ_TRIGGER_AXIS(offset) \
|
||||
(Sint16) HIDAPI_RemapVal((float)((int)(((data[offset] | (data[offset + 1] << 8)) & 0x3ff) - 0x200)), 0x00 - 0x200, 0x3ff - 0x200, SDL_MIN_SINT16, SDL_MAX_SINT16)
|
||||
{
|
||||
Sint16 axis = READ_TRIGGER_AXIS(9);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
|
||||
axis = READ_TRIGGER_AXIS(11);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
|
||||
}
|
||||
#undef READ_TRIGGER_AXIS
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = 0;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
|
||||
#ifdef DEBUG_LUNA_PROTOCOL
|
||||
HIDAPI_DumpPacket("Amazon Luna packet: size = %d", data, size);
|
||||
#endif
|
||||
if (!joystick) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
case 10:
|
||||
HIDAPI_DriverLuna_HandleUSBStatePacket(joystick, ctx, data, size);
|
||||
break;
|
||||
default:
|
||||
HIDAPI_DriverLuna_HandleBluetoothStatePacket(joystick, ctx, data, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
// Read error, device is disconnected
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
return (size >= 0);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverLuna_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverLuna_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_LUNA,
|
||||
true,
|
||||
HIDAPI_DriverLuna_RegisterHints,
|
||||
HIDAPI_DriverLuna_UnregisterHints,
|
||||
HIDAPI_DriverLuna_IsEnabled,
|
||||
HIDAPI_DriverLuna_IsSupportedDevice,
|
||||
HIDAPI_DriverLuna_InitDevice,
|
||||
HIDAPI_DriverLuna_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverLuna_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverLuna_UpdateDevice,
|
||||
HIDAPI_DriverLuna_OpenJoystick,
|
||||
HIDAPI_DriverLuna_RumbleJoystick,
|
||||
HIDAPI_DriverLuna_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverLuna_GetJoystickCapabilities,
|
||||
HIDAPI_DriverLuna_SetJoystickLED,
|
||||
HIDAPI_DriverLuna_SendJoystickEffect,
|
||||
HIDAPI_DriverLuna_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverLuna_CloseJoystick,
|
||||
HIDAPI_DriverLuna_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_LUNA
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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.
|
||||
*/
|
||||
|
||||
// These are values used in the controller type byte of the controller GUID
|
||||
|
||||
// These values come directly out of the hardware, so don't change them
|
||||
typedef enum
|
||||
{
|
||||
k_eSwitchDeviceInfoControllerType_Unknown = 0,
|
||||
k_eSwitchDeviceInfoControllerType_JoyConLeft = 1,
|
||||
k_eSwitchDeviceInfoControllerType_JoyConRight = 2,
|
||||
k_eSwitchDeviceInfoControllerType_ProController = 3,
|
||||
k_eSwitchDeviceInfoControllerType_LicProController = 6,
|
||||
k_eSwitchDeviceInfoControllerType_HVCLeft = 7,
|
||||
k_eSwitchDeviceInfoControllerType_HVCRight = 8,
|
||||
k_eSwitchDeviceInfoControllerType_NESLeft = 9,
|
||||
k_eSwitchDeviceInfoControllerType_NESRight = 10,
|
||||
k_eSwitchDeviceInfoControllerType_SNES = 11,
|
||||
k_eSwitchDeviceInfoControllerType_N64 = 12,
|
||||
k_eSwitchDeviceInfoControllerType_SEGA_Genesis = 13,
|
||||
} ESwitchDeviceInfoControllerType;
|
||||
|
||||
// These values are used internally but can be updated as needed
|
||||
typedef enum
|
||||
{
|
||||
k_eWiiExtensionControllerType_Unknown = 0,
|
||||
k_eWiiExtensionControllerType_None = 128,
|
||||
k_eWiiExtensionControllerType_Nunchuk = 129,
|
||||
k_eWiiExtensionControllerType_Gamepad = 130,
|
||||
k_eWiiExtensionControllerType_WiiUPro = 131,
|
||||
} EWiiExtensionControllerType;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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
|
||||
|
||||
// Handle rumble on a separate thread so it doesn't block the application
|
||||
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
|
||||
typedef struct SDL_HIDAPI_RumbleRequest
|
||||
{
|
||||
SDL_HIDAPI_Device *device;
|
||||
Uint8 data[2 * USB_PACKET_LENGTH]; // need enough space for the biggest report: dualshock4 is 78 bytes
|
||||
int size;
|
||||
SDL_HIDAPI_RumbleSentCallback callback;
|
||||
void *userdata;
|
||||
struct SDL_HIDAPI_RumbleRequest *prev;
|
||||
|
||||
} SDL_HIDAPI_RumbleRequest;
|
||||
|
||||
typedef struct SDL_HIDAPI_RumbleContext
|
||||
{
|
||||
SDL_AtomicInt initialized;
|
||||
SDL_AtomicInt running;
|
||||
SDL_Thread *thread;
|
||||
SDL_Semaphore *request_sem;
|
||||
SDL_HIDAPI_RumbleRequest *requests_head;
|
||||
SDL_HIDAPI_RumbleRequest *requests_tail;
|
||||
} SDL_HIDAPI_RumbleContext;
|
||||
|
||||
#ifndef SDL_THREAD_SAFETY_ANALYSIS
|
||||
static
|
||||
#endif
|
||||
SDL_Mutex *SDL_HIDAPI_rumble_lock;
|
||||
static SDL_HIDAPI_RumbleContext rumble_context SDL_GUARDED_BY(SDL_HIDAPI_rumble_lock);
|
||||
|
||||
static int SDLCALL SDL_HIDAPI_RumbleThread(void *data)
|
||||
{
|
||||
SDL_HIDAPI_RumbleContext *ctx = (SDL_HIDAPI_RumbleContext *)data;
|
||||
|
||||
SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
|
||||
while (SDL_GetAtomicInt(&ctx->running)) {
|
||||
SDL_HIDAPI_RumbleRequest *request = NULL;
|
||||
|
||||
SDL_WaitSemaphore(ctx->request_sem);
|
||||
|
||||
SDL_LockMutex(SDL_HIDAPI_rumble_lock);
|
||||
request = ctx->requests_tail;
|
||||
if (request) {
|
||||
if (request == ctx->requests_head) {
|
||||
ctx->requests_head = NULL;
|
||||
}
|
||||
ctx->requests_tail = request->prev;
|
||||
}
|
||||
SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);
|
||||
|
||||
if (request) {
|
||||
SDL_LockMutex(request->device->dev_lock);
|
||||
if (request->device->dev) {
|
||||
#ifdef DEBUG_RUMBLE
|
||||
HIDAPI_DumpPacket("Rumble packet: size = %d", request->data, request->size);
|
||||
#endif
|
||||
SDL_hid_write(request->device->dev, request->data, request->size);
|
||||
}
|
||||
SDL_UnlockMutex(request->device->dev_lock);
|
||||
if (request->callback) {
|
||||
request->callback(request->userdata);
|
||||
}
|
||||
(void)SDL_AtomicDecRef(&request->device->rumble_pending);
|
||||
SDL_free(request);
|
||||
|
||||
// Make sure we're not starving report reads when there's lots of rumble
|
||||
SDL_Delay(10);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext *ctx)
|
||||
{
|
||||
SDL_HIDAPI_RumbleRequest *request;
|
||||
|
||||
SDL_SetAtomicInt(&ctx->running, false);
|
||||
|
||||
if (ctx->thread) {
|
||||
int result;
|
||||
|
||||
SDL_SignalSemaphore(ctx->request_sem);
|
||||
SDL_WaitThread(ctx->thread, &result);
|
||||
ctx->thread = NULL;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_HIDAPI_rumble_lock);
|
||||
while (ctx->requests_tail) {
|
||||
request = ctx->requests_tail;
|
||||
if (request == ctx->requests_head) {
|
||||
ctx->requests_head = NULL;
|
||||
}
|
||||
ctx->requests_tail = request->prev;
|
||||
|
||||
if (request->callback) {
|
||||
request->callback(request->userdata);
|
||||
}
|
||||
(void)SDL_AtomicDecRef(&request->device->rumble_pending);
|
||||
SDL_free(request);
|
||||
}
|
||||
SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);
|
||||
|
||||
if (ctx->request_sem) {
|
||||
SDL_DestroySemaphore(ctx->request_sem);
|
||||
ctx->request_sem = NULL;
|
||||
}
|
||||
|
||||
if (SDL_HIDAPI_rumble_lock) {
|
||||
SDL_DestroyMutex(SDL_HIDAPI_rumble_lock);
|
||||
SDL_HIDAPI_rumble_lock = NULL;
|
||||
}
|
||||
|
||||
SDL_SetAtomicInt(&ctx->initialized, false);
|
||||
}
|
||||
|
||||
static bool SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext *ctx)
|
||||
{
|
||||
SDL_HIDAPI_rumble_lock = SDL_CreateMutex();
|
||||
if (!SDL_HIDAPI_rumble_lock) {
|
||||
SDL_HIDAPI_StopRumbleThread(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->request_sem = SDL_CreateSemaphore(0);
|
||||
if (!ctx->request_sem) {
|
||||
SDL_HIDAPI_StopRumbleThread(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_SetAtomicInt(&ctx->running, true);
|
||||
ctx->thread = SDL_CreateThread(SDL_HIDAPI_RumbleThread, "HIDAPI Rumble", ctx);
|
||||
if (!ctx->thread) {
|
||||
SDL_HIDAPI_StopRumbleThread(ctx);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDL_HIDAPI_LockRumble(void)
|
||||
{
|
||||
SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
|
||||
|
||||
if (SDL_CompareAndSwapAtomicInt(&ctx->initialized, false, true)) {
|
||||
if (!SDL_HIDAPI_StartRumbleThread(ctx)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_HIDAPI_rumble_lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size)
|
||||
{
|
||||
SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
|
||||
SDL_HIDAPI_RumbleRequest *request, *found;
|
||||
|
||||
found = NULL;
|
||||
for (request = ctx->requests_tail; request; request = request->prev) {
|
||||
if (request->device == device) {
|
||||
found = request;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
*data = found->data;
|
||||
*size = &found->size;
|
||||
*maximum_size = sizeof(found->data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size)
|
||||
{
|
||||
return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, data, size, NULL, NULL);
|
||||
}
|
||||
|
||||
int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata)
|
||||
{
|
||||
SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
|
||||
SDL_HIDAPI_RumbleRequest *request;
|
||||
|
||||
if (size > sizeof(request->data)) {
|
||||
SDL_HIDAPI_UnlockRumble();
|
||||
SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, (int)sizeof(request->data));
|
||||
return -1;
|
||||
}
|
||||
|
||||
request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request));
|
||||
if (!request) {
|
||||
SDL_HIDAPI_UnlockRumble();
|
||||
return -1;
|
||||
}
|
||||
request->device = device;
|
||||
SDL_memcpy(request->data, data, size);
|
||||
request->size = size;
|
||||
request->callback = callback;
|
||||
request->userdata = userdata;
|
||||
|
||||
SDL_AtomicIncRef(&device->rumble_pending);
|
||||
|
||||
if (ctx->requests_head) {
|
||||
ctx->requests_head->prev = request;
|
||||
} else {
|
||||
ctx->requests_tail = request;
|
||||
}
|
||||
ctx->requests_head = request;
|
||||
|
||||
// Make sure we unlock before posting the semaphore so the rumble thread can run immediately
|
||||
SDL_HIDAPI_UnlockRumble();
|
||||
|
||||
SDL_SignalSemaphore(ctx->request_sem);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void SDL_HIDAPI_UnlockRumble(void)
|
||||
{
|
||||
SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);
|
||||
}
|
||||
|
||||
int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size)
|
||||
{
|
||||
Uint8 *pending_data;
|
||||
int *pending_size;
|
||||
int maximum_size;
|
||||
|
||||
if (size <= 0) {
|
||||
SDL_SetError("Tried to send rumble with invalid size");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!SDL_HIDAPI_LockRumble()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// check if there is a pending request for the device and update it
|
||||
if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size) &&
|
||||
size == *pending_size && data[0] == pending_data[0]) {
|
||||
SDL_memcpy(pending_data, data, size);
|
||||
SDL_HIDAPI_UnlockRumble();
|
||||
return size;
|
||||
}
|
||||
|
||||
return SDL_HIDAPI_SendRumbleAndUnlock(device, data, size);
|
||||
}
|
||||
|
||||
void SDL_HIDAPI_QuitRumble(void)
|
||||
{
|
||||
SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
|
||||
|
||||
if (SDL_GetAtomicInt(&ctx->running)) {
|
||||
SDL_HIDAPI_StopRumbleThread(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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
|
||||
|
||||
// Handle rumble on a separate thread so it doesn't block the application
|
||||
|
||||
// Advanced API
|
||||
#ifdef SDL_THREAD_SAFETY_ANALYSIS
|
||||
extern SDL_Mutex *SDL_HIDAPI_rumble_lock;
|
||||
#endif
|
||||
bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(0, SDL_HIDAPI_rumble_lock);
|
||||
bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size);
|
||||
int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
|
||||
typedef void (*SDL_HIDAPI_RumbleSentCallback)(void *userdata);
|
||||
int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
|
||||
void SDL_HIDAPI_UnlockRumble(void) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
|
||||
|
||||
// Simple API, will replace any pending rumble with the new data
|
||||
int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size);
|
||||
void SDL_HIDAPI_QuitRumble(void);
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_SHIELD
|
||||
|
||||
// Define this if you want to log all packets from the controller
|
||||
// #define DEBUG_SHIELD_PROTOCOL
|
||||
|
||||
#define CMD_BATTERY_STATE 0x07
|
||||
#define CMD_RUMBLE 0x39
|
||||
#define CMD_CHARGE_STATE 0x3A
|
||||
|
||||
// Milliseconds between polls of battery state
|
||||
#define BATTERY_POLL_INTERVAL_MS 60000
|
||||
|
||||
// Milliseconds between retransmission of rumble to keep motors running
|
||||
#define RUMBLE_REFRESH_INTERVAL_MS 500
|
||||
|
||||
// Reports that are too small are dropped over Bluetooth
|
||||
#define HID_REPORT_SIZE 33
|
||||
|
||||
enum
|
||||
{
|
||||
SDL_GAMEPAD_BUTTON_SHIELD_SHARE = 11,
|
||||
SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD,
|
||||
SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS,
|
||||
SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS,
|
||||
SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS,
|
||||
|
||||
SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS = SDL_GAMEPAD_BUTTON_SHIELD_SHARE + 1,
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
k_ShieldReportIdControllerState = 0x01,
|
||||
k_ShieldReportIdControllerTouch = 0x02,
|
||||
k_ShieldReportIdCommandResponse = 0x03,
|
||||
k_ShieldReportIdCommandRequest = 0x04,
|
||||
} EShieldReportId;
|
||||
|
||||
// This same report structure is used for both requests and responses
|
||||
typedef struct
|
||||
{
|
||||
Uint8 report_id;
|
||||
Uint8 cmd;
|
||||
Uint8 seq_num;
|
||||
Uint8 payload[HID_REPORT_SIZE - 3];
|
||||
} ShieldCommandReport_t;
|
||||
SDL_COMPILE_TIME_ASSERT(ShieldCommandReport_t, sizeof(ShieldCommandReport_t) == HID_REPORT_SIZE);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Uint8 seq_num;
|
||||
|
||||
bool has_charging;
|
||||
Uint8 charging;
|
||||
bool has_battery_level;
|
||||
Uint8 battery_level;
|
||||
Uint64 last_battery_query_time;
|
||||
|
||||
bool rumble_report_pending;
|
||||
bool rumble_update_pending;
|
||||
Uint8 left_motor_amplitude;
|
||||
Uint8 right_motor_amplitude;
|
||||
Uint64 last_rumble_time;
|
||||
|
||||
Uint8 last_state[USB_PACKET_LENGTH];
|
||||
} SDL_DriverShield_Context;
|
||||
|
||||
static void HIDAPI_DriverShield_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverShield_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_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)
|
||||
{
|
||||
return SDL_IsJoystickNVIDIASHIELDController(vendor_id, product_id);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverShield_Context *ctx;
|
||||
|
||||
ctx = (SDL_DriverShield_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
device->context = ctx;
|
||||
|
||||
HIDAPI_SetDeviceName(device, "NVIDIA SHIELD Controller");
|
||||
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverShield_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverShield_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd, const void *data, int size)
|
||||
{
|
||||
SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
|
||||
ShieldCommandReport_t cmd_pkt;
|
||||
|
||||
if (size > sizeof(cmd_pkt.payload)) {
|
||||
return SDL_SetError("Command data exceeds HID report size");
|
||||
}
|
||||
|
||||
if (!SDL_HIDAPI_LockRumble()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cmd_pkt.report_id = k_ShieldReportIdCommandRequest;
|
||||
cmd_pkt.cmd = cmd;
|
||||
cmd_pkt.seq_num = ctx->seq_num++;
|
||||
if (data) {
|
||||
SDL_memcpy(cmd_pkt.payload, data, size);
|
||||
}
|
||||
|
||||
// Zero unused data in the payload
|
||||
if (size != sizeof(cmd_pkt.payload)) {
|
||||
SDL_memset(&cmd_pkt.payload[size], 0, sizeof(cmd_pkt.payload) - size);
|
||||
}
|
||||
|
||||
if (SDL_HIDAPI_SendRumbleAndUnlock(device, (Uint8 *)&cmd_pkt, sizeof(cmd_pkt)) != sizeof(cmd_pkt)) {
|
||||
return SDL_SetError("Couldn't send command packet");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
ctx->rumble_report_pending = false;
|
||||
ctx->rumble_update_pending = false;
|
||||
ctx->left_motor_amplitude = 0;
|
||||
ctx->right_motor_amplitude = 0;
|
||||
ctx->last_rumble_time = 0;
|
||||
SDL_zeroa(ctx->last_state);
|
||||
|
||||
// Initialize the joystick capabilities
|
||||
if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {
|
||||
joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS;
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
|
||||
SDL_PrivateJoystickAddTouchpad(joystick, 1);
|
||||
} else {
|
||||
joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS;
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
}
|
||||
|
||||
// Request battery and charging info
|
||||
ctx->last_battery_query_time = SDL_GetTicks();
|
||||
HIDAPI_DriverShield_SendCommand(device, CMD_CHARGE_STATE, NULL, 0);
|
||||
HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_SendNextRumble(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
|
||||
Uint8 rumble_data[3];
|
||||
|
||||
if (!ctx->rumble_update_pending) {
|
||||
return true;
|
||||
}
|
||||
|
||||
rumble_data[0] = 0x01; // enable
|
||||
rumble_data[1] = ctx->left_motor_amplitude;
|
||||
rumble_data[2] = ctx->right_motor_amplitude;
|
||||
|
||||
ctx->rumble_update_pending = false;
|
||||
ctx->last_rumble_time = SDL_GetTicks();
|
||||
|
||||
return HIDAPI_DriverShield_SendCommand(device, CMD_RUMBLE, rumble_data, sizeof(rumble_data));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {
|
||||
Uint8 rumble_packet[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
rumble_packet[2] = (low_frequency_rumble >> 8);
|
||||
rumble_packet[4] = (high_frequency_rumble >> 8);
|
||||
|
||||
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
|
||||
return SDL_SetError("Couldn't send rumble packet");
|
||||
}
|
||||
return true;
|
||||
|
||||
} else {
|
||||
SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
|
||||
|
||||
// The rumble motors are quite intense, so tone down the intensity like the official driver does
|
||||
ctx->left_motor_amplitude = low_frequency_rumble >> 11;
|
||||
ctx->right_motor_amplitude = high_frequency_rumble >> 11;
|
||||
ctx->rumble_update_pending = true;
|
||||
|
||||
if (ctx->rumble_report_pending) {
|
||||
// We will service this after the hardware acknowledges the previous request
|
||||
return true;
|
||||
}
|
||||
|
||||
return HIDAPI_DriverShield_SendNextRumble(device);
|
||||
}
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverShield_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
return SDL_JOYSTICK_CAP_RUMBLE;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
const Uint8 *data_bytes = (const Uint8 *)data;
|
||||
|
||||
if (size > 1) {
|
||||
// Single command byte followed by a variable length payload
|
||||
return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], &data_bytes[1], size - 1);
|
||||
} else if (size == 1) {
|
||||
// Single command byte with no payload
|
||||
return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], NULL, 0);
|
||||
} else {
|
||||
return SDL_SetError("Effect data must at least contain a command byte");
|
||||
}
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
if (ctx->last_state[3] != data[3]) {
|
||||
Uint8 hat;
|
||||
|
||||
switch (data[3]) {
|
||||
case 0:
|
||||
hat = SDL_HAT_UP;
|
||||
break;
|
||||
case 1:
|
||||
hat = SDL_HAT_RIGHTUP;
|
||||
break;
|
||||
case 2:
|
||||
hat = SDL_HAT_RIGHT;
|
||||
break;
|
||||
case 3:
|
||||
hat = SDL_HAT_RIGHTDOWN;
|
||||
break;
|
||||
case 4:
|
||||
hat = SDL_HAT_DOWN;
|
||||
break;
|
||||
case 5:
|
||||
hat = SDL_HAT_LEFTDOWN;
|
||||
break;
|
||||
case 6:
|
||||
hat = SDL_HAT_LEFT;
|
||||
break;
|
||||
case 7:
|
||||
hat = SDL_HAT_LEFTUP;
|
||||
break;
|
||||
default:
|
||||
hat = SDL_HAT_CENTERED;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
}
|
||||
|
||||
if (ctx->last_state[1] != data[1]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x80) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[2] != data[2]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS, ((data[2] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS, ((data[2] & 0x10) != 0));
|
||||
//SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0));
|
||||
//SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[2] & 0x80) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x80) != 0));
|
||||
}
|
||||
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[4]) - 0x8000);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[6]) - 0x8000);
|
||||
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[8]) - 0x8000);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[10]) - 0x8000);
|
||||
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[12]) - 0x8000);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[14]) - 0x8000);
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
#undef clamp
|
||||
#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
|
||||
|
||||
static void HIDAPI_DriverShield_HandleTouchPacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, const Uint8 *data, int size)
|
||||
{
|
||||
bool touchpad_down;
|
||||
float touchpad_x, touchpad_y;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD, ((data[1] & 0x01) != 0));
|
||||
|
||||
// It's a triangular pad, but just use the center as the usable touch area
|
||||
touchpad_down = ((data[1] & 0x80) == 0);
|
||||
touchpad_x = clamp((float)(data[2] - 0x70) / 0x50, 0.0f, 1.0f);
|
||||
touchpad_y = clamp((float)(data[4] - 0x40) / 0x15, 0.0f, 1.0f);
|
||||
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x, touchpad_y, touchpad_down ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
if (size < 23) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->last_state[2] != data[2]) {
|
||||
Uint8 hat;
|
||||
|
||||
switch (data[2]) {
|
||||
case 0:
|
||||
hat = SDL_HAT_UP;
|
||||
break;
|
||||
case 1:
|
||||
hat = SDL_HAT_RIGHTUP;
|
||||
break;
|
||||
case 2:
|
||||
hat = SDL_HAT_RIGHT;
|
||||
break;
|
||||
case 3:
|
||||
hat = SDL_HAT_RIGHTDOWN;
|
||||
break;
|
||||
case 4:
|
||||
hat = SDL_HAT_DOWN;
|
||||
break;
|
||||
case 5:
|
||||
hat = SDL_HAT_LEFTDOWN;
|
||||
break;
|
||||
case 6:
|
||||
hat = SDL_HAT_LEFT;
|
||||
break;
|
||||
case 7:
|
||||
hat = SDL_HAT_LEFTUP;
|
||||
break;
|
||||
default:
|
||||
hat = SDL_HAT_CENTERED;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
}
|
||||
|
||||
if (ctx->last_state[3] != data[3]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[3] & 0x80) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[4] != data[4]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[4] & 0x01) != 0));
|
||||
}
|
||||
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[9]) - 0x8000);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[11]) - 0x8000);
|
||||
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[13]) - 0x8000);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[15]) - 0x8000);
|
||||
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[19]) - 0x8000);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[21]) - 0x8000);
|
||||
|
||||
if (ctx->last_state[17] != data[17]) {
|
||||
//SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[17] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[17] & 0x02) != 0));
|
||||
//SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[17] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[17] & 0x01) != 0));
|
||||
}
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverShield_UpdatePowerInfo(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx)
|
||||
{
|
||||
if (!ctx->has_charging || !ctx->has_battery_level) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_PowerState state = ctx->charging ? SDL_POWERSTATE_CHARGING : SDL_POWERSTATE_ON_BATTERY;
|
||||
int percent = ctx->battery_level * 20;
|
||||
SDL_SendJoystickPowerInfo(joystick, state, percent);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = 0;
|
||||
ShieldCommandReport_t *cmd_resp_report;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
|
||||
#ifdef DEBUG_SHIELD_PROTOCOL
|
||||
HIDAPI_DumpPacket("NVIDIA SHIELD packet: size = %d", data, size);
|
||||
#endif
|
||||
|
||||
// Byte 0 is HID report ID
|
||||
switch (data[0]) {
|
||||
case k_ShieldReportIdControllerState:
|
||||
if (!joystick) {
|
||||
break;
|
||||
}
|
||||
if (size == 16) {
|
||||
HIDAPI_DriverShield_HandleStatePacketV103(joystick, ctx, data, size);
|
||||
} else {
|
||||
HIDAPI_DriverShield_HandleStatePacketV104(joystick, ctx, data, size);
|
||||
}
|
||||
break;
|
||||
case k_ShieldReportIdControllerTouch:
|
||||
if (!joystick) {
|
||||
break;
|
||||
}
|
||||
HIDAPI_DriverShield_HandleTouchPacketV103(joystick, ctx, data, size);
|
||||
break;
|
||||
case k_ShieldReportIdCommandResponse:
|
||||
cmd_resp_report = (ShieldCommandReport_t *)data;
|
||||
switch (cmd_resp_report->cmd) {
|
||||
case CMD_RUMBLE:
|
||||
ctx->rumble_report_pending = false;
|
||||
HIDAPI_DriverShield_SendNextRumble(device);
|
||||
break;
|
||||
case CMD_CHARGE_STATE:
|
||||
ctx->has_charging = true;
|
||||
ctx->charging = cmd_resp_report->payload[0];
|
||||
HIDAPI_DriverShield_UpdatePowerInfo(joystick, ctx);
|
||||
break;
|
||||
case CMD_BATTERY_STATE:
|
||||
ctx->has_battery_level = true;
|
||||
ctx->battery_level = cmd_resp_report->payload[2];
|
||||
HIDAPI_DriverShield_UpdatePowerInfo(joystick, ctx);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ask for battery state again if we're due for an update
|
||||
if (joystick && SDL_GetTicks() >= (ctx->last_battery_query_time + BATTERY_POLL_INTERVAL_MS)) {
|
||||
ctx->last_battery_query_time = SDL_GetTicks();
|
||||
HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0);
|
||||
}
|
||||
|
||||
// Retransmit rumble packets if they've lasted longer than the hardware supports
|
||||
if ((ctx->left_motor_amplitude != 0 || ctx->right_motor_amplitude != 0) &&
|
||||
SDL_GetTicks() >= (ctx->last_rumble_time + RUMBLE_REFRESH_INTERVAL_MS)) {
|
||||
ctx->rumble_update_pending = true;
|
||||
HIDAPI_DriverShield_SendNextRumble(device);
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
// Read error, device is disconnected
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
return (size >= 0);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverShield_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverShield_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_SHIELD,
|
||||
true,
|
||||
HIDAPI_DriverShield_RegisterHints,
|
||||
HIDAPI_DriverShield_UnregisterHints,
|
||||
HIDAPI_DriverShield_IsEnabled,
|
||||
HIDAPI_DriverShield_IsSupportedDevice,
|
||||
HIDAPI_DriverShield_InitDevice,
|
||||
HIDAPI_DriverShield_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverShield_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverShield_UpdateDevice,
|
||||
HIDAPI_DriverShield_OpenJoystick,
|
||||
HIDAPI_DriverShield_RumbleJoystick,
|
||||
HIDAPI_DriverShield_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverShield_GetJoystickCapabilities,
|
||||
HIDAPI_DriverShield_SetJoystickLED,
|
||||
HIDAPI_DriverShield_SendJoystickEffect,
|
||||
HIDAPI_DriverShield_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverShield_CloseJoystick,
|
||||
HIDAPI_DriverShield_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_SHIELD
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
@@ -0,0 +1,907 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 2025 Mitchell Cairns <mitch.cairns@handheldlegend.com>
|
||||
|
||||
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_c.h"
|
||||
#include "../SDL_sysjoystick.h"
|
||||
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_SINPUT
|
||||
|
||||
/*****************************************************************************************************/
|
||||
// This protocol is documented at:
|
||||
// https://docs.handheldlegend.com/s/sinput/doc/sinput-hid-protocol-TkPYWlDMAg
|
||||
/*****************************************************************************************************/
|
||||
|
||||
// Define this if you want to log all packets from the controller
|
||||
#if 0
|
||||
#define DEBUG_SINPUT_PROTOCOL
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
#define DEBUG_SINPUT_INIT
|
||||
#endif
|
||||
|
||||
#define SINPUT_DEVICE_REPORT_SIZE 64 // Size of input reports (And CMD Input reports)
|
||||
#define SINPUT_DEVICE_REPORT_COMMAND_SIZE 48 // Size of command OUTPUT reports
|
||||
|
||||
#define SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT 0x01
|
||||
#define SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT 0x02
|
||||
#define SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT 0x03
|
||||
|
||||
#define SINPUT_DEVICE_COMMAND_HAPTIC 0x01
|
||||
#define SINPUT_DEVICE_COMMAND_FEATURES 0x02
|
||||
#define SINPUT_DEVICE_COMMAND_PLAYERLED 0x03
|
||||
#define SINPUT_DEVICE_COMMAND_JOYSTICKRGB 0x04
|
||||
|
||||
#define SINPUT_HAPTIC_TYPE_PRECISE 0x01
|
||||
#define SINPUT_HAPTIC_TYPE_ERMSIMULATION 0x02
|
||||
|
||||
#define SINPUT_DEFAULT_GYRO_SENS 2000
|
||||
#define SINPUT_DEFAULT_ACCEL_SENS 8
|
||||
|
||||
#define SINPUT_REPORT_IDX_BUTTONS_0 3
|
||||
#define SINPUT_REPORT_IDX_BUTTONS_1 4
|
||||
#define SINPUT_REPORT_IDX_BUTTONS_2 5
|
||||
#define SINPUT_REPORT_IDX_BUTTONS_3 6
|
||||
#define SINPUT_REPORT_IDX_LEFT_X 7
|
||||
#define SINPUT_REPORT_IDX_LEFT_Y 9
|
||||
#define SINPUT_REPORT_IDX_RIGHT_X 11
|
||||
#define SINPUT_REPORT_IDX_RIGHT_Y 13
|
||||
#define SINPUT_REPORT_IDX_LEFT_TRIGGER 15
|
||||
#define SINPUT_REPORT_IDX_RIGHT_TRIGGER 17
|
||||
#define SINPUT_REPORT_IDX_IMU_TIMESTAMP 19
|
||||
#define SINPUT_REPORT_IDX_IMU_ACCEL_X 23
|
||||
#define SINPUT_REPORT_IDX_IMU_ACCEL_Y 25
|
||||
#define SINPUT_REPORT_IDX_IMU_ACCEL_Z 27
|
||||
#define SINPUT_REPORT_IDX_IMU_GYRO_X 29
|
||||
#define SINPUT_REPORT_IDX_IMU_GYRO_Y 31
|
||||
#define SINPUT_REPORT_IDX_IMU_GYRO_Z 33
|
||||
#define SINPUT_REPORT_IDX_TOUCH1_X 35
|
||||
#define SINPUT_REPORT_IDX_TOUCH1_Y 37
|
||||
#define SINPUT_REPORT_IDX_TOUCH1_P 39
|
||||
#define SINPUT_REPORT_IDX_TOUCH2_X 41
|
||||
#define SINPUT_REPORT_IDX_TOUCH2_Y 43
|
||||
#define SINPUT_REPORT_IDX_TOUCH2_P 45
|
||||
|
||||
#define SINPUT_BUTTON_IDX_SOUTH 0
|
||||
#define SINPUT_BUTTON_IDX_EAST 1
|
||||
#define SINPUT_BUTTON_IDX_WEST 2
|
||||
#define SINPUT_BUTTON_IDX_NORTH 3
|
||||
#define SINPUT_BUTTON_IDX_DPAD_UP 4
|
||||
#define SINPUT_BUTTON_IDX_DPAD_DOWN 5
|
||||
#define SINPUT_BUTTON_IDX_DPAD_LEFT 6
|
||||
#define SINPUT_BUTTON_IDX_DPAD_RIGHT 7
|
||||
#define SINPUT_BUTTON_IDX_LEFT_STICK 8
|
||||
#define SINPUT_BUTTON_IDX_RIGHT_STICK 9
|
||||
#define SINPUT_BUTTON_IDX_LEFT_BUMPER 10
|
||||
#define SINPUT_BUTTON_IDX_RIGHT_BUMPER 11
|
||||
#define SINPUT_BUTTON_IDX_LEFT_TRIGGER 12
|
||||
#define SINPUT_BUTTON_IDX_RIGHT_TRIGGER 13
|
||||
#define SINPUT_BUTTON_IDX_LEFT_PADDLE1 14
|
||||
#define SINPUT_BUTTON_IDX_RIGHT_PADDLE1 15
|
||||
#define SINPUT_BUTTON_IDX_START 16
|
||||
#define SINPUT_BUTTON_IDX_BACK 17
|
||||
#define SINPUT_BUTTON_IDX_GUIDE 18
|
||||
#define SINPUT_BUTTON_IDX_CAPTURE 19
|
||||
#define SINPUT_BUTTON_IDX_LEFT_PADDLE2 20
|
||||
#define SINPUT_BUTTON_IDX_RIGHT_PADDLE2 21
|
||||
#define SINPUT_BUTTON_IDX_TOUCHPAD1 22
|
||||
#define SINPUT_BUTTON_IDX_TOUCHPAD2 23
|
||||
#define SINPUT_BUTTON_IDX_POWER 24
|
||||
#define SINPUT_BUTTON_IDX_MISC4 25
|
||||
#define SINPUT_BUTTON_IDX_MISC5 26
|
||||
#define SINPUT_BUTTON_IDX_MISC6 27
|
||||
#define SINPUT_BUTTON_IDX_MISC7 28
|
||||
#define SINPUT_BUTTON_IDX_MISC8 29
|
||||
#define SINPUT_BUTTON_IDX_MISC9 30
|
||||
#define SINPUT_BUTTON_IDX_MISC10 31
|
||||
|
||||
#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_ID 1
|
||||
#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK 2
|
||||
|
||||
#define SINPUT_REPORT_IDX_PLUG_STATUS 1
|
||||
#define SINPUT_REPORT_IDX_CHARGE_LEVEL 2
|
||||
|
||||
#define SINPUT_MAX_ALLOWED_TOUCHPADS 2
|
||||
|
||||
#ifndef EXTRACTSINT16
|
||||
#define EXTRACTSINT16(data, idx) ((Sint16)((data)[(idx)] | ((data)[(idx) + 1] << 8)))
|
||||
#endif
|
||||
|
||||
#ifndef EXTRACTUINT16
|
||||
#define EXTRACTUINT16(data, idx) ((Uint16)((data)[(idx)] | ((data)[(idx) + 1] << 8)))
|
||||
#endif
|
||||
|
||||
#ifndef EXTRACTUINT32
|
||||
#define EXTRACTUINT32(data, idx) ((Uint32)((data)[(idx)] | ((data)[(idx) + 1] << 8) | ((data)[(idx) + 2] << 16) | ((data)[(idx) + 3] << 24)))
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t type;
|
||||
|
||||
union {
|
||||
// Frequency Amplitude pairs
|
||||
struct {
|
||||
struct {
|
||||
uint16_t frequency_1;
|
||||
uint16_t amplitude_1;
|
||||
uint16_t frequency_2;
|
||||
uint16_t amplitude_2;
|
||||
} left;
|
||||
|
||||
struct {
|
||||
uint16_t frequency_1;
|
||||
uint16_t amplitude_1;
|
||||
uint16_t frequency_2;
|
||||
uint16_t amplitude_2;
|
||||
} right;
|
||||
|
||||
} type_1;
|
||||
|
||||
// Basic ERM simulation model
|
||||
struct {
|
||||
struct {
|
||||
uint8_t amplitude;
|
||||
bool brake;
|
||||
} left;
|
||||
|
||||
struct {
|
||||
uint8_t amplitude;
|
||||
bool brake;
|
||||
} right;
|
||||
|
||||
} type_2;
|
||||
};
|
||||
} SINPUT_HAPTIC_S;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_HIDAPI_Device *device;
|
||||
Uint16 protocol_version;
|
||||
bool sensors_enabled;
|
||||
|
||||
Uint8 player_idx;
|
||||
|
||||
bool player_leds_supported;
|
||||
bool joystick_rgb_supported;
|
||||
bool rumble_supported;
|
||||
bool accelerometer_supported;
|
||||
bool gyroscope_supported;
|
||||
bool left_analog_stick_supported;
|
||||
bool right_analog_stick_supported;
|
||||
bool left_analog_trigger_supported;
|
||||
bool right_analog_trigger_supported;
|
||||
bool dpad_supported;
|
||||
bool touchpad_supported;
|
||||
|
||||
Uint8 touchpad_count; // 2 touchpads maximum
|
||||
Uint8 touchpad_finger_count; // 2 fingers for one touchpad, or 1 per touchpad (2 max)
|
||||
|
||||
Uint8 polling_rate_ms;
|
||||
Uint8 sub_type; // Subtype of the device, 0 in most cases
|
||||
|
||||
Uint16 accelRange; // Example would be 2,4,8,16 +/- (g-force)
|
||||
Uint16 gyroRange; // Example would be 1000,2000,4000 +/- (degrees per second)
|
||||
|
||||
float accelScale; // Scale factor for accelerometer values
|
||||
float gyroScale; // Scale factor for gyroscope values
|
||||
Uint8 last_state[USB_PACKET_LENGTH];
|
||||
|
||||
Uint8 buttons_count;
|
||||
Uint8 usage_masks[4];
|
||||
|
||||
Uint32 last_imu_timestamp_us;
|
||||
|
||||
Uint64 imu_timestamp_ns; // Nanoseconds. We accumulate with received deltas
|
||||
} SDL_DriverSInput_Context;
|
||||
|
||||
// Converts raw int16_t gyro scale setting
|
||||
static inline float CalculateGyroScale(uint16_t dps_range)
|
||||
{
|
||||
return SDL_PI_F / 180.0f / (32768.0f / (float)dps_range);
|
||||
}
|
||||
|
||||
// Converts raw int16_t accel scale setting
|
||||
static inline float CalculateAccelScale(uint16_t g_range)
|
||||
{
|
||||
return SDL_STANDARD_GRAVITY / (32768.0f / (float)g_range);
|
||||
}
|
||||
|
||||
static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data)
|
||||
{
|
||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
||||
|
||||
// Obtain protocol version
|
||||
ctx->protocol_version = EXTRACTUINT16(data, 0);
|
||||
|
||||
// Bitfields are not portable, so we unpack them into a struct value
|
||||
ctx->rumble_supported = (data[2] & 0x01) != 0;
|
||||
ctx->player_leds_supported = (data[2] & 0x02) != 0;
|
||||
ctx->accelerometer_supported = (data[2] & 0x04) != 0;
|
||||
ctx->gyroscope_supported = (data[2] & 0x08) != 0;
|
||||
|
||||
ctx->left_analog_stick_supported = (data[2] & 0x10) != 0;
|
||||
ctx->right_analog_stick_supported = (data[2] & 0x20) != 0;
|
||||
ctx->left_analog_trigger_supported = (data[2] & 0x40) != 0;
|
||||
ctx->right_analog_trigger_supported = (data[2] & 0x80) != 0;
|
||||
|
||||
ctx->touchpad_supported = (data[3] & 0x01) != 0;
|
||||
ctx->joystick_rgb_supported = (data[3] & 0x02) != 0;
|
||||
|
||||
SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
|
||||
type = (SDL_GamepadType)SDL_clamp(data[4], SDL_GAMEPAD_TYPE_UNKNOWN, SDL_GAMEPAD_TYPE_COUNT);
|
||||
device->type = type;
|
||||
|
||||
// The 4 MSB represent a button layout style SDL_GamepadFaceStyle
|
||||
// The 4 LSB represent a device sub-type
|
||||
device->guid.data[15] = data[5];
|
||||
|
||||
#if defined(DEBUG_SINPUT_INIT)
|
||||
SDL_Log("SInput Face Style: %d", (data[5] & 0xF0) >> 4);
|
||||
SDL_Log("SInput Sub-type: %d", (data[5] & 0xF));
|
||||
#endif
|
||||
|
||||
ctx->polling_rate_ms = data[6];
|
||||
|
||||
ctx->accelRange = EXTRACTUINT16(data, 8);
|
||||
ctx->gyroRange = EXTRACTUINT16(data, 10);
|
||||
|
||||
// Masks in LSB to MSB
|
||||
// South, East, West, North, DUp, DDown, DLeft, DRight
|
||||
ctx->usage_masks[0] = data[12];
|
||||
|
||||
// Stick Left, Stick Right, L Shoulder, R Shoulder,
|
||||
// L Trigger, R Trigger, L Paddle 1, R Paddle 1
|
||||
ctx->usage_masks[1] = data[13];
|
||||
|
||||
// Start, Back, Guide, Capture, L Paddle 2, R Paddle 2, Touchpad L, Touchpad R
|
||||
ctx->usage_masks[2] = data[14];
|
||||
|
||||
// Power, Misc 4 to 10
|
||||
ctx->usage_masks[3] = data[15];
|
||||
|
||||
// Derive button count from mask
|
||||
for (Uint8 byte = 0; byte < 4; ++byte) {
|
||||
for (Uint8 bit = 0; bit < 8; ++bit) {
|
||||
if ((ctx->usage_masks[byte] & (1 << bit)) != 0) {
|
||||
++ctx->buttons_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert DPAD to hat
|
||||
const int DPAD_MASK = (1 << SINPUT_BUTTON_IDX_DPAD_UP) |
|
||||
(1 << SINPUT_BUTTON_IDX_DPAD_DOWN) |
|
||||
(1 << SINPUT_BUTTON_IDX_DPAD_LEFT) |
|
||||
(1 << SINPUT_BUTTON_IDX_DPAD_RIGHT);
|
||||
if ((ctx->usage_masks[0] & DPAD_MASK) == DPAD_MASK) {
|
||||
ctx->dpad_supported = true;
|
||||
ctx->usage_masks[0] &= ~DPAD_MASK;
|
||||
ctx->buttons_count -= 4;
|
||||
}
|
||||
|
||||
#if defined(DEBUG_SINPUT_INIT)
|
||||
SDL_Log("Buttons count: %d", ctx->buttons_count);
|
||||
#endif
|
||||
|
||||
// Get and validate touchpad parameters
|
||||
ctx->touchpad_count = data[16];
|
||||
ctx->touchpad_finger_count = data[17];
|
||||
|
||||
// Get device Serial - MAC address
|
||||
char serial[18];
|
||||
(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
|
||||
data[18], data[19], data[20], data[21], data[22], data[23]);
|
||||
#if defined(DEBUG_SINPUT_INIT)
|
||||
SDL_Log("Serial num: %s", serial);
|
||||
#endif
|
||||
HIDAPI_SetDeviceSerial(device, serial);
|
||||
|
||||
#if defined(DEBUG_SINPUT_INIT)
|
||||
SDL_Log("Accelerometer Range: %d", ctx->accelRange);
|
||||
#endif
|
||||
|
||||
#if defined(DEBUG_SINPUT_INIT)
|
||||
SDL_Log("Gyro Range: %d", ctx->gyroRange);
|
||||
#endif
|
||||
|
||||
ctx->accelScale = CalculateAccelScale(ctx->accelRange);
|
||||
ctx->gyroScale = CalculateGyroScale(ctx->gyroRange);
|
||||
}
|
||||
|
||||
static bool RetrieveSDLFeatures(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
int written = 0;
|
||||
|
||||
// Attempt to send the SDL features get command.
|
||||
for (int attempt = 0; attempt < 8; ++attempt) {
|
||||
const Uint8 featuresGetCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_FEATURES };
|
||||
// This write will occasionally return -1, so ignore failure here and try again
|
||||
written = SDL_hid_write(device->dev, featuresGetCommand, sizeof(featuresGetCommand));
|
||||
|
||||
if (written == SINPUT_DEVICE_REPORT_COMMAND_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (written < SINPUT_DEVICE_REPORT_COMMAND_SIZE) {
|
||||
SDL_SetError("SInput device SDL Features GET command could not write");
|
||||
return false;
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
|
||||
// Read the reply
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
SDL_Delay(1);
|
||||
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
read = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0);
|
||||
if (read < 0) {
|
||||
SDL_SetError("SInput device SDL Features GET command could not read");
|
||||
return false;
|
||||
}
|
||||
if (read == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SINPUT_PROTOCOL
|
||||
HIDAPI_DumpPacket("SInput packet: size = %d", data, size);
|
||||
#endif
|
||||
|
||||
if ((read == USB_PACKET_LENGTH) && (data[0] == SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT) && (data[1] == SINPUT_DEVICE_COMMAND_FEATURES)) {
|
||||
ProcessSDLFeaturesResponse(device, &(data[SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK]));
|
||||
#if defined(DEBUG_SINPUT_INIT)
|
||||
SDL_Log("Received SInput SDL Features command response");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Type 2 haptics are for more traditional rumble such as
|
||||
// ERM motors or simulated ERM motors
|
||||
static inline void HapticsType2Pack(SINPUT_HAPTIC_S *in, Uint8 *out)
|
||||
{
|
||||
// Type of haptics
|
||||
out[0] = 2;
|
||||
|
||||
out[1] = in->type_2.left.amplitude;
|
||||
out[2] = in->type_2.left.brake;
|
||||
|
||||
out[3] = in->type_2.right.amplitude;
|
||||
out[4] = in->type_2.right.brake;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSInput_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSInput_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSInput_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSInput_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)
|
||||
{
|
||||
return SDL_IsJoystickSInputController(vendor_id, product_id);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSInput_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
#if defined(DEBUG_SINPUT_INIT)
|
||||
SDL_Log("SInput device Init");
|
||||
#endif
|
||||
|
||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->device = device;
|
||||
device->context = ctx;
|
||||
|
||||
if (!RetrieveSDLFeatures(device)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverSInput_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSInput_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
||||
|
||||
if (ctx->player_leds_supported) {
|
||||
player_index = SDL_clamp(player_index + 1, 0, 255);
|
||||
Uint8 player_num = (Uint8)player_index;
|
||||
|
||||
ctx->player_idx = player_num;
|
||||
|
||||
// Set player number, finalizing the setup
|
||||
Uint8 playerLedCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_PLAYERLED, ctx->player_idx };
|
||||
int playerNumBytesWritten = SDL_hid_write(device->dev, playerLedCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE);
|
||||
|
||||
if (playerNumBytesWritten < 0) {
|
||||
SDL_SetError("SInput device player led command could not write");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef DEG2RAD
|
||||
#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
|
||||
#endif
|
||||
|
||||
|
||||
static bool HIDAPI_DriverSInput_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
#if defined(DEBUG_SINPUT_INIT)
|
||||
SDL_Log("SInput device Open");
|
||||
#endif
|
||||
|
||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
joystick->nbuttons = ctx->buttons_count;
|
||||
|
||||
SDL_zeroa(ctx->last_state);
|
||||
|
||||
int axes = 0;
|
||||
if (ctx->left_analog_stick_supported) {
|
||||
axes += 2;
|
||||
}
|
||||
|
||||
if (ctx->right_analog_stick_supported) {
|
||||
axes += 2;
|
||||
}
|
||||
|
||||
if (ctx->left_analog_trigger_supported) {
|
||||
++axes;
|
||||
}
|
||||
|
||||
if (ctx->right_analog_trigger_supported) {
|
||||
++axes;
|
||||
}
|
||||
|
||||
joystick->naxes = axes;
|
||||
|
||||
if (ctx->dpad_supported) {
|
||||
joystick->nhats = 1;
|
||||
}
|
||||
|
||||
if (ctx->accelerometer_supported) {
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 1000.0f / ctx->polling_rate_ms);
|
||||
}
|
||||
|
||||
if (ctx->gyroscope_supported) {
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 1000.0f / ctx->polling_rate_ms);
|
||||
}
|
||||
|
||||
if (ctx->touchpad_supported) {
|
||||
// If touchpad is supported, minimum 1, max is capped
|
||||
ctx->touchpad_count = SDL_clamp(ctx->touchpad_count, 1, SINPUT_MAX_ALLOWED_TOUCHPADS);
|
||||
|
||||
if (ctx->touchpad_count > 1) {
|
||||
// Support two separate touchpads with 1 finger each
|
||||
// or support one touchpad with 2 fingers max
|
||||
ctx->touchpad_finger_count = 1;
|
||||
}
|
||||
|
||||
if (ctx->touchpad_count > 0) {
|
||||
SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count);
|
||||
}
|
||||
|
||||
if (ctx->touchpad_count > 1) {
|
||||
SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSInput_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
|
||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
||||
|
||||
if (ctx->rumble_supported) {
|
||||
SINPUT_HAPTIC_S hapticData = { 0 };
|
||||
Uint8 hapticReport[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_HAPTIC };
|
||||
|
||||
// Low Frequency = Left
|
||||
// High Frequency = Right
|
||||
hapticData.type_2.left.amplitude = (Uint8) (low_frequency_rumble >> 8);
|
||||
hapticData.type_2.right.amplitude = (Uint8)(high_frequency_rumble >> 8);
|
||||
|
||||
HapticsType2Pack(&hapticData, &(hapticReport[2]));
|
||||
|
||||
SDL_HIDAPI_SendRumble(device, hapticReport, SINPUT_DEVICE_REPORT_COMMAND_SIZE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSInput_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverSInput_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
||||
|
||||
Uint32 caps = 0;
|
||||
if (ctx->rumble_supported) {
|
||||
caps |= SDL_JOYSTICK_CAP_RUMBLE;
|
||||
}
|
||||
|
||||
if (ctx->player_leds_supported) {
|
||||
caps |= SDL_JOYSTICK_CAP_PLAYER_LED;
|
||||
}
|
||||
|
||||
if (ctx->joystick_rgb_supported) {
|
||||
caps |= SDL_JOYSTICK_CAP_RGB_LED;
|
||||
}
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSInput_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
||||
|
||||
if (ctx->player_leds_supported) {
|
||||
|
||||
// Set player number, finalizing the setup
|
||||
Uint8 joystickRGBCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_JOYSTICKRGB, red, green, blue };
|
||||
int joystickRGBBytesWritten = SDL_hid_write(device->dev, joystickRGBCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE);
|
||||
|
||||
if (joystickRGBBytesWritten < 0) {
|
||||
SDL_SetError("SInput device joystick rgb command could not write");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSInput_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSInput_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
||||
|
||||
if (ctx->accelerometer_supported || ctx->gyroscope_supported) {
|
||||
ctx->sensors_enabled = enabled;
|
||||
return true;
|
||||
}
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSInput_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSInput_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Sint16 axis = 0;
|
||||
Sint16 accel = 0;
|
||||
Sint16 gyro = 0;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
float imu_values[3] = { 0 };
|
||||
Uint8 output_idx = 0;
|
||||
|
||||
// Process digital buttons according to the supplied
|
||||
// button mask to create a contiguous button input set
|
||||
for (Uint8 processes = 0; processes < 4; ++processes) {
|
||||
|
||||
Uint8 button_idx = SINPUT_REPORT_IDX_BUTTONS_0 + processes;
|
||||
|
||||
for (Uint8 buttons = 0; buttons < 8; ++buttons) {
|
||||
|
||||
// If a button is enabled by our usage mask
|
||||
const Uint8 mask = (0x01 << buttons);
|
||||
if ((ctx->usage_masks[processes] & mask) != 0) {
|
||||
|
||||
bool down = (data[button_idx] & mask) != 0;
|
||||
|
||||
if ( (output_idx < SDL_GAMEPAD_BUTTON_COUNT) && (ctx->last_state[button_idx] != data[button_idx]) ) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, output_idx, down);
|
||||
}
|
||||
|
||||
++output_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->dpad_supported) {
|
||||
Uint8 hat = SDL_HAT_CENTERED;
|
||||
|
||||
if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_UP)) {
|
||||
hat |= SDL_HAT_UP;
|
||||
}
|
||||
if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_DOWN)) {
|
||||
hat |= SDL_HAT_DOWN;
|
||||
}
|
||||
if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_LEFT)) {
|
||||
hat |= SDL_HAT_LEFT;
|
||||
}
|
||||
if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_RIGHT)) {
|
||||
hat |= SDL_HAT_RIGHT;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
}
|
||||
|
||||
// Analog inputs map to a signed Sint16 range of -32768 to 32767 from the device.
|
||||
// Use an axis index because not all gamepads will have the same axis inputs.
|
||||
Uint8 axis_idx = 0;
|
||||
|
||||
// Left Analog Stick
|
||||
axis = 0; // Reset axis value for joystick
|
||||
if (ctx->left_analog_stick_supported) {
|
||||
axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_X);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
|
||||
++axis_idx;
|
||||
|
||||
axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_Y);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
|
||||
++axis_idx;
|
||||
}
|
||||
|
||||
// Right Analog Stick
|
||||
axis = 0; // Reset axis value for joystick
|
||||
if (ctx->right_analog_stick_supported) {
|
||||
axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_X);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
|
||||
++axis_idx;
|
||||
|
||||
axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_Y);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
|
||||
++axis_idx;
|
||||
}
|
||||
|
||||
// Left Analog Trigger
|
||||
axis = SDL_MIN_SINT16; // Reset axis value for trigger
|
||||
if (ctx->left_analog_trigger_supported) {
|
||||
axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_TRIGGER);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
|
||||
++axis_idx;
|
||||
}
|
||||
|
||||
// Right Analog Trigger
|
||||
axis = SDL_MIN_SINT16; // Reset axis value for trigger
|
||||
if (ctx->right_analog_trigger_supported) {
|
||||
axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_TRIGGER);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
|
||||
}
|
||||
|
||||
// Battery/Power state handling
|
||||
if (ctx->last_state[SINPUT_REPORT_IDX_PLUG_STATUS] != data[SINPUT_REPORT_IDX_PLUG_STATUS] ||
|
||||
ctx->last_state[SINPUT_REPORT_IDX_CHARGE_LEVEL] != data[SINPUT_REPORT_IDX_CHARGE_LEVEL]) {
|
||||
|
||||
SDL_PowerState state = SDL_POWERSTATE_UNKNOWN;
|
||||
Uint8 status = data[SINPUT_REPORT_IDX_PLUG_STATUS];
|
||||
int percent = data[SINPUT_REPORT_IDX_CHARGE_LEVEL];
|
||||
|
||||
percent = SDL_clamp(percent, 0, 100); // Ensure percent is within valid range
|
||||
|
||||
switch (status) {
|
||||
case 1:
|
||||
state = SDL_POWERSTATE_NO_BATTERY;
|
||||
percent = 0;
|
||||
break;
|
||||
case 2:
|
||||
state = SDL_POWERSTATE_CHARGING;
|
||||
break;
|
||||
case 3:
|
||||
state = SDL_POWERSTATE_CHARGED;
|
||||
percent = 100;
|
||||
break;
|
||||
case 4:
|
||||
state = SDL_POWERSTATE_ON_BATTERY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (state != SDL_POWERSTATE_UNKNOWN) {
|
||||
SDL_SendJoystickPowerInfo(joystick, state, percent);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the IMU timestamp delta (in microseconds)
|
||||
Uint32 imu_timestamp_us = EXTRACTUINT32(data, SINPUT_REPORT_IDX_IMU_TIMESTAMP);
|
||||
Uint32 imu_time_delta_us = 0;
|
||||
|
||||
// Check if we should process IMU data and if sensors are enabled
|
||||
if (ctx->sensors_enabled) {
|
||||
|
||||
if (imu_timestamp_us >= ctx->last_imu_timestamp_us) {
|
||||
imu_time_delta_us = (imu_timestamp_us - ctx->last_imu_timestamp_us);
|
||||
} else {
|
||||
// Handle rollover case
|
||||
imu_time_delta_us = (UINT32_MAX - ctx->last_imu_timestamp_us) + imu_timestamp_us + 1;
|
||||
}
|
||||
|
||||
// Convert delta to nanoseconds and update running timestamp
|
||||
ctx->imu_timestamp_ns += (Uint64)imu_time_delta_us * 1000;
|
||||
|
||||
// Update last timestamp
|
||||
ctx->last_imu_timestamp_us = imu_timestamp_us;
|
||||
|
||||
// Process Accelerometer
|
||||
if (ctx->accelerometer_supported) {
|
||||
|
||||
accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Y);
|
||||
imu_values[2] = -(float)accel * ctx->accelScale; // Y-axis acceleration
|
||||
|
||||
accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Z);
|
||||
imu_values[1] = (float)accel * ctx->accelScale; // Z-axis acceleration
|
||||
|
||||
accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_X);
|
||||
imu_values[0] = -(float)accel * ctx->accelScale; // X-axis acceleration
|
||||
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->imu_timestamp_ns, imu_values, 3);
|
||||
}
|
||||
|
||||
// Process Gyroscope
|
||||
if (ctx->gyroscope_supported) {
|
||||
|
||||
gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Y);
|
||||
imu_values[2] = -(float)gyro * ctx->gyroScale; // Y-axis rotation
|
||||
|
||||
gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Z);
|
||||
imu_values[1] = (float)gyro * ctx->gyroScale; // Z-axis rotation
|
||||
|
||||
gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_X);
|
||||
imu_values[0] = -(float)gyro * ctx->gyroScale; // X-axis rotation
|
||||
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->imu_timestamp_ns, imu_values, 3);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should process touchpad
|
||||
if (ctx->touchpad_supported && ctx->touchpad_count > 0) {
|
||||
Uint8 touchpad = 0;
|
||||
Uint8 finger = 0;
|
||||
|
||||
Sint16 touch1X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_X);
|
||||
Sint16 touch1Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_Y);
|
||||
Uint16 touch1P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH1_P);
|
||||
|
||||
Sint16 touch2X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_X);
|
||||
Sint16 touch2Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_Y);
|
||||
Uint16 touch2P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH2_P);
|
||||
|
||||
SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger,
|
||||
touch1P > 0,
|
||||
touch1X / 65536.0f + 0.5f,
|
||||
touch1Y / 65536.0f + 0.5f,
|
||||
touch1P / 32768.0f);
|
||||
|
||||
if (ctx->touchpad_count > 1) {
|
||||
++touchpad;
|
||||
} else if (ctx->touchpad_finger_count > 1) {
|
||||
++finger;
|
||||
}
|
||||
|
||||
if ((touchpad > 0) || (finger > 0)) {
|
||||
SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger,
|
||||
touch2P > 0,
|
||||
touch2X / 65536.0f + 0.5f,
|
||||
touch2Y / 65536.0f + 0.5f,
|
||||
touch2P / 32768.0f);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSInput_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = 0;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
|
||||
#ifdef DEBUG_SINPUT_PROTOCOL
|
||||
HIDAPI_DumpPacket("SInput packet: size = %d", data, size);
|
||||
#endif
|
||||
if (!joystick) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle command response information
|
||||
if (data[0] == SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT) {
|
||||
HIDAPI_DriverSInput_HandleStatePacket(joystick, ctx, data, size);
|
||||
}
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
// Read error, device is disconnected
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
return (size >= 0);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSInput_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSInput_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_SINPUT,
|
||||
true,
|
||||
HIDAPI_DriverSInput_RegisterHints,
|
||||
HIDAPI_DriverSInput_UnregisterHints,
|
||||
HIDAPI_DriverSInput_IsEnabled,
|
||||
HIDAPI_DriverSInput_IsSupportedDevice,
|
||||
HIDAPI_DriverSInput_InitDevice,
|
||||
HIDAPI_DriverSInput_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverSInput_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverSInput_UpdateDevice,
|
||||
HIDAPI_DriverSInput_OpenJoystick,
|
||||
HIDAPI_DriverSInput_RumbleJoystick,
|
||||
HIDAPI_DriverSInput_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverSInput_GetJoystickCapabilities,
|
||||
HIDAPI_DriverSInput_SetJoystickLED,
|
||||
HIDAPI_DriverSInput_SendJoystickEffect,
|
||||
HIDAPI_DriverSInput_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverSInput_CloseJoystick,
|
||||
HIDAPI_DriverSInput_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_SINPUT
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_STADIA
|
||||
|
||||
// Define this if you want to log all packets from the controller
|
||||
// #define DEBUG_STADIA_PROTOCOL
|
||||
|
||||
enum
|
||||
{
|
||||
SDL_GAMEPAD_BUTTON_STADIA_CAPTURE = 11,
|
||||
SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT,
|
||||
SDL_GAMEPAD_NUM_STADIA_BUTTONS,
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool rumble_supported;
|
||||
Uint8 last_state[USB_PACKET_LENGTH];
|
||||
} SDL_DriverStadia_Context;
|
||||
|
||||
static void HIDAPI_DriverStadia_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverStadia_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STADIA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_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)
|
||||
{
|
||||
return SDL_IsJoystickGoogleStadiaController(vendor_id, product_id);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverStadia_Context *ctx;
|
||||
|
||||
ctx = (SDL_DriverStadia_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
device->context = ctx;
|
||||
|
||||
// Check whether rumble is supported
|
||||
{
|
||||
Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
if (SDL_hid_write(device->dev, rumble_packet, sizeof(rumble_packet)) >= 0) {
|
||||
ctx->rumble_supported = true;
|
||||
}
|
||||
}
|
||||
|
||||
HIDAPI_SetDeviceName(device, "Google Stadia Controller");
|
||||
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverStadia_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverStadia_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
SDL_zeroa(ctx->last_state);
|
||||
|
||||
// Initialize the joystick capabilities
|
||||
joystick->nbuttons = SDL_GAMEPAD_NUM_STADIA_BUTTONS;
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
|
||||
|
||||
if (ctx->rumble_supported) {
|
||||
Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
|
||||
rumble_packet[1] = (low_frequency_rumble & 0xFF);
|
||||
rumble_packet[2] = (low_frequency_rumble >> 8);
|
||||
rumble_packet[3] = (high_frequency_rumble & 0xFF);
|
||||
rumble_packet[4] = (high_frequency_rumble >> 8);
|
||||
|
||||
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
|
||||
return SDL_SetError("Couldn't send rumble packet");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverStadia_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
|
||||
Uint32 caps = 0;
|
||||
|
||||
if (ctx->rumble_supported) {
|
||||
caps |= SDL_JOYSTICK_CAP_RUMBLE;
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverStadia_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Sint16 axis;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
// The format is the same but the original FW will send 10 bytes and January '21 FW update will send 11
|
||||
if (size < 10 || data[0] != 0x03) {
|
||||
// We don't know how to handle this report
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->last_state[1] != data[1]) {
|
||||
Uint8 hat;
|
||||
|
||||
switch (data[1]) {
|
||||
case 0:
|
||||
hat = SDL_HAT_UP;
|
||||
break;
|
||||
case 1:
|
||||
hat = SDL_HAT_RIGHTUP;
|
||||
break;
|
||||
case 2:
|
||||
hat = SDL_HAT_RIGHT;
|
||||
break;
|
||||
case 3:
|
||||
hat = SDL_HAT_RIGHTDOWN;
|
||||
break;
|
||||
case 4:
|
||||
hat = SDL_HAT_DOWN;
|
||||
break;
|
||||
case 5:
|
||||
hat = SDL_HAT_LEFTDOWN;
|
||||
break;
|
||||
case 6:
|
||||
hat = SDL_HAT_LEFT;
|
||||
break;
|
||||
case 7:
|
||||
hat = SDL_HAT_LEFTUP;
|
||||
break;
|
||||
default:
|
||||
hat = SDL_HAT_CENTERED;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
}
|
||||
|
||||
if (ctx->last_state[2] != data[2]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_CAPTURE, ((data[2] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, ((data[2] & 0x02) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[3] != data[3]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x01) != 0));
|
||||
}
|
||||
|
||||
#define READ_STICK_AXIS(offset) \
|
||||
(data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), 0x01 - 0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16))
|
||||
{
|
||||
axis = READ_STICK_AXIS(4);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
|
||||
axis = READ_STICK_AXIS(5);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
|
||||
axis = READ_STICK_AXIS(6);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
|
||||
axis = READ_STICK_AXIS(7);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
|
||||
}
|
||||
#undef READ_STICK_AXIS
|
||||
|
||||
#define READ_TRIGGER_AXIS(offset) \
|
||||
(Sint16)(((int)data[offset] * 257) - 32768)
|
||||
{
|
||||
axis = READ_TRIGGER_AXIS(8);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
|
||||
axis = READ_TRIGGER_AXIS(9);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
|
||||
}
|
||||
#undef READ_TRIGGER_AXIS
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverStadia_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = 0;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
|
||||
#ifdef DEBUG_STADIA_PROTOCOL
|
||||
HIDAPI_DumpPacket("Google Stadia packet: size = %d", data, size);
|
||||
#endif
|
||||
if (!joystick) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HIDAPI_DriverStadia_HandleStatePacket(joystick, ctx, data, size);
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
// Read error, device is disconnected
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
return (size >= 0);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverStadia_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverStadia_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_STADIA,
|
||||
true,
|
||||
HIDAPI_DriverStadia_RegisterHints,
|
||||
HIDAPI_DriverStadia_UnregisterHints,
|
||||
HIDAPI_DriverStadia_IsEnabled,
|
||||
HIDAPI_DriverStadia_IsSupportedDevice,
|
||||
HIDAPI_DriverStadia_InitDevice,
|
||||
HIDAPI_DriverStadia_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverStadia_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverStadia_UpdateDevice,
|
||||
HIDAPI_DriverStadia_OpenJoystick,
|
||||
HIDAPI_DriverStadia_RumbleJoystick,
|
||||
HIDAPI_DriverStadia_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverStadia_GetJoystickCapabilities,
|
||||
HIDAPI_DriverStadia_SetJoystickLED,
|
||||
HIDAPI_DriverStadia_SendJoystickEffect,
|
||||
HIDAPI_DriverStadia_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverStadia_CloseJoystick,
|
||||
HIDAPI_DriverStadia_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_STADIA
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
#include "../SDL_joystick_c.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI
|
||||
|
||||
/* Define this if you want to log all packets from the controller */
|
||||
/*#define DEBUG_HORI_PROTOCOL*/
|
||||
|
||||
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
|
||||
|
||||
enum
|
||||
{
|
||||
SDL_GAMEPAD_BUTTON_HORI_QAM = 11,
|
||||
SDL_GAMEPAD_BUTTON_HORI_FR,
|
||||
SDL_GAMEPAD_BUTTON_HORI_FL,
|
||||
SDL_GAMEPAD_BUTTON_HORI_M1,
|
||||
SDL_GAMEPAD_BUTTON_HORI_M2,
|
||||
SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L,
|
||||
SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R,
|
||||
SDL_GAMEPAD_NUM_HORI_BUTTONS
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Uint8 last_state[USB_PACKET_LENGTH];
|
||||
Uint64 sensor_ticks;
|
||||
Uint32 last_tick;
|
||||
bool wireless;
|
||||
bool serial_needs_init;
|
||||
} SDL_DriverSteamHori_Context;
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device);
|
||||
|
||||
static void HIDAPI_DriverSteamHori_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSteamHori_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_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)
|
||||
{
|
||||
return SDL_IsJoystickHoriSteamController(vendor_id, product_id);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverSteamHori_Context *ctx;
|
||||
|
||||
ctx = (SDL_DriverSteamHori_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device->context = ctx;
|
||||
ctx->serial_needs_init = true;
|
||||
|
||||
HIDAPI_SetDeviceName(device, "Wireless HORIPAD For Steam");
|
||||
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverSteamHori_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSteamHori_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
SDL_zeroa(ctx->last_state);
|
||||
|
||||
/* Initialize the joystick capabilities */
|
||||
joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS;
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
|
||||
ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT;
|
||||
|
||||
if (ctx->wireless && device->serial) {
|
||||
joystick->serial = SDL_strdup(device->serial);
|
||||
ctx->serial_needs_init = false;
|
||||
} else if (!ctx->wireless) {
|
||||
// Need to actual read from the device to init the serial
|
||||
HIDAPI_DriverSteamHori_UpdateDevice(device);
|
||||
}
|
||||
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
// Device doesn't support rumble
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverSteamHori_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef clamp
|
||||
#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
|
||||
|
||||
#ifndef DEG2RAD
|
||||
#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
|
||||
#endif
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Scale and clamp values to a range
|
||||
//---------------------------------------------------------------------------
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#define REPORT_HEADER_USB 0x07
|
||||
#define REPORT_HEADER_BT 0x00
|
||||
|
||||
static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSteamHori_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Sint16 axis;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
// Make sure it's gamepad state and not OTA FW update info
|
||||
if (data[0] != REPORT_HEADER_USB && data[0] != REPORT_HEADER_BT) {
|
||||
/* We don't know how to handle this report */
|
||||
return;
|
||||
}
|
||||
|
||||
#define READ_STICK_AXIS(offset) \
|
||||
(data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), -0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16))
|
||||
{
|
||||
axis = READ_STICK_AXIS(1);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
|
||||
axis = READ_STICK_AXIS(2);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
|
||||
axis = READ_STICK_AXIS(3);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
|
||||
axis = READ_STICK_AXIS(4);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
|
||||
}
|
||||
#undef READ_STICK_AXIS
|
||||
|
||||
if (ctx->last_state[5] != data[5]) {
|
||||
Uint8 hat;
|
||||
|
||||
switch (data[5] & 0xF) {
|
||||
case 0:
|
||||
hat = SDL_HAT_UP;
|
||||
break;
|
||||
case 1:
|
||||
hat = SDL_HAT_RIGHTUP;
|
||||
break;
|
||||
case 2:
|
||||
hat = SDL_HAT_RIGHT;
|
||||
break;
|
||||
case 3:
|
||||
hat = SDL_HAT_RIGHTDOWN;
|
||||
break;
|
||||
case 4:
|
||||
hat = SDL_HAT_DOWN;
|
||||
break;
|
||||
case 5:
|
||||
hat = SDL_HAT_LEFTDOWN;
|
||||
break;
|
||||
case 6:
|
||||
hat = SDL_HAT_LEFT;
|
||||
break;
|
||||
case 7:
|
||||
hat = SDL_HAT_LEFTUP;
|
||||
break;
|
||||
default:
|
||||
hat = SDL_HAT_CENTERED;
|
||||
break;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_QAM, ((data[5] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x80) != 0));
|
||||
|
||||
}
|
||||
|
||||
if (ctx->last_state[6] != data[6]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[6] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M1 /* M1 */, ((data[6] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[6] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[6] & 0x08) != 0));
|
||||
|
||||
// TODO: can we handle the digital trigger mode? The data seems to come through analog regardless of the trigger state
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x80) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[7] != data[7]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[7] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, ((data[7] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, ((data[7] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0));
|
||||
}
|
||||
|
||||
if (!ctx->wireless && ctx->serial_needs_init) {
|
||||
char serial[18];
|
||||
(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
|
||||
data[38], data[39], data[40], data[41], data[42], data[43]);
|
||||
|
||||
joystick->serial = SDL_strdup(serial);
|
||||
ctx->serial_needs_init = false;
|
||||
}
|
||||
|
||||
#define READ_TRIGGER_AXIS(offset) \
|
||||
(Sint16)(((int)data[offset] * 257) - 32768)
|
||||
{
|
||||
axis = READ_TRIGGER_AXIS(8);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
|
||||
axis = READ_TRIGGER_AXIS(9);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
|
||||
}
|
||||
#undef READ_TRIGGER_AXIS
|
||||
|
||||
if (1) {
|
||||
Uint64 sensor_timestamp;
|
||||
float imu_data[3];
|
||||
|
||||
/* 16-bit timestamp */
|
||||
Uint32 delta;
|
||||
Uint16 tick = LOAD16(data[10],
|
||||
data[11]);
|
||||
if (ctx->last_tick < tick) {
|
||||
delta = (tick - ctx->last_tick);
|
||||
} else {
|
||||
delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);
|
||||
}
|
||||
|
||||
ctx->last_tick = tick;
|
||||
ctx->sensor_ticks += delta;
|
||||
|
||||
/* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */
|
||||
sensor_timestamp = timestamp; // if the values were good we woudl call SDL_US_TO_NS(ctx->sensor_ticks);
|
||||
|
||||
const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f;
|
||||
const float gyroScale = DEG2RAD(2048);
|
||||
|
||||
imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
|
||||
imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
|
||||
imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
|
||||
|
||||
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3);
|
||||
|
||||
// SDL_Log("%u %f, %f, %f ", data[0], imu_data[0], imu_data[1], imu_data[2] );
|
||||
imu_data[2] = LOAD16(data[18], data[19]) * accelScale;
|
||||
imu_data[1] = -1 * LOAD16(data[20], data[21]) * accelScale;
|
||||
imu_data[0] = LOAD16(data[22], data[23]) * accelScale;
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, imu_data, 3);
|
||||
}
|
||||
|
||||
if (ctx->last_state[24] != data[24]) {
|
||||
bool bCharging = (data[24] & 0x10) != 0;
|
||||
int percent = (data[24] & 0xF) * 10;
|
||||
SDL_PowerState state;
|
||||
if (bCharging) {
|
||||
state = SDL_POWERSTATE_CHARGING;
|
||||
} else if (ctx->wireless) {
|
||||
state = SDL_POWERSTATE_ON_BATTERY;
|
||||
} else {
|
||||
state = SDL_POWERSTATE_CHARGED;
|
||||
}
|
||||
|
||||
SDL_SendJoystickPowerInfo(joystick, state, percent);
|
||||
}
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = 0;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
|
||||
#ifdef DEBUG_HORI_PROTOCOL
|
||||
HIDAPI_DumpPacket("Google Hori packet: size = %d", data, size);
|
||||
#endif
|
||||
if (!joystick) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HIDAPI_DriverSteamHori_HandleStatePacket(joystick, ctx, data, size);
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
/* Read error, device is disconnected */
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
return (size >= 0);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSteamHori_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSteamHori_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI,
|
||||
true,
|
||||
HIDAPI_DriverSteamHori_RegisterHints,
|
||||
HIDAPI_DriverSteamHori_UnregisterHints,
|
||||
HIDAPI_DriverSteamHori_IsEnabled,
|
||||
HIDAPI_DriverSteamHori_IsSupportedDevice,
|
||||
HIDAPI_DriverSteamHori_InitDevice,
|
||||
HIDAPI_DriverSteamHori_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverSteamHori_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverSteamHori_UpdateDevice,
|
||||
HIDAPI_DriverSteamHori_OpenJoystick,
|
||||
HIDAPI_DriverSteamHori_RumbleJoystick,
|
||||
HIDAPI_DriverSteamHori_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverSteamHori_GetJoystickCapabilities,
|
||||
HIDAPI_DriverSteamHori_SetJoystickLED,
|
||||
HIDAPI_DriverSteamHori_SendJoystickEffect,
|
||||
HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverSteamHori_CloseJoystick,
|
||||
HIDAPI_DriverSteamHori_FreeDevice,
|
||||
};
|
||||
|
||||
#endif /* SDL_JOYSTICK_HIDAPI_STEAM_HORI */
|
||||
|
||||
#endif /* SDL_JOYSTICK_HIDAPI */
|
||||
@@ -0,0 +1,466 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 2023 Max Maisel <max.maisel@posteo.de>
|
||||
|
||||
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_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
|
||||
|
||||
/*****************************************************************************************************/
|
||||
|
||||
#include "steam/controller_constants.h"
|
||||
#include "steam/controller_structs.h"
|
||||
|
||||
enum
|
||||
{
|
||||
SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11,
|
||||
SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
|
||||
SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
|
||||
SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
|
||||
SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
|
||||
SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS,
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
STEAMDECK_LBUTTON_R2 = 0x00000001,
|
||||
STEAMDECK_LBUTTON_L2 = 0x00000002,
|
||||
STEAMDECK_LBUTTON_R = 0x00000004,
|
||||
STEAMDECK_LBUTTON_L = 0x00000008,
|
||||
STEAMDECK_LBUTTON_Y = 0x00000010,
|
||||
STEAMDECK_LBUTTON_B = 0x00000020,
|
||||
STEAMDECK_LBUTTON_X = 0x00000040,
|
||||
STEAMDECK_LBUTTON_A = 0x00000080,
|
||||
STEAMDECK_LBUTTON_DPAD_UP = 0x00000100,
|
||||
STEAMDECK_LBUTTON_DPAD_RIGHT = 0x00000200,
|
||||
STEAMDECK_LBUTTON_DPAD_LEFT = 0x00000400,
|
||||
STEAMDECK_LBUTTON_DPAD_DOWN = 0x00000800,
|
||||
STEAMDECK_LBUTTON_VIEW = 0x00001000,
|
||||
STEAMDECK_LBUTTON_STEAM = 0x00002000,
|
||||
STEAMDECK_LBUTTON_MENU = 0x00004000,
|
||||
STEAMDECK_LBUTTON_L5 = 0x00008000,
|
||||
STEAMDECK_LBUTTON_R5 = 0x00010000,
|
||||
STEAMDECK_LBUTTON_LEFT_PAD = 0x00020000,
|
||||
STEAMDECK_LBUTTON_RIGHT_PAD = 0x00040000,
|
||||
STEAMDECK_LBUTTON_L3 = 0x00400000,
|
||||
STEAMDECK_LBUTTON_R3 = 0x04000000,
|
||||
|
||||
STEAMDECK_HBUTTON_L4 = 0x00000200,
|
||||
STEAMDECK_HBUTTON_R4 = 0x00000400,
|
||||
STEAMDECK_HBUTTON_QAM = 0x00040000,
|
||||
} SteamDeckButtons;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Uint32 update_rate_us;
|
||||
Uint32 sensor_timestamp_us;
|
||||
Uint64 last_button_state;
|
||||
Uint8 watchdog_counter;
|
||||
} SDL_DriverSteamDeck_Context;
|
||||
|
||||
static bool DisableDeckLizardMode(SDL_hid_device *dev)
|
||||
{
|
||||
int rc;
|
||||
Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
|
||||
FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
|
||||
|
||||
msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;
|
||||
|
||||
rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
|
||||
if (rc != sizeof(buffer))
|
||||
return false;
|
||||
|
||||
msg->header.type = ID_SET_SETTINGS_VALUES;
|
||||
msg->header.length = 5 * sizeof(ControllerSetting);
|
||||
msg->payload.setSettingsValues.settings[0].settingNum = SETTING_SMOOTH_ABSOLUTE_MOUSE;
|
||||
msg->payload.setSettingsValues.settings[0].settingValue = 0;
|
||||
msg->payload.setSettingsValues.settings[1].settingNum = SETTING_LEFT_TRACKPAD_MODE;
|
||||
msg->payload.setSettingsValues.settings[1].settingValue = TRACKPAD_NONE;
|
||||
msg->payload.setSettingsValues.settings[2].settingNum = SETTING_RIGHT_TRACKPAD_MODE; // disable mouse
|
||||
msg->payload.setSettingsValues.settings[2].settingValue = TRACKPAD_NONE;
|
||||
msg->payload.setSettingsValues.settings[3].settingNum = SETTING_LEFT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad
|
||||
msg->payload.setSettingsValues.settings[3].settingValue = 0xFFFF;
|
||||
msg->payload.setSettingsValues.settings[4].settingNum = SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad
|
||||
msg->payload.setSettingsValues.settings[4].settingValue = 0xFFFF;
|
||||
|
||||
rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
|
||||
if (rc != sizeof(buffer))
|
||||
return false;
|
||||
|
||||
// There may be a lingering report read back after changing settings.
|
||||
// Discard it.
|
||||
SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool FeedDeckLizardWatchdog(SDL_hid_device *dev)
|
||||
{
|
||||
int rc;
|
||||
Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
|
||||
FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
|
||||
|
||||
msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;
|
||||
|
||||
rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
|
||||
if (rc != sizeof(buffer))
|
||||
return false;
|
||||
|
||||
msg->header.type = ID_SET_SETTINGS_VALUES;
|
||||
msg->header.length = 1 * sizeof(ControllerSetting);
|
||||
msg->payload.setSettingsValues.settings[0].settingNum = SETTING_RIGHT_TRACKPAD_MODE;
|
||||
msg->payload.setSettingsValues.settings[0].settingValue = TRACKPAD_NONE;
|
||||
|
||||
rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
|
||||
if (rc != sizeof(buffer))
|
||||
return false;
|
||||
|
||||
// There may be a lingering report read back after changing settings.
|
||||
// Discard it.
|
||||
SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device,
|
||||
SDL_Joystick *joystick,
|
||||
ValveInReport_t *pInReport)
|
||||
{
|
||||
float values[3];
|
||||
SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
if (pInReport->payload.deckState.ulButtons != ctx->last_button_state) {
|
||||
Uint8 hat = 0;
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_A) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_B) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_X) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_Y) != 0));
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R) != 0));
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_VIEW) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_MENU) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_STEAM) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM,
|
||||
((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_QAM) != 0));
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L3) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R3) != 0));
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
|
||||
((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_R4) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
|
||||
((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_L4) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R5) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
|
||||
((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L5) != 0));
|
||||
|
||||
if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) {
|
||||
hat |= SDL_HAT_UP;
|
||||
}
|
||||
if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_DOWN) {
|
||||
hat |= SDL_HAT_DOWN;
|
||||
}
|
||||
if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_LEFT) {
|
||||
hat |= SDL_HAT_LEFT;
|
||||
}
|
||||
if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_RIGHT) {
|
||||
hat |= SDL_HAT_RIGHT;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
|
||||
ctx->last_button_state = pInReport->payload.deckState.ulButtons;
|
||||
}
|
||||
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
|
||||
(int)pInReport->payload.deckState.sTriggerRawL * 2 - 32768);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
|
||||
(int)pInReport->payload.deckState.sTriggerRawR * 2 - 32768);
|
||||
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX,
|
||||
pInReport->payload.deckState.sLeftStickX);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY,
|
||||
-pInReport->payload.deckState.sLeftStickY);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX,
|
||||
pInReport->payload.deckState.sRightStickX);
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY,
|
||||
-pInReport->payload.deckState.sRightStickY);
|
||||
|
||||
ctx->sensor_timestamp_us += ctx->update_rate_us;
|
||||
|
||||
values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
|
||||
values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
|
||||
values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3);
|
||||
|
||||
values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
|
||||
values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
|
||||
values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
|
||||
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3);
|
||||
|
||||
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0,
|
||||
pInReport->payload.deckState.sPressurePadLeft > 0,
|
||||
pInReport->payload.deckState.sLeftPadX / 65536.0f + 0.5f,
|
||||
pInReport->payload.deckState.sLeftPadY / 65536.0f + 0.5f,
|
||||
pInReport->payload.deckState.sPressurePadLeft / 32768.0f);
|
||||
|
||||
SDL_SendJoystickTouchpad(timestamp, joystick, 1, 0,
|
||||
pInReport->payload.deckState.sPressurePadRight > 0,
|
||||
pInReport->payload.deckState.sRightPadX / 65536.0f + 0.5f,
|
||||
pInReport->payload.deckState.sRightPadY / 65536.0f + 0.5f,
|
||||
pInReport->payload.deckState.sPressurePadRight / 32768.0f);
|
||||
}
|
||||
|
||||
/*****************************************************************************************************/
|
||||
|
||||
static void HIDAPI_DriverSteamDeck_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSteamDeck_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,
|
||||
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_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)
|
||||
{
|
||||
return SDL_IsJoystickSteamDeck(vendor_id, product_id);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
int size;
|
||||
Uint8 data[64];
|
||||
SDL_DriverSteamDeck_Context *ctx;
|
||||
|
||||
ctx = (SDL_DriverSteamDeck_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (ctx == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always 1kHz according to USB descriptor, but actually about 4 ms.
|
||||
ctx->update_rate_us = 4000;
|
||||
|
||||
device->context = ctx;
|
||||
|
||||
// Read a report to see if this is the correct endpoint.
|
||||
// Mouse, Keyboard and Controller have the same VID/PID but
|
||||
// only the controller hidraw device receives hid reports.
|
||||
size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);
|
||||
if (size == 0)
|
||||
return false;
|
||||
|
||||
if (!DisableDeckLizardMode(device->dev))
|
||||
return false;
|
||||
|
||||
HIDAPI_SetDeviceName(device, "Steam Deck");
|
||||
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverSteamDeck_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSteamDeck_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
int r;
|
||||
uint8_t data[64];
|
||||
ValveInReport_t *pInReport = (ValveInReport_t *)data;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
if (joystick == NULL) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx->watchdog_counter++ > 200) {
|
||||
ctx->watchdog_counter = 0;
|
||||
if (!FeedDeckLizardWatchdog(device->dev))
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_memset(data, 0, sizeof(data));
|
||||
|
||||
do {
|
||||
r = SDL_hid_read(device->dev, data, sizeof(data));
|
||||
|
||||
if (r < 0) {
|
||||
// Failed to read from controller
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
return false;
|
||||
} else if (r == 64 &&
|
||||
pInReport->header.unReportVersion == k_ValveInReportMsgVersion &&
|
||||
pInReport->header.ucType == ID_CONTROLLER_DECK_STATE &&
|
||||
pInReport->header.ucLength == 64) {
|
||||
HIDAPI_DriverSteamDeck_HandleState(device, joystick, pInReport);
|
||||
}
|
||||
} while (r > 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
|
||||
float update_rate_in_hz = 1.0f / (float)(ctx->update_rate_us) * 1.0e6f;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
// Initialize the joystick capabilities
|
||||
joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS;
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);
|
||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);
|
||||
|
||||
SDL_PrivateJoystickAddTouchpad(joystick, 1);
|
||||
SDL_PrivateJoystickAddTouchpad(joystick, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
int rc;
|
||||
Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
|
||||
FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
|
||||
|
||||
msg->header.type = ID_TRIGGER_RUMBLE_CMD;
|
||||
msg->payload.simpleRumble.unRumbleType = 0;
|
||||
msg->payload.simpleRumble.unIntensity = HAPTIC_INTENSITY_SYSTEM;
|
||||
msg->payload.simpleRumble.unLeftMotorSpeed = low_frequency_rumble;
|
||||
msg->payload.simpleRumble.unRightMotorSpeed = high_frequency_rumble;
|
||||
msg->payload.simpleRumble.nLeftGain = 2;
|
||||
msg->payload.simpleRumble.nRightGain = 0;
|
||||
|
||||
rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer));
|
||||
if (rc != sizeof(buffer))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverSteamDeck_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
return SDL_JOYSTICK_CAP_RUMBLE;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverSteamDeck_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
// On steam deck, sensors are enabled by default. Nothing to do here.
|
||||
return true;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSteamDeck_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
// Lizard mode id automatically re-enabled by watchdog. Nothing to do here.
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverSteamDeck_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,
|
||||
true,
|
||||
HIDAPI_DriverSteamDeck_RegisterHints,
|
||||
HIDAPI_DriverSteamDeck_UnregisterHints,
|
||||
HIDAPI_DriverSteamDeck_IsEnabled,
|
||||
HIDAPI_DriverSteamDeck_IsSupportedDevice,
|
||||
HIDAPI_DriverSteamDeck_InitDevice,
|
||||
HIDAPI_DriverSteamDeck_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverSteamDeck_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverSteamDeck_UpdateDevice,
|
||||
HIDAPI_DriverSteamDeck_OpenJoystick,
|
||||
HIDAPI_DriverSteamDeck_RumbleJoystick,
|
||||
HIDAPI_DriverSteamDeck_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverSteamDeck_GetJoystickCapabilities,
|
||||
HIDAPI_DriverSteamDeck_SetJoystickLED,
|
||||
HIDAPI_DriverSteamDeck_SendJoystickEffect,
|
||||
HIDAPI_DriverSteamDeck_SetSensorsEnabled,
|
||||
HIDAPI_DriverSteamDeck_CloseJoystick,
|
||||
HIDAPI_DriverSteamDeck_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_STEAMDECK
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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_c.h"
|
||||
#include "../SDL_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
|
||||
|
||||
// Define this if you want to log all packets from the controller
|
||||
// #define DEBUG_XBOX_PROTOCOL
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_HIDAPI_Device *device;
|
||||
SDL_Joystick *joystick;
|
||||
int player_index;
|
||||
bool player_lights;
|
||||
Uint8 last_state[USB_PACKET_LENGTH];
|
||||
} SDL_DriverXbox360_Context;
|
||||
|
||||
static void HIDAPI_DriverXbox360_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,
|
||||
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
|
||||
{
|
||||
const int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
||||
|
||||
if (vendor_id == USB_VENDOR_ASTRO && product_id == USB_PRODUCT_ASTRO_C40_XBOX360) {
|
||||
// This is the ASTRO C40 in Xbox 360 mode
|
||||
return true;
|
||||
}
|
||||
if (vendor_id == USB_VENDOR_NVIDIA) {
|
||||
// This is the NVIDIA Shield controller which doesn't talk Xbox controller protocol
|
||||
return false;
|
||||
}
|
||||
if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER)) ||
|
||||
(type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) {
|
||||
// This is the wireless dongle, which talks a different protocol
|
||||
return false;
|
||||
}
|
||||
if (interface_number > 0) {
|
||||
// This is the chatpad or other input interface, not the Xbox 360 interface
|
||||
return false;
|
||||
}
|
||||
#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI)
|
||||
if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) {
|
||||
// GCController support doesn't work with the Steam Virtual Gamepad
|
||||
return true;
|
||||
} else {
|
||||
// On macOS you can't write output reports to wired XBox controllers,
|
||||
// so we'll just use the GCController support instead.
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return (type == SDL_GAMEPAD_TYPE_XBOX360);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on)
|
||||
{
|
||||
const bool blink = false;
|
||||
Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;
|
||||
Uint8 led_packet[] = { 0x01, 0x03, 0x00 };
|
||||
|
||||
led_packet[2] = mode;
|
||||
if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void UpdateSlotLED(SDL_DriverXbox360_Context *ctx)
|
||||
{
|
||||
if (ctx->player_lights && ctx->player_index >= 0) {
|
||||
SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true);
|
||||
} else {
|
||||
SetSlotLED(ctx->device->dev, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
|
||||
{
|
||||
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)userdata;
|
||||
bool player_lights = SDL_GetStringBoolean(hint, true);
|
||||
|
||||
if (player_lights != ctx->player_lights) {
|
||||
ctx->player_lights = player_lights;
|
||||
|
||||
UpdateSlotLED(ctx);
|
||||
HIDAPI_UpdateDeviceProperties(ctx->device);
|
||||
}
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverXbox360_Context *ctx;
|
||||
|
||||
ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
ctx->device = device;
|
||||
|
||||
device->context = ctx;
|
||||
|
||||
device->type = SDL_GAMEPAD_TYPE_XBOX360;
|
||||
|
||||
if (SDL_IsJoystickSteamVirtualGamepad(device->vendor_id, device->product_id, device->version) &&
|
||||
device->product_string && SDL_strncmp(device->product_string, "GamePad-", 8) == 0) {
|
||||
int slot = 0;
|
||||
SDL_sscanf(device->product_string, "GamePad-%d", &slot);
|
||||
device->steam_virtual_gamepad_slot = (slot - 1);
|
||||
}
|
||||
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverXbox360_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
|
||||
|
||||
if (!ctx->joystick) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->player_index = player_index;
|
||||
|
||||
UpdateSlotLED(ctx);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
ctx->joystick = joystick;
|
||||
SDL_zeroa(ctx->last_state);
|
||||
|
||||
// Initialize player index (needed for setting LEDs)
|
||||
ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
|
||||
ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true);
|
||||
UpdateSlotLED(ctx);
|
||||
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
|
||||
SDL_PlayerLEDHintChanged, ctx);
|
||||
|
||||
// Initialize the joystick capabilities
|
||||
joystick->nbuttons = 11;
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
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);
|
||||
|
||||
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
|
||||
return SDL_SetError("Couldn't send rumble packet");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverXbox360_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
|
||||
Uint32 result = SDL_JOYSTICK_CAP_RUMBLE;
|
||||
|
||||
if (ctx->player_lights) {
|
||||
result |= SDL_JOYSTICK_CAP_PLAYER_LED;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Sint16 axis;
|
||||
#ifdef SDL_PLATFORM_MACOS
|
||||
const bool invert_y_axes = false;
|
||||
#else
|
||||
const bool invert_y_axes = true;
|
||||
#endif
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
if (ctx->last_state[2] != data[2]) {
|
||||
Uint8 hat = 0;
|
||||
|
||||
if (data[2] & 0x01) {
|
||||
hat |= SDL_HAT_UP;
|
||||
}
|
||||
if (data[2] & 0x02) {
|
||||
hat |= SDL_HAT_DOWN;
|
||||
}
|
||||
if (data[2] & 0x04) {
|
||||
hat |= SDL_HAT_LEFT;
|
||||
}
|
||||
if (data[2] & 0x08) {
|
||||
hat |= SDL_HAT_RIGHT;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[3] != data[3]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0));
|
||||
}
|
||||
|
||||
axis = ((int)data[4] * 257) - 32768;
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
|
||||
axis = ((int)data[5] * 257) - 32768;
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
|
||||
axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
|
||||
axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));
|
||||
if (invert_y_axes) {
|
||||
axis = ~axis;
|
||||
}
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
|
||||
axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
|
||||
axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));
|
||||
if (invert_y_axes) {
|
||||
axis = ~axis;
|
||||
}
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min((size_t)size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size = 0;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
|
||||
#ifdef DEBUG_XBOX_PROTOCOL
|
||||
HIDAPI_DumpPacket("Xbox 360 packet: size = %d", data, size);
|
||||
#endif
|
||||
if (!joystick) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data[0] == 0x00) {
|
||||
HIDAPI_DriverXbox360_HandleStatePacket(joystick, ctx, data, size);
|
||||
}
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
// Read error, device is disconnected
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
return (size >= 0);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
|
||||
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
|
||||
SDL_PlayerLEDHintChanged, ctx);
|
||||
|
||||
ctx->joystick = NULL;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,
|
||||
true,
|
||||
HIDAPI_DriverXbox360_RegisterHints,
|
||||
HIDAPI_DriverXbox360_UnregisterHints,
|
||||
HIDAPI_DriverXbox360_IsEnabled,
|
||||
HIDAPI_DriverXbox360_IsSupportedDevice,
|
||||
HIDAPI_DriverXbox360_InitDevice,
|
||||
HIDAPI_DriverXbox360_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverXbox360_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverXbox360_UpdateDevice,
|
||||
HIDAPI_DriverXbox360_OpenJoystick,
|
||||
HIDAPI_DriverXbox360_RumbleJoystick,
|
||||
HIDAPI_DriverXbox360_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverXbox360_GetJoystickCapabilities,
|
||||
HIDAPI_DriverXbox360_SetJoystickLED,
|
||||
HIDAPI_DriverXbox360_SendJoystickEffect,
|
||||
HIDAPI_DriverXbox360_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverXbox360_CloseJoystick,
|
||||
HIDAPI_DriverXbox360_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_XBOX360
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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_c.h"
|
||||
#include "../SDL_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
#include "SDL_hidapi_rumble.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
|
||||
|
||||
// Define this if you want to log all packets from the controller
|
||||
// #define DEBUG_XBOX_PROTOCOL
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_HIDAPI_Device *device;
|
||||
bool connected;
|
||||
int player_index;
|
||||
bool player_lights;
|
||||
Uint8 last_state[USB_PACKET_LENGTH];
|
||||
} SDL_DriverXbox360W_Context;
|
||||
|
||||
static void HIDAPI_DriverXbox360W_RegisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360W_UnregisterHints(SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_IsEnabled(void)
|
||||
{
|
||||
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS,
|
||||
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
|
||||
{
|
||||
const int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
||||
|
||||
if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY1 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER) && interface_protocol == 0) ||
|
||||
(type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on)
|
||||
{
|
||||
const bool blink = false;
|
||||
Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;
|
||||
Uint8 led_packet[] = { 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
led_packet[3] = 0x40 + (mode % 0x0e);
|
||||
if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void UpdateSlotLED(SDL_DriverXbox360W_Context *ctx)
|
||||
{
|
||||
if (ctx->player_lights && ctx->player_index >= 0) {
|
||||
SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true);
|
||||
} else {
|
||||
SetSlotLED(ctx->device->dev, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
|
||||
{
|
||||
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)userdata;
|
||||
bool player_lights = SDL_GetStringBoolean(hint, true);
|
||||
|
||||
if (player_lights != ctx->player_lights) {
|
||||
ctx->player_lights = player_lights;
|
||||
|
||||
UpdateSlotLED(ctx);
|
||||
HIDAPI_UpdateDeviceProperties(ctx->device);
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdatePowerLevel(SDL_Joystick *joystick, Uint8 level)
|
||||
{
|
||||
int percent = (int)SDL_roundf((level / 255.0f) * 100.0f);
|
||||
SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_InitDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverXbox360W_Context *ctx;
|
||||
|
||||
// Requests controller presence information from the wireless dongle
|
||||
const Uint8 init_packet[] = { 0x08, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
HIDAPI_SetDeviceName(device, "Xbox 360 Wireless Controller");
|
||||
|
||||
ctx = (SDL_DriverXbox360W_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
ctx->device = device;
|
||||
|
||||
device->context = ctx;
|
||||
|
||||
if (SDL_hid_write(device->dev, init_packet, sizeof(init_packet)) != sizeof(init_packet)) {
|
||||
SDL_SetError("Couldn't write init packet");
|
||||
return false;
|
||||
}
|
||||
|
||||
device->type = SDL_GAMEPAD_TYPE_XBOX360;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int HIDAPI_DriverXbox360W_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360W_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
|
||||
{
|
||||
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->player_index = player_index;
|
||||
|
||||
UpdateSlotLED(ctx);
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
|
||||
|
||||
SDL_AssertJoysticksLocked();
|
||||
|
||||
SDL_zeroa(ctx->last_state);
|
||||
|
||||
// Initialize player index (needed for setting LEDs)
|
||||
ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
|
||||
ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true);
|
||||
UpdateSlotLED(ctx);
|
||||
|
||||
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
|
||||
SDL_PlayerLEDHintChanged, ctx);
|
||||
|
||||
// Initialize the joystick capabilities
|
||||
joystick->nbuttons = 11;
|
||||
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
joystick->nhats = 1;
|
||||
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
|
||||
{
|
||||
Uint8 rumble_packet[] = { 0x00, 0x01, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
rumble_packet[5] = (low_frequency_rumble >> 8);
|
||||
rumble_packet[6] = (high_frequency_rumble >> 8);
|
||||
|
||||
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
|
||||
return SDL_SetError("Couldn't send rumble packet");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static Uint32 HIDAPI_DriverXbox360W_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
|
||||
Uint32 result = SDL_JOYSTICK_CAP_RUMBLE;
|
||||
|
||||
if (ctx->player_lights) {
|
||||
result |= SDL_JOYSTICK_CAP_PLAYER_LED;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360W_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverXbox360W_Context *ctx, Uint8 *data, int size)
|
||||
{
|
||||
Sint16 axis;
|
||||
const bool invert_y_axes = true;
|
||||
Uint64 timestamp = SDL_GetTicksNS();
|
||||
|
||||
if (ctx->last_state[2] != data[2]) {
|
||||
Uint8 hat = 0;
|
||||
|
||||
if (data[2] & 0x01) {
|
||||
hat |= SDL_HAT_UP;
|
||||
}
|
||||
if (data[2] & 0x02) {
|
||||
hat |= SDL_HAT_DOWN;
|
||||
}
|
||||
if (data[2] & 0x04) {
|
||||
hat |= SDL_HAT_LEFT;
|
||||
}
|
||||
if (data[2] & 0x08) {
|
||||
hat |= SDL_HAT_RIGHT;
|
||||
}
|
||||
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
|
||||
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));
|
||||
}
|
||||
|
||||
if (ctx->last_state[3] != data[3]) {
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0));
|
||||
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0));
|
||||
}
|
||||
|
||||
axis = ((int)data[4] * 257) - 32768;
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
|
||||
axis = ((int)data[5] * 257) - 32768;
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
|
||||
axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
|
||||
axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));
|
||||
if (invert_y_axes) {
|
||||
axis = ~axis;
|
||||
}
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
|
||||
axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
|
||||
axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));
|
||||
if (invert_y_axes) {
|
||||
axis = ~axis;
|
||||
}
|
||||
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
|
||||
|
||||
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
|
||||
}
|
||||
|
||||
static bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
Uint8 data[USB_PACKET_LENGTH];
|
||||
int size;
|
||||
|
||||
if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
}
|
||||
|
||||
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
|
||||
#ifdef DEBUG_XBOX_PROTOCOL
|
||||
HIDAPI_DumpPacket("Xbox 360 wireless packet: size = %d", data, size);
|
||||
#endif
|
||||
if (size == 2 && data[0] == 0x08) {
|
||||
bool connected = (data[1] & 0x80) ? true : false;
|
||||
#ifdef DEBUG_JOYSTICK
|
||||
SDL_Log("Connected = %s", connected ? "TRUE" : "FALSE");
|
||||
#endif
|
||||
if (connected != ctx->connected) {
|
||||
ctx->connected = connected;
|
||||
|
||||
if (connected) {
|
||||
SDL_JoystickID joystickID;
|
||||
|
||||
HIDAPI_JoystickConnected(device, &joystickID);
|
||||
|
||||
} else if (device->num_joysticks > 0) {
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
}
|
||||
} else if (size == 29 && data[0] == 0x00 && data[1] == 0x0f && data[2] == 0x00 && data[3] == 0xf0) {
|
||||
// Serial number is data[7-13]
|
||||
#ifdef DEBUG_JOYSTICK
|
||||
SDL_Log("Battery status (initial): %d", data[17]);
|
||||
#endif
|
||||
if (joystick) {
|
||||
UpdatePowerLevel(joystick, data[17]);
|
||||
}
|
||||
} else if (size == 29 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x13) {
|
||||
#ifdef DEBUG_JOYSTICK
|
||||
SDL_Log("Battery status: %d", data[4]);
|
||||
#endif
|
||||
if (joystick) {
|
||||
UpdatePowerLevel(joystick, data[4]);
|
||||
}
|
||||
} else if (size == 29 && data[0] == 0x00 && (data[1] & 0x01) == 0x01) {
|
||||
if (joystick) {
|
||||
HIDAPI_DriverXbox360W_HandleStatePacket(joystick, device->dev, ctx, data + 4, size - 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (size < 0 && device->num_joysticks > 0) {
|
||||
// Read error, device is disconnected
|
||||
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
|
||||
}
|
||||
return (size >= 0);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360W_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
|
||||
|
||||
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
|
||||
SDL_PlayerLEDHintChanged, ctx);
|
||||
}
|
||||
|
||||
static void HIDAPI_DriverXbox360W_FreeDevice(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W = {
|
||||
SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS,
|
||||
true,
|
||||
HIDAPI_DriverXbox360W_RegisterHints,
|
||||
HIDAPI_DriverXbox360W_UnregisterHints,
|
||||
HIDAPI_DriverXbox360W_IsEnabled,
|
||||
HIDAPI_DriverXbox360W_IsSupportedDevice,
|
||||
HIDAPI_DriverXbox360W_InitDevice,
|
||||
HIDAPI_DriverXbox360W_GetDevicePlayerIndex,
|
||||
HIDAPI_DriverXbox360W_SetDevicePlayerIndex,
|
||||
HIDAPI_DriverXbox360W_UpdateDevice,
|
||||
HIDAPI_DriverXbox360W_OpenJoystick,
|
||||
HIDAPI_DriverXbox360W_RumbleJoystick,
|
||||
HIDAPI_DriverXbox360W_RumbleJoystickTriggers,
|
||||
HIDAPI_DriverXbox360W_GetJoystickCapabilities,
|
||||
HIDAPI_DriverXbox360W_SetJoystickLED,
|
||||
HIDAPI_DriverXbox360W_SendJoystickEffect,
|
||||
HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled,
|
||||
HIDAPI_DriverXbox360W_CloseJoystick,
|
||||
HIDAPI_DriverXbox360W_FreeDevice,
|
||||
};
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_XBOX360
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 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 "../usb_ids.h"
|
||||
|
||||
// This is the full set of HIDAPI drivers available
|
||||
#define SDL_JOYSTICK_HIDAPI_GAMECUBE
|
||||
#define SDL_JOYSTICK_HIDAPI_LUNA
|
||||
#define SDL_JOYSTICK_HIDAPI_PS3
|
||||
#define SDL_JOYSTICK_HIDAPI_PS4
|
||||
#define SDL_JOYSTICK_HIDAPI_PS5
|
||||
#define SDL_JOYSTICK_HIDAPI_STADIA
|
||||
#define SDL_JOYSTICK_HIDAPI_STEAM
|
||||
#define SDL_JOYSTICK_HIDAPI_STEAMDECK
|
||||
#define SDL_JOYSTICK_HIDAPI_SWITCH
|
||||
#define SDL_JOYSTICK_HIDAPI_WII
|
||||
#define SDL_JOYSTICK_HIDAPI_XBOX360
|
||||
#define SDL_JOYSTICK_HIDAPI_XBOXONE
|
||||
#define SDL_JOYSTICK_HIDAPI_SHIELD
|
||||
#define SDL_JOYSTICK_HIDAPI_STEAM_HORI
|
||||
#define SDL_JOYSTICK_HIDAPI_LG4FF
|
||||
#define SDL_JOYSTICK_HIDAPI_8BITDO
|
||||
#define SDL_JOYSTICK_HIDAPI_FLYDIGI
|
||||
#define SDL_JOYSTICK_HIDAPI_GIP
|
||||
#define SDL_JOYSTICK_HIDAPI_SINPUT
|
||||
|
||||
// Joystick capability definitions
|
||||
#define SDL_JOYSTICK_CAP_MONO_LED 0x00000001
|
||||
#define SDL_JOYSTICK_CAP_RGB_LED 0x00000002
|
||||
#define SDL_JOYSTICK_CAP_PLAYER_LED 0x00000004
|
||||
#define SDL_JOYSTICK_CAP_RUMBLE 0x00000010
|
||||
#define SDL_JOYSTICK_CAP_TRIGGER_RUMBLE 0x00000020
|
||||
|
||||
// Whether HIDAPI is enabled by default
|
||||
#if defined(SDL_PLATFORM_ANDROID) || \
|
||||
defined(SDL_PLATFORM_IOS) || \
|
||||
defined(SDL_PLATFORM_TVOS) || \
|
||||
defined(SDL_PLATFORM_VISIONOS)
|
||||
// On Android, HIDAPI prompts for permissions and acquires exclusive access to the device, and on Apple mobile platforms it doesn't do anything except for handling Bluetooth Steam Controllers, so we'll leave it off by default.
|
||||
#define SDL_HIDAPI_DEFAULT false
|
||||
#else
|
||||
#define SDL_HIDAPI_DEFAULT true
|
||||
#endif
|
||||
|
||||
// The maximum size of a USB packet for HID devices
|
||||
#define USB_PACKET_LENGTH 64
|
||||
|
||||
// Forward declaration
|
||||
struct SDL_HIDAPI_DeviceDriver;
|
||||
|
||||
typedef struct SDL_HIDAPI_Device
|
||||
{
|
||||
char *name;
|
||||
char *manufacturer_string;
|
||||
char *product_string;
|
||||
char *path;
|
||||
Uint16 vendor_id;
|
||||
Uint16 product_id;
|
||||
Uint16 version;
|
||||
char *serial;
|
||||
SDL_GUID guid;
|
||||
int interface_number; // Available on Windows and Linux
|
||||
int interface_class;
|
||||
int interface_subclass;
|
||||
int interface_protocol;
|
||||
Uint16 usage_page; // Available on Windows and macOS
|
||||
Uint16 usage; // Available on Windows and macOS
|
||||
bool is_bluetooth;
|
||||
SDL_JoystickType joystick_type;
|
||||
SDL_GamepadType type;
|
||||
int steam_virtual_gamepad_slot;
|
||||
|
||||
struct SDL_HIDAPI_DeviceDriver *driver;
|
||||
void *context;
|
||||
SDL_Mutex *dev_lock;
|
||||
SDL_hid_device *dev;
|
||||
SDL_AtomicInt rumble_pending;
|
||||
int num_joysticks;
|
||||
SDL_JoystickID *joysticks;
|
||||
|
||||
// Used during scanning for device changes
|
||||
bool seen;
|
||||
|
||||
// Used to flag that the device is being updated
|
||||
bool updating;
|
||||
|
||||
// Used to flag devices that failed open
|
||||
// This can happen on Windows with Bluetooth devices that have turned off
|
||||
bool broken;
|
||||
|
||||
struct SDL_HIDAPI_Device *parent;
|
||||
int num_children;
|
||||
struct SDL_HIDAPI_Device **children;
|
||||
|
||||
struct SDL_HIDAPI_Device *next;
|
||||
} SDL_HIDAPI_Device;
|
||||
|
||||
typedef struct SDL_HIDAPI_DeviceDriver
|
||||
{
|
||||
const char *name;
|
||||
bool enabled;
|
||||
void (*RegisterHints)(SDL_HintCallback callback, void *userdata);
|
||||
void (*UnregisterHints)(SDL_HintCallback callback, void *userdata);
|
||||
bool (*IsEnabled)(void);
|
||||
bool (*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);
|
||||
bool (*InitDevice)(SDL_HIDAPI_Device *device);
|
||||
int (*GetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id);
|
||||
void (*SetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index);
|
||||
bool (*UpdateDevice)(SDL_HIDAPI_Device *device);
|
||||
bool (*OpenJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
|
||||
bool (*RumbleJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble);
|
||||
bool (*RumbleJoystickTriggers)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble);
|
||||
Uint32 (*GetJoystickCapabilities)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
|
||||
bool (*SetJoystickLED)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue);
|
||||
bool (*SendJoystickEffect)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size);
|
||||
bool (*SetJoystickSensorsEnabled)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled);
|
||||
void (*CloseJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
|
||||
void (*FreeDevice)(SDL_HIDAPI_Device *device);
|
||||
|
||||
} SDL_HIDAPI_DeviceDriver;
|
||||
|
||||
// HIDAPI device support
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3ThirdParty;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3SonySixaxis;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi;
|
||||
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput;
|
||||
|
||||
// Return true if a HID device is present and supported as a joystick of the given type
|
||||
extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type);
|
||||
|
||||
// Return true if a HID device is present and supported as a joystick
|
||||
extern bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);
|
||||
|
||||
// Return the name of a connected device, which should be freed with SDL_free(), or NULL if it's not available
|
||||
extern char *HIDAPI_GetDeviceProductName(Uint16 vendor_id, Uint16 product_id);
|
||||
|
||||
// Return the manufacturer of a connected device, which should be freed with SDL_free(), or NULL if it's not available
|
||||
extern char *HIDAPI_GetDeviceManufacturerName(Uint16 vendor_id, Uint16 product_id);
|
||||
|
||||
// Return the type of a joystick if it's present and supported
|
||||
extern SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_GUID guid);
|
||||
|
||||
// Return the type of a game controller if it's present and supported
|
||||
extern SDL_GamepadType HIDAPI_GetGamepadTypeFromGUID(SDL_GUID guid);
|
||||
|
||||
extern void HIDAPI_UpdateDevices(void);
|
||||
extern void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name);
|
||||
extern void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 vendor_id, Uint16 product_id);
|
||||
extern void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial);
|
||||
extern bool HIDAPI_HasConnectedUSBDevice(const char *serial);
|
||||
extern void HIDAPI_DisconnectBluetoothDevice(const char *serial);
|
||||
extern bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID);
|
||||
extern void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID);
|
||||
extern void HIDAPI_UpdateDeviceProperties(SDL_HIDAPI_Device *device);
|
||||
|
||||
extern void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size);
|
||||
|
||||
extern bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product);
|
||||
|
||||
extern float HIDAPI_RemapVal(float val, float val_min, float val_max, float output_min, float output_max);
|
||||
|
||||
#endif // SDL_JOYSTICK_HIDAPI_H
|
||||
@@ -0,0 +1,582 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 2021 Valve Corporation
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef _CONTROLLER_CONSTANTS_
|
||||
#define _CONTROLLER_CONSTANTS_
|
||||
|
||||
#include "controller_structs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FEATURE_REPORT_SIZE 64
|
||||
|
||||
#define VALVE_USB_VID 0x28DE
|
||||
|
||||
// Frame update rate (in ms).
|
||||
#define FAST_SCAN_INTERVAL 6
|
||||
#define SLOW_SCAN_INTERVAL 9
|
||||
|
||||
// Contains each of the USB PIDs for Valve controllers (only add to this enum and never change the order)
|
||||
enum ValveControllerPID
|
||||
{
|
||||
BASTILLE_PID = 0x2202,
|
||||
CHELL_PID = 0x1101,
|
||||
D0G_PID = 0x1102,
|
||||
ELI_PID = 0x1103,
|
||||
FREEMAN_PID = 0x1104,
|
||||
D0G_BLE_PID = 0x1105,
|
||||
D0G_BLE2_PID = 0x1106,
|
||||
D0GGLE_PID = 0x1142,
|
||||
|
||||
JUPITER_PID = 0x1205,
|
||||
};
|
||||
|
||||
// This enum contains all of the messages exchanged between the host and the target (only add to this enum and never change the order)
|
||||
enum FeatureReportMessageIDs
|
||||
{
|
||||
ID_SET_DIGITAL_MAPPINGS = 0x80,
|
||||
ID_CLEAR_DIGITAL_MAPPINGS = 0x81,
|
||||
ID_GET_DIGITAL_MAPPINGS = 0x82,
|
||||
ID_GET_ATTRIBUTES_VALUES = 0x83,
|
||||
ID_GET_ATTRIBUTE_LABEL = 0x84,
|
||||
ID_SET_DEFAULT_DIGITAL_MAPPINGS = 0x85,
|
||||
ID_FACTORY_RESET = 0x86,
|
||||
ID_SET_SETTINGS_VALUES = 0x87,
|
||||
ID_CLEAR_SETTINGS_VALUES = 0x88,
|
||||
ID_GET_SETTINGS_VALUES = 0x89,
|
||||
ID_GET_SETTING_LABEL = 0x8A,
|
||||
ID_GET_SETTINGS_MAXS = 0x8B,
|
||||
ID_GET_SETTINGS_DEFAULTS = 0x8C,
|
||||
ID_SET_CONTROLLER_MODE = 0x8D,
|
||||
ID_LOAD_DEFAULT_SETTINGS = 0x8E,
|
||||
ID_TRIGGER_HAPTIC_PULSE = 0x8F,
|
||||
|
||||
ID_TURN_OFF_CONTROLLER = 0x9F,
|
||||
|
||||
ID_GET_DEVICE_INFO = 0xA1,
|
||||
|
||||
ID_CALIBRATE_TRACKPADS = 0xA7,
|
||||
ID_RESERVED_0 = 0xA8,
|
||||
ID_SET_SERIAL_NUMBER = 0xA9,
|
||||
ID_GET_TRACKPAD_CALIBRATION = 0xAA,
|
||||
ID_GET_TRACKPAD_FACTORY_CALIBRATION = 0xAB,
|
||||
ID_GET_TRACKPAD_RAW_DATA = 0xAC,
|
||||
ID_ENABLE_PAIRING = 0xAD,
|
||||
ID_GET_STRING_ATTRIBUTE = 0xAE,
|
||||
ID_RADIO_ERASE_RECORDS = 0xAF,
|
||||
ID_RADIO_WRITE_RECORD = 0xB0,
|
||||
ID_SET_DONGLE_SETTING = 0xB1,
|
||||
ID_DONGLE_DISCONNECT_DEVICE = 0xB2,
|
||||
ID_DONGLE_COMMIT_DEVICE = 0xB3,
|
||||
ID_DONGLE_GET_WIRELESS_STATE = 0xB4,
|
||||
ID_CALIBRATE_GYRO = 0xB5,
|
||||
ID_PLAY_AUDIO = 0xB6,
|
||||
ID_AUDIO_UPDATE_START = 0xB7,
|
||||
ID_AUDIO_UPDATE_DATA = 0xB8,
|
||||
ID_AUDIO_UPDATE_COMPLETE = 0xB9,
|
||||
ID_GET_CHIPID = 0xBA,
|
||||
|
||||
ID_CALIBRATE_JOYSTICK = 0xBF,
|
||||
ID_CALIBRATE_ANALOG_TRIGGERS = 0xC0,
|
||||
ID_SET_AUDIO_MAPPING = 0xC1,
|
||||
ID_CHECK_GYRO_FW_LOAD = 0xC2,
|
||||
ID_CALIBRATE_ANALOG = 0xC3,
|
||||
ID_DONGLE_GET_CONNECTED_SLOTS = 0xC4,
|
||||
|
||||
ID_RESET_IMU = 0xCE,
|
||||
|
||||
// Deck only
|
||||
ID_TRIGGER_HAPTIC_CMD = 0xEA,
|
||||
ID_TRIGGER_RUMBLE_CMD = 0xEB,
|
||||
};
|
||||
|
||||
|
||||
// Enumeration of all wireless dongle events
|
||||
typedef enum WirelessEventTypes
|
||||
{
|
||||
WIRELESS_EVENT_DISCONNECT = 1,
|
||||
WIRELESS_EVENT_CONNECT = 2,
|
||||
WIRELESS_EVENT_PAIR = 3,
|
||||
} EWirelessEventType;
|
||||
|
||||
|
||||
// Enumeration of generic digital inputs - not all of these will be supported on all controllers (only add to this enum and never change the order)
|
||||
typedef enum
|
||||
{
|
||||
IO_DIGITAL_BUTTON_NONE = -1,
|
||||
IO_DIGITAL_BUTTON_RIGHT_TRIGGER,
|
||||
IO_DIGITAL_BUTTON_LEFT_TRIGGER,
|
||||
IO_DIGITAL_BUTTON_1,
|
||||
IO_DIGITAL_BUTTON_Y=IO_DIGITAL_BUTTON_1,
|
||||
IO_DIGITAL_BUTTON_2,
|
||||
IO_DIGITAL_BUTTON_B=IO_DIGITAL_BUTTON_2,
|
||||
IO_DIGITAL_BUTTON_3,
|
||||
IO_DIGITAL_BUTTON_X=IO_DIGITAL_BUTTON_3,
|
||||
IO_DIGITAL_BUTTON_4,
|
||||
IO_DIGITAL_BUTTON_A=IO_DIGITAL_BUTTON_4,
|
||||
IO_DIGITAL_BUTTON_RIGHT_BUMPER,
|
||||
IO_DIGITAL_BUTTON_LEFT_BUMPER,
|
||||
IO_DIGITAL_BUTTON_LEFT_JOYSTICK_CLICK,
|
||||
IO_DIGITAL_BUTTON_ESCAPE,
|
||||
IO_DIGITAL_BUTTON_STEAM,
|
||||
IO_DIGITAL_BUTTON_MENU,
|
||||
IO_DIGITAL_STICK_UP,
|
||||
IO_DIGITAL_STICK_DOWN,
|
||||
IO_DIGITAL_STICK_LEFT,
|
||||
IO_DIGITAL_STICK_RIGHT,
|
||||
IO_DIGITAL_TOUCH_1,
|
||||
IO_DIGITAL_BUTTON_UP=IO_DIGITAL_TOUCH_1,
|
||||
IO_DIGITAL_TOUCH_2,
|
||||
IO_DIGITAL_BUTTON_RIGHT=IO_DIGITAL_TOUCH_2,
|
||||
IO_DIGITAL_TOUCH_3,
|
||||
IO_DIGITAL_BUTTON_LEFT=IO_DIGITAL_TOUCH_3,
|
||||
IO_DIGITAL_TOUCH_4,
|
||||
IO_DIGITAL_BUTTON_DOWN=IO_DIGITAL_TOUCH_4,
|
||||
IO_DIGITAL_BUTTON_BACK_LEFT,
|
||||
IO_DIGITAL_BUTTON_BACK_RIGHT,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_N,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_NE,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_E,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_SE,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_S,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_SW,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_W,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_NW,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_N,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_NE,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_E,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_SE,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_S,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_SW,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_W,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_NW,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_DOUBLE_TAP,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_DOUBLE_TAP,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_OUTER_RADIUS,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_OUTER_RADIUS,
|
||||
IO_DIGITAL_LEFT_TRACKPAD_CLICK,
|
||||
IO_DIGITAL_RIGHT_TRACKPAD_CLICK,
|
||||
IO_DIGITAL_BATTERY_LOW,
|
||||
IO_DIGITAL_LEFT_TRIGGER_THRESHOLD,
|
||||
IO_DIGITAL_RIGHT_TRIGGER_THRESHOLD,
|
||||
IO_DIGITAL_BUTTON_BACK_LEFT2,
|
||||
IO_DIGITAL_BUTTON_BACK_RIGHT2,
|
||||
IO_DIGITAL_BUTTON_ALWAYS_ON,
|
||||
IO_DIGITAL_BUTTON_ANCILLARY_1,
|
||||
IO_DIGITAL_BUTTON_MACRO_0,
|
||||
IO_DIGITAL_BUTTON_MACRO_1,
|
||||
IO_DIGITAL_BUTTON_MACRO_2,
|
||||
IO_DIGITAL_BUTTON_MACRO_3,
|
||||
IO_DIGITAL_BUTTON_MACRO_4,
|
||||
IO_DIGITAL_BUTTON_MACRO_5,
|
||||
IO_DIGITAL_BUTTON_MACRO_6,
|
||||
IO_DIGITAL_BUTTON_MACRO_7,
|
||||
IO_DIGITAL_BUTTON_MACRO_1FINGER,
|
||||
IO_DIGITAL_BUTTON_MACRO_2FINGER,
|
||||
IO_DIGITAL_COUNT
|
||||
} DigitalIO ;
|
||||
|
||||
// Enumeration of generic analog inputs - not all of these will be supported on all controllers (only add to this enum and never change the order)
|
||||
typedef enum
|
||||
{
|
||||
IO_ANALOG_LEFT_STICK_X,
|
||||
IO_ANALOG_LEFT_STICK_Y,
|
||||
IO_ANALOG_RIGHT_STICK_X,
|
||||
IO_ANALOG_RIGHT_STICK_Y,
|
||||
IO_ANALOG_LEFT_TRIGGER,
|
||||
IO_ANALOG_RIGHT_TRIGGER,
|
||||
IO_MOUSE1_X,
|
||||
IO_MOUSE1_Y,
|
||||
IO_MOUSE1_Z,
|
||||
IO_ACCEL_X,
|
||||
IO_ACCEL_Y,
|
||||
IO_ACCEL_Z,
|
||||
IO_GYRO_X,
|
||||
IO_GYRO_Y,
|
||||
IO_GYRO_Z,
|
||||
IO_GYRO_QUAT_W,
|
||||
IO_GYRO_QUAT_X,
|
||||
IO_GYRO_QUAT_Y,
|
||||
IO_GYRO_QUAT_Z,
|
||||
IO_GYRO_STEERING_VEC,
|
||||
IO_RAW_TRIGGER_LEFT,
|
||||
IO_RAW_TRIGGER_RIGHT,
|
||||
IO_RAW_JOYSTICK_X,
|
||||
IO_RAW_JOYSTICK_Y,
|
||||
IO_GYRO_TILT_VEC,
|
||||
IO_PRESSURE_LEFT_PAD,
|
||||
IO_PRESSURE_RIGHT_PAD,
|
||||
IO_PRESSURE_LEFT_BUMPER,
|
||||
IO_PRESSURE_RIGHT_BUMPER,
|
||||
IO_PRESSURE_LEFT_GRIP,
|
||||
IO_PRESSURE_RIGHT_GRIP,
|
||||
IO_ANALOG_LEFT_TRIGGER_THRESHOLD,
|
||||
IO_ANALOG_RIGHT_TRIGGER_THRESHOLD,
|
||||
IO_PRESSURE_RIGHT_PAD_THRESHOLD,
|
||||
IO_PRESSURE_LEFT_PAD_THRESHOLD,
|
||||
IO_PRESSURE_RIGHT_BUMPER_THRESHOLD,
|
||||
IO_PRESSURE_LEFT_BUMPER_THRESHOLD,
|
||||
IO_PRESSURE_RIGHT_GRIP_THRESHOLD,
|
||||
IO_PRESSURE_LEFT_GRIP_THRESHOLD,
|
||||
IO_PRESSURE_RIGHT_PAD_RAW,
|
||||
IO_PRESSURE_LEFT_PAD_RAW,
|
||||
IO_PRESSURE_RIGHT_BUMPER_RAW,
|
||||
IO_PRESSURE_LEFT_BUMPER_RAW,
|
||||
IO_PRESSURE_RIGHT_GRIP_RAW,
|
||||
IO_PRESSURE_LEFT_GRIP_RAW,
|
||||
IO_PRESSURE_RIGHT_GRIP2_THRESHOLD,
|
||||
IO_PRESSURE_LEFT_GRIP2_THRESHOLD,
|
||||
IO_PRESSURE_LEFT_GRIP2,
|
||||
IO_PRESSURE_RIGHT_GRIP2,
|
||||
IO_PRESSURE_RIGHT_GRIP2_RAW,
|
||||
IO_PRESSURE_LEFT_GRIP2_RAW,
|
||||
IO_ANALOG_COUNT
|
||||
} AnalogIO;
|
||||
|
||||
|
||||
// Contains list of all types of devices that the controller emulates (only add to this enum and never change the order)
|
||||
enum DeviceTypes
|
||||
{
|
||||
DEVICE_KEYBOARD,
|
||||
DEVICE_MOUSE,
|
||||
DEVICE_GAMEPAD,
|
||||
DEVICE_MODE_ADJUST,
|
||||
DEVICE_COUNT
|
||||
};
|
||||
|
||||
// Scan codes for HID keyboards
|
||||
enum HIDKeyboardKeys
|
||||
{
|
||||
KEY_INVALID,
|
||||
KEY_FIRST = 0x04,
|
||||
KEY_A = KEY_FIRST, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L,
|
||||
KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_1, KEY_2,
|
||||
KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_RETURN, KEY_ESCAPE, KEY_BACKSPACE, KEY_TAB, KEY_SPACE, KEY_DASH, KEY_EQUALS, KEY_LEFT_BRACKET,
|
||||
KEY_RIGHT_BRACKET, KEY_BACKSLASH, KEY_UNUSED1, KEY_SEMICOLON, KEY_SINGLE_QUOTE, KEY_BACK_TICK, KEY_COMMA, KEY_PERIOD, KEY_FORWARD_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6,
|
||||
KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_PRINT_SCREEN, KEY_SCROLL_LOCK, KEY_BREAK, KEY_INSERT, KEY_HOME, KEY_PAGE_UP, KEY_DELETE, KEY_END, KEY_PAGE_DOWN, KEY_RIGHT_ARROW,
|
||||
KEY_LEFT_ARROW, KEY_DOWN_ARROW, KEY_UP_ARROW, KEY_NUM_LOCK, KEY_KEYPAD_FORWARD_SLASH, KEY_KEYPAD_ASTERISK, KEY_KEYPAD_DASH, KEY_KEYPAD_PLUS, KEY_KEYPAD_ENTER, KEY_KEYPAD_1, KEY_KEYPAD_2, KEY_KEYPAD_3, KEY_KEYPAD_4, KEY_KEYPAD_5, KEY_KEYPAD_6, KEY_KEYPAD_7,
|
||||
KEY_KEYPAD_8, KEY_KEYPAD_9, KEY_KEYPAD_0, KEY_KEYPAD_PERIOD,
|
||||
KEY_LALT,
|
||||
KEY_LSHIFT,
|
||||
KEY_LWIN,
|
||||
KEY_LCONTROL,
|
||||
KEY_RALT,
|
||||
KEY_RSHIFT,
|
||||
KEY_RWIN,
|
||||
KEY_RCONTROL,
|
||||
KEY_VOLUP,
|
||||
KEY_VOLDOWN,
|
||||
KEY_MUTE,
|
||||
KEY_PLAY,
|
||||
KEY_STOP,
|
||||
KEY_NEXT,
|
||||
KEY_PREV,
|
||||
KEY_LAST = KEY_PREV
|
||||
};
|
||||
|
||||
enum ModifierMasks
|
||||
{
|
||||
KEY_LCONTROL_MASK = (1<<0),
|
||||
KEY_LSHIFT_MASK = (1<<1),
|
||||
KEY_LALT_MASK = (1<<2),
|
||||
KEY_LWIN_MASK = (1<<3),
|
||||
KEY_RCONTROL_MASK = (1<<4),
|
||||
KEY_RSHIFT_MASK = (1<<5),
|
||||
KEY_RALT_MASK = (1<<6),
|
||||
KEY_RWIN_MASK = (1<<7)
|
||||
};
|
||||
|
||||
// Standard mouse buttons as specified in the HID mouse spec
|
||||
enum MouseButtons
|
||||
{
|
||||
MOUSE_BTN_LEFT,
|
||||
MOUSE_BTN_RIGHT,
|
||||
MOUSE_BTN_MIDDLE,
|
||||
MOUSE_BTN_BACK,
|
||||
MOUSE_BTN_FORWARD,
|
||||
MOUSE_SCROLL_UP,
|
||||
MOUSE_SCROLL_DOWN,
|
||||
MOUSE_BTN_COUNT
|
||||
};
|
||||
|
||||
// Gamepad buttons
|
||||
enum GamepadButtons
|
||||
{
|
||||
GAMEPAD_BTN_TRIGGER_LEFT=1,
|
||||
GAMEPAD_BTN_TRIGGER_RIGHT,
|
||||
GAMEPAD_BTN_A,
|
||||
GAMEPAD_BTN_B,
|
||||
GAMEPAD_BTN_Y,
|
||||
GAMEPAD_BTN_X,
|
||||
GAMEPAD_BTN_SHOULDER_LEFT,
|
||||
GAMEPAD_BTN_SHOULDER_RIGHT,
|
||||
GAMEPAD_BTN_LEFT_JOYSTICK,
|
||||
GAMEPAD_BTN_RIGHT_JOYSTICK,
|
||||
GAMEPAD_BTN_START,
|
||||
GAMEPAD_BTN_SELECT,
|
||||
GAMEPAD_BTN_STEAM,
|
||||
GAMEPAD_BTN_DPAD_UP,
|
||||
GAMEPAD_BTN_DPAD_DOWN,
|
||||
GAMEPAD_BTN_DPAD_LEFT,
|
||||
GAMEPAD_BTN_DPAD_RIGHT,
|
||||
GAMEPAD_BTN_LSTICK_UP,
|
||||
GAMEPAD_BTN_LSTICK_DOWN,
|
||||
GAMEPAD_BTN_LSTICK_LEFT,
|
||||
GAMEPAD_BTN_LSTICK_RIGHT,
|
||||
GAMEPAD_BTN_RSTICK_UP,
|
||||
GAMEPAD_BTN_RSTICK_DOWN,
|
||||
GAMEPAD_BTN_RSTICK_LEFT,
|
||||
GAMEPAD_BTN_RSTICK_RIGHT,
|
||||
GAMEPAD_BTN_COUNT
|
||||
};
|
||||
|
||||
// Mode adjust
|
||||
enum ModeAdjustModes
|
||||
{
|
||||
MODE_ADJUST_SENSITITY=1,
|
||||
MODE_ADJUST_LEFT_PAD_SECONDARY_MODE,
|
||||
MODE_ADJUST_RIGHT_PAD_SECONDARY_MODE,
|
||||
MODE_ADJUST_COUNT
|
||||
};
|
||||
|
||||
// Read-only attributes of controllers (only add to this enum and never change the order)
|
||||
typedef enum
|
||||
{
|
||||
ATTRIB_UNIQUE_ID,
|
||||
ATTRIB_PRODUCT_ID,
|
||||
ATTRIB_PRODUCT_REVISON, // deprecated
|
||||
ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing
|
||||
ATTRIB_FIRMWARE_VERSION, // deprecated
|
||||
ATTRIB_FIRMWARE_BUILD_TIME,
|
||||
ATTRIB_RADIO_FIRMWARE_BUILD_TIME,
|
||||
ATTRIB_RADIO_DEVICE_ID0,
|
||||
ATTRIB_RADIO_DEVICE_ID1,
|
||||
ATTRIB_DONGLE_FIRMWARE_BUILD_TIME,
|
||||
ATTRIB_BOARD_REVISION,
|
||||
ATTRIB_BOOTLOADER_BUILD_TIME,
|
||||
ATTRIB_CONNECTION_INTERVAL_IN_US,
|
||||
ATTRIB_COUNT
|
||||
} ControllerAttributes;
|
||||
|
||||
// Read-only string attributes of controllers (only add to this enum and never change the order)
|
||||
typedef enum
|
||||
{
|
||||
ATTRIB_STR_BOARD_SERIAL,
|
||||
ATTRIB_STR_UNIT_SERIAL,
|
||||
ATTRIB_STR_COUNT
|
||||
} ControllerStringAttributes;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
STATUS_CODE_NORMAL,
|
||||
STATUS_CODE_CRITICAL_BATTERY,
|
||||
STATUS_CODE_GYRO_INIT_ERROR,
|
||||
} ControllerStatusEventCodes;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
STATUS_STATE_LOW_BATTERY=0,
|
||||
} ControllerStatusStateFlags;
|
||||
|
||||
typedef enum {
|
||||
TRACKPAD_ABSOLUTE_MOUSE,
|
||||
TRACKPAD_RELATIVE_MOUSE,
|
||||
TRACKPAD_DPAD_FOUR_WAY_DISCRETE,
|
||||
TRACKPAD_DPAD_FOUR_WAY_OVERLAP,
|
||||
TRACKPAD_DPAD_EIGHT_WAY,
|
||||
TRACKPAD_RADIAL_MODE,
|
||||
TRACKPAD_ABSOLUTE_DPAD,
|
||||
TRACKPAD_NONE,
|
||||
TRACKPAD_GESTURE_KEYBOARD,
|
||||
TRACKPAD_NUM_MODES
|
||||
} TrackpadDPadMode;
|
||||
|
||||
// Read-write controller settings (only add to this enum and never change the order)
|
||||
typedef enum
|
||||
{
|
||||
SETTING_MOUSE_SENSITIVITY,
|
||||
SETTING_MOUSE_ACCELERATION,
|
||||
SETTING_TRACKBALL_ROTATION_ANGLE,
|
||||
SETTING_HAPTIC_INTENSITY_UNUSED,
|
||||
SETTING_LEFT_GAMEPAD_STICK_ENABLED,
|
||||
SETTING_RIGHT_GAMEPAD_STICK_ENABLED,
|
||||
SETTING_USB_DEBUG_MODE,
|
||||
SETTING_LEFT_TRACKPAD_MODE,
|
||||
SETTING_RIGHT_TRACKPAD_MODE,
|
||||
SETTING_MOUSE_POINTER_ENABLED,
|
||||
|
||||
// 10
|
||||
SETTING_DPAD_DEADZONE,
|
||||
SETTING_MINIMUM_MOMENTUM_VEL,
|
||||
SETTING_MOMENTUM_DECAY_AMOUNT,
|
||||
SETTING_TRACKPAD_RELATIVE_MODE_TICKS_PER_PIXEL,
|
||||
SETTING_HAPTIC_INCREMENT,
|
||||
SETTING_DPAD_ANGLE_SIN,
|
||||
SETTING_DPAD_ANGLE_COS,
|
||||
SETTING_MOMENTUM_VERTICAL_DIVISOR,
|
||||
SETTING_MOMENTUM_MAXIMUM_VELOCITY,
|
||||
SETTING_TRACKPAD_Z_ON,
|
||||
|
||||
// 20
|
||||
SETTING_TRACKPAD_Z_OFF,
|
||||
SETTING_SENSITIVITY_SCALE_AMOUNT,
|
||||
SETTING_LEFT_TRACKPAD_SECONDARY_MODE,
|
||||
SETTING_RIGHT_TRACKPAD_SECONDARY_MODE,
|
||||
SETTING_SMOOTH_ABSOLUTE_MOUSE,
|
||||
SETTING_STEAMBUTTON_POWEROFF_TIME,
|
||||
SETTING_UNUSED_1,
|
||||
SETTING_TRACKPAD_OUTER_RADIUS,
|
||||
SETTING_TRACKPAD_Z_ON_LEFT,
|
||||
SETTING_TRACKPAD_Z_OFF_LEFT,
|
||||
|
||||
// 30
|
||||
SETTING_TRACKPAD_OUTER_SPIN_VEL,
|
||||
SETTING_TRACKPAD_OUTER_SPIN_RADIUS,
|
||||
SETTING_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY,
|
||||
SETTING_TRACKPAD_RELATIVE_MODE_DEADZONE,
|
||||
SETTING_TRACKPAD_RELATIVE_MODE_MAX_VEL,
|
||||
SETTING_TRACKPAD_RELATIVE_MODE_INVERT_Y,
|
||||
SETTING_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED,
|
||||
SETTING_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD,
|
||||
SETTING_TRACKPAD_DOUBLE_TAP_BEEP_COUNT,
|
||||
SETTING_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION,
|
||||
|
||||
// 40
|
||||
SETTING_RADIAL_MODE_ANGLE,
|
||||
SETTING_HAPTIC_INTENSITY_MOUSE_MODE,
|
||||
SETTING_LEFT_DPAD_REQUIRES_CLICK,
|
||||
SETTING_RIGHT_DPAD_REQUIRES_CLICK,
|
||||
SETTING_LED_BASELINE_BRIGHTNESS,
|
||||
SETTING_LED_USER_BRIGHTNESS,
|
||||
SETTING_ENABLE_RAW_JOYSTICK,
|
||||
SETTING_ENABLE_FAST_SCAN,
|
||||
SETTING_IMU_MODE,
|
||||
SETTING_WIRELESS_PACKET_VERSION,
|
||||
|
||||
// 50
|
||||
SETTING_SLEEP_INACTIVITY_TIMEOUT,
|
||||
SETTING_TRACKPAD_NOISE_THRESHOLD,
|
||||
SETTING_LEFT_TRACKPAD_CLICK_PRESSURE,
|
||||
SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE,
|
||||
SETTING_LEFT_BUMPER_CLICK_PRESSURE,
|
||||
SETTING_RIGHT_BUMPER_CLICK_PRESSURE,
|
||||
SETTING_LEFT_GRIP_CLICK_PRESSURE,
|
||||
SETTING_RIGHT_GRIP_CLICK_PRESSURE,
|
||||
SETTING_LEFT_GRIP2_CLICK_PRESSURE,
|
||||
SETTING_RIGHT_GRIP2_CLICK_PRESSURE,
|
||||
|
||||
// 60
|
||||
SETTING_PRESSURE_MODE,
|
||||
SETTING_CONTROLLER_TEST_MODE,
|
||||
SETTING_TRIGGER_MODE,
|
||||
SETTING_TRACKPAD_Z_THRESHOLD,
|
||||
SETTING_FRAME_RATE,
|
||||
SETTING_TRACKPAD_FILT_CTRL,
|
||||
SETTING_TRACKPAD_CLIP,
|
||||
SETTING_DEBUG_OUTPUT_SELECT,
|
||||
SETTING_TRIGGER_THRESHOLD_PERCENT,
|
||||
SETTING_TRACKPAD_FREQUENCY_HOPPING,
|
||||
|
||||
// 70
|
||||
SETTING_HAPTICS_ENABLED,
|
||||
SETTING_STEAM_WATCHDOG_ENABLE,
|
||||
SETTING_TIMP_TOUCH_THRESHOLD_ON,
|
||||
SETTING_TIMP_TOUCH_THRESHOLD_OFF,
|
||||
SETTING_FREQ_HOPPING,
|
||||
SETTING_TEST_CONTROL,
|
||||
SETTING_HAPTIC_MASTER_GAIN_DB,
|
||||
SETTING_THUMB_TOUCH_THRESH,
|
||||
SETTING_DEVICE_POWER_STATUS,
|
||||
SETTING_HAPTIC_INTENSITY,
|
||||
|
||||
// 80
|
||||
SETTING_STABILIZER_ENABLED,
|
||||
SETTING_TIMP_MODE_MTE,
|
||||
SETTING_COUNT,
|
||||
|
||||
// This is a special setting value use for callbacks and should not be set/get explicitly.
|
||||
SETTING_ALL=0xFF
|
||||
} ControllerSettings;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SETTING_DEFAULT,
|
||||
SETTING_MIN,
|
||||
SETTING_MAX,
|
||||
SETTING_DEFAULTMINMAXCOUNT
|
||||
} SettingDefaultMinMax;
|
||||
|
||||
// Bitmask that define which IMU features to enable.
|
||||
typedef enum
|
||||
{
|
||||
SETTING_GYRO_MODE_OFF = 0x0000,
|
||||
SETTING_GYRO_MODE_STEERING = 0x0001,
|
||||
SETTING_GYRO_MODE_TILT = 0x0002,
|
||||
SETTING_GYRO_MODE_SEND_ORIENTATION = 0x0004,
|
||||
SETTING_GYRO_MODE_SEND_RAW_ACCEL = 0x0008,
|
||||
SETTING_GYRO_MODE_SEND_RAW_GYRO = 0x0010,
|
||||
} SettingGyroMode;
|
||||
|
||||
// Bitmask for haptic pulse flags
|
||||
typedef enum
|
||||
{
|
||||
HAPTIC_PULSE_NORMAL = 0x0000,
|
||||
HAPTIC_PULSE_HIGH_PRIORITY = 0x0001,
|
||||
HAPTIC_PULSE_VERY_HIGH_PRIORITY = 0x0002,
|
||||
HAPTIC_PULSE_IGNORE_USER_PREFS = 0x0003,
|
||||
} SettingHapticPulseFlags;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// default,min,max in this array in that order
|
||||
short defaultminmax[SETTING_DEFAULTMINMAXCOUNT];
|
||||
} SettingValueRange_t;
|
||||
|
||||
// below is from controller_constants.c which should be compiled into any code that uses this
|
||||
extern const SettingValueRange_t g_DefaultSettingValues[SETTING_COUNT];
|
||||
|
||||
// Read-write settings for dongle (only add to this enum and never change the order)
|
||||
typedef enum
|
||||
{
|
||||
DONGLE_SETTING_MOUSE_KEYBOARD_ENABLED,
|
||||
DONGLE_SETTING_COUNT,
|
||||
} DongleSettings;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
AUDIO_STARTUP = 0,
|
||||
AUDIO_SHUTDOWN = 1,
|
||||
AUDIO_PAIR = 2,
|
||||
AUDIO_PAIR_SUCCESS = 3,
|
||||
AUDIO_IDENTIFY = 4,
|
||||
AUDIO_LIZARDMODE = 5,
|
||||
AUDIO_NORMALMODE = 6,
|
||||
|
||||
AUDIO_MAX_SLOT = 15
|
||||
} ControllerAudio;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _CONTROLLER_CONSTANTS_H
|
||||
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 2020 Valve Corporation
|
||||
|
||||
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.
|
||||
*/
|
||||
#ifndef _CONTROLLER_STRUCTS_
|
||||
#define _CONTROLLER_STRUCTS_
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
#define HID_FEATURE_REPORT_BYTES 64
|
||||
|
||||
// Header for all host <==> target messages
|
||||
typedef struct
|
||||
{
|
||||
unsigned char type;
|
||||
unsigned char length;
|
||||
} FeatureReportHeader;
|
||||
|
||||
// Generic controller settings structure
|
||||
typedef struct
|
||||
{
|
||||
unsigned char settingNum;
|
||||
unsigned short settingValue;
|
||||
} ControllerSetting;
|
||||
|
||||
// Generic controller attribute structure
|
||||
typedef struct
|
||||
{
|
||||
unsigned char attributeTag;
|
||||
uint32_t attributeValue;
|
||||
} ControllerAttribute;
|
||||
|
||||
// Generic controller settings structure
|
||||
typedef struct
|
||||
{
|
||||
ControllerSetting settings[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerSetting ) ];
|
||||
} MsgSetSettingsValues, MsgGetSettingsValues, MsgGetSettingsDefaults, MsgGetSettingsMaxs;
|
||||
|
||||
// Generic controller settings structure
|
||||
typedef struct
|
||||
{
|
||||
ControllerAttribute attributes[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerAttribute ) ];
|
||||
} MsgGetAttributes;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char attributeTag;
|
||||
char attributeValue[20];
|
||||
} MsgGetStringAttribute;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char mode;
|
||||
} MsgSetControllerMode;
|
||||
|
||||
// Trigger a haptic pulse
|
||||
typedef struct {
|
||||
unsigned char which_pad;
|
||||
unsigned short pulse_duration;
|
||||
unsigned short pulse_interval;
|
||||
unsigned short pulse_count;
|
||||
short dBgain;
|
||||
unsigned char priority;
|
||||
} MsgFireHapticPulse;
|
||||
|
||||
typedef struct {
|
||||
uint8_t mode;
|
||||
} MsgHapticSetMode;
|
||||
|
||||
typedef enum {
|
||||
HAPTIC_TYPE_OFF,
|
||||
HAPTIC_TYPE_TICK,
|
||||
HAPTIC_TYPE_CLICK,
|
||||
HAPTIC_TYPE_TONE,
|
||||
HAPTIC_TYPE_RUMBLE,
|
||||
HAPTIC_TYPE_NOISE,
|
||||
HAPTIC_TYPE_SCRIPT,
|
||||
HAPTIC_TYPE_LOG_SWEEP,
|
||||
} haptic_type_t;
|
||||
|
||||
typedef enum {
|
||||
HAPTIC_INTENSITY_SYSTEM,
|
||||
HAPTIC_INTENSITY_SHORT,
|
||||
HAPTIC_INTENSITY_MEDIUM,
|
||||
HAPTIC_INTENSITY_LONG,
|
||||
HAPTIC_INTENSITY_INSANE,
|
||||
} haptic_intensity_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t side; // 0x01 = L, 0x02 = R, 0x03 = Both
|
||||
uint8_t cmd; // 0 = Off, 1 = tick, 2 = click, 3 = tone, 4 = rumble, 5 =
|
||||
// rumble_noise, 6 = script, 7 = sweep,
|
||||
uint8_t ui_intensity; // 0-4 (0 = default)
|
||||
int8_t dBgain; // dB Can be positive (reasonable clipping / limiting will apply)
|
||||
uint16_t freq; // Frequency of tone (if applicable)
|
||||
int16_t dur_ms; // Duration of tone / rumble (if applicable) (neg = infinite)
|
||||
|
||||
uint16_t noise_intensity;
|
||||
uint16_t lfo_freq; // Drives both tone and rumble geneators
|
||||
uint8_t lfo_depth; // percentage, typically 100
|
||||
uint8_t rand_tone_gain; // Randomize each LFO cycle's gain
|
||||
uint8_t script_id; // Used w/ dBgain for scripted haptics
|
||||
|
||||
uint16_t lss_start_freq; // Used w/ Log Sine Sweep
|
||||
uint16_t lss_end_freq; // Ditto
|
||||
} MsgTriggerHaptic;
|
||||
|
||||
typedef struct {
|
||||
uint8_t unRumbleType;
|
||||
uint16_t unIntensity;
|
||||
uint16_t unLeftMotorSpeed;
|
||||
uint16_t unRightMotorSpeed;
|
||||
int8_t nLeftGain;
|
||||
int8_t nRightGain;
|
||||
} MsgSimpleRumbleCmd;
|
||||
|
||||
// This is the only message struct that application code should use to interact with feature request messages. Any new
|
||||
// messages should be added to the union. The structures defined here should correspond to the ones defined in
|
||||
// ValveDeviceCore.cpp.
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
FeatureReportHeader header;
|
||||
union
|
||||
{
|
||||
MsgSetSettingsValues setSettingsValues;
|
||||
MsgGetSettingsValues getSettingsValues;
|
||||
MsgGetSettingsMaxs getSettingsMaxs;
|
||||
MsgGetSettingsDefaults getSettingsDefaults;
|
||||
MsgGetAttributes getAttributes;
|
||||
MsgSetControllerMode controllerMode;
|
||||
MsgFireHapticPulse fireHapticPulse;
|
||||
MsgGetStringAttribute getStringAttribute;
|
||||
MsgHapticSetMode hapticMode;
|
||||
MsgTriggerHaptic triggerHaptic;
|
||||
MsgSimpleRumbleCmd simpleRumble;
|
||||
} payload;
|
||||
|
||||
} FeatureReportMsg;
|
||||
|
||||
// Roll this version forward anytime that you are breaking compatibility of existing
|
||||
// message types within ValveInReport_t or the header itself. Hopefully this should
|
||||
// be super rare and instead you should just add new message payloads to the union,
|
||||
// or just add fields to the end of existing payload structs which is expected to be
|
||||
// safe in all code consuming these as they should just consume/copy up to the prior size
|
||||
// they were aware of when processing.
|
||||
#define k_ValveInReportMsgVersion 0x01
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ID_CONTROLLER_STATE = 1,
|
||||
ID_CONTROLLER_DEBUG = 2,
|
||||
ID_CONTROLLER_WIRELESS = 3,
|
||||
ID_CONTROLLER_STATUS = 4,
|
||||
ID_CONTROLLER_DEBUG2 = 5,
|
||||
ID_CONTROLLER_SECONDARY_STATE = 6,
|
||||
ID_CONTROLLER_BLE_STATE = 7,
|
||||
ID_CONTROLLER_DECK_STATE = 9,
|
||||
ID_CONTROLLER_MSG_COUNT
|
||||
} ValveInReportMessageIDs;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned short unReportVersion;
|
||||
|
||||
unsigned char ucType;
|
||||
unsigned char ucLength;
|
||||
|
||||
} ValveInReportHeader_t;
|
||||
|
||||
// State payload
|
||||
typedef struct
|
||||
{
|
||||
// If packet num matches that on your prior call, then the controller state hasn't been changed since
|
||||
// your last call and there is no need to process it
|
||||
Uint32 unPacketNum;
|
||||
|
||||
// Button bitmask and trigger data.
|
||||
union
|
||||
{
|
||||
Uint64 ulButtons;
|
||||
struct
|
||||
{
|
||||
unsigned char _pad0[3];
|
||||
unsigned char nLeft;
|
||||
unsigned char nRight;
|
||||
unsigned char _pad1[3];
|
||||
} Triggers;
|
||||
} ButtonTriggerData;
|
||||
|
||||
// Left pad coordinates
|
||||
short sLeftPadX;
|
||||
short sLeftPadY;
|
||||
|
||||
// Right pad coordinates
|
||||
short sRightPadX;
|
||||
short sRightPadY;
|
||||
|
||||
// This is redundant, packed above, but still sent over wired
|
||||
unsigned short sTriggerL;
|
||||
unsigned short sTriggerR;
|
||||
|
||||
// FIXME figure out a way to grab this stuff over wireless
|
||||
short sAccelX;
|
||||
short sAccelY;
|
||||
short sAccelZ;
|
||||
|
||||
short sGyroX;
|
||||
short sGyroY;
|
||||
short sGyroZ;
|
||||
|
||||
short sGyroQuatW;
|
||||
short sGyroQuatX;
|
||||
short sGyroQuatY;
|
||||
short sGyroQuatZ;
|
||||
|
||||
} ValveControllerStatePacket_t;
|
||||
|
||||
// BLE State payload this has to be re-formatted from the normal state because BLE controller shows up as
|
||||
//a HID device and we don't want to send all the optional parts of the message. Keep in sync with struct above.
|
||||
typedef struct
|
||||
{
|
||||
// If packet num matches that on your prior call, then the controller state hasn't been changed since
|
||||
// your last call and there is no need to process it
|
||||
Uint32 unPacketNum;
|
||||
|
||||
// Button bitmask and trigger data.
|
||||
union
|
||||
{
|
||||
Uint64 ulButtons;
|
||||
struct
|
||||
{
|
||||
unsigned char _pad0[3];
|
||||
unsigned char nLeft;
|
||||
unsigned char nRight;
|
||||
unsigned char _pad1[3];
|
||||
} Triggers;
|
||||
} ButtonTriggerData;
|
||||
|
||||
// Left pad coordinates
|
||||
short sLeftPadX;
|
||||
short sLeftPadY;
|
||||
|
||||
// Right pad coordinates
|
||||
short sRightPadX;
|
||||
short sRightPadY;
|
||||
|
||||
//This mimcs how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode
|
||||
unsigned char ucGyroDataType; //TODO could maybe find some unused bits in the button field for this info (is only 2bits)
|
||||
short sGyro[4];
|
||||
|
||||
} ValveControllerBLEStatePacket_t;
|
||||
|
||||
// Define a payload for reporting debug information
|
||||
typedef struct
|
||||
{
|
||||
// Left pad coordinates
|
||||
short sLeftPadX;
|
||||
short sLeftPadY;
|
||||
|
||||
// Right pad coordinates
|
||||
short sRightPadX;
|
||||
short sRightPadY;
|
||||
|
||||
// Left mouse deltas
|
||||
short sLeftPadMouseDX;
|
||||
short sLeftPadMouseDY;
|
||||
|
||||
// Right mouse deltas
|
||||
short sRightPadMouseDX;
|
||||
short sRightPadMouseDY;
|
||||
|
||||
// Left mouse filtered deltas
|
||||
short sLeftPadMouseFilteredDX;
|
||||
short sLeftPadMouseFilteredDY;
|
||||
|
||||
// Right mouse filtered deltas
|
||||
short sRightPadMouseFilteredDX;
|
||||
short sRightPadMouseFilteredDY;
|
||||
|
||||
// Pad Z values
|
||||
unsigned char ucLeftZ;
|
||||
unsigned char ucRightZ;
|
||||
|
||||
// FingerPresent
|
||||
unsigned char ucLeftFingerPresent;
|
||||
unsigned char ucRightFingerPresent;
|
||||
|
||||
// Timestamps
|
||||
unsigned char ucLeftTimestamp;
|
||||
unsigned char ucRightTimestamp;
|
||||
|
||||
// Double tap state
|
||||
unsigned char ucLeftTapState;
|
||||
unsigned char ucRightTapState;
|
||||
|
||||
unsigned int unDigitalIOStates0;
|
||||
unsigned int unDigitalIOStates1;
|
||||
|
||||
} ValveControllerDebugPacket_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char ucPadNum;
|
||||
unsigned char ucPad[3]; // need Data to be word aligned
|
||||
short Data[20];
|
||||
unsigned short unNoise;
|
||||
} ValveControllerTrackpadImage_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char ucPadNum;
|
||||
unsigned char ucOffset;
|
||||
unsigned char ucPad[2]; // need Data to be word aligned
|
||||
short rgData[28];
|
||||
} ValveControllerRawTrackpadImage_t;
|
||||
|
||||
// Payload for wireless metadata
|
||||
typedef struct
|
||||
{
|
||||
unsigned char ucEventType;
|
||||
} SteamControllerWirelessEvent_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// Current packet number.
|
||||
unsigned int unPacketNum;
|
||||
|
||||
// Event codes and state information.
|
||||
unsigned short sEventCode;
|
||||
unsigned short unStateFlags;
|
||||
|
||||
// Current battery voltage (mV).
|
||||
unsigned short sBatteryVoltage;
|
||||
|
||||
// Current battery level (0-100).
|
||||
unsigned char ucBatteryLevel;
|
||||
} SteamControllerStatusEvent_t;
|
||||
|
||||
// Deck State payload
|
||||
typedef struct
|
||||
{
|
||||
// If packet num matches that on your prior call, then the controller
|
||||
// state hasn't been changed since your last call and there is no need to
|
||||
// process it
|
||||
Uint32 unPacketNum;
|
||||
|
||||
// Button bitmask and trigger data.
|
||||
union
|
||||
{
|
||||
Uint64 ulButtons;
|
||||
struct
|
||||
{
|
||||
Uint32 ulButtonsL;
|
||||
Uint32 ulButtonsH;
|
||||
};
|
||||
};
|
||||
|
||||
// Left pad coordinates
|
||||
short sLeftPadX;
|
||||
short sLeftPadY;
|
||||
|
||||
// Right pad coordinates
|
||||
short sRightPadX;
|
||||
short sRightPadY;
|
||||
|
||||
// Accelerometer values
|
||||
short sAccelX;
|
||||
short sAccelY;
|
||||
short sAccelZ;
|
||||
|
||||
// Gyroscope values
|
||||
short sGyroX;
|
||||
short sGyroY;
|
||||
short sGyroZ;
|
||||
|
||||
// Gyro quaternions
|
||||
short sGyroQuatW;
|
||||
short sGyroQuatX;
|
||||
short sGyroQuatY;
|
||||
short sGyroQuatZ;
|
||||
|
||||
// Uncalibrated trigger values
|
||||
unsigned short sTriggerRawL;
|
||||
unsigned short sTriggerRawR;
|
||||
|
||||
// Left stick values
|
||||
short sLeftStickX;
|
||||
short sLeftStickY;
|
||||
|
||||
// Right stick values
|
||||
short sRightStickX;
|
||||
short sRightStickY;
|
||||
|
||||
// Touchpad pressures
|
||||
unsigned short sPressurePadLeft;
|
||||
unsigned short sPressurePadRight;
|
||||
} SteamDeckStatePacket_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ValveInReportHeader_t header;
|
||||
|
||||
union
|
||||
{
|
||||
ValveControllerStatePacket_t controllerState;
|
||||
ValveControllerBLEStatePacket_t controllerBLEState;
|
||||
ValveControllerDebugPacket_t debugState;
|
||||
ValveControllerTrackpadImage_t padImage;
|
||||
ValveControllerRawTrackpadImage_t rawPadImage;
|
||||
SteamControllerWirelessEvent_t wirelessEvent;
|
||||
SteamControllerStatusEvent_t statusEvent;
|
||||
SteamDeckStatePacket_t deckState;
|
||||
} payload;
|
||||
|
||||
} ValveInReport_t;
|
||||
|
||||
|
||||
// Enumeration for BLE packet protocol
|
||||
enum EBLEPacketReportNums
|
||||
{
|
||||
// Skipping past 2-3 because they are escape characters in Uart protocol
|
||||
k_EBLEReportState = 4,
|
||||
k_EBLEReportStatus = 5,
|
||||
};
|
||||
|
||||
|
||||
// Enumeration of data chunks in BLE state packets
|
||||
enum EBLEOptionDataChunksBitmask
|
||||
{
|
||||
// First byte upper nibble
|
||||
k_EBLEButtonChunk1 = 0x10,
|
||||
k_EBLEButtonChunk2 = 0x20,
|
||||
k_EBLEButtonChunk3 = 0x40,
|
||||
k_EBLELeftJoystickChunk = 0x80,
|
||||
|
||||
// Second full byte
|
||||
k_EBLELeftTrackpadChunk = 0x100,
|
||||
k_EBLERightTrackpadChunk = 0x200,
|
||||
k_EBLEIMUAccelChunk = 0x400,
|
||||
k_EBLEIMUGyroChunk = 0x800,
|
||||
k_EBLEIMUQuatChunk = 0x1000,
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
#endif // _CONTROLLER_STRUCTS
|
||||
Reference in New Issue
Block a user