Skip to content

Commit c9cf4c4

Browse files
committed
Improved HIDAPI support for Flydigi controllers
1 parent ead32c7 commit c9cf4c4

File tree

5 files changed

+177
-81
lines changed

5 files changed

+177
-81
lines changed

src/hidapi/SDL_hidapi.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,7 @@ bool SDL_HIDAPI_ShouldIgnoreDevice(int bus, Uint16 vendor_id, Uint16 product_id,
11091109
(usage == USB_USAGE_GENERIC_KEYBOARD || usage == USB_USAGE_GENERIC_MOUSE)) {
11101110
return true;
11111111
}
1112-
} else if (vendor_id == USB_VENDOR_FLYDIGI && product_id == USB_PRODUCT_FLYDIGI_VADER4_PRO) {
1112+
} else if (vendor_id == USB_VENDOR_FLYDIGI && product_id == USB_PRODUCT_FLYDIGI_GAMEPAD) {
11131113
if (usage_page == USB_USAGEPAGE_VENDOR_FLYDIGI) {
11141114
return false;
11151115
}

src/joystick/SDL_gamepad.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,20 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
831831
/* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */
832832
SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string));
833833
} else if (SDL_IsJoystickFlydigiController(vendor, product)) {
834-
SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,misc1:b15,misc2:b16,misc3:b17", sizeof(mapping_string));
834+
SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,", sizeof(mapping_string));
835+
switch (guid.data[15]) {
836+
case 20:
837+
case 21:
838+
case 22:
839+
case 23:
840+
case 28:
841+
case 80:
842+
case 81:
843+
case 85:
844+
// Vader series of controllers have C/Z buttons
845+
SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string));
846+
break;
847+
}
835848
} else if (vendor == USB_VENDOR_8BITDO && product == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) {
836849
SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string));
837850
} else {

src/joystick/SDL_joystick.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3177,7 +3177,7 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id)
31773177

31783178
bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id)
31793179
{
3180-
return vendor_id == USB_VENDOR_FLYDIGI && (product_id == USB_PRODUCT_FLYDIGI_VADER4_PRO);
3180+
return vendor_id == USB_VENDOR_FLYDIGI && (product_id == USB_PRODUCT_FLYDIGI_GAMEPAD);
31813181
}
31823182

31833183
bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id)

src/joystick/hidapi/SDL_hidapi_flydigi.c

Lines changed: 159 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -39,60 +39,30 @@ enum
3939
SDL_GAMEPAD_BUTTON_FLYDIGI_M2,
4040
SDL_GAMEPAD_BUTTON_FLYDIGI_M3,
4141
SDL_GAMEPAD_BUTTON_FLYDIGI_M4,
42-
SDL_GAMEPAD_BUTTON_FLYDIGI_FN,
43-
SDL_GAMEPAD_BUTTON_FLYDIGI_C,
44-
SDL_GAMEPAD_BUTTON_FLYDIGI_Z,
45-
SDL_GAMEPAD_NUM_FLYDIGI_BUTTONS_WITH_CZ,
42+
SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS
4643
};
47-
#define SDL_GAMEPAD_NUM_FLYDIGI_BUTTONS_WITHOUT_CZ SDL_GAMEPAD_BUTTON_FLYDIGI_C
4844

49-
#define FLYDIGI_ACCEL_SCALE 256.f
5045
#define SENSOR_INTERVAL_NS 8000000ULL
5146
#define FLYDIGI_CMD_REPORT_ID 0x05
5247
#define FLYDIGI_HAPTIC_COMMAND 0x0F
5348
#define FLYDIGI_GET_CONFIG_COMMAND 0xEB
49+
#define FLYDIGI_GET_INFO_COMMAND 0xEC
5450

5551
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
5652

