Skip to content

Commit 4fe152a

Browse files
committed
sinput: add capability sub_types for most common controllers
The previous commit 6e66d80 ("sinput: refactor to make unknown controllers fully dynamic") introduces the ability to add dynamic capabilities for uknown controllers without hardcoding them to SDL. This commit introduces 8 major types of controller capabilities that are found in the market. Specifically, it adds a normal XInput capability map. Then, it adds XInput + share, which covers most handhelds without extra buttons, switch controllers, and new Xbox controllers. Following, it adds combinations with 2 and 4 paddles, which include e.g., devices such as the Legion Go S and Stadia controllers with 2 paddles, and Legion Go/Xbox Elite with 4 paddles. Finally, it adds the same paddle combos + clicks, where e.g., click + 2 paddles covers the Go S when emulating the touchpad and click + 4 paddles covers the dualsense edge controller capabilities fully.
1 parent 2f53f0a commit 4fe152a

File tree

7 files changed

+142
-28
lines changed

7 files changed

+142
-28
lines changed

src/joystick/SDL_gamepad.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "controller_type.h"
3131
#include "usb_ids.h"
3232
#include "hidapi/SDL_hidapi_nintendo.h"
33+
#include "hidapi/SDL_hidapi_sinput.h"
3334
#include "../events/SDL_events_c.h"
3435

3536

@@ -817,8 +818,29 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
817818
case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC:
818819
// Apply mapping profile for type
819820
switch (sinput_id) {
821+
case k_eSinputControllerType_XInputOnly:
822+
SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,start:b8,back:b9,guide:b10,", sizeof(mapping_string));
823+
break;
824+
case k_eSinputControllerType_XInputShareNone:
825+
SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,start:b8,back:b9,guide:b10,misc1:b11,", sizeof(mapping_string));
826+
break;
827+
case k_eSinputControllerType_XInputShareDual:
828+
SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,paddle1:b8,paddle2:b9,start:b10,back:b11,guide:b12,misc1:b13,", sizeof(mapping_string));
829+
break;
830+
case k_eSinputControllerType_XInputShareQuad:
831+
SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,paddle1:b8,paddle2:b9,start:b10,back:b11,guide:b12,misc1:b13,paddle3:b14,paddle4:b15,", sizeof(mapping_string));
832+
break;
833+
case k_eSinputControllerType_XInputShareNoneClick:
834+
SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,start:b8,back:b9,guide:b10,misc1:b11,touchpad:b12,", sizeof(mapping_string));
835+
break;
836+
case k_eSinputControllerType_XInputShareDualClick:
837+
SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,paddle1:b8,paddle2:b9,start:b10,back:b11,guide:b12,misc1:b13,touchpad:b14,", sizeof(mapping_string));
838+
break;
839+
case k_eSinputControllerType_XInputShareQuadClick:
840+
SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,paddle1:b8,paddle2:b9,start:b10,back:b11,guide:b12,misc1:b13,paddle3:b14,paddle4:b15,touchpad:b16,", sizeof(mapping_string));
841+
break;
820842
default:
821-
case 0:
843+
case k_eSinputControllerType_FullMapping:
822844
// Default Fully Exposed Mapping
823845
SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,paddle1:b10,paddle2:b11,start:b12,back:b13,guide:b14,misc1:b15,paddle3:b16,paddle4:b17,touchpad:b18,misc2:b19,misc3:b20,misc4:b21,misc5:b22,misc6:b23", sizeof(mapping_string));
824846
break;

src/joystick/SDL_joystick.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3216,7 +3216,9 @@ bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id)
32163216
return true;
32173217
}
32183218
}
3219-
return false;
3219+
3220+
EControllerType eType = GuessControllerType(vendor_id, product_id);
3221+
return eType == k_eControllerType_Sinput;
32203222
}
32213223

32223224
bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id)

