blob: 55048f60830e800074be559f63a06e2a0e4d2a99 [file] [log] [blame]
/*
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"
#include "SDL_hidapi_flydigi.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 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 connection observed in testcontroller 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;
}
}
}
Uint8 controller_type = SDL_FLYDIGI_UNKNOWN;
switch (ctx->deviceID) {
case 19:
controller_type = SDL_FLYDIGI_APEX2;
break;
case 24:
case 26:
case 29:
controller_type = SDL_FLYDIGI_APEX3;
break;
case 84:
controller_type = SDL_FLYDIGI_APEX4;
break;
case 20:
case 21:
case 23:
controller_type = SDL_FLYDIGI_VADER2;
break;
case 22:
controller_type = SDL_FLYDIGI_VADER2_PRO;
break;
case 28:
controller_type = SDL_FLYDIGI_VADER3;
break;
case 80:
case 81:
controller_type = SDL_FLYDIGI_VADER3_PRO;
break;
case 85:
case 91:
case 105:
controller_type = SDL_FLYDIGI_VADER4_PRO;
break;
default:
// Try to guess from the name of the controller
if (SDL_strstr(device->name, "VADER") != NULL) {
if (SDL_strstr(device->name, "VADER2") != NULL) {
controller_type = SDL_FLYDIGI_VADER2;
} else if (SDL_strstr(device->name, "VADER3") != NULL) {
controller_type = SDL_FLYDIGI_VADER3;
} else if (SDL_strstr(device->name, "VADER4") != NULL) {
controller_type = SDL_FLYDIGI_VADER4;
}
} else if (SDL_strstr(device->name, "APEX") != NULL) {
if (SDL_strstr(device->name, "APEX2") != NULL) {
controller_type = SDL_FLYDIGI_APEX2;
} else if (SDL_strstr(device->name, "APEX3") != NULL) {
controller_type = SDL_FLYDIGI_APEX3;
} else if (SDL_strstr(device->name, "APEX4") != NULL) {
controller_type = SDL_FLYDIGI_APEX4;
} else if (SDL_strstr(device->name, "APEX5") != NULL) {
controller_type = SDL_FLYDIGI_APEX5;
}
}
break;
}
device->guid.data[15] = controller_type;
// 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 (controller_type) {
case SDL_FLYDIGI_APEX2:
HIDAPI_SetDeviceName(device, "Flydigi Apex 2");
break;
case SDL_FLYDIGI_APEX3:
HIDAPI_SetDeviceName(device, "Flydigi Apex 3");
break;
case SDL_FLYDIGI_APEX4:
// The Apex 4 controller has sensors, but they're only reported when gyro mouse is enabled
HIDAPI_SetDeviceName(device, "Flydigi Apex 4");
break;
case SDL_FLYDIGI_APEX5:
HIDAPI_SetDeviceName(device, "Flydigi Apex 5");
break;
case SDL_FLYDIGI_VADER2:
// 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 SDL_FLYDIGI_VADER2_PRO:
HIDAPI_SetDeviceName(device, "Flydigi Vader 2 Pro");
ctx->has_cz = true;
break;
case SDL_FLYDIGI_VADER3:
HIDAPI_SetDeviceName(device, "Flydigi Vader 3");
ctx->has_cz = true;
break;
case SDL_FLYDIGI_VADER3_PRO:
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 SDL_FLYDIGI_VADER4:
case SDL_FLYDIGI_VADER4_PRO:
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[1] != 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 0x01u:
hat = SDL_HAT_UP;
break;
case 0x02u | 0x01u:
hat = SDL_HAT_RIGHTUP;
break;
case 0x02u:
hat = SDL_HAT_RIGHT;
break;
case 0x02u | 0x04u:
hat = SDL_HAT_RIGHTDOWN;
break;
case 0x04u:
hat = SDL_HAT_DOWN;
break;
case 0x08u | 0x04u:
hat = SDL_HAT_LEFTDOWN;
break;
case 0x08u:
hat = SDL_HAT_LEFT;
break;
case 0x08u | 0x01u:
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