5753
typedef struct
5854
{
55+
Uint8 deviceID;
56+
bool has_cz;
57+
bool wireless;
5958
bool sensors_supported;
6059
bool sensors_enabled;
61-
bool touchpad_01_supported;
62-
bool touchpad_02_supported;
63-
bool rumble_supported;
64-
bool rumble_type;
65-
bool rgb_supported;
66-
bool player_led_supported;
67-
bool powerstate_supported;
68-
bool has_cz;
69-
Uint8 serial[6];
70-
Uint16 version;
71-
Uint16 version_beta;
60+
Uint16 firmware_version;
61+
Uint64 sensor_timestamp; // Microseconds. Simulate onboard clock. Advance by known rate: SENSOR_INTERVAL_NS == 8ms = 125 Hz
7262
float accelScale;
73-
float gyroScale;
7463
Uint8 last_state[USB_PACKET_LENGTH];
75-
Uint64 sensor_timestamp; // Microseconds. Simulate onboard clock. Advance by known rate: SENSOR_INTERVAL_NS == 8ms = 125 Hz
7664
} SDL_DriverFlydigi_Context;
7765

78-
#pragma pack(push,1)
79-
typedef struct
80-
{
81-
bool sensors_supported;
82-
bool touchpad_01_supported;
83-
bool touchpad_02_supported;
84-
bool rumble_supported;
85-
bool rumble_type;
86-
bool rgb_supported;
87-
Uint8 device_type;
88-
Uint8 serial[6];
89-
Uint16 version;
90-
Uint16 version_beta;
91-
Uint16 pid;
92-
} FLYDIGI_DEVICE_INFO;
93-
94-
#pragma pack(pop)
95-
9666

9767
static void HIDAPI_DriverFlydigi_RegisterHints(SDL_HintCallback callback, void *userdata)
9868
{
@@ -114,6 +84,129 @@ static bool HIDAPI_DriverFlydigi_IsSupportedDevice(SDL_HIDAPI_Device *device, co
11484
return SDL_IsJoystickFlydigiController(vendor_id, product_id) && interface_number == 2;
11585
}
11686

87+
static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
88+
{
89+
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
90+
91+
for (int attempt = 0; ctx->deviceID == 0 && attempt < 3; ++attempt) {
92+
const Uint8 request[] = { FLYDIGI_CMD_REPORT_ID, FLYDIGI_GET_INFO_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
93+
int size = SDL_hid_write(device->dev, request, sizeof(request));
94+
if (size < 0) {
95+
break;
96+
}
97+
98+
// Read the reply
99+
for (int i = 0; i < 100; ++i) {
100+
SDL_Delay(1);
101+
102+
Uint8 data[USB_PACKET_LENGTH];
103+
size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0);
104+
if (size < 0) {
105+
break;
106+
}
107+
if (size == 0) {
108+
continue;
109+
}
110+
111+
#ifdef DEBUG_FLYDIGI_PROTOCOL
112+
HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size);
113+
#endif
114+
if (size == 32 && data[15] == 236) {
115+
ctx->deviceID = data[3];
116+
ctx->firmware_version = data[9] | (data[10] << 8);
117+
118+
char serial[9];
119+
(void)SDL_snprintf(serial, sizeof(serial), "%.2x%.2x%.2x%.2x", data[5], data[6], data[7], data[8]);
120+
HIDAPI_SetDeviceSerial(device, serial);
121+
122+
// The Vader 2 with firmware 6.0.4.9 doesn't report the connection state
123+
if (ctx->firmware_version >= 0x6400) {
124+
switch (data[13]) {
125+
case 0:
126+
// Wireless connection
127+
ctx->wireless = true;
128+
break;
129+
case 1:
130+
// Wired connection
131+
ctx->wireless = false;
132+
break;
133+
default:
134+
break;
135+
}
136+
}
137+
138+
// Done!
139+
break;
140+
}
141+
}
142+
}
143+
144+
if (ctx->deviceID == 0) {
145+
// Try to guess from the name of the controller
146+
if (SDL_strstr(device->name, "VADER") != NULL) {
147+
if (SDL_strstr(device->name, "VADER2") != NULL) {
148+
ctx->deviceID = 20;
149+
} else if (SDL_strstr(device->name, "VADER3") != NULL) {
150+
ctx->deviceID = 28;
151+
} else if (SDL_strstr(device->name, "VADER4") != NULL) {
152+
ctx->deviceID = 85;
153+
}
154+
} else if (SDL_strstr(device->name, "APEX") != NULL) {
155+
if (SDL_strstr(device->name, "APEX2") != NULL) {
156+
ctx->deviceID = 19;
157+
} else if (SDL_strstr(device->name, "APEX3") != NULL) {
158+
ctx->deviceID = 24;
159+
} else if (SDL_strstr(device->name, "APEX4") != NULL) {
160+
ctx->deviceID = 84;
161+
}
162+
}
163+
}
164+
device->guid.data[15] = ctx->deviceID;
165+
166+
switch (ctx->deviceID) {
167+
case 19:
168+
HIDAPI_SetDeviceName(device, "Flydigi Apex 2");
169+
break;
170+
case 24:
171+
case 26:
172+
case 29:
173+
HIDAPI_SetDeviceName(device, "Flydigi Apex 3");
174+
break;
175+
case 84:
176+
// The Apex 4 controller has sensors, but they're only reported when gyro mouse is enabled
177+
HIDAPI_SetDeviceName(device, "Flydigi Apex 4");
178+
break;
179+
case 20:
180+
case 21:
181+
case 23:
182+
// The Vader 2 controller has sensors, but they're only reported when gyro mouse is enabled
183+
HIDAPI_SetDeviceName(device, "Flydigi Vader 2");
184+
ctx->has_cz = true;
185+
break;
186+
case 22:
187+
HIDAPI_SetDeviceName(device, "Flydigi Vader 2 Pro");
188+
ctx->has_cz = true;
189+
break;
190+
case 28:
191+
HIDAPI_SetDeviceName(device, "Flydigi Vader 3");
192+
ctx->has_cz = true;
193+
break;
194+
case 80:
195+
case 81:
196+
HIDAPI_SetDeviceName(device, "Flydigi Vader 3 Pro");
197+
ctx->has_cz = true;
198+
break;
199+
case 85:
200+
HIDAPI_SetDeviceName(device, "Flydigi Vader 4 Pro");
201+
ctx->has_cz = true;
202+
ctx->sensors_supported = true;
203+
ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f;
204+
break;
205+
default:
206+
break;
207+
}
208+
}
209+
117210
static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device)
118211
{
119212
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)SDL_calloc(1, sizeof(*ctx));
@@ -122,18 +215,7 @@ static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device)
122215
}
123216
device->context = ctx;
124217