src/joystick/controller_list.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,4 +598,7 @@ static const ControllerDescription_t arrControllers[] = {
598598
{ MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL }, // Valve wired Steam Controller (HEADCRAB)
599599
{ MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL }, // Valve Bluetooth Steam Controller (HEADCRAB)
600600
{ MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamControllerNeptune, NULL }, // Valve Steam Deck Builtin Controller
601+
602+
// Sinput dynamic layout controllers
603+
{ MAKE_CONTROLLER_ID( 0x16d0, 0x145b ), k_eControllerType_Sinput, NULL },
601604
};

src/joystick/controller_type.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ EControllerType GuessControllerType( int nVID, int nPID )
106106
{
107107
return k_eControllerType_SteamController;
108108
}
109+
if ( SDL_strncasecmp( pszOverride, "sinput", 5 ) == 0 )
110+
{
111+
return k_eControllerType_Sinput;
112+
}
109113
return k_eControllerType_UnknownNonSteamController;
110114
}
111115

src/joystick/controller_type.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ typedef enum
5757
k_eControllerType_XInputSwitchController = 44, // Client-side only, used to mark Nintendo Switch style controllers as using XInput instead of the Nintendo Switch protocol
5858
k_eControllerType_PS5Controller = 45,
5959
k_eControllerType_XInputPS4Controller = 46, // Client-side only, used to mark DualShock 4 style controllers using XInput instead of the DualShock 4 controller protocol
60+
k_eControllerType_Sinput = 47,
6061
k_eControllerType_LastController, // Don't add game controllers below this enumeration - this enumeration can change value
6162

6263
// Keyboards and Mice

src/joystick/hidapi/SDL_hidapi_sinput.c

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include "SDL_hidapijoystick_c.h"
2929
#include "SDL_hidapi_rumble.h"
30+
#include "SDL_hidapi_sinput.h"
3031

3132
#ifdef SDL_JOYSTICK_HIDAPI_SINPUT
3233

@@ -291,35 +292,76 @@ static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data)
291292
// For backwards compatibility with existing firmwares
292293
// when we know the PID, we will passthrough the mask.
293294
// sub_type is only 5 bits, so 0xff can be used.
294-
bool known = (
295+
if (
295296
device->product_id == USB_PRODUCT_HANDHELDLEGEND_PROGCC ||
296-
device->product_id == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE
297-
);
298-
if (known)
299-
ctx->sub_type = 0xFF;
297+
device->product_id == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE)
298+
ctx->sub_type = k_eSinputControllerType_LoadFirmware;
300299

