Skip to content

Commit 2614924

Browse files
committed
sinput: refactor to make unknown controllers fully dynamic
Currently, the generic raspberry pi vid:pid pair must be used for generic mask functionality. Moreover, the axes and buttons are hardcoded in different places in the hid driver, making them difficult to use. First, refactor SDL_gamepad.c to always use autodetection for unknown controllers. Then, in the HID driver, generate valid masks based on a given subtype that will always match the output SDL string and compare them to the ones in the HID descriptor. If they are different, emit a warning but use the ones from the subtype anyway. For already known controllers, add a path to skip the check and load the masks from firmware, assuming what is there matches SDL. This is done on faith, so it should be avoided for future controllers. Moving forward, this commit allows following the same procedure. Assume company X releases controller Y. If the controller uses a completely new layout, company asks for a sub_type. Company gets sub_type and puts it into the controller firmware and releases the controller. In the period that the bundled Steam SDL does not have that sub_type, it uses the full mapping which is correct for the controller and quite useable, but misses a capability match. If it does have the sub_type, capabilities are correct without any additions to SDL. And in the scenario that a controller is too obscure to warrant a sub_type, 0 can be used, and a mapping can be added to its vid:pid pair for proper capabilities to avoid using sub_types.
1 parent 352dbaf commit 2614924

File tree

2 files changed

+90
-54
lines changed

2 files changed

+90
-54
lines changed

src/joystick/SDL_gamepad.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,8 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
812812
// GC Ultimate Map
813813
SDL_strlcat(mapping_string, "a:b0,b:b2,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b4,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b13,misc2:b14,rightshoulder:b7,rightstick:b5,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b1,y:b3,misc3:b8,misc4:b9,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string));
814814
break;
815-
815+
default:
816+
case USB_PRODUCT_BONJIRICHANNEL_FIREBIRD:
816817
case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC:
817818
// Apply mapping profile for type
818819
switch (sinput_id) {
@@ -840,11 +841,6 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
840841
break;
841842
}
842843
break;
843-
844-
default:
845-
case USB_PRODUCT_BONJIRICHANNEL_FIREBIRD:
846-
// Unmapped devices
847-
return NULL;
848844
}
849845
} else {
850846
// All other gamepads have the standard set of 19 buttons and 6 axes

src/joystick/hidapi/SDL_hidapi_sinput.c

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -239,69 +239,119 @@ static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data)
239239

240240
// Obtain protocol version
241241
ctx->protocol_version = EXTRACTUINT16(data, 0);
242-
243-
// Bitfields are not portable, so we unpack them into a struct value
244-
ctx->rumble_supported = (data[2] & 0x01) != 0;
245-
ctx->player_leds_supported = (data[2] & 0x02) != 0;
246-
ctx->accelerometer_supported = (data[2] & 0x04) != 0;
247-
ctx->gyroscope_supported = (data[2] & 0x08) != 0;
248-
249-
ctx->left_analog_stick_supported = (data[2] & 0x10) != 0;
250-
ctx->right_analog_stick_supported = (data[2] & 0x20) != 0;
251-
ctx->left_analog_trigger_supported = (data[2] & 0x40) != 0;
252-
ctx->right_analog_trigger_supported = (data[2] & 0x80) != 0;
253-
254-
ctx->touchpad_supported = (data[3] & 0x01) != 0;
255-
ctx->joystick_rgb_supported = (data[3] & 0x02) != 0;
256-
257-
ctx->is_handheld = (data[3] & 0x04) != 0;
258-
242+
Uint8 *fflags = data + 2;
243+
Uint8 *buttons = data + 12;
244+
245+
//
246+
// Unpack feature flags into context
247+
//
248+
ctx->rumble_supported = (fflags[0] & 0x01) != 0;
249+
ctx->player_leds_supported = (fflags[0] & 0x02) != 0;
250+
ctx->accelerometer_supported = (fflags[0] & 0x04) != 0;
251+
ctx->gyroscope_supported = (fflags[0] & 0x08) != 0;
252+
253+
// Axes cannot be dynamic, so we only sanity check them
254+
bool left_analog_stick_supported = (fflags[0] & 0x10) != 0;
255+
bool right_analog_stick_supported = (fflags[0] & 0x20) != 0;
256+
bool left_analog_trigger_supported = (fflags[0] & 0x40) != 0;
257+
bool right_analog_trigger_supported = (fflags[0] & 0x80) != 0;
258+
259+
ctx->touchpad_supported = (fflags[1] & 0x01) != 0;
260+
ctx->joystick_rgb_supported = (fflags[1] & 0x02) != 0;
261+
ctx->is_handheld = (fflags[1] & 0x04) != 0;
262+
263+
//
264+
// Gamepad Info
265+
//
259266
SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
260267
type = (SDL_GamepadType)SDL_clamp(data[4], SDL_GAMEPAD_TYPE_UNKNOWN, SDL_GAMEPAD_TYPE_COUNT);
261268
device->type = type;
262269

263270
// The 3 MSB represent a button layout style SDL_GamepadFaceStyle
264271
// The 5 LSB represent a device sub-type
265272
device->guid.data[15] = data[5];
266-
267273
ctx->sub_type = (data[5] & 0x1F);
268274

269-
ctx->polling_rate_ms = data[6];
275+
#if defined(DEBUG_SINPUT_INIT)
276+
SDL_Log("SInput Face Style: %d", (data[5] & 0xE0) >> 5);
277+
SDL_Log("SInput Sub-type: %d", (data[5] & 0x1F));
278+
#endif
270279