125-
if (device->product_id == USB_PRODUCT_FLYDIGI_VADER4_PRO) {
126-
const int VADER4PRO_REPORT_SIZE = 32;
127-
Uint8 data[USB_PACKET_LENGTH];
128-
int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80);
129-
if (size == VADER4PRO_REPORT_SIZE) {
130-
ctx->sensors_supported = true;
131-
ctx->rumble_supported = true;
132-
}
133-
const char VADER3_NAME[] = "Flydigi VADER3";
134-
const char VADER4_NAME[] = "Flydigi VADER4";
135-
ctx->has_cz = SDL_strncmp(device->name, VADER3_NAME, sizeof(VADER3_NAME)) == 0 || SDL_strncmp(device->name, VADER4_NAME, sizeof(VADER4_NAME)) == 0;
136-
}
218+
UpdateDeviceIdentity(device);
137219

138220
return HIDAPI_JoystickConnected(device, NULL);
139221
}
@@ -160,36 +242,35 @@ static bool HIDAPI_DriverFlydigi_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
160242
SDL_zeroa(ctx->last_state);
161243

162244
// Initialize the joystick capabilities
163-
joystick->nbuttons = ctx->has_cz ? SDL_GAMEPAD_NUM_FLYDIGI_BUTTONS_WITH_CZ : SDL_GAMEPAD_NUM_FLYDIGI_BUTTONS_WITHOUT_CZ;
245+
joystick->nbuttons = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS;
246+
if (ctx->has_cz) {
247+
joystick->nbuttons += 2;
248+
}
164249
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
165250
joystick->nhats = 1;
166251

252+
if (ctx->wireless) {
253+
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
254+
}
255+
167256
if (ctx->sensors_supported) {
168257
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 125.0f);
169258
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 125.0f);
170-
171-
172-
ctx->accelScale = SDL_STANDARD_GRAVITY / FLYDIGI_ACCEL_SCALE;
173259
}
174260