301300
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;
312-
default:
313-
ctx->usage_masks[0] = 0xFF;
314-
ctx->usage_masks[1] = 0xFF;
315-
ctx->usage_masks[2] = 0xFF;
316-
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;
321-
break;
322-
}
301+
case k_eSinputControllerType_XInputOnly:
302+
case k_eSinputControllerType_XInputShareNone:
303+
case k_eSinputControllerType_XInputShareDual:
304+
case k_eSinputControllerType_XInputShareQuad:
305+
case k_eSinputControllerType_XInputShareNoneClick:
306+
case k_eSinputControllerType_XInputShareDualClick:
307+
case k_eSinputControllerType_XInputShareQuadClick:
308+
// Basic xinput mask with share
309+
ctx->usage_masks[0] = 0xFF;
310+
ctx->usage_masks[1] = 0x0F;
311+
ctx->usage_masks[2] = 0x0F;
312+
ctx->usage_masks[3] = 0x00;
313+
ctx->left_analog_stick_supported = true;
314+
ctx->right_analog_stick_supported = true;
315+
ctx->left_analog_trigger_supported = true;
316+
ctx->right_analog_trigger_supported = true;
317+
318+
// Add primary paddles
319+
if (
320+
ctx->sub_type == k_eSinputControllerType_XInputShareDual ||
321+
ctx->sub_type == k_eSinputControllerType_XInputShareQuad ||
322+
ctx->sub_type == k_eSinputControllerType_XInputShareDualClick ||
323+
ctx->sub_type == k_eSinputControllerType_XInputShareQuadClick)
324+
ctx->usage_masks[1] |= 0xC0;
325+
326+
// Remove share/capture button if not supported
327+
if (ctx->sub_type == k_eSinputControllerType_XInputOnly)
328+
ctx->usage_masks[2] &= ~0x08;
329+
330+
// Add secondary paddles
331+
if (
332+
ctx->sub_type == k_eSinputControllerType_XInputShareQuad ||
333+
ctx->sub_type == k_eSinputControllerType_XInputShareQuadClick)
334+
ctx->usage_masks[2] |= 0x30;
335+
336+
// Add touchpad click
337+
if (
338+
ctx->sub_type == k_eSinputControllerType_XInputShareNoneClick ||
339+
ctx->sub_type == k_eSinputControllerType_XInputShareDualClick ||
340+
ctx->sub_type == k_eSinputControllerType_XInputShareQuadClick)
341+
ctx->usage_masks[2] |= 0x40;
342+
break;
343+
case k_eSinputControllerType_LoadFirmware:
344+
ctx->usage_masks[0] = buttons[0];
345+
ctx->usage_masks[1] = buttons[1];
346+
ctx->usage_masks[2] = buttons[2];
347+
ctx->usage_masks[3] = buttons[3];
348+
ctx->left_analog_stick_supported = left_analog_stick_supported;
349+
ctx->right_analog_stick_supported = right_analog_stick_supported;
350+
ctx->left_analog_trigger_supported = left_analog_trigger_supported;
351+
ctx->right_analog_trigger_supported = right_analog_trigger_supported;
352+
break;
353+
case k_eSinputControllerType_FullMapping:
354+
default:
355+
ctx->usage_masks[0] = 0xFF;
356+
ctx->usage_masks[1] = 0xFF;
357+
ctx->usage_masks[2] = 0xFF;
358+
ctx->usage_masks[3] = 0xFF;
359+
ctx->left_analog_stick_supported = true;
360+
ctx->right_analog_stick_supported = true;
361+
ctx->left_analog_trigger_supported = true;
362+
ctx->right_analog_trigger_supported = true;
363+
break;
364+
}
323365

324366
// Since SDL uses fixed mappings, unfortunately we cannot use the
325367
// button mask from the protocol. SInput defines a set of predefined
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Simple DirectMedia Layer
3+
Copyright (C) 2025 Antheas Kapenekakis <[email protected]>
4+
5+
This software is provided 'as-is', without any express or implied
6+
warranty. In no event will the authors be held liable for any damages
7+
arising from the use of this software.
8+
9+
Permission is granted to anyone to use this software for any purpose,
10+
including commercial applications, and to alter it and redistribute it
11+
freely, subject to the following restrictions:
12+
13+
1. The origin of this software must not be misrepresented; you must not
14+
claim that you wrote the original software. If you use this software
15+
in a product, an acknowledgment in the product documentation would be
16+
appreciated but is not required.
17+
2. Altered source versions must be plainly marked as such, and must not be
18+
misrepresented as being the original software.
19+
3. This notice may not be removed or altered from any source distribution.
20+
*/
21+
22+
// Subtypes define the buttons and axes available on the device to
23+
// generate a mapping string. Everything else is dynamic.
24+
// Xinput means all Xbox360 buttons.
25+
// Xinput_share adds a share button, and then dual, quad add 2, 4 paddles
26+
// to those. Click adds a touchpad click. Touchpad is not used in the name
27+
// because not all of them have clicks. In fact, there are no controllers
28+
// with dual touchpads w/ clicks so there is no point in adding them. Yet.
29+
typedef enum
30+
{
31+
k_eSinputControllerType_FullMapping = 0x00,
32+
k_eSinputControllerType_XInputOnly = 0x01,
33+
k_eSinputControllerType_XInputShareNone = 0x02,
34+
k_eSinputControllerType_XInputShareDual = 0x03,
35+
k_eSinputControllerType_XInputShareQuad = 0x04,
36+
k_eSinputControllerType_XInputShareNoneClick = 0x05,
37+
k_eSinputControllerType_XInputShareDualClick = 0x06,
38+
k_eSinputControllerType_XInputShareQuadClick = 0x07,
39+
k_eSinputControllerType_LoadFirmware = 0xff,
40+
} ESinputControllerType;

0 commit comments

Comments
 (0)