From a82e8a701d129c933a588f201505cc8c0d2a690f Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 1 Nov 2025 13:55:07 -0700
Subject: [PATCH] Allow dynamically switching mode of the Apex 5 controller
The controller can be in XInput mode or enhanced mode, so it will always show up as an XInput controller, and the enhanced mode controller will come and go as enhanced mode is enabled or disabled in the FlyDigi Space Station app.
---
src/joystick/hidapi/SDL_hidapi_flydigi.c | 96 ++++++++++++++++--------
src/joystick/hidapi/SDL_hidapijoystick.c | 5 ++
2 files changed, 71 insertions(+), 30 deletions(-)
diff --git a/src/joystick/hidapi/SDL_hidapi_flydigi.c b/src/joystick/hidapi/SDL_hidapi_flydigi.c
index a36b022834fc8..73b185becfa8c 100644
--- a/src/joystick/hidapi/SDL_hidapi_flydigi.c
+++ b/src/joystick/hidapi/SDL_hidapi_flydigi.c
@@ -81,7 +81,9 @@ enum
typedef struct
{
+ SDL_HIDAPI_Device *device;
Uint8 deviceID;
+ bool available;
bool has_cz;
bool has_lmrm;
bool wireless;
@@ -173,9 +175,31 @@ static bool HIDAPI_DriverFlydigi_InitControllerV1(SDL_HIDAPI_Device *device)
}
}
}
+ ctx->available = true;
+
return true;
}
+static void HIDAPI_DriverFlydigi_SetAvailable(SDL_HIDAPI_Device* device, bool available)
+{
+ SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
+
+ if (available == ctx->available) {
+ return;
+ }
+
+ if (available) {
+ if (device->num_joysticks == 0) {
+ HIDAPI_JoystickConnected(device, NULL);
+ }
+ } else {
+ if (device->num_joysticks > 0) {
+ HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
+ }
+ }
+ ctx->available = available;
+}
+
static bool GetReply(SDL_HIDAPI_Device* device, Uint8 command, Uint8* data, size_t length)
{
for (int i = 0; i < 100; ++i) {
@@ -218,12 +242,12 @@ static bool SDL_HIDAPI_Flydigi_SendAcquireRequest(SDL_HIDAPI_Device *device, boo
return true;
}
-static bool HIDAPI_DriverFlydigi_HandleAcquireResponse(Uint8 *data, int size)
+static void HIDAPI_DriverFlydigi_HandleAcquireResponse(SDL_HIDAPI_Device *device, Uint8 *data, int size)
{
if (data[5] != 1 && data[6] == 0) {
- return SDL_SetError("Controller acquiring has been disabled");
+ // Controller acquiring failed or has been disabled
+ HIDAPI_DriverFlydigi_SetAvailable(device, false);
}
- return true;
}
static bool HIDAPI_DriverFlydigi_InitControllerV2(SDL_HIDAPI_Device *device)
@@ -267,13 +291,14 @@ static bool HIDAPI_DriverFlydigi_InitControllerV2(SDL_HIDAPI_Device *device)
if (!GetReply(device, FLYDIGI_V2_GET_STATUS_COMMAND, data, sizeof(data))) {
return SDL_SetError("Couldn't get controller status");
}
- if (data[10] != 1) {
+ if (data[10] == 1) {
+ ctx->available = true;
+ } else {
#ifdef SDL_PLATFORM_WINDOWS
// Click "Allow third-party apps to take over mappings" in the FlyDigi Space Station app
- return SDL_SetError("Controller acquiring is disabled");
#else
// The FlyDigi Space Station app isn't available, we need to enable this ourselves
- Uint8 enable_acquire[32] = {
+ Uint8 enable_acquire[] = {
FLYDIGI_V2_CMD_REPORT_ID,
FLYDIGI_V2_MAGIC1,
FLYDIGI_V2_MAGIC2,
@@ -419,6 +444,8 @@ static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device)
if (!ctx) {
return false;
}
+ ctx->device = device;
+
device->context = ctx;
bool initialized;
@@ -433,7 +460,12 @@ static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device)
HIDAPI_DriverFlydigi_UpdateDeviceIdentity(device);
- return HIDAPI_JoystickConnected(device, NULL);
+ if (ctx->available) {
+ return HIDAPI_JoystickConnected(device, NULL);
+ } else {
+ // We'll connect it once it becomes available
+ return true;
+ }
}
static int HIDAPI_DriverFlydigi_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
@@ -657,15 +689,16 @@ static void HIDAPI_DriverFlydigi_HandleStatePacketV1(SDL_Joystick *joystick, SDL
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
-static bool HIDAPI_DriverFlydigi_HandlePacketV1(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size)
+static void HIDAPI_DriverFlydigi_HandlePacketV1(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size)
{
if (data[0] != 0x04 || data[1] != 0xFE) {
// We don't know how to handle this report, ignore it
- return true;
+ return;
}
- HIDAPI_DriverFlydigi_HandleStatePacketV1(joystick, ctx, data, size);
- return true;
+ if (joystick) {
+ HIDAPI_DriverFlydigi_HandleStatePacketV1(joystick, ctx, data, size);
+ }
}
static void HIDAPI_DriverFlydigi_HandleStatePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size)
@@ -792,7 +825,18 @@ static void HIDAPI_DriverFlydigi_HandleStatePacketV2(SDL_Joystick *joystick, SDL
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
-static bool HIDAPI_DriverFlydigi_HandlePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size)
+static void HIDAPI_DriverFlydigi_HandleStatusUpdate(SDL_HIDAPI_Device *device, Uint8 *data, int size)
+{
+ if (data[9] == 1) {
+ // We can now acquire the controller
+ HIDAPI_DriverFlydigi_SetAvailable(device, true);
+ } else {
+ // We can no longer acquire the controller
+ HIDAPI_DriverFlydigi_SetAvailable(device, false);
+ }
+}
+
+static void HIDAPI_DriverFlydigi_HandlePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size)
{
if (size > 0 && data[0] != 0x5A) {
// If first byte is not 0x5A, it must be REPORT_ID, we need to remove it.
@@ -801,23 +845,25 @@ static bool HIDAPI_DriverFlydigi_HandlePacketV2(SDL_Joystick *joystick, SDL_Driv
}
if (size < 31 || data[0] != FLYDIGI_V2_MAGIC1 || data[1] != FLYDIGI_V2_MAGIC2) {
// We don't know how to handle this report, ignore it
- return true;
+ return;
}
switch (data[2]) {
case FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND:
- if (!HIDAPI_DriverFlydigi_HandleAcquireResponse(data, size)) {
- return false;
- }
+ HIDAPI_DriverFlydigi_HandleAcquireResponse(ctx->device, data, size);
break;
case FLYDIGI_V2_INPUT_REPORT:
- HIDAPI_DriverFlydigi_HandleStatePacketV2(joystick, ctx, data, size);
+ if (joystick) {
+ HIDAPI_DriverFlydigi_HandleStatePacketV2(joystick, ctx, data, size);
+ }
+ break;
+ case FLYDIGI_V2_SET_STATUS_COMMAND:
+ HIDAPI_DriverFlydigi_HandleStatusUpdate(ctx->device, data, size);
break;
default:
// We don't recognize this command, ignore it
break;
}
- return true;
}
static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device)
@@ -845,20 +891,10 @@ static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device)
#endif
ctx->last_packet = now;
- if (!joystick) {
- continue;
- }
-
if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) {
- if (!HIDAPI_DriverFlydigi_HandlePacketV1(joystick, ctx, data, size)) {
- HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
- return false;
- }
+ HIDAPI_DriverFlydigi_HandlePacketV1(joystick, ctx, data, size);
} else {
- if (!HIDAPI_DriverFlydigi_HandlePacketV2(joystick, ctx, data, size)) {
- HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
- return false;
- }
+ HIDAPI_DriverFlydigi_HandlePacketV2(joystick, ctx, data, size);
}
}
diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index 7b83ffba7fffc..137822b4ad3ce 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -1305,6 +1305,11 @@ bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version,
*/
SDL_LockJoysticks();
for (device = SDL_HIDAPI_devices; device; device = device->next) {
+ if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) {
+ // Ignore the Apex 5, as it can dynamically switch between Xbox and HIDAPI mode
+ continue;
+ }
+
if (device->driver &&
HIDAPI_IsEquivalentToDevice(vendor_id, product_id, device)) {
result = true;