175261
return true;
176262
}
177263

178264
static bool HIDAPI_DriverFlydigi_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
179265
{
180-
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
181-
if (ctx->rumble_supported) {
182-
Uint8 rumble_packet[4] = { FLYDIGI_CMD_REPORT_ID, FLYDIGI_HAPTIC_COMMAND, 0x00, 0x00 };
183-
rumble_packet[2] = low_frequency_rumble >> 8;
184-
rumble_packet[3] = high_frequency_rumble >> 8;
266+
Uint8 rumble_packet[4] = { FLYDIGI_CMD_REPORT_ID, FLYDIGI_HAPTIC_COMMAND, 0x00, 0x00 };
267+
rumble_packet[2] = low_frequency_rumble >> 8;
268+
rumble_packet[3] = high_frequency_rumble >> 8;
185269

186-
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
187-
return SDL_SetError("Couldn't send rumble packet");
188-
}
189-
return true;
190-
} else {
191-
return SDL_Unsupported();
270+
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
271+
return SDL_SetError("Couldn't send rumble packet");
192272
}
273+
return true;
193274
}
194275

195276
static bool HIDAPI_DriverFlydigi_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
@@ -199,12 +280,7 @@ static bool HIDAPI_DriverFlydigi_RumbleJoystickTriggers(SDL_HIDAPI_Device *devic
199280

200281
static Uint32 HIDAPI_DriverFlydigi_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
201282
{
202-
SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context;
203-
Uint32 caps = 0;
204-
if (ctx->rumble_supported) {
205-
caps |= SDL_JOYSTICK_CAP_RUMBLE;
206-
}
207-
return caps;
283+
return SDL_JOYSTICK_CAP_RUMBLE;
208284
}
209285

210286
static bool HIDAPI_DriverFlydigi_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
@@ -235,10 +311,7 @@ static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_D
235311
return;
236312
}
237313

238-
if (ctx->last_state[8] != data[8]) {
239-
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_FN, ((data[8] & 0x01) != 0));
240-
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[8] & 0x08) != 0));
241-
}
314+
Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS;
242315

243316
if (ctx->last_state[9] != data[9]) {
244317
Uint8 hat;
@@ -290,12 +363,22 @@ static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_D
290363
}
291364

292365
if (ctx->last_state[7] != data[7]) {
293-
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_C, ((data[7] & 0x01) != 0));
294-
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_Z, ((data[7] & 0x02) != 0));
295366
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M1, ((data[7] & 0x04) != 0));
296367
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M2, ((data[7] & 0x08) != 0));
297368
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M3, ((data[7] & 0x10) != 0));
298369
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M4, ((data[7] & 0x20) != 0));
370+
if (ctx->has_cz) {
371+
SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x01) != 0));
372+
SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x02) != 0));
373+
}
374+
}
375+
376+
if (ctx->last_state[8] != data[8]) {
377+
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[8] & 0x08) != 0));
378+
// The '+' button is used to toggle gyro mouse mode, so don't pass that to the application
379+
//SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x01) != 0));
380+
// The '-' button is only available on the Vader 2, for simplicity let's ignore that
381+
//SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x10) != 0));
299382
}
300383

301384
#define READ_STICK_AXIS(offset) \

src/joystick/usb_ids.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
#define USB_VENDOR_BACKBONE 0x358a
3333
#define USB_VENDOR_GAMESIR 0x3537
3434
#define USB_VENDOR_DRAGONRISE 0x0079
35-
#define USB_VENDOR_FLYDIGI 0x04B4
35+
#define USB_VENDOR_FLYDIGI 0x04b4
3636
#define USB_VENDOR_GOOGLE 0x18d1
3737
#define USB_VENDOR_HORI 0x0f0d
3838
#define USB_VENDOR_HP 0x03f0
@@ -78,7 +78,7 @@
7878
#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843
7979
#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1844
8080
#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3 0x1846
81-
#define USB_PRODUCT_FLYDIGI_VADER4_PRO 0x2412
81+
#define USB_PRODUCT_FLYDIGI_GAMEPAD 0x2412
8282
#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS4 0x011c
8383
#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184
8484
#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184

0 commit comments

Comments
 (0)