280+
//
281+
// IMU Info
282+
//
283+
ctx->polling_rate_ms = data[6];
271284
ctx->accelRange = EXTRACTUINT16(data, 8);
272285
ctx->gyroRange = EXTRACTUINT16(data, 10);
273286

274-
if ((device->product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC) && (device->vendor_id == USB_VENDOR_RASPBERRYPI)) {
275-
276-
#if defined(DEBUG_SINPUT_INIT)
277-
SDL_Log("SInput Face Style: %d", (data[5] & 0xE0) >> 5);
278-
SDL_Log("SInput Sub-type: %d", (data[5] & 0x1F));
279-
#endif
280-
281-
switch (ctx->sub_type) {
282-
// Default generic device, exposes all buttons
287+
//
288+
// Get mappings based on SDL subtype and assert that they match.
289+
//
290+
291+
// For backwards compatibility with existing firmwares
292+
// when we know the PID, we will passthrough the mask.
293+
// sub_type is only 5 bits, so 0xff can be used.
294+
bool known = (
295+
device->product_id == USB_PRODUCT_HANDHELDLEGEND_PROGCC ||
296+
device->product_id == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE
297+
);
298+
if (known)
299+
ctx->sub_type = 0xFF;
300+
301+
switch (ctx->sub_type) {
302+
case 0xFF:
303+
ctx->usage_masks[0] = buttons[0];
304+
ctx->usage_masks[1] = buttons[1];
305+
ctx->usage_masks[2] = buttons[2];
306+
ctx->usage_masks[3] = buttons[3];
307+
ctx->left_analog_stick_supported = left_analog_stick_supported;
308+
ctx->right_analog_stick_supported = right_analog_stick_supported;
309+
ctx->left_analog_trigger_supported = left_analog_trigger_supported;
310+
ctx->right_analog_trigger_supported = right_analog_trigger_supported;
311+
break;
283312
default:
284-
case 0:
285313
ctx->usage_masks[0] = 0xFF;
286314
ctx->usage_masks[1] = 0xFF;
287315
ctx->usage_masks[2] = 0xFF;
288316
ctx->usage_masks[3] = 0xFF;
317+
ctx->left_analog_stick_supported = true;
318+
ctx->right_analog_stick_supported = true;
319+
ctx->left_analog_trigger_supported = true;
320+
ctx->right_analog_trigger_supported = true;
289321
break;
290322
}
291-
} else {
323+
324+
// Since SDL uses fixed mappings, unfortunately we cannot use the
325+
// button mask from the protocol. SInput defines a set of predefined
326+
// sub-types for this use. However, we can check it matches our expectations.
327+
if (
292328
// Masks in LSB to MSB
293329
// South, East, West, North, DUp, DDown, DLeft, DRight
294-
ctx->usage_masks[0] = data[12];
295-
296-
// Stick Left, Stick Right, L Shoulder, R Shoulder,
330+
ctx->usage_masks[0] != buttons[0] ||
331+
// Left Stick, Right Stick, L Shoulder, R Shoulder,
297332
// L Trigger, R Trigger, L Paddle 1, R Paddle 1
298-
ctx->usage_masks[1] = data[13];
299-
333+
ctx->usage_masks[1] != buttons[1] ||
300334
// Start, Back, Guide, Capture, L Paddle 2, R Paddle 2, Touchpad L, Touchpad R
301-
ctx->usage_masks[2] = data[14];
302-
335+
ctx->usage_masks[2] != buttons[2] ||
303336
// Power, Misc 4 to 10
304-
ctx->usage_masks[3] = data[15];
337+
ctx->usage_masks[3] != buttons[3] ||
338+
// Check Axes
339+
ctx->left_analog_stick_supported != left_analog_stick_supported ||
340+
ctx->right_analog_stick_supported != right_analog_stick_supported ||
341+
ctx->left_analog_trigger_supported != left_analog_trigger_supported ||
342+
ctx->right_analog_trigger_supported != right_analog_trigger_supported
343+
) {
344+
SDL_LogWarn(
345+
SDL_LOG_CATEGORY_INPUT,
346+
"SInput device %s has different button mask than subtype 0x%.2x or that type is uknown. Found: %.2x%.2x%.2x%.2x-%d%d%d%d and will use: %.2x%.2x%.2x%.2x-%d%d%d%d",
347+
device->name, ctx->sub_type,
348+
buttons[0], buttons[1], buttons[2], buttons[3],
349+
left_analog_stick_supported, right_analog_stick_supported,
350+
left_analog_trigger_supported, right_analog_trigger_supported,
351+
ctx->usage_masks[0], ctx->usage_masks[1], ctx->usage_masks[2], ctx->usage_masks[3],
352+
ctx->left_analog_stick_supported, ctx->right_analog_stick_supported,
353+
ctx->left_analog_trigger_supported, ctx->right_analog_trigger_supported
354+
);
305355
}
306356

307357
// Derive button count from mask
@@ -533,16 +583,6 @@ static bool HIDAPI_DriverSInput_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
533583
++axes;
534584
}
535585

536-
if ((device->product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC) && (device->vendor_id == USB_VENDOR_RASPBERRYPI)) {
537-
switch (ctx->sub_type) {
538-
// Default generic device, exposes all axes
539-
default:
540-
case 0:
541-
axes = 6;
542-
break;
543-
}
544-
}
545-
546586
joystick->naxes = axes;
547587

548588
if (ctx->dpad_supported) {

0 commit comments

Comments
 (0)