update SDL3 from 3.2.20 to 3.4.2

This commit is contained in:
Sven Balzer
2026-04-01 18:25:03 +02:00
parent 1daf4d79f1
commit 05b19704f8
1626 changed files with 124218 additions and 191491 deletions
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
+24 -62
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -46,6 +46,7 @@ static const char *s_GamepadMappings[] = {
#endif
#ifdef SDL_JOYSTICK_DINPUT
"03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,",
"03000000c82d00001930000000000000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a5,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a2,misc1:b17,misc2:+a3,rightshoulder:b7,righttrigger:b9,start:b11,x:+a5,y:-a3,",
"03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000c82d00001038000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000c82d00000650000000000000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,",
@@ -197,7 +198,6 @@ static const char *s_GamepadMappings[] = {
"03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
"03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,",
"03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
"030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,",
"0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,",
"03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
@@ -207,7 +207,8 @@ static const char *s_GamepadMappings[] = {
"03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,",
"030000005509000000b4000000000000,NVIDIA Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000004b120000014d000000000000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
"03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
"03000000790000004418000000000000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
"03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,",
"030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,",
@@ -242,6 +243,7 @@ static const char *s_GamepadMappings[] = {
"03000000d62000002640000000000000,PowerA OPS v1 Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
"03000000d62000003340000000000000,PowerA OPS v3 Pro Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
"03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000d620000011a7000000000000,PowerA Wired GameCube Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
@@ -310,7 +312,6 @@ static const char *s_GamepadMappings[] = {
"03000000110100003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
"03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,",
"03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
"03000000d620000011a7000000000000,Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000004f04000007d0000000000000,T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
"03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
@@ -339,6 +340,7 @@ static const char *s_GamepadMappings[] = {
"03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,",
"03000000c0160000e105000000000000,Xin-Mo Dual Arcade,crc:2246,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */
"03000000790000004f18000000000000,ZD-T Android,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
"03000000073500000400000000000000,ZENAIM ARCADE CONTROLLER,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
"03000000120c0000101e000000000000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
"03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
@@ -347,6 +349,7 @@ static const char *s_GamepadMappings[] = {
"03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
#endif
#ifdef SDL_PLATFORM_MACOS
"03000000c82d00001930000000000000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b17,misc2:+a2,misc3:b10,rightshoulder:b7,righttrigger:b9,start:b11,x:+a3,y:-a2,",
"03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000c82d00000650000001000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
@@ -413,13 +416,13 @@ static const char *s_GamepadMappings[] = {
"03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000380700008433000000010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000380700008483000000010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
"0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,",
"03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,",
"03000000853200008906000000010000,NACON Revolution X Unlimited,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,",
"03000000550900001472000025050000,NVIDIA Controller v01.04,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,",
"030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
"03000000790000004418000000010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
"030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
@@ -481,12 +484,15 @@ static const char *s_GamepadMappings[] = {
"030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,",
"03000000c0160000e105000000040000,Xin-Mo Dual Arcade,crc:82d5,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,", /* Ultimate Atari Fight Stick */
"03000000073500000400000000010000,ZENAIM ARCADE CONTROLLER,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
"03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000120c0000101e000000010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,",
#endif
#if defined(SDL_JOYSTICK_LINUX) || defined(SDL_PLATFORM_OPENBSD)
"03000000c82d00001930000011010000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b17,misc2:+a2,misc3:b10,rightshoulder:b7,righttrigger:a4,start:b11,x:+a3,y:-a2,",
"05000000c82d00001930000001000000,8BitDo 64 Bluetooth Controller,a:b0,b:b1,back:-a3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b17,misc2:+a2,misc3:b10,rightshoulder:b7,righttrigger:a4,start:b11,x:+a3,y:-a2,",
"03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
@@ -498,16 +504,14 @@ static const char *s_GamepadMappings[] = {
"03000000c82d00000190000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000c82d00002038000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000c82d00000020000000000000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"06000000c82d00000020000006010000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000c82d00000660000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000c82d00000960000011010000,8BitDo Pro 3,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000c82d00000061000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000102800000900000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000c82d00003028000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"030000003512000020ab000010010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"05000000c82d00001b30000001000000,8BitDo Ultimate 2C Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", /* Bluetooth */
@@ -524,9 +528,6 @@ static const char *s_GamepadMappings[] = {
"05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,",
"03000000050b00000579000011010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b36,paddle1:b52,paddle2:b53,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"05000000050b00000679000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b21,paddle1:b22,paddle2:b23,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
"05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"03000000491900001904000011010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,",
@@ -537,7 +538,6 @@ static const char *s_GamepadMappings[] = {
"03000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,",
"05000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,",
"05000000503200000210000000000000128804098,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,",
"030000005e0400008e02000047010000,Atari Xbox 360 Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000120c0000f70e000011010000,Brook Universal Fighting Board,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:,lefty:,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:,righty:,start:b9,x:b0,y:b3,",
@@ -549,25 +549,21 @@ static const char *s_GamepadMappings[] = {
"050000004c050000f20d000000010000,DualSense Edge Wireless Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000790000001100000010010000,Elecom Gamepad,crc:e86c,a:b2,b:b3,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b0,y:b1,",
"0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"0500000047532067616d657061640000,GS Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
"03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,",
"03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"0500000049190000020400001b010000,GameSir T4 Pro,crc:8283,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b23,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"03000000373500009710000001020000,GameSir-K1 FLUX,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"03000000ac0500001a06000011010000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"03000000c01100000140000011010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
"030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000d11800000094000011010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
"05000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
"03000000280400000140000000010000,Gravis Gamepad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
"030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,",
"030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
"03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
"030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
"030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
@@ -581,30 +577,23 @@ static const char *s_GamepadMappings[] = {
"030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
"030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,",
"030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000000d0f00008600000002010000,Hori Fighting Commander,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,",
"03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,",
"03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,",
"05000000491900000204000000000000,Ipega PG-9087S,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,",
"03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
"03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
"030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000006d040000d1ca000011010000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* Guide button doesn't seem to be sent in DInput mode. */
"030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,",
"03000000c62400002b89000011010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
@@ -616,24 +605,12 @@ static const char *s_GamepadMappings[] = {
"03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,",
"03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
"03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,",
"030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,",
"030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
"030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
"030000005e0400008902000020010000,Microsoft Xbox Controller S,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
"05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
"030000006b140000010c000010010000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,",
@@ -642,7 +619,9 @@ static const char *s_GamepadMappings[] = {
"05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,",
"030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
"03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"0300000085320000030c000011010000,Nacon GC100,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
"03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
"03000000790000004418000010010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
"030000007e0500001920000011810000,Nintendo N64 Controller,crc:d670,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b3,start:b11,x:b4,y:b10,",
"050000007e0500001920000001800000,Nintendo N64 Controller,crc:5e1c,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b3,start:b11,x:b4,y:b10,",
"030000007e0500001e20000011810000,Nintendo SEGA Genesis Controller,crc:bb22,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,misc1:b3,rightshoulder:b2,righttrigger:b4,start:b5,",
@@ -654,7 +633,6 @@ static const char *s_GamepadMappings[] = {
"060000007e0500000820000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"050000007e0500000720000001800000,Nintendo Switch Joy-Con (R),a:b1,b:b2,guide:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000d620000013a7000011010000,Nintendo Switch PowerA Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"03000000d620000011a7000011010000,Nintendo Switch PowerA Core Plus Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"050000004c69632050726f20436f6e00,Nintendo Switch Pro Controller,crc:15b7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
@@ -666,9 +644,7 @@ static const char *s_GamepadMappings[] = {
"030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,",
"05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,",
"05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,",
"030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
"03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
"030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000006f0e00000901000011010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
"03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,",
"03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
@@ -701,8 +677,7 @@ static const char *s_GamepadMappings[] = {
"050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"050000004c050000e60c000000810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
"030000004c050000da0c000011010000,Playstation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,",
"03000000c62400003a54000001010000,PowerA XBox One Controller,a:b0,b:b1,back:b6,dpdown:h0.7,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000d620000011a7000011010000,PowerA Wired GameCube Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,",
"03000000222c00000225000011010000,Qanba Dragon Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000222c00000025000011010000,Qanba Dragon Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
@@ -711,25 +686,18 @@ static const char *s_GamepadMappings[] = {
"03000000222c00000020000011010000,Qanba Drone Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,",
"03000000222c00000223000011010000,Qanba Obsidian Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000222c00000023000011010000,Qanba Obsidian Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"03000000321500000011000011010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
"050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
"0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,",
"03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
"03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"03000000457500002211000010010000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
@@ -743,8 +711,6 @@ static const char *s_GamepadMappings[] = {
"03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
"03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
"03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,",
"030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
"03000000de2800000112000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,",
"03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
@@ -760,7 +726,6 @@ static const char *s_GamepadMappings[] = {
"03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b18,leftshoulder:b6,leftstick:b13,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:+a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,",
"0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,",
"030000004f0400000ed0000011010000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
@@ -781,23 +746,18 @@ static const char *s_GamepadMappings[] = {
"030000006f0e00000702000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
"030000000d0f0000ab01000011010000,Wireless HORIPAD For Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"050000000d0f00009601000091000000,Wireless HORIPAD For Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,",
"030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
"050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
"05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,",
"03000000c0160000e105000010010000,Xin-Mo Dual Arcade,crc:82d5,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */
"03000000073500000400000011010000,ZENAIM ARCADE CONTROLLER,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
"03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000120c0000182e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
"03000000790000002201000011010000,ZhiXu GuliKit Controller D,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,",
"03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
"050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
@@ -857,8 +817,8 @@ static const char *s_GamepadMappings[] = {
"050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
"050000005e040000e00200000ffe3f80,Xbox One Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b17,y:b2,",
"050000005e040000fd020000ffff3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
"050000005e040000120b000000783f80,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
"050000005e040000130b0000ffff3f00,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
"050000005e040000120b000000783f80,Xbox Series X Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
"050000005e040000130b0000ffff3f00,Xbox Series X Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
"050000005e04000091020000ff073f80,Xbox Wireless Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", /* The DPAD doesn't seem to work on this controller on Android TV? */
"050000001727000044310000ffff3f80,XiaoMi Game Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a4,righty:a5,start:b6,x:b2,y:b3,",
"0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
@@ -888,6 +848,8 @@ static const char *s_GamepadMappings[] = {
"050000004c050000cc090000ff870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,",
"050000004c050000e60c0000df870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,touchpad:b10,x:b2,y:b3,",
"050000004c050000e60c0000ff870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,",
"05000000ac050000040000008e586d04,PlayStation VR2 Sense Controller (L),crc:9342,+leftx:+a3,+lefty:+a1,-leftx:+a2,-lefty:+a4,back:b2,leftstick:b4,lefttrigger:+a7,paddle2:b3,x:b0,y:b1,",
"05000000ac050000040000000eb86d04,PlayStation VR2 Sense Controller (R),crc:334b,+rightx:+a3,+righty:+a1,-rightx:+a2,-righty:+a4,a:b0,b:b1,paddle1:b3,rightstick:b4,righttrigger:+a7,start:b2,",
"05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,",
"050000005e040000050b0000df070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b10,paddle2:b12,paddle3:b11,paddle4:b13,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
"050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,",
@@ -897,7 +859,7 @@ static const char *s_GamepadMappings[] = {
"050000005e040000e0020000ff070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,",
#endif
#ifdef SDL_JOYSTICK_EMSCRIPTEN
"default,Standard Gamepad,a:b0,b:b1,back:b8,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b16,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
"default,*,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
#endif
#ifdef SDL_JOYSTICK_PS2
"0000000050533220436f6e74726f6c00,PS2 Controller,crc:ed87,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,",
+318 -103
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -123,8 +123,175 @@ static bool SDL_joystick_being_added;
static SDL_Joystick *SDL_joysticks SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
static int SDL_joystick_player_count SDL_GUARDED_BY(SDL_joystick_lock) = 0;
static SDL_JoystickID *SDL_joystick_players SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
static SDL_HashTable *SDL_joystick_names SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
static bool SDL_joystick_allows_background_events = false;
static Uint32 initial_old_xboxone_controllers[] = {
MAKE_VIDPID(0x0000, 0x6686),
MAKE_VIDPID(0x0079, 0x18a1),
MAKE_VIDPID(0x0079, 0x18c2),
MAKE_VIDPID(0x0079, 0x18c8),
MAKE_VIDPID(0x0079, 0x18cf),
MAKE_VIDPID(0x03f0, 0x0495),
MAKE_VIDPID(0x045e, 0x02d1),
MAKE_VIDPID(0x045e, 0x02dd),
MAKE_VIDPID(0x045e, 0x02e0),
MAKE_VIDPID(0x045e, 0x02e3),
MAKE_VIDPID(0x045e, 0x02ea),
MAKE_VIDPID(0x045e, 0x02fd),
MAKE_VIDPID(0x045e, 0x02ff),
MAKE_VIDPID(0x045e, 0x0867),
MAKE_VIDPID(0x045e, 0x0b00),
MAKE_VIDPID(0x045e, 0x0b05),
MAKE_VIDPID(0x045e, 0x0b0a),
MAKE_VIDPID(0x045e, 0x0b0c),
MAKE_VIDPID(0x045e, 0x0b20),
MAKE_VIDPID(0x045e, 0x0b21),
MAKE_VIDPID(0x045e, 0x0b22),
MAKE_VIDPID(0x046d, 0x0000),
MAKE_VIDPID(0x046d, 0x1004),
MAKE_VIDPID(0x046d, 0x1007),
MAKE_VIDPID(0x046d, 0x1008),
MAKE_VIDPID(0x046d, 0xf301),
MAKE_VIDPID(0x0738, 0x02a0),
MAKE_VIDPID(0x0738, 0x4a01),
MAKE_VIDPID(0x0738, 0x7263),
MAKE_VIDPID(0x0738, 0xb738),
MAKE_VIDPID(0x0738, 0xcb29),
MAKE_VIDPID(0x0738, 0xf401),
MAKE_VIDPID(0x0c12, 0x0e17),
MAKE_VIDPID(0x0c12, 0x0e1c),
MAKE_VIDPID(0x0c12, 0x0e22),
MAKE_VIDPID(0x0c12, 0x0e30),
MAKE_VIDPID(0x0d62, 0x9a1a),
MAKE_VIDPID(0x0d62, 0x9a1b),
MAKE_VIDPID(0x0e00, 0x0e00),
MAKE_VIDPID(0x0e6f, 0x012a),
MAKE_VIDPID(0x0e6f, 0x0139),
MAKE_VIDPID(0x0e6f, 0x013B),
MAKE_VIDPID(0x0e6f, 0x013a),
MAKE_VIDPID(0x0e6f, 0x0145),
MAKE_VIDPID(0x0e6f, 0x0146),
MAKE_VIDPID(0x0e6f, 0x0152),
MAKE_VIDPID(0x0e6f, 0x015b),
MAKE_VIDPID(0x0e6f, 0x015c),
MAKE_VIDPID(0x0e6f, 0x015d),
MAKE_VIDPID(0x0e6f, 0x015f),
MAKE_VIDPID(0x0e6f, 0x0160),
MAKE_VIDPID(0x0e6f, 0x0161),
MAKE_VIDPID(0x0e6f, 0x0162),
MAKE_VIDPID(0x0e6f, 0x0163),
MAKE_VIDPID(0x0e6f, 0x0164),
MAKE_VIDPID(0x0e6f, 0x0165),
MAKE_VIDPID(0x0e6f, 0x0166),
MAKE_VIDPID(0x0e6f, 0x0167),
MAKE_VIDPID(0x0e6f, 0x0205),
MAKE_VIDPID(0x0e6f, 0x0206),
MAKE_VIDPID(0x0e6f, 0x0246),
MAKE_VIDPID(0x0e6f, 0x0261),
MAKE_VIDPID(0x0e6f, 0x0262),
MAKE_VIDPID(0x0e6f, 0x02a0),
MAKE_VIDPID(0x0e6f, 0x02a1),
MAKE_VIDPID(0x0e6f, 0x02a2),
MAKE_VIDPID(0x0e6f, 0x02a3),
MAKE_VIDPID(0x0e6f, 0x02a4),
MAKE_VIDPID(0x0e6f, 0x02a5),
MAKE_VIDPID(0x0e6f, 0x02a6),
MAKE_VIDPID(0x0e6f, 0x02a7),
MAKE_VIDPID(0x0e6f, 0x02a8),
MAKE_VIDPID(0x0e6f, 0x02a9),
MAKE_VIDPID(0x0e6f, 0x02aa),
MAKE_VIDPID(0x0e6f, 0x02ab),
MAKE_VIDPID(0x0e6f, 0x02ac),
MAKE_VIDPID(0x0e6f, 0x02ad),
MAKE_VIDPID(0x0e6f, 0x02ae),
MAKE_VIDPID(0x0e6f, 0x02af),
MAKE_VIDPID(0x0e6f, 0x02b0),
MAKE_VIDPID(0x0e6f, 0x02b1),
MAKE_VIDPID(0x0e6f, 0x02b2),
MAKE_VIDPID(0x0e6f, 0x02b3),
MAKE_VIDPID(0x0e6f, 0x02b5),
MAKE_VIDPID(0x0e6f, 0x02b6),
MAKE_VIDPID(0x0e6f, 0x02b8),
MAKE_VIDPID(0x0e6f, 0x02bd),
MAKE_VIDPID(0x0e6f, 0x02be),
MAKE_VIDPID(0x0e6f, 0x02bf),
MAKE_VIDPID(0x0e6f, 0x02c0),
MAKE_VIDPID(0x0e6f, 0x02c1),
MAKE_VIDPID(0x0e6f, 0x02c2),
MAKE_VIDPID(0x0e6f, 0x02c3),
MAKE_VIDPID(0x0e6f, 0x02c4),
MAKE_VIDPID(0x0e6f, 0x02c5),
MAKE_VIDPID(0x0e6f, 0x02c6),
MAKE_VIDPID(0x0e6f, 0x02c7),
MAKE_VIDPID(0x0e6f, 0x02c8),
MAKE_VIDPID(0x0e6f, 0x02c9),
MAKE_VIDPID(0x0e6f, 0x02ca),
MAKE_VIDPID(0x0e6f, 0x02cb),
MAKE_VIDPID(0x0e6f, 0x02cd),
MAKE_VIDPID(0x0e6f, 0x02ce),
MAKE_VIDPID(0x0e6f, 0x02cf),
MAKE_VIDPID(0x0e6f, 0x02d5),
MAKE_VIDPID(0x0e6f, 0x0346),
MAKE_VIDPID(0x0e6f, 0x0446),
MAKE_VIDPID(0x0e6f, 0xf501),
MAKE_VIDPID(0x0f0d, 0x0063),
MAKE_VIDPID(0x0f0d, 0x0067),
MAKE_VIDPID(0x0f0d, 0x0078),
MAKE_VIDPID(0x0f0d, 0x0097),
MAKE_VIDPID(0x0f0d, 0x00ba),
MAKE_VIDPID(0x0f0d, 0x00c0),
MAKE_VIDPID(0x0f0d, 0x00c5),
MAKE_VIDPID(0x0f0d, 0x00d8),
MAKE_VIDPID(0x0f0d, 0x00ed),
MAKE_VIDPID(0x0fff, 0x02a1),
MAKE_VIDPID(0x12ab, 0x0304),
MAKE_VIDPID(0x1430, 0x0291),
MAKE_VIDPID(0x1430, 0x02a9),
MAKE_VIDPID(0x1430, 0x070b),
MAKE_VIDPID(0x1430, 0x0719),
MAKE_VIDPID(0x146b, 0x0611),
MAKE_VIDPID(0x1532, 0x0a00),
MAKE_VIDPID(0x1532, 0x0a03),
MAKE_VIDPID(0x1532, 0x0a14),
MAKE_VIDPID(0x1532, 0x0a15),
MAKE_VIDPID(0x16d0, 0x0f3f),
MAKE_VIDPID(0x1bad, 0x028e),
MAKE_VIDPID(0x1bad, 0x02a0),
MAKE_VIDPID(0x1bad, 0x5500),
MAKE_VIDPID(0x20ab, 0x55ef),
MAKE_VIDPID(0x24c6, 0x541a),
MAKE_VIDPID(0x24c6, 0x542a),
MAKE_VIDPID(0x24c6, 0x543a),
MAKE_VIDPID(0x24c6, 0x5509),
MAKE_VIDPID(0x24c6, 0x551a),
MAKE_VIDPID(0x24c6, 0x561a),
MAKE_VIDPID(0x24c6, 0x581a),
MAKE_VIDPID(0x24c6, 0x591a),
MAKE_VIDPID(0x24c6, 0x592a),
MAKE_VIDPID(0x24c6, 0x791a),
MAKE_VIDPID(0x2516, 0x0069),
MAKE_VIDPID(0x25b1, 0x0360),
MAKE_VIDPID(0x2c22, 0x2203),
MAKE_VIDPID(0x2e24, 0x0652),
MAKE_VIDPID(0x2e24, 0x1618),
MAKE_VIDPID(0x2e24, 0x1688),
MAKE_VIDPID(0x2f24, 0x0011),
MAKE_VIDPID(0x2f24, 0x002e),
MAKE_VIDPID(0x2f24, 0x0050),
MAKE_VIDPID(0x2f24, 0x0053),
MAKE_VIDPID(0x2f24, 0x008f),
MAKE_VIDPID(0x2f24, 0x0091),
MAKE_VIDPID(0x2f24, 0x00b7),
MAKE_VIDPID(0xd2d2, 0xd2d2),
};
static SDL_vidpid_list old_xboxone_controllers = {
"SDL_JOYSTICK_OLD_XBOXONE_CONTROLLERS", 0, 0, NULL,
"SDL_JOYSTICK_OLD_XBOXONE_CONTROLLERS_EXCLUDED", 0, 0, NULL,
SDL_arraysize(initial_old_xboxone_controllers), initial_old_xboxone_controllers,
false
};
static Uint32 initial_arcadestick_devices[] = {
MAKE_VIDPID(0x0079, 0x181a), // Venom Arcade Stick
MAKE_VIDPID(0x0079, 0x181b), // Venom Arcade Stick
@@ -273,11 +440,17 @@ static Uint32 initial_blacklist_devices[] = {
MAKE_VIDPID(0x04d9, 0x8009), // OBINLB USB-HID Keyboard (Anne Pro II)
MAKE_VIDPID(0x04d9, 0xa292), // OBINLB USB-HID Keyboard (Anne Pro II)
MAKE_VIDPID(0x04d9, 0xa293), // OBINLB USB-HID Keyboard (Anne Pro II)
MAKE_VIDPID(0x04f2, 0xa13c), // HP Deluxe Webcam KQ246AA
MAKE_VIDPID(0x0e6f, 0x018a), // PDP REALMz Wireless Controller for Switch, USB charging
MAKE_VIDPID(0x1532, 0x0266), // Razer Huntsman V2 Analog, non-functional DInput device
MAKE_VIDPID(0x1532, 0x0282), // Razer Huntsman Mini Analog, non-functional DInput device
MAKE_VIDPID(0x26ce, 0x01a2), // ASRock LED Controller
MAKE_VIDPID(0x20d6, 0x0002), // PowerA Enhanced Wireless Controller for Nintendo Switch (charging port only)
MAKE_VIDPID(0x31e3, 0x1310), // Wooting 60HE (ARM)
MAKE_VIDPID(0x3297, 0x1969), // Moonlander MK1 Keyboard
MAKE_VIDPID(0x3434, 0x0211), // Keychron K1 Pro System Control
MAKE_VIDPID(0x3434, 0x0353), // Keychron V5 System Control
MAKE_VIDPID(0x3434, 0xd030), // Keychron Link
};
static SDL_vidpid_list blacklist_devices = {
SDL_HINT_JOYSTICK_BLACKLIST_DEVICES, 0, 0, NULL,
@@ -313,7 +486,14 @@ static SDL_vidpid_list flightstick_devices = {
};
static Uint32 initial_gamecube_devices[] = {
MAKE_VIDPID(0x0079, 0x1843), // DragonRise GameCube Controller Adapter
MAKE_VIDPID(0x0079, 0x1844), // DragonRise GameCube Controller Adapter
MAKE_VIDPID(0x0079, 0x1846), // DragonRise GameCube Controller Adapter
MAKE_VIDPID(0x057e, 0x0337), // Nintendo Wii U GameCube Controller Adapter
MAKE_VIDPID(0x057e, 0x2073), // Nintendo Switch 2 NSO GameCube Controller
MAKE_VIDPID(0x0926, 0x8888), // Cyber Gadget GameCube Controller
MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch
MAKE_VIDPID(0x1a34, 0xf705), // GameCube {HuiJia USB box}
MAKE_VIDPID(0x20d6, 0xa711), // PowerA Wired Controller Nintendo GameCube Style
};
static SDL_vidpid_list gamecube_devices = {
@@ -440,18 +620,18 @@ static SDL_vidpid_list zero_centered_devices = {
false
};
#define CHECK_JOYSTICK_MAGIC(joystick, result) \
if (!SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK)) { \
SDL_InvalidParamError("joystick"); \
SDL_UnlockJoysticks(); \
return result; \
#define CHECK_JOYSTICK_MAGIC(joystick, result) \
CHECK_PARAM(!SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK)) { \
SDL_InvalidParamError("joystick"); \
SDL_UnlockJoysticks(); \
return result; \
}
#define CHECK_JOYSTICK_VIRTUAL(joystick, result) \
if (!joystick->is_virtual) { \
SDL_SetError("joystick isn't virtual"); \
SDL_UnlockJoysticks(); \
return result; \
#define CHECK_JOYSTICK_VIRTUAL(joystick, result) \
CHECK_PARAM(!joystick->is_virtual) { \
SDL_SetError("joystick isn't virtual"); \
SDL_UnlockJoysticks(); \
return result; \
}
bool SDL_JoysticksInitialized(void)
@@ -656,8 +836,9 @@ bool SDL_InitJoysticks(void)
SDL_joysticks_initialized = true;
SDL_InitGamepadMappings();
SDL_joystick_names = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, SDL_DestroyHashValue, NULL);
SDL_LoadVIDPIDList(&old_xboxone_controllers);
SDL_LoadVIDPIDList(&arcadestick_devices);
SDL_LoadVIDPIDList(&blacklist_devices);
SDL_LoadVIDPIDList(&flightstick_devices);
@@ -667,6 +848,8 @@ bool SDL_InitJoysticks(void)
SDL_LoadVIDPIDList(&wheel_devices);
SDL_LoadVIDPIDList(&zero_centered_devices);
SDL_InitGamepadMappings();
// See if we should allow joystick events while in the background
SDL_AddHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
SDL_JoystickAllowBackgroundEventsChanged, NULL);
@@ -802,20 +985,54 @@ const SDL_SteamVirtualGamepadInfo *SDL_GetJoystickVirtualGamepadInfoForID(SDL_Jo
/*
* Get the implementation dependent name of a joystick
*/
const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id)
static const char *SDL_UpdateJoystickNameForID(SDL_JoystickID instance_id)
{
SDL_JoystickDriver *driver;
int device_index;
const char *name = NULL;
const char *current_name = NULL;
const SDL_SteamVirtualGamepadInfo *info;
SDL_LockJoysticks();
SDL_AssertJoysticksLocked();
info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);
if (info) {
name = SDL_GetPersistentString(info->name);
current_name = info->name;
} else if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
name = SDL_GetPersistentString(driver->GetDeviceName(device_index));
current_name = driver->GetDeviceName(device_index);
}
if (!SDL_joystick_names) {
return SDL_GetPersistentString(current_name);
}
char *name = NULL;
bool found = SDL_FindInHashTable(SDL_joystick_names, (const void *)(uintptr_t)instance_id, (const void **)&name);
if (!current_name) {
if (!found) {
SDL_SetError("Joystick %" SDL_PRIu32 " not found", instance_id);
return NULL;
}
if (!name) {
// SDL_strdup() failed during insert
SDL_OutOfMemory();
return NULL;
}
return name;
}
if (!name || SDL_strcmp(name, current_name) != 0) {
name = SDL_strdup(current_name);
SDL_InsertIntoHashTable(SDL_joystick_names, (const void *)(uintptr_t)instance_id, name, true);
}
return name;
}
const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id)
{
const char *name;
SDL_LockJoysticks();
name = SDL_UpdateJoystickNameForID(instance_id);
SDL_UnlockJoysticks();
return name;
@@ -865,6 +1082,8 @@ static bool SDL_JoystickAxesCenteredAtZero(SDL_Joystick *joystick)
{
// printf("JOYSTICK '%s' VID/PID 0x%.4x/0x%.4x AXES: %d\n", joystick->name, vendor, product, joystick->naxes);
SDL_AssertJoysticksLocked();
if (joystick->naxes == 2) {
// Assume D-pad or thumbstick style axes are centered at 0
return true;
@@ -1144,8 +1363,6 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
joystick->battery_percent = -1;
#ifdef SDL_JOYSTICK_VIRTUAL
joystick->is_virtual = (driver == &SDL_VIRTUAL_JoystickDriver);
#else
joystick->is_virtual = false;
#endif
if (!driver->Open(joystick, device_index)) {
@@ -1483,9 +1700,17 @@ int SDL_GetNumJoystickHats(SDL_Joystick *joystick)
*/
int SDL_GetNumJoystickBalls(SDL_Joystick *joystick)
{
CHECK_JOYSTICK_MAGIC(joystick, -1);
int result;
return joystick->nballs;
SDL_LockJoysticks();
{
CHECK_JOYSTICK_MAGIC(joystick, -1);
result = joystick->nballs;
}
SDL_UnlockJoysticks();
return result;
}
/*
@@ -2046,6 +2271,7 @@ void SDL_QuitJoysticks(void)
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
SDL_JoystickAllowBackgroundEventsChanged, NULL);
SDL_FreeVIDPIDList(&old_xboxone_controllers);
SDL_FreeVIDPIDList(&arcadestick_devices);
SDL_FreeVIDPIDList(&blacklist_devices);
SDL_FreeVIDPIDList(&flightstick_devices);
@@ -2057,6 +2283,11 @@ void SDL_QuitJoysticks(void)
SDL_QuitGamepadMappings();
if (SDL_joystick_names) {
SDL_DestroyHashTable(SDL_joystick_names);
SDL_joystick_names = NULL;
}
SDL_joysticks_quitting = false;
SDL_joysticks_initialized = false;
@@ -2166,6 +2397,8 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id)
SDL_SetJoystickIDForPlayerIndex(player_index, instance_id);
}
SDL_UpdateJoystickNameForID(instance_id);
{
SDL_Event event;
@@ -2459,6 +2692,8 @@ static void SendSteamHandleUpdateEvents(void)
SDL_Joystick *joystick;
const SDL_SteamVirtualGamepadInfo *info;
SDL_AssertJoysticksLocked();
// Check to see if any Steam handles changed
for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
bool changed = false;
@@ -2499,7 +2734,7 @@ void SDL_UpdateJoysticks(void)
Uint64 now;
SDL_Joystick *joystick;
if (!SDL_WasInit(SDL_INIT_JOYSTICK)) {
if (!SDL_joysticks_initialized) {
return;
}
@@ -2779,10 +3014,14 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons
} else if (vendor == 0x0001 && product == 0x0001) {
type = SDL_GAMEPAD_TYPE_STANDARD;
} else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) {
} else if (vendor == USB_VENDOR_NINTENDO &&
(product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT ||
product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT)) {
type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT;
} else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) {
} else if (vendor == USB_VENDOR_NINTENDO &&
(product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT ||
product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT)) {
if (name && SDL_strstr(name, "NES Controller") != NULL) {
// We don't have a type for the Nintendo Online NES Controller
type = SDL_GAMEPAD_TYPE_STANDARD;
@@ -2797,12 +3036,13 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons
type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;
}
} else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) {
} else if (vendor == USB_VENDOR_NINTENDO &&
(product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR ||
product == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR)) {
type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR;
} else if (forUI && SDL_IsJoystickGameCube(vendor, product)) {
// We don't have a type for the Nintendo GameCube controller
type = SDL_GAMEPAD_TYPE_STANDARD;
type = SDL_GAMEPAD_TYPE_GAMECUBE;
} else {
switch (GuessControllerType(vendor, product)) {
@@ -2904,78 +3144,12 @@ bool SDL_IsJoystickXboxOneElite(Uint16 vendor_id, Uint16 product_id)
bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id)
{
if (vendor_id == USB_VENDOR_MICROSOFT) {
if (product_id == USB_PRODUCT_XBOX_SERIES_X ||
product_id == USB_PRODUCT_XBOX_SERIES_X_BLE) {
return true;
}
// Most new controllers have the share button, so we'll default to true and
// have a list of older XBox One controllers that are known not to have it.
if (SDL_VIDPIDInList(vendor_id, product_id, &old_xboxone_controllers)) {
return false;
}
if (vendor_id == USB_VENDOR_PDP) {
if (product_id == USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT ||
product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE ||
product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW) {
return true;
}
}
if (vendor_id == USB_VENDOR_POWERA_ALT) {
if ((product_id >= 0x2001 && product_id <= 0x201a) ||
product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 ||
product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 ||
product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB ||
product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE ||
product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA ||
product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA) {
return true;
}
}
if (vendor_id == USB_VENDOR_HORI) {
if (product_id == USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X ||
product_id == USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X ||
product_id == USB_PRODUCT_HORI_TAIKO_DRUM_CONTROLLER) {
return true;
}
}
if (vendor_id == USB_VENDOR_HP) {
if (product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX ||
product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB) {
return true;
}
}
if (vendor_id == USB_VENDOR_RAZER) {
if (product_id == USB_PRODUCT_RAZER_WOLVERINE_V2 ||
product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_CHROMA ||
product_id == USB_PRODUCT_RAZER_WOLVERINE_V3_PRO) {
return true;
}
}
if (vendor_id == USB_VENDOR_THRUSTMASTER) {
if (product_id == USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X) {
return true;
}
}
if (vendor_id == USB_VENDOR_TURTLE_BEACH) {
if (product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R ||
product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON) {
return true;
}
}
if (vendor_id == USB_VENDOR_8BITDO) {
if (product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 ||
product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER2) {
return true;
}
}
if (vendor_id == USB_VENDOR_GAMESIR) {
if (product_id == USB_PRODUCT_GAMESIR_G7) {
return true;
}
}
if (vendor_id == USB_VENDOR_ASUS) {
if (product_id == USB_PRODUCT_ROG_RAIKIRI) {
return true;
}
}
return false;
return true;
}
bool SDL_IsJoystickBluetoothXboxOne(Uint16 vendor_id, Uint16 product_id)
@@ -3054,7 +3228,9 @@ bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id)
bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id)
{
return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
return vendor_id == USB_VENDOR_NINTENDO &&
(product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR ||
product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR);
}
bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id)
@@ -3100,12 +3276,47 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id)
return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT);
}
bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id)
{
if (vendor_id == USB_VENDOR_RASPBERRYPI) {
if (product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC ||
product_id == USB_PRODUCT_HANDHELDLEGEND_PROGCC ||
product_id == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE ||
product_id == USB_PRODUCT_BONZIRICHANNEL_FIREBIRD ||
product_id == USB_PRODUCT_VOIDGAMING_PS4FIREBIRD) {
return true;
}
}
return false;
}
bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id)
{
if (vendor_id == USB_VENDOR_FLYDIGI_V1) {
if (product_id == USB_PRODUCT_FLYDIGI_V1_GAMEPAD) {
return true;
}
}
if (vendor_id == USB_VENDOR_FLYDIGI_V2) {
if (product_id == USB_PRODUCT_FLYDIGI_V2_APEX || product_id == USB_PRODUCT_FLYDIGI_V2_VADER) {
return true;
}
}
return false;
}
bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id)
{
EControllerType eType = GuessControllerType(vendor_id, product_id);
return eType == k_eControllerType_SteamControllerNeptune;
}
bool SDL_IsJoystickSteamTriton(Uint16 vendor_id, Uint16 product_id)
{
EControllerType eType = GuessControllerType(vendor_id, product_id);
return eType == k_eControllerType_SteamControllerTriton;
}
bool SDL_IsJoystickXInput(SDL_GUID guid)
{
return (guid.data[14] == 'x') ? true : false;
@@ -3136,7 +3347,7 @@ bool SDL_IsJoystickVIRTUAL(SDL_GUID guid)
return (guid.data[14] == 'v') ? true : false;
}
static bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id)
bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id)
{
return SDL_VIDPIDInList(vendor_id, product_id, &wheel_devices);
}
@@ -3672,9 +3883,7 @@ static void SDL_LoadVIDPIDListFromHint(const char *hint, int *num_entries, int *
(*entries)[(*num_entries)++] = entry;
}
if (file) {
SDL_free(file);
}
SDL_free(file);
}
void SDL_LoadVIDPIDListFromHints(SDL_vidpid_list *list, const char *included_list, const char *excluded_list)
@@ -3752,9 +3961,15 @@ void SDL_LoadVIDPIDList(SDL_vidpid_list *list)
if (list->included_hint_name) {
included_list = SDL_GetHint(list->included_hint_name);
if (!included_list) {
included_list = SDL_getenv_unsafe(list->included_hint_name);
}
}
if (list->excluded_hint_name) {
excluded_list = SDL_GetHint(list->excluded_hint_name);
if (!excluded_list) {
excluded_list = SDL_getenv_unsafe(list->excluded_hint_name);
}
}
SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list);
}
+13 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -135,9 +135,18 @@ extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is a HORI Steam controller
extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is an SInput (Open Format) controller
extern bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is a Flydigi controller
extern bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is a Steam Deck
extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is a Steam Triton
extern bool SDL_IsJoystickSteamTriton(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick guid comes from the XInput driver
extern bool SDL_IsJoystickXInput(SDL_GUID guid);
@@ -156,6 +165,9 @@ extern bool SDL_IsJoystickRAWINPUT(SDL_GUID guid);
// Function to return whether a joystick guid comes from the Virtual driver
extern bool SDL_IsJoystickVIRTUAL(SDL_GUID guid);
// Function to return whether a joystick is a wheel
bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick should be ignored
extern bool SDL_ShouldIgnoreJoystick(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -23,6 +23,9 @@
#include "SDL_joystick_c.h"
#include "SDL_steam_virtual_gamepad.h"
#ifdef SDL_PLATFORM_LINUX
#include "../core/unix/SDL_appid.h"
#endif
#ifdef SDL_PLATFORM_WIN32
#include "../core/windows/SDL_windows.h"
#else
@@ -30,8 +33,6 @@
#include <sys/stat.h>
#endif
#define SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE "SteamVirtualGamepadInfo"
static char *SDL_steam_virtual_gamepad_info_file SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
static Uint64 SDL_steam_virtual_gamepad_info_file_mtime SDL_GUARDED_BY(SDL_joystick_lock) = 0;
static Uint64 SDL_steam_virtual_gamepad_info_check_time SDL_GUARDED_BY(SDL_joystick_lock) = 0;
@@ -132,8 +133,17 @@ void SDL_InitSteamVirtualGamepadInfo(void)
return;
}
file = SDL_GetHint(SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE);
file = SDL_getenv_unsafe("SteamVirtualGamepadInfo");
if (file && *file) {
#ifdef SDL_PLATFORM_LINUX
// Older versions of Wine will blacklist the Steam Virtual Gamepad if
// it appears to have the real controller's VID/PID, so ignore this.
const char *exe = SDL_GetExeName();
if (exe && SDL_strcmp(exe, "wine64-preloader") == 0) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Wine launched by Steam, ignoring SteamVirtualGamepadInfo");
return;
}
#endif
SDL_steam_virtual_gamepad_info_file = SDL_strdup(file);
}
SDL_UpdateSteamVirtualGamepadInfo();
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -106,7 +106,8 @@ static int keycode_to_SDL(int keycode)
button = SDL_GAMEPAD_BUTTON_GUIDE;
break;
case AKEYCODE_BUTTON_L2:
button = 15;
case AKEYCODE_MEDIA_RECORD:
button = SDL_GAMEPAD_BUTTON_MISC1;
break;
case AKEYCODE_BUTTON_R2:
button = 16;
@@ -305,7 +306,7 @@ bool Android_OnHat(int device_id, int hat_id, int x, int y)
return false;
}
void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble)
void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble, bool has_rgb_led)
{
SDL_joylist_item *item;
SDL_GUID guid;
@@ -328,6 +329,10 @@ void Android_AddJoystick(int device_id, const char *name, const char *desc, int
goto done;
}
if (SDL_ShouldIgnoreJoystick(vendor_id, product_id, 0, name)) {
goto done;
}
#ifdef DEBUG_JOYSTICK
SDL_Log("Joystick: %s, descriptor %s, vendor = 0x%.4x, product = 0x%.4x, %d axes, %d hats", name, desc, vendor_id, product_id, naxes, nhats);
#endif
@@ -376,6 +381,7 @@ void Android_AddJoystick(int device_id, const char *name, const char *desc, int
item->naxes = naxes;
item->nhats = nhats;
item->can_rumble = can_rumble;
item->has_rgb_led = has_rgb_led;
item->device_instance = SDL_GetNextObjectID();
if (!SDL_joylist_tail) {
SDL_joylist = SDL_joylist_tail = item;
@@ -577,6 +583,10 @@ static bool ANDROID_JoystickOpen(SDL_Joystick *joystick, int device_index)
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
}
if (item->has_rgb_led) {
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true);
}
return true;
}
@@ -603,7 +613,15 @@ static bool ANDROID_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_r
static bool ANDROID_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
if (!item) {
return SDL_SetError("SetLED failed, device disconnected");
}
if (!item->has_rgb_led) {
return SDL_Unsupported();
}
Android_JNI_JoystickSetLED(item->device_id, red, green, blue);
return true;
}
static bool ANDROID_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -32,7 +32,7 @@ extern bool Android_OnPadDown(int device_id, int keycode);
extern bool Android_OnPadUp(int device_id, int keycode);
extern bool Android_OnJoy(int device_id, int axisnum, float value);
extern bool Android_OnHat(int device_id, int hat_id, int x, int y);
extern void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble);
extern void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble, bool has_rgb_led);
extern void Android_RemoveJoystick(int device_id);
// A linked list of available joysticks
@@ -46,6 +46,7 @@ typedef struct SDL_joylist_item
int nbuttons, naxes, nhats;
int dpad_state;
bool can_rumble;
bool has_rgb_led;
struct SDL_joylist_item *next;
} SDL_joylist_item;
+69 -34
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -27,6 +27,10 @@
#include "../usb_ids.h"
#include "../../events/SDL_events_c.h"
#ifdef SDL_VIDEO_DRIVER_UIKIT
#include "../../video/uikit/SDL_uikitvideo.h"
#endif
#include "SDL_mfijoystick_c.h"
@@ -158,6 +162,13 @@ static bool IsControllerSwitchJoyConPair(GCController *controller)
}
return false;
}
static bool IsControllerNVIDIASHIELD(GCController *controller)
{
if ([controller.vendorName hasPrefix:@"NVIDIA Controller"]) {
return true;
}
return false;
}
static bool IsControllerStadia(GCController *controller)
{
if ([controller.vendorName hasPrefix:@"Stadia"]) {
@@ -302,6 +313,12 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
if (controller.vendorName) {
name = controller.vendorName.UTF8String;
} else {
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
if (controller.productCategory) {
name = controller.productCategory.UTF8String;
}
}
}
if (!name) {
@@ -314,7 +331,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
NSLog(@"Product name: %@\n", controller.vendorName);
NSLog(@"Product category: %@\n", controller.productCategory);
NSLog(@"Elements available:\n");
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
for (id key in controller.physicalInputProfile.buttons) {
NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital");
@@ -333,6 +350,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
device->is_ps5 = IsControllerPS5(controller);
device->is_switch_pro = IsControllerSwitchPro(controller);
device->is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller);
device->is_shield = IsControllerNVIDIASHIELD(controller);
device->is_stadia = IsControllerStadia(controller);
device->is_backbone_one = IsControllerBackboneOne(controller);
device->is_switch_joyconL = IsControllerSwitchJoyConL(controller);
@@ -344,9 +362,18 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
(device->is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) ||
(device->is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) ||
(device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) ||
(device->is_shield && HIDAPI_IsDevicePresent(USB_VENDOR_NVIDIA, USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104, 0, "")) ||
(device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) ||
(device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) ||
(device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) {
(device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, "")) ||
(SDL_strstr(name, "GameCube Controller Adapter") &&
(HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER, 0, "") ||
HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1, 0, "") ||
HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2, 0, "") ||
HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3, 0, ""))) ||
(SDL_strcmp(name, "8Bitdo SN30 Pro") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO_BT, 0, ""))) ||
(SDL_strcmp(name, "8BitDo Pro 2") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2_BT, 0, ""))) ||
(SDL_startswith(name, "8BitDo Ultimate 2 Wireless") && HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS, 0, ""))) {
// The HIDAPI driver is taking care of this device
return false;
}
@@ -362,7 +389,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
return false;
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) {
device->has_dualshock_touchpad = TRUE;
}
@@ -417,7 +444,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
} else if (device->is_switch_joyconR) {
vendor = USB_VENDOR_NINTENDO;
product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT;
} else if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
} else if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
vendor = USB_VENDOR_APPLE;
product = 4;
subtype = 4;
@@ -440,7 +467,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
return false;
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
// Provide both axes and analog buttons as SDL axes
@@ -480,7 +507,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
subtype = 4;
#ifdef DEBUG_CONTROLLER_PROFILE
NSLog(@"Elements used:\n", controller.vendorName);
NSLog(@"Elements used:\n");
for (id key in device->buttons) {
NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital");
}
@@ -571,7 +598,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
}
Uint16 signature;
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
signature = 0;
signature = SDL_crc16(signature, device->name, SDL_strlen(device->name));
for (id key in device->axes) {
@@ -725,7 +752,7 @@ static bool IOS_JoystickInit(void)
}
#ifdef SDL_PLATFORM_MACOS
if (@available(macOS 10.16, *)) {
if (@available(macOS 11.0, *)) {
// Continue with initialization on macOS 11+
} else {
return true;
@@ -782,6 +809,10 @@ static bool IOS_JoystickInit(void)
SDL_UnlockJoysticks();
}];
#endif // SDL_JOYSTICK_MFI
#ifdef SDL_VIDEO_DRIVER_UIKIT
UIKit_SetGameControllerInteraction(true);
#endif
}
return true;
@@ -887,7 +918,7 @@ static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index)
};
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
GCController *controller = joystick->hwdata->controller;
GCMotion *motion = controller.motion;
if (motion && motion.hasRotationRate) {
@@ -898,7 +929,7 @@ static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index)
}
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
GCController *controller = joystick->hwdata->controller;
for (id key in controller.physicalInputProfile.buttons) {
GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
@@ -908,7 +939,7 @@ static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index)
}
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
GCController *controller = device->controller;
if (controller.light) {
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true);
@@ -969,7 +1000,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
Uint64 timestamp = SDL_GetTicksNS();
#ifdef DEBUG_CONTROLLER_STATE
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
if (controller.physicalInputProfile) {
for (id key in controller.physicalInputProfile.buttons) {
GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
@@ -995,7 +1026,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
}
#endif // DEBUG_CONTROLLER_STATE
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
NSDictionary<NSString *, GCControllerButtonInput *> *buttons = controller.physicalInputProfile.buttons;
@@ -1125,7 +1156,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
}
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
if (device->has_dualshock_touchpad) {
GCControllerDirectionPad *dpad;
@@ -1145,7 +1176,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
}
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
GCMotion *motion = controller.motion;
if (motion && motion.sensorsActive) {
float data[3];
@@ -1167,7 +1198,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
}
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
GCDeviceBattery *battery = controller.battery;
if (battery) {
SDL_PowerState state = SDL_POWERSTATE_UNKNOWN;
@@ -1196,8 +1227,8 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
#ifdef SDL_JOYSTICK_MFI
@interface SDL3_RumbleMotor : NSObject
@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0));
@property(nonatomic, strong) id<CHHapticPatternPlayer> player API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0));
@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0));
@property(nonatomic, strong) id<CHHapticPatternPlayer> player API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0));
@property bool active;
@end
@@ -1208,7 +1239,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
- (void)cleanup
{
@autoreleasepool {
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
if (self.player != nil) {
[self.player cancelAndReturnError:nil];
self.player = nil;
@@ -1224,7 +1255,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
- (bool)setIntensity:(float)intensity
{
@autoreleasepool {
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
NSError *error = nil;
CHHapticDynamicParameter *param;
@@ -1271,7 +1302,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
}
}
- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(10.16), ios(14.0), tvos(14.0))
- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
{
@autoreleasepool {
NSError *error;
@@ -1373,7 +1404,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
static SDL3_RumbleContext *IOS_JoystickInitRumble(GCController *controller)
{
@autoreleasepool {
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
SDL3_RumbleMotor *low_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle];
SDL3_RumbleMotor *high_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle];
SDL3_RumbleMotor *left_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger];
@@ -1400,7 +1431,7 @@ static bool IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumb
return SDL_SetError("Controller is no longer connected");
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
if (!device->rumble && device->controller && device->controller.haptics) {
SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
if (rumble) {
@@ -1426,7 +1457,7 @@ static bool IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumbl
return SDL_SetError("Controller is no longer connected");
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
if (!device->rumble && device->controller && device->controller.haptics) {
SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
if (rumble) {
@@ -1452,7 +1483,7 @@ static bool IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, U
return SDL_SetError("Controller is no longer connected");
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
GCController *controller = device->controller;
GCDeviceLight *light = controller.light;
if (light) {
@@ -1480,7 +1511,7 @@ static bool IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
return SDL_SetError("Controller is no longer connected");
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
GCController *controller = device->controller;
GCMotion *motion = controller.motion;
if (motion) {
@@ -1531,7 +1562,7 @@ static void IOS_JoystickClose(SDL_Joystick *joystick)
controller.controllerPausedHandler = nil;
controller.playerIndex = -1;
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
for (id key in controller.physicalInputProfile.buttons) {
GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
if ([button isBoundToSystemGesture]) {
@@ -1573,6 +1604,10 @@ static void IOS_JoystickQuit(void)
while (deviceList != NULL) {
IOS_RemoveJoystickDevice(deviceList);
}
#ifdef SDL_VIDEO_DRIVER_UIKIT
UIKit_SetGameControllerInteraction(false);
#endif
}
numjoysticks = 0;
@@ -1585,7 +1620,7 @@ static bool IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *
return false;
}
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
int axis = 0;
for (id key in device->axes) {
if ([(NSString *)key isEqualToString:@"Left Thumbstick X Axis"] ||
@@ -1722,7 +1757,7 @@ bool IOS_SupportedHIDDevice(IOHIDDeviceRef device)
return false;
}
if (@available(macOS 10.16, *)) {
if (@available(macOS 11.0, *)) {
const int MAX_ATTEMPTS = 3;
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
if ([GCController supportsHIDDevice:device]) {
@@ -1741,7 +1776,7 @@ bool IOS_SupportedHIDDevice(IOHIDDeviceRef device)
/* NOLINTNEXTLINE(readability-non-const-parameter): getCString takes a non-const char* */
static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *name)
{
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
if (element) {
[element.sfSymbolsName getCString:name maxLength:255 encoding:NSASCIIStringEncoding];
}
@@ -1750,7 +1785,7 @@ static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *
static GCControllerDirectionPad *GetDirectionalPadForController(GCController *controller)
{
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
return controller.physicalInputProfile.dpads[GCInputDirectionPad];
}
@@ -1773,7 +1808,7 @@ const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_Gamepad
#ifdef SDL_JOYSTICK_MFI
if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) {
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller;
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
switch (button) {
@@ -1889,7 +1924,7 @@ const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAx
#ifdef SDL_JOYSTICK_MFI
if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) {
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller;
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
switch (axis) {
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -53,6 +53,7 @@ typedef struct joystick_hwdata
bool is_switch_joycon_pair;
bool is_switch_joyconL;
bool is_switch_joyconR;
bool is_shield;
bool is_stadia;
bool is_backbone_one;
int is_siri_remote;
+5 -5
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -710,10 +710,10 @@ static void BSD_JoystickUpdate(SDL_Joystick *joy)
//default:
// no-op
}
SDL_PrivateJoystickHat(joy, 0, (dpad[0] * HAT_UP) |
(dpad[1] * HAT_DOWN) |
(dpad[2] * HAT_RIGHT) |
(dpad[3] * HAT_LEFT) );
SDL_SendJoystickHat(timestamp, joy, 0, (dpad[0] * HAT_UP) |
(dpad[1] * HAT_DOWN) |
(dpad[2] * HAT_RIGHT) |
(dpad[3] * HAT_LEFT) );
#endif
break;
}
+18 -16
View File
@@ -159,6 +159,8 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x1532, 0x100b ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wired)
{ MAKE_CONTROLLER_ID( 0x1532, 0x100c ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wireless)
{ MAKE_CONTROLLER_ID( 0x1532, 0x1012 ), k_eControllerType_PS5Controller, NULL }, // Razer Kitsune
{ MAKE_CONTROLLER_ID( 0x1532, 0x1024 ), k_eControllerType_PS5Controller, NULL }, // Razer Raiju V5 Pro (PS5 mode wired)
{ MAKE_CONTROLLER_ID( 0x1532, 0x1026 ), k_eControllerType_PS5Controller, NULL }, // Razer Raiju V5 Pro (PS5 mode with dongle)
{ MAKE_CONTROLLER_ID( 0x3285, 0x0d18 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode with dongle)
{ MAKE_CONTROLLER_ID( 0x3285, 0x0d19 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode wired)
{ MAKE_CONTROLLER_ID( 0x358a, 0x0104 ), k_eControllerType_PS5Controller, NULL }, // Backbone One PlayStation Edition for iOS
@@ -418,7 +420,7 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x20d6, 0x2012 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X Cuphead EnWired Controller - Mugman
{ MAKE_CONTROLLER_ID( 0x20d6, 0x2015 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Blue Hint
{ MAKE_CONTROLLER_ID( 0x20d6, 0x2016 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Green Hint
{ MAKE_CONTROLLER_ID( 0x20d6, 0x2017 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Cntroller - Arctic Camo
{ MAKE_CONTROLLER_ID( 0x20d6, 0x2017 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Arctic Camo
{ MAKE_CONTROLLER_ID( 0x20d6, 0x2018 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Arc Lightning
{ MAKE_CONTROLLER_ID( 0x20d6, 0x2019 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Royal Purple
{ MAKE_CONTROLLER_ID( 0x20d6, 0x201a ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Nebula
@@ -435,14 +437,14 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x24c6, 0x592a ), k_eControllerType_XBoxOneController, NULL }, // BDA XB1 Spectra Pro
{ MAKE_CONTROLLER_ID( 0x24c6, 0x791a ), k_eControllerType_XBoxOneController, NULL }, // PowerA Fusion Fight Pad
{ MAKE_CONTROLLER_ID( 0x2dc8, 0x2002 ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate Wired Controller for Xbox
{ MAKE_CONTROLLER_ID( 0x2dc8, 0x3106 ), k_eControllerType_XBoxOneController, NULL }, // 8Bitdo Ultimate Wired Controller. Windows, Android, Switch.
{ MAKE_CONTROLLER_ID( 0x2dc8, 0x3106 ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate Wired Controller. Windows, Android, Switch.
{ MAKE_CONTROLLER_ID( 0x2dc8, 0x310a ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate 2C Wireless Controller
{ MAKE_CONTROLLER_ID( 0x2e24, 0x0652 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke
{ MAKE_CONTROLLER_ID( 0x2e24, 0x1618 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke
{ MAKE_CONTROLLER_ID( 0x2e24, 0x1688 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin X91
{ MAKE_CONTROLLER_ID( 0x146b, 0x0611 ), k_eControllerType_XBoxOneController, NULL }, // Xbox Controller Mode for NACON Revolution 3
// These have been added via Minidump for unrecognized Xinput controller assert
{ MAKE_CONTROLLER_ID( 0x0000, 0x0000 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0x045e, 0x02a2 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller - Microsoft VID
{ MAKE_CONTROLLER_ID( 0x0e6f, 0x1414 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0x0e6f, 0x0159 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
@@ -468,7 +470,6 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x046d, 0x0291 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput
{ MAKE_CONTROLLER_ID( 0x0079, 0x18d3 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0x0f0d, 0x00b1 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0x0001, 0x0001 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0x0079, 0x188e ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0x0079, 0x187c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0x0079, 0x189c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
@@ -481,10 +482,8 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0xf0d, 0xed ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xf0d, 0xc0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x152 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2a7 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0x46d, 0x1007 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2b8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2a8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0x79, 0x18a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
// Added from Minidumps 10-9-19
@@ -525,14 +524,7 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0xd62, 0x9a1b ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe00, 0xe00 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x12a ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2a2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2a5 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2b2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2bd ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2bf ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2c0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xe6f, 0x2c6 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xf0d, 0x97 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xf0d, 0xba ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
{ MAKE_CONTROLLER_ID( 0xf0d, 0xd8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
@@ -549,8 +541,11 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x05ac, 0x0002 ), k_eControllerType_AppleController, NULL }, // MFI Standard Gamepad (generic entry for iOS/tvOS)
{ MAKE_CONTROLLER_ID( 0x057e, 0x2006 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch Joy-Con (Left)
{ MAKE_CONTROLLER_ID( 0x057e, 0x2067 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch 2 Joy-Con (Left)
{ MAKE_CONTROLLER_ID( 0x057e, 0x2007 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch Joy-Con (Right)
{ MAKE_CONTROLLER_ID( 0x057e, 0x2066 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch 2 Joy-Con (Right)
{ MAKE_CONTROLLER_ID( 0x057e, 0x2008 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch Joy-Con (Left+Right Combined)
{ MAKE_CONTROLLER_ID( 0x057e, 0x2068 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch 2 Joy-Con (Left+Right Combined)
// This same controller ID is spoofed by many 3rd-party Switch controllers.
// The ones we currently know of are:
@@ -559,6 +554,7 @@ static const ControllerDescription_t arrControllers[] = {
// * ZhiXu Gamepad Wireless
// * Sunwaytek Wireless Motion Controller for Nintendo Switch
{ MAKE_CONTROLLER_ID( 0x057e, 0x2009 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch Pro Controller
{ MAKE_CONTROLLER_ID( 0x057e, 0x2069 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch 2 Pro Controller
//{ MAKE_CONTROLLER_ID( 0x057e, 0x2017 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SNES Controller
//{ MAKE_CONTROLLER_ID( 0x057e, 0x2019 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online N64 Controller
//{ MAKE_CONTROLLER_ID( 0x057e, 0x201e ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SEGA Genesis Controller
@@ -583,6 +579,7 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x0e6f, 0x0186 ), k_eControllerType_SwitchProController, NULL }, // PDP Afterglow Wireless Switch Controller - working gyro. USB is for charging only. Many later "Wireless" line devices w/ gyro also use this vid/pid
{ MAKE_CONTROLLER_ID( 0x0e6f, 0x0187 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Rockcandy Wired Controller
{ MAKE_CONTROLLER_ID( 0x0e6f, 0x0188 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Afterglow Wired Deluxe+ Audio Controller
{ MAKE_CONTROLLER_ID( 0x0e6f, 0x018c ), k_eControllerType_SwitchProController, "PDP REALMz Wireless Controller" }, // PDP REALMz Wireless Controller for Switch
{ MAKE_CONTROLLER_ID( 0x0f0d, 0x00aa ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Real Arcade Pro V Hayabusa in Switch Mode
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa711 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Wired Controller Plus/PowerA Wired Controller Nintendo GameCube Style
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa712 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Fight Pad
@@ -591,9 +588,10 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa715 ), k_eControllerType_SwitchInputOnlyController, NULL }, // Power A Fusion Wireless Arcade Stick (USB Mode) Over BT is shows up as 057e 2009
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa716 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Pro Controller - USB requires toggling switch on back of device
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa718 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Nano Wired Controller
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ??
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ??
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red
{ MAKE_CONTROLLER_ID( 0x0f0d, 0x00f0 ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Taiko Controller For Switch
// Valve products
{ MAKE_CONTROLLER_ID( 0x0000, 0x11fb ), k_eControllerType_MobileTouch, NULL }, // Streaming mobile touch virtual controls
@@ -606,4 +604,8 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL }, // Valve wired Steam Controller (HEADCRAB)
{ MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL }, // Valve Bluetooth Steam Controller (HEADCRAB)
{ MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamControllerNeptune, NULL }, // Valve Steam Deck Builtin Controller
{ MAKE_CONTROLLER_ID( 0x28de, 0x1302 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Triton Controller
{ MAKE_CONTROLLER_ID( 0x28de, 0x1303 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Triton Controller (BLE)
{ MAKE_CONTROLLER_ID( 0x28de, 0x1304 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Proteus Dongle (Proprietary)
{ MAKE_CONTROLLER_ID( 0x28de, 0x1305 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Nereid Dongle (Proprietary)
};
+2
View File
@@ -39,6 +39,8 @@ typedef enum
k_eControllerType_SteamControllerV2 = 3,
k_eControllerType_SteamControllerNeptune = 4,
k_eControllerType_SteamControllerTriton = 10,
// Other Controllers
k_eControllerType_UnknownNonSteamController = 30,
k_eControllerType_XBox360Controller = 31,
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -27,6 +27,7 @@
#include "SDL_sysjoystick_c.h"
#include "../SDL_joystick_c.h"
#include "../usb_ids.h"
static SDL_joylist_item *JoystickByIndex(int index);
@@ -34,10 +35,60 @@ static SDL_joylist_item *SDL_joylist = NULL;
static SDL_joylist_item *SDL_joylist_tail = NULL;
static int numjoysticks = 0;
EM_JS(int, SDL_GetEmscriptenJoystickVendor, (int device_index), {
// Let's assume that if we're calling these function then the gamepad object definitely exists
let gamepad = navigator['getGamepads']()[device_index];
// Chrome, Edge, Opera: Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 09cc)
let vendor_str = 'Vendor: ';
if (gamepad['id']['indexOf'](vendor_str) > 0) {
let vendor_str_index = gamepad['id']['indexOf'](vendor_str) + vendor_str['length'];
return parseInt(gamepad['id']['substr'](vendor_str_index, 4), 16);
}
// Firefox, Safari: 046d-c216-Logitech Dual Action (or 46d-c216-Logicool Dual Action)
let id_split = gamepad['id']['split']('-');
if (id_split['length'] > 1 && !isNaN(parseInt(id_split[0], 16))) {
return parseInt(id_split[0], 16);
}
return 0;
});
EM_JS(int, SDL_GetEmscriptenJoystickProduct, (int device_index), {
let gamepad = navigator['getGamepads']()[device_index];
// Chrome, Edge, Opera: Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 09cc)
let product_str = 'Product: ';
if (gamepad['id']['indexOf'](product_str) > 0) {
let product_str_index = gamepad['id']['indexOf'](product_str) + product_str['length'];
return parseInt(gamepad['id']['substr'](product_str_index, 4), 16);
}
// Firefox, Safari: 046d-c216-Logitech Dual Action (or 46d-c216-Logicool Dual Action)
let id_split = gamepad['id']['split']('-');
if (id_split['length'] > 1 && !isNaN(parseInt(id_split[1], 16))) {
return parseInt(id_split[1], 16);
}
return 0;
});
EM_JS(int, SDL_IsEmscriptenJoystickXInput, (int device_index), {
let gamepad = navigator['getGamepads']()[device_index];
// Chrome, Edge, Opera: Xbox 360 Controller (XInput STANDARD GAMEPAD)
// Firefox: xinput
// TODO: Safari
return gamepad['id']['toLowerCase']()['indexOf']('xinput') >= 0;
});
static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
{
SDL_joylist_item *item;
int i;
Uint16 vendor, product;
bool is_xinput;
SDL_LockJoysticks();
@@ -53,12 +104,32 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
SDL_zerop(item);
item->index = gamepadEvent->index;
item->name = SDL_CreateJoystickName(0, 0, NULL, gamepadEvent->id);
vendor = SDL_GetEmscriptenJoystickVendor(gamepadEvent->index);
product = SDL_GetEmscriptenJoystickProduct(gamepadEvent->index);
is_xinput = SDL_IsEmscriptenJoystickXInput(gamepadEvent->index);
// Use a generic VID/PID representing an XInput controller
if (!vendor && !product && is_xinput) {
vendor = USB_VENDOR_MICROSOFT;
product = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
}
item->name = SDL_CreateJoystickName(vendor, product, NULL, gamepadEvent->id);
if (!item->name) {
SDL_free(item);
goto done;
}
if (vendor && product) {
item->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_UNKNOWN, vendor, product, 0, NULL, item->name, 0, 0);
} else {
item->guid = SDL_CreateJoystickGUIDForName(item->name);
}
if (is_xinput) {
item->guid.data[14] = 'x'; // See SDL_IsJoystickXInput
}
item->mapping = SDL_strdup(gamepadEvent->mapping);
if (!item->mapping) {
SDL_free(item->name);
@@ -66,19 +137,74 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
goto done;
}
item->naxes = gamepadEvent->numAxes;
item->nbuttons = gamepadEvent->numButtons;
const int real_button_count = gamepadEvent->numButtons;
const int real_axis_count = gamepadEvent->numAxes;
int first_trigger_button = -1;
int first_hat_button = -1;
int num_buttons = gamepadEvent->numButtons;
int num_axes = gamepadEvent->numAxes;
bool triggers_are_buttons = false;
if ((SDL_strcmp(gamepadEvent->mapping, "standard") == 0) && (num_buttons >= 16)) { // maps to a game console gamepad layout, turn the d-pad into a hat, treat triggers as analog.
num_buttons -= 4; // 4 dpad buttons become a hat.
first_hat_button = 12;
if (num_axes == 4) { // Chrome gives the triggers analog button values, Firefox exposes them as extra axes. Both have the digital buttons.
num_axes += 2; // the two trigger "buttons"
triggers_are_buttons = true;
}
// dump the digital trigger buttons in any case.
first_trigger_button = 6;
num_buttons -= 2;
}
item->first_hat_button = first_hat_button;
item->first_trigger_button = first_trigger_button;
item->triggers_are_buttons = triggers_are_buttons;
item->nhats = (first_hat_button >= 0) ? 1 : 0;
item->naxes = num_axes;
item->nbuttons = num_buttons;
item->device_instance = SDL_GetNextObjectID();
item->timestamp = gamepadEvent->timestamp;
for (i = 0; i < item->naxes; i++) {
int buttonidx = 0;
for (i = 0; i < real_button_count; i++, buttonidx++) {
if (buttonidx == first_hat_button) {
buttonidx += 4; // skip these buttons, we're treating them as hat input.
} else if (buttonidx == first_trigger_button) {
buttonidx += 2; // skip these buttons, we're treating them as axes.
}
item->analogButton[i] = gamepadEvent->analogButton[buttonidx];
item->digitalButton[i] = gamepadEvent->digitalButton[buttonidx];
}
for (i = 0; i < real_axis_count; i++) {
item->axis[i] = gamepadEvent->axis[i];
}
for (i = 0; i < item->nbuttons; i++) {
item->analogButton[i] = gamepadEvent->analogButton[i];
item->digitalButton[i] = gamepadEvent->digitalButton[i];
if (item->triggers_are_buttons) {
item->axis[real_axis_count] = (gamepadEvent->analogButton[first_trigger_button] * 2.0f) - 1.0f;
item->axis[real_axis_count+1] = (gamepadEvent->analogButton[first_trigger_button+1] * 2.0f) - 1.0f;
}
SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons.
if (first_hat_button != -1) {
Uint8 value = SDL_HAT_CENTERED;
// this currently expects the first button to be up, then down, then left, then right.
if (gamepadEvent->digitalButton[first_hat_button + 0]) {
value |= SDL_HAT_UP;
}
if (gamepadEvent->digitalButton[first_hat_button + 1]) {
value |= SDL_HAT_DOWN;
}
if (gamepadEvent->digitalButton[first_hat_button + 2]) {
value |= SDL_HAT_LEFT;
}
if (gamepadEvent->digitalButton[first_hat_button + 3]) {
value |= SDL_HAT_RIGHT;
}
item->hat = value;
}
if (!SDL_joylist_tail) {
@@ -305,6 +431,7 @@ static SDL_JoystickID EMSCRIPTEN_JoystickGetDeviceInstanceID(int device_index)
static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index)
{
SDL_joylist_item *item = JoystickByDeviceIndex(device_index);
bool rumble_available = false;
if (!item) {
return SDL_SetError("No such device");
@@ -317,12 +444,27 @@ static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index)
joystick->hwdata = (struct joystick_hwdata *)item;
item->joystick = joystick;
// HTML5 Gamepad API doesn't say anything about these
joystick->nhats = 0;
// HTML5 Gamepad API doesn't offer hats, but we can fake it from the d-pad buttons on the "standard" mapping.
joystick->nhats = item->nhats;
joystick->nbuttons = item->nbuttons;
joystick->naxes = item->naxes;
rumble_available = MAIN_THREAD_EM_ASM_INT({
let gamepads = navigator['getGamepads']();
if (!gamepads) {
return 0;
}
let gamepad = gamepads[$0];
if (!gamepad || !gamepad['vibrationActuator']) {
return 0;
}
return 1;
}, item->index);
if (rumble_available) {
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
}
return true;
}
@@ -344,28 +486,65 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
result = emscripten_get_gamepad_status(item->index, &gamepadState);
if (result == EMSCRIPTEN_RESULT_SUCCESS) {
if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) {
for (i = 0; i < item->nbuttons; i++) {
if (item->digitalButton[i] != gamepadState.digitalButton[i]) {
bool down = (gamepadState.digitalButton[i] != 0);
const int first_hat_button = item->first_hat_button;
const int first_trigger_button = item->first_trigger_button;
const int real_button_count = gamepadState.numButtons;
const int real_axis_count = gamepadState.numAxes;
int buttonidx = 0;
for (i = 0; i < real_button_count; i++, buttonidx++) {
if (buttonidx == first_hat_button) {
buttonidx += 4; // skip these buttons, we're treating them as hat input.
} else if (buttonidx == first_trigger_button) {
buttonidx += 2; // skip these buttons, we're treating them as axes.
}
if (item->digitalButton[i] != gamepadState.digitalButton[buttonidx]) {
const bool down = (gamepadState.digitalButton[buttonidx] != 0);
SDL_SendJoystickButton(timestamp, item->joystick, i, down);
}
// store values to compare them in the next update
item->analogButton[i] = gamepadState.analogButton[i];
item->digitalButton[i] = gamepadState.digitalButton[i];
item->analogButton[i] = gamepadState.analogButton[buttonidx];
item->digitalButton[i] = gamepadState.digitalButton[buttonidx];
}
for (i = 0; i < item->naxes; i++) {
for (i = 0; i < real_axis_count; i++) {
if (item->axis[i] != gamepadState.axis[i]) {
// do we need to do conversion?
SDL_SendJoystickAxis(timestamp, item->joystick, i,
(Sint16)(32767. * gamepadState.axis[i]));
SDL_SendJoystickAxis(timestamp, item->joystick, i, (Sint16)(32767.0f * gamepadState.axis[i]));
item->axis[i] = gamepadState.axis[i];
}
// store to compare in next update
item->axis[i] = gamepadState.axis[i];
}
if (item->triggers_are_buttons) {
for (i = 0; i < 2; i++) {
if (item->axis[real_axis_count+i] != gamepadState.analogButton[first_trigger_button+i]) {
SDL_SendJoystickAxis(timestamp, item->joystick, real_axis_count+i, (Sint16)(32767.0f * ((gamepadState.analogButton[first_trigger_button+i] * 2.0f) - 1.0f)));
item->axis[real_axis_count+i] = gamepadState.analogButton[first_trigger_button+i];
}
}
}
SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons.
if (item->nhats) {
Uint8 value = SDL_HAT_CENTERED;
// this currently expects the first button to be up, then down, then left, then right.
if (gamepadState.digitalButton[first_hat_button + 0]) {
value |= SDL_HAT_UP;
} else if (gamepadState.digitalButton[first_hat_button + 1]) {
value |= SDL_HAT_DOWN;
}
if (gamepadState.digitalButton[first_hat_button + 2]) {
value |= SDL_HAT_LEFT;
} else if (gamepadState.digitalButton[first_hat_button + 3]) {
value |= SDL_HAT_RIGHT;
}
if (item->hat != value) {
item->hat = value;
SDL_SendJoystickHat(timestamp, item->joystick, 0, value);
}
}
item->timestamp = gamepadState.timestamp;
}
}
@@ -383,14 +562,34 @@ static void EMSCRIPTEN_JoystickClose(SDL_Joystick *joystick)
static SDL_GUID EMSCRIPTEN_JoystickGetDeviceGUID(int device_index)
{
// the GUID is just the name for now
const char *name = EMSCRIPTEN_JoystickGetDeviceName(device_index);
return SDL_CreateJoystickGUIDForName(name);
return JoystickByDeviceIndex(device_index)->guid;
}
static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
return SDL_Unsupported();
SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
// clang-format off
bool result = MAIN_THREAD_EM_ASM_INT({
let gamepads = navigator['getGamepads']();
if (!gamepads) {
return 0;
}
let gamepad = gamepads[$0];
if (!gamepad || !gamepad['vibrationActuator']) {
return 0;
}
gamepad['vibrationActuator']['playEffect']('dual-rumble', {
'startDelay': 0,
'duration': 3000,
'weakMagnitude': $2 / 0xFFFF,
'strongMagnitude': $1 / 0xFFFF,
});
return 1;
}, item->index, low_frequency_rumble, high_frequency_rumble);
return result;
}
static bool EMSCRIPTEN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -34,12 +34,18 @@ typedef struct SDL_joylist_item
char *mapping;
SDL_JoystickID device_instance;
SDL_Joystick *joystick;
int first_hat_button;
int first_trigger_button;
bool triggers_are_buttons;
int nhats;
SDL_GUID guid;
int nbuttons;
int naxes;
double timestamp;
double axis[64];
double axis[64]; // !!! FIXME: don't hardcode 64 on all of these.
double analogButton[64];
EM_BOOL digitalButton[64];
Uint8 hat; // there is (currently) only ever one of these, faked from the d-pad buttons.
struct SDL_joylist_item *next;
} SDL_joylist_item;
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -24,6 +24,7 @@
#include "../SDL_sysjoystick.h"
#include "../usb_ids.h"
#include "../../core/windows/SDL_windows.h"
#include "../../core/windows/SDL_gameinput.h"
// Default value for SDL_HINT_JOYSTICK_GAMEINPUT
@@ -33,6 +34,11 @@
#define SDL_GAMEINPUT_DEFAULT false
#endif
// Enable sensor support in GameInput 2.0, once we have a device that can be used for testing
#if GAMEINPUT_API_VERSION >= 2
//#define GAMEINPUT_SENSOR_SUPPORT
#endif
enum
{
SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11
@@ -46,6 +52,7 @@ typedef struct GAMEINPUT_InternalDevice
SDL_GUID guid; // generated by SDL
SDL_JoystickID device_instance; // generated by SDL
const GameInputDeviceInfo *info;
int steam_virtual_gamepad_slot;
bool isAdded;
bool isDeleteRequested;
} GAMEINPUT_InternalDevice;
@@ -66,7 +73,7 @@ typedef struct joystick_hwdata
static GAMEINPUT_InternalList g_GameInputList = { NULL };
static IGameInput *g_pGameInput = NULL;
static GameInputCallbackToken g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
static GameInputCallbackToken g_GameInputCallbackToken = 0;
static Uint64 g_GameInputTimestampOffset;
static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info)
@@ -77,6 +84,18 @@ static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info)
return false;
}
#if GAMEINPUT_API_VERSION >= 1
static int GetSteamVirtualGamepadSlot(const char *device_path)
{
int slot = -1;
// The format for the raw input device path is documented here:
// https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices
(void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot);
return slot;
}
#endif // GAMEINPUT_API_VERSION >= 1
static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice)
{
GAMEINPUT_InternalDevice **devicelist = NULL;
@@ -93,15 +112,22 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice)
SDL_AssertJoysticksLocked();
info = IGameInputDevice_GetDeviceInfo(pDevice);
if (info->capabilities & GameInputDeviceCapabilityWireless) {
#if GAMEINPUT_API_VERSION >= 1
HRESULT hr = pDevice->GetDeviceInfo(&info);
if (FAILED(hr)) {
return WIN_SetErrorFromHRESULT("IGameInputDevice::GetDeviceInfo", hr);
}
#else
info = pDevice->GetDeviceInfo();
#endif
if (false /*info->capabilities & GameInputDeviceCapabilityWireless*/) {
bus = SDL_HARDWARE_BUS_BLUETOOTH;
} else {
bus = SDL_HARDWARE_BUS_USB;
}
vendor = info->vendorId;
product = info->productId;
version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor;
//version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor;
if (SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, "")) {
return true;
@@ -130,23 +156,30 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice)
// Generate a device path
for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) {
SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]);
SDL_strlcat(elem->path, tmp, SDL_arraysize(tmp));
}
if (info->deviceStrings) {
// In theory we could get the manufacturer and product strings here, but they're NULL for all the controllers I've tested
SDL_strlcat(elem->path, tmp, SDL_arraysize(elem->path));
}
#if GAMEINPUT_API_VERSION >= 1
if (info->displayName) {
// This could give us a product string, but it's NULL for all the controllers I've tested
product_string = info->displayName;
}
#else
if (info->displayName) {
product_string = info->displayName->data;
}
#endif
IGameInputDevice_AddRef(pDevice);
pDevice->AddRef();
elem->device = pDevice;
elem->name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, manufacturer_string, product_string, 'g', 0);
elem->device_instance = SDL_GetNextObjectID();
elem->info = info;
#if GAMEINPUT_API_VERSION >= 1
elem->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(info->pnpPath);
#else
elem->steam_virtual_gamepad_slot = -1;
#endif
g_GameInputList.devices = devicelist;
g_GameInputList.devices[g_GameInputList.count++] = elem;
@@ -168,7 +201,7 @@ static bool GAMEINPUT_InternalRemoveByIndex(int idx)
elem = g_GameInputList.devices[idx];
if (elem) {
IGameInputDevice_Release(elem->device);
elem->device->Release();
SDL_free(elem->name);
SDL_free(elem);
}
@@ -199,8 +232,8 @@ static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx)
static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback(
_In_ GameInputCallbackToken callbackToken,
_In_ void* context,
_In_ IGameInputDevice* device,
_In_ void *context,
_In_ IGameInputDevice *device,
_In_ uint64_t timestamp,
_In_ GameInputDeviceStatus currentStatus,
_In_ GameInputDeviceStatus previousStatus)
@@ -232,10 +265,11 @@ static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback(
}
static void GAMEINPUT_JoystickDetect(void);
static void GAMEINPUT_JoystickQuit(void);
static bool GAMEINPUT_JoystickInit(void)
{
HRESULT hR;
HRESULT hr;
if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) {
return true;
@@ -245,21 +279,27 @@ static bool GAMEINPUT_JoystickInit(void)
return false;
}
hR = IGameInput_RegisterDeviceCallback(g_pGameInput,
NULL,
#if GAMEINPUT_API_VERSION >= 2
// Allow background controller input
// SDL manages focus policy at a higher level, so we can set this unconditionally.
g_pGameInput->SetFocusPolicy(GameInputEnableBackgroundInput | GameInputEnableBackgroundGuideButton | GameInputEnableBackgroundShareButton);
#endif
hr = g_pGameInput->RegisterDeviceCallback(NULL,
GameInputKindController,
GameInputDeviceConnected,
GameInputBlockingEnumeration,
NULL,
GAMEINPUT_InternalJoystickDeviceCallback,
&g_GameInputCallbackToken);
if (FAILED(hR)) {
return SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08lX", hR);
if (FAILED(hr)) {
GAMEINPUT_JoystickQuit();
return WIN_SetErrorFromHRESULT("IGameInput::RegisterDeviceCallback", hr);
}
// Calculate the relative offset between SDL timestamps and GameInput timestamps
Uint64 now = SDL_GetTicksNS();
uint64_t timestampUS = IGameInput_GetCurrentTimestamp(g_pGameInput);
uint64_t timestampUS = g_pGameInput->GetCurrentTimestamp();
g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS);
GAMEINPUT_JoystickDetect();
@@ -292,7 +332,7 @@ static void GAMEINPUT_JoystickDetect(void)
elem->isAdded = true;
}
if (elem->isDeleteRequested || !(IGameInputDevice_GetDeviceStatus(elem->device) & GameInputDeviceConnected)) {
if (elem->isDeleteRequested || !(elem->device->GetDeviceStatus() & GameInputDeviceConnected)) {
SDL_PrivateJoystickRemoved(elem->device_instance);
GAMEINPUT_InternalRemoveByIndex(idx--);
}
@@ -333,7 +373,7 @@ static const char *GAMEINPUT_JoystickGetDevicePath(int device_index)
static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
{
return -1;
return GAMEINPUT_InternalFindByIndex(device_index)->steam_virtual_gamepad_slot;
}
static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index)
@@ -357,6 +397,7 @@ static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index)
static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device)
{
#if 0
GameInputBatteryState battery_state;
SDL_PowerState state;
int percent = 0;
@@ -385,10 +426,10 @@ static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *
percent = (int)SDL_roundf((battery_state.remainingCapacity / battery_state.fullChargeCapacity) * 100.0f);
}
SDL_SendJoystickPowerInfo(joystick, state, percent);
#endif
}
#ifdef IGameInput_RegisterSystemButtonCallback
#if GAMEINPUT_API_VERSION >= 1
static void CALLBACK GAMEINPUT_InternalSystemButtonCallback(
_In_ GameInputCallbackToken callbackToken,
_In_ void * context,
@@ -415,8 +456,7 @@ static void CALLBACK GAMEINPUT_InternalSystemButtonCallback(
SDL_UnlockJoysticks();
}
}
#endif // IGameInput_RegisterSystemButtonCallback
#endif // GAMEINPUT_API_VERSION >= 1
static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
{
@@ -441,19 +481,15 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
joystick->nbuttons = 11;
joystick->nhats = 1;
#ifdef IGameInput_RegisterSystemButtonCallback
#if GAMEINPUT_API_VERSION >= 1
if (info->supportedSystemButtons != GameInputSystemButtonNone) {
if (info->supportedSystemButtons & GameInputSystemButtonShare) {
++joystick->nbuttons;
}
#if 1 // The C macro in GameInput.h version 10.0.26100 refers to a focus policy which I guess has been removed from the final API?
#undef IGameInput_RegisterSystemButtonCallback
#define IGameInput_RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken) ((This)->lpVtbl->RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken))
#endif
IGameInput_RegisterSystemButtonCallback(g_pGameInput, elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token);
g_pGameInput->RegisterSystemButtonCallback(elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token);
}
#endif // IGameInput_RegisterSystemButtonCallback
#endif // GAMEINPUT_API_VERSION >= 1
} else {
joystick->naxes = info->controllerAxisCount;
joystick->nbuttons = info->controllerButtonCount;
@@ -467,21 +503,17 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
}
if (info->supportedInput & GameInputKindTouch) {
SDL_PrivateJoystickAddTouchpad(joystick, info->touchPointCount);
}
if (info->supportedInput & GameInputKindMotion) {
#ifdef GAMEINPUT_SENSOR_SUPPORT
if (info->supportedInput & GameInputKindSensors) {
// FIXME: What's the sensor update rate?
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
}
if (info->capabilities & GameInputDeviceCapabilityWireless) {
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
} else {
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
if (info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) {
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
}
if (info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) {
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
}
}
#endif
return true;
}
@@ -492,7 +524,7 @@ static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequenc
GameInputRumbleParams *params = &hwdata->rumbleParams;
params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16;
params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16;
IGameInputDevice_SetRumbleState(hwdata->devref->device, params);
hwdata->devref->device->SetRumbleState(params);
return true;
}
@@ -503,7 +535,7 @@ static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left
GameInputRumbleParams *params = &hwdata->rumbleParams;
params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16;
params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16;
IGameInputDevice_SetRumbleState(hwdata->devref->device, params);
hwdata->devref->device->SetRumbleState(params);
return true;
}
@@ -531,15 +563,15 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
IGameInputReading *reading = NULL;
Uint64 timestamp;
GameInputGamepadState state;
HRESULT hR;
HRESULT hr;
hR = IGameInput_GetCurrentReading(g_pGameInput, info->supportedInput, device, &reading);
if (FAILED(hR)) {
hr = g_pGameInput->GetCurrentReading(info->supportedInput, device, &reading);
if (FAILED(hr)) {
// don't SetError here since there can be a legitimate case when there's no reading avail
return;
}
timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + g_GameInputTimestampOffset);
timestamp = SDL_US_TO_NS(reading->GetTimestamp() + g_GameInputTimestampOffset);
if (GAMEINPUT_InternalIsGamepad(info)) {
static WORD s_XInputButtons[] = {
@@ -557,7 +589,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
};
Uint8 btnidx = 0, hat = 0;
if (IGameInputReading_GetGamepadState(reading, &state)) {
if (reading->GetGamepadState(&state)) {
for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) {
WORD button_mask = s_XInputButtons[btnidx];
if (!button_mask) {
@@ -599,7 +631,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
if (button_state) {
uint32_t i;
uint32_t button_count = IGameInputReading_GetControllerButtonState(reading, info->controllerButtonCount, button_state);
uint32_t button_count = reading->GetControllerButtonState(info->controllerButtonCount, button_state);
for (i = 0; i < button_count; ++i) {
SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]);
}
@@ -609,7 +641,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
#define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f)
if (axis_state) {
uint32_t i;
uint32_t axis_count = IGameInputReading_GetControllerAxisState(reading, info->controllerAxisCount, axis_state);
uint32_t axis_count = reading->GetControllerAxisState(info->controllerAxisCount, axis_state);
for (i = 0; i < axis_count; ++i) {
SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i]));
}
@@ -619,7 +651,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
if (switch_state) {
uint32_t i;
uint32_t switch_count = IGameInputReading_GetControllerSwitchState(reading, info->controllerSwitchCount, switch_state);
uint32_t switch_count = reading->GetControllerSwitchState(info->controllerSwitchCount, switch_state);
for (i = 0; i < switch_count; ++i) {
Uint8 hat;
switch (switch_state[i]) {
@@ -658,40 +690,47 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
}
}
if (info->supportedInput & GameInputKindTouch) {
GameInputTouchState *touch_state = SDL_stack_alloc(GameInputTouchState, info->touchPointCount);
if (touch_state) {
uint32_t i;
uint32_t touch_count = IGameInputReading_GetTouchState(reading, info->touchPointCount, touch_state);
for (i = 0; i < touch_count; ++i) {
GameInputTouchState *touch = &touch_state[i];
// FIXME: We should use touch->touchId to track fingers instead of using i below
SDL_SendJoystickTouchpad(timestamp, joystick, 0, i, true, touch->positionX * info->touchSensorInfo[i].resolutionX, touch->positionY * info->touchSensorInfo[0].resolutionY, touch->pressure);
}
SDL_stack_free(touch_state);
}
}
#ifdef GAMEINPUT_SENSOR_SUPPORT
if (hwdata->report_sensors) {
GameInputMotionState motion_state;
GameInputSensorsState sensor_state;
if (IGameInputReading_GetMotionState(reading, &motion_state)) {
// FIXME: How do we interpret the motion data?
if (reading->GetSensorsState(&sensor_state)) {
if ((info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) != 0) {
float data[3] = {
sensor_state.angularVelocityInRadPerSecX,
sensor_state.angularVelocityInRadPerSecY,
sensor_state.angularVelocityInRadPerSecZ
};
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, SDL_arraysize(data));
}
if ((info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) != 0) {
float data[3] = {
sensor_state.accelerationInGX * SDL_STANDARD_GRAVITY,
sensor_state.accelerationInGY * SDL_STANDARD_GRAVITY,
sensor_state.accelerationInGZ * SDL_STANDARD_GRAVITY
};
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, SDL_arraysize(data));
}
}
}
#endif // GAMEINPUT_SENSOR_SUPPORT
IGameInputReading_Release(reading);
reading->Release();
// FIXME: We can poll this at a much lower rate
GAMEINPUT_UpdatePowerInfo(joystick, device);
}
static void GAMEINPUT_JoystickClose(SDL_Joystick* joystick)
static void GAMEINPUT_JoystickClose(SDL_Joystick *joystick)
{
GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
if (hwdata->system_button_callback_token) {
IGameInput_UnregisterCallback(g_pGameInput, hwdata->system_button_callback_token, 5000);
#if GAMEINPUT_API_VERSION >= 1
g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token);
#else
g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token, 10000);
#endif
}
SDL_free(hwdata);
@@ -702,8 +741,14 @@ static void GAMEINPUT_JoystickQuit(void)
{
if (g_pGameInput) {
// free the callback
IGameInput_UnregisterCallback(g_pGameInput, g_GameInputCallbackToken, /*timeoutInUs:*/ 10000);
g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
if (g_GameInputCallbackToken) {
#if GAMEINPUT_API_VERSION >= 1
g_pGameInput->UnregisterCallback(g_GameInputCallbackToken);
#else
g_pGameInput->UnregisterCallback(g_GameInputCallbackToken, 10000);
#endif
g_GameInputCallbackToken = 0;
}
// free the list
while (g_GameInputList.count > 0) {
@@ -738,7 +783,7 @@ static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap
out->back.kind = EMappingKind_Button;
out->back.target = SDL_GAMEPAD_BUTTON_BACK;
#ifdef IGameInput_RegisterSystemButtonCallback
#if GAMEINPUT_API_VERSION >= 1
if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) {
out->guide.kind = EMappingKind_Button;
out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE;
@@ -748,7 +793,7 @@ static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap
out->misc1.kind = EMappingKind_Button;
out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE;
}
#endif
#endif // GAMEINPUT_API_VERSION >= 1
out->start.kind = EMappingKind_Button;
out->start.target = SDL_GAMEPAD_BUTTON_START;
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -0,0 +1,711 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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"
#ifdef SDL_JOYSTICK_HIDAPI_8BITDO
// Define this if you want to log all packets from the controller
#if 0
#define DEBUG_8BITDO_PROTOCOL
#endif
enum
{
SDL_GAMEPAD_BUTTON_8BITDO_L4 = 11,
SDL_GAMEPAD_BUTTON_8BITDO_R4,
SDL_GAMEPAD_BUTTON_8BITDO_PL,
SDL_GAMEPAD_BUTTON_8BITDO_PR,
SDL_GAMEPAD_NUM_8BITDO_BUTTONS,
};
#define SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID 0x06
#define SDL_8BITDO_REPORTID_SDL_REPORTID 0x04
#define SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID 0x03
#define SDL_8BITDO_BT_REPORTID_SDL_REPORTID 0x01
#define SDL_8BITDO_SENSOR_TIMESTAMP_ENABLE 0xAA
#define ABITDO_ACCEL_SCALE 4096.f
#define ABITDO_GYRO_MAX_DEGREES_PER_SECOND 2000.f
#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \
(((Uint32)(B)) << 8) | \
(((Uint32)(C)) << 16) | \
(((Uint32)(D)) << 24))
typedef struct
{
bool sensors_supported;
bool sensors_enabled;
bool touchpad_01_supported;
bool touchpad_02_supported;
bool rumble_supported;
bool rumble_type;
bool rgb_supported;
bool player_led_supported;
bool powerstate_supported;
bool sensor_timestamp_supported;
Uint8 serial[6];
Uint16 version;
Uint16 version_beta;
float accelScale;
float gyroScale;
Uint8 last_state[USB_PACKET_LENGTH];
Uint64 sensor_timestamp; // Nanoseconds. Simulate onboard clock. Different models have different rates vs different connection styles.
Uint64 sensor_timestamp_interval;
Uint32 last_tick;
} SDL_Driver8BitDo_Context;
#pragma pack(push,1)
typedef struct
{
bool sensors_supported;
bool touchpad_01_supported;
bool touchpad_02_supported;
bool rumble_supported;
bool rumble_type;
bool rgb_supported;
Uint8 device_type;
Uint8 serial[6];
Uint16 version;
Uint16 version_beta;
Uint16 pid;
} ABITDO_DEVICE_INFO;
typedef struct
{
// Accelerometer values
short sAccelX;
short sAccelY;
short sAccelZ;
// Gyroscope values
short sGyroX;
short sGyroY;
short sGyroZ;
} ABITDO_SENSORS;
#pragma pack(pop)
static void HIDAPI_Driver8BitDo_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata);
}
static void HIDAPI_Driver8BitDo_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata);
}
static bool HIDAPI_Driver8BitDo_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
}
static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
{
SDL_memset(report, 0, length);
report[0] = report_id;
return SDL_hid_get_feature_report(dev, report, length);
}
static bool HIDAPI_Driver8BitDo_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)
{
if (vendor_id == USB_VENDOR_8BITDO) {
switch (product_id) {
case USB_PRODUCT_8BITDO_SF30_PRO:
case USB_PRODUCT_8BITDO_SF30_PRO_BT:
case USB_PRODUCT_8BITDO_SN30_PRO:
case USB_PRODUCT_8BITDO_SN30_PRO_BT:
case USB_PRODUCT_8BITDO_PRO_2:
case USB_PRODUCT_8BITDO_PRO_2_BT:
case USB_PRODUCT_8BITDO_PRO_3:
case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS:
return true;
default:
break;
}
}
return false;
}
static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)SDL_calloc(1, sizeof(*ctx));
if (!ctx) {
return false;
}
device->context = ctx;
if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) {
// The Ultimate 2 Wireless v1.02 firmware has 12 byte reports, v1.03 firmware has 34 byte reports
const int ULTIMATE2_WIRELESS_V103_REPORT_SIZE = 34;
const int MAX_ATTEMPTS = 3;
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
Uint8 data[USB_PACKET_LENGTH];
int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80);
if (size == 0) {
// Try again
continue;
}
if (size >= ULTIMATE2_WIRELESS_V103_REPORT_SIZE) {
ctx->sensors_supported = true;
ctx->rumble_supported = true;
ctx->powerstate_supported = true;
}
break;
}
} else {
Uint8 data[USB_PACKET_LENGTH];
const int MAX_ATTEMPTS = 5;
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID, data, sizeof(data));
if (size > 0) {
#ifdef DEBUG_8BITDO_PROTOCOL
HIDAPI_DumpPacket("8BitDo features packet: size = %d", data, size);
#endif
ctx->sensors_supported = true;
ctx->rumble_supported = true;
ctx->powerstate_supported = true;
if (size >= 14 && data[13] == SDL_8BITDO_SENSOR_TIMESTAMP_ENABLE) {
ctx->sensor_timestamp_supported = true;
}
// Set the serial number to the Bluetooth MAC address
if (size >= 12 && data[10] != 0) {
char serial[18];
(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
data[10], data[9], data[8], data[7], data[6], data[5]);
HIDAPI_SetDeviceSerial(device, serial);
}
break;
}
// Try again
SDL_Delay(10);
}
}
if (device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_SF30_PRO_BT) {
HIDAPI_SetDeviceName(device, "8BitDo SF30 Pro");
} else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT) {
HIDAPI_SetDeviceName(device, "8BitDo SN30 Pro");
} else if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) {
HIDAPI_SetDeviceName(device, "8BitDo Pro 2");
} else if (device->product_id == USB_PRODUCT_8BITDO_PRO_3) {
HIDAPI_SetDeviceName(device, "8BitDo Pro 3");
}
return HIDAPI_JoystickConnected(device, NULL);
}
static int HIDAPI_Driver8BitDo_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_Driver8BitDo_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static Uint64 HIDAPI_Driver8BitDo_GetIMURateForProductID(SDL_HIDAPI_Device *device)
{
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
// TODO: If sensor time stamp is sent, these fixed settings from observation can be replaced
switch (device->product_id) {
case USB_PRODUCT_8BITDO_SF30_PRO:
case USB_PRODUCT_8BITDO_SF30_PRO_BT:
case USB_PRODUCT_8BITDO_SN30_PRO:
case USB_PRODUCT_8BITDO_SN30_PRO_BT:
if (device->is_bluetooth) {
// Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool
return 70; // Observed to be anywhere between 60-90 hz. Possibly lossy in current state
} else if (ctx->sensor_timestamp_supported) {
// This firmware appears to update at 200 Hz over USB
return 200;
} else {
// This firmware appears to update at 100 Hz over USB
return 100;
}
case USB_PRODUCT_8BITDO_PRO_2:
case USB_PRODUCT_8BITDO_PRO_2_BT: // Note, labeled as "BT" but appears this way when wired.
case USB_PRODUCT_8BITDO_PRO_3:
if (device->is_bluetooth) {
// Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool
return 85; // Observed Bluetooth packet rate seems to be 80-90hz
} else if (ctx->sensor_timestamp_supported) {
// This firmware appears to update at 200 Hz over USB
return 200;
} else {
// This firmware appears to update at 100 Hz over USB
return 100;
}
case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS:
if (device->is_bluetooth) {
// Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool
return 120; // Observed Bluetooth packet rate seems to be 120hz
} else {
// This firmware appears to update at 1000 Hz over USB dongle
return 1000;
}
default:
return 120;
}
}
#ifndef DEG2RAD
#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
#endif
static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
SDL_AssertJoysticksLocked();
SDL_zeroa(ctx->last_state);
// Initialize the joystick capabilities
if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 ||
device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT ||
device->product_id == USB_PRODUCT_8BITDO_PRO_3 ||
device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) {
// This controller has additional buttons
joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS;
} else {
joystick->nbuttons = 11;
}
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
if (ctx->sensors_supported) {
// Different 8Bitdo controllers in different connection modes have different polling rates.
const Uint64 imu_polling_rate = HIDAPI_Driver8BitDo_GetIMURateForProductID(device);
ctx->sensor_timestamp_interval = SDL_NS_PER_SECOND / imu_polling_rate;
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, (float)imu_polling_rate);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, (float)imu_polling_rate);
ctx->accelScale = SDL_STANDARD_GRAVITY / ABITDO_ACCEL_SCALE;
// Hardware senses +/- N Degrees per second mapped to +/- INT16_MAX
ctx->gyroScale = DEG2RAD(ABITDO_GYRO_MAX_DEGREES_PER_SECOND) / INT16_MAX;
}
return true;
}
static bool HIDAPI_Driver8BitDo_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
if (ctx->rumble_supported) {
Uint8 rumble_packet[5] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
rumble_packet[1] = low_frequency_rumble >> 8;
rumble_packet[2] = 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;
} else {
return SDL_Unsupported();
}
}
static bool HIDAPI_Driver8BitDo_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_Driver8BitDo_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
Uint32 caps = 0;
if (ctx->rumble_supported) {
caps |= SDL_JOYSTICK_CAP_RUMBLE;
}
if (ctx->rgb_supported) {
caps |= SDL_JOYSTICK_CAP_RGB_LED;
}
return caps;
}
static bool HIDAPI_Driver8BitDo_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static bool HIDAPI_Driver8BitDo_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static bool HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
{
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context;
if (ctx->sensors_supported) {
ctx->sensors_enabled = enabled;
return true;
}
return SDL_Unsupported();
}
static void HIDAPI_Driver8BitDo_HandleOldStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[2] != data[2]) {
Uint8 hat;
switch (data[2]) {
case 0:
hat = SDL_HAT_UP;
break;
case 1:
hat = SDL_HAT_RIGHTUP;
break;
case 2:
hat = SDL_HAT_RIGHT;
break;
case 3:
hat = SDL_HAT_RIGHTDOWN;
break;
case 4:
hat = SDL_HAT_DOWN;
break;
case 5:
hat = SDL_HAT_LEFTDOWN;
break;
case 6:
hat = SDL_HAT_LEFT;
break;
case 7:
hat = SDL_HAT_LEFTUP;
break;
default:
hat = SDL_HAT_CENTERED;
break;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
}
if (ctx->last_state[0] != data[0]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x01) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x40) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x80) != 0));
}
if (ctx->last_state[1] != data[1]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x04) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x20) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x40) != 0));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[1] & 0x01) ? SDL_MAX_SINT16 : SDL_MIN_SINT16);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[1] & 0x02) ? SDL_MAX_SINT16 : SDL_MIN_SINT16);
}
#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(3);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = READ_STICK_AXIS(4);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = READ_STICK_AXIS(5);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = READ_STICK_AXIS(6);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
}
#undef READ_STICK_AXIS
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS();
switch (data[0]) {
case SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID: // Firmware without enhanced mode
case SDL_8BITDO_REPORTID_SDL_REPORTID: // Enhanced mode USB report
case SDL_8BITDO_BT_REPORTID_SDL_REPORTID: // Enhanced mode Bluetooth report
break;
default:
// We don't know how to handle this report
return;
}
if (ctx->last_state[1] != data[1]) {
Uint8 hat;
switch (data[1]) {
case 0:
hat = SDL_HAT_UP;
break;
case 1:
hat = SDL_HAT_RIGHTUP;
break;
case 2:
hat = SDL_HAT_RIGHT;
break;
case 3:
hat = SDL_HAT_RIGHTDOWN;
break;
case 4:
hat = SDL_HAT_DOWN;
break;
case 5:
hat = SDL_HAT_LEFTDOWN;
break;
case 6:
hat = SDL_HAT_LEFT;
break;
case 7:
hat = SDL_HAT_LEFTUP;
break;
default:
hat = SDL_HAT_CENTERED;
break;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
}
if (ctx->last_state[8] != data[8]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[8] & 0x01) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[8] & 0x02) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[8] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[8] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[8] & 0x40) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[8] & 0x80) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PL, ((data[8] & 0x20) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PR, ((data[8] & 0x04) != 0));
}
if (ctx->last_state[9] != data[9]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[9] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[9] & 0x04) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[9] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[9] & 0x20) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0));
}
if (size > 10 && ctx->last_state[10] != data[10]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_L4, ((data[10] & 0x01) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_R4, ((data[10] & 0x02) != 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(2);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = READ_STICK_AXIS(3);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = READ_STICK_AXIS(4);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = READ_STICK_AXIS(5);
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(7);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = READ_TRIGGER_AXIS(6);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
}
#undef READ_TRIGGER_AXIS
if (ctx->powerstate_supported) {
SDL_PowerState state;
int percent;
Uint8 status = data[14] >> 7;
Uint8 level = (data[14] & 0x7f);
if (level == 100) {
status = 2;
}
switch (status) {
case 0:
state = SDL_POWERSTATE_ON_BATTERY;
percent = level;
break;
case 1:
state = SDL_POWERSTATE_CHARGING;
percent = level;
break;
case 2:
state = SDL_POWERSTATE_CHARGED;
percent = 100;
break;
default:
state = SDL_POWERSTATE_UNKNOWN;
percent = 0;
break;
}
SDL_SendJoystickPowerInfo(joystick, state, percent);
}
if (ctx->sensors_enabled) {
Uint64 sensor_timestamp;
float values[3];
ABITDO_SENSORS *sensors = (ABITDO_SENSORS *)&data[15];
if (ctx->sensor_timestamp_supported) {
Uint32 delta;
Uint32 tick = LOAD32(data[27], data[28], data[29], data[30]);
if (ctx->last_tick) {
if (ctx->last_tick < tick) {
delta = (tick - ctx->last_tick);
} else {
delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1);
}
// Sanity check the delta value
if (delta < 100000) {
ctx->sensor_timestamp_interval = SDL_US_TO_NS(delta);
}
}
ctx->last_tick = tick;
}
// Note: we cannot use the time stamp of the receiving computer due to packet delay creating "spiky" timings.
// The imu time stamp is intended to be the sample time of the on-board hardware.
// In the absence of time stamp data from the data[], we can simulate that by
// advancing a time stamp by the observed/known imu clock rate. This is 8ms = 125 Hz
sensor_timestamp = ctx->sensor_timestamp;
ctx->sensor_timestamp += ctx->sensor_timestamp_interval;
// This device's IMU values are reported differently from SDL
// Thus we perform a rotation of the coordinate system to match the SDL standard.
// By observation of this device:
// Hardware x is reporting roll (rotation about the power jack's axis)
// Hardware y is reporting pitch (rotation about the horizontal axis)
// Hardware z is reporting yaw (rotation about the joysticks' center axis)
values[0] = -sensors->sGyroY * ctx->gyroScale; // Rotation around pitch axis
values[1] = sensors->sGyroZ * ctx->gyroScale; // Rotation around yaw axis
values[2] = -sensors->sGyroX * ctx->gyroScale; // Rotation around roll axis
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3);
// By observation of this device:
// Accelerometer X is positive when front of the controller points toward the sky.
// Accelerometer y is positive when left side of the controller points toward the sky.
// Accelerometer Z is positive when sticks point toward the sky.
values[0] = -sensors->sAccelY * ctx->accelScale; // Acceleration along pitch axis
values[1] = sensors->sAccelZ * ctx->accelScale; // Acceleration along yaw axis
values[2] = -sensors->sAccelX * 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_Driver8BitDo_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_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_8BITDO_PROTOCOL
HIDAPI_DumpPacket("8BitDo packet: size = %d", data, size);
#endif
if (!joystick) {
continue;
}
if (size == 9) {
// Old firmware USB report for the SF30 Pro and SN30 Pro controllers
HIDAPI_Driver8BitDo_HandleOldStatePacket(joystick, ctx, data, size);
} else {
HIDAPI_Driver8BitDo_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_Driver8BitDo_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
}
static void HIDAPI_Driver8BitDo_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo = {
SDL_HINT_JOYSTICK_HIDAPI_8BITDO,
true,
HIDAPI_Driver8BitDo_RegisterHints,
HIDAPI_Driver8BitDo_UnregisterHints,
HIDAPI_Driver8BitDo_IsEnabled,
HIDAPI_Driver8BitDo_IsSupportedDevice,
HIDAPI_Driver8BitDo_InitDevice,
HIDAPI_Driver8BitDo_GetDevicePlayerIndex,
HIDAPI_Driver8BitDo_SetDevicePlayerIndex,
HIDAPI_Driver8BitDo_UpdateDevice,
HIDAPI_Driver8BitDo_OpenJoystick,
HIDAPI_Driver8BitDo_RumbleJoystick,
HIDAPI_Driver8BitDo_RumbleJoystickTriggers,
HIDAPI_Driver8BitDo_GetJoystickCapabilities,
HIDAPI_Driver8BitDo_SetJoystickLED,
HIDAPI_Driver8BitDo_SendJoystickEffect,
HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled,
HIDAPI_Driver8BitDo_CloseJoystick,
HIDAPI_Driver8BitDo_FreeDevice,
};
#endif // SDL_JOYSTICK_HIDAPI_8BITDO
#endif // SDL_JOYSTICK_HIDAPI
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -76,9 +76,7 @@ static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo
child = device->children[i];
child->driver->CloseJoystick(child, joystick);
}
if (serial) {
SDL_free(serial);
}
SDL_free(serial);
return false;
}
@@ -102,9 +100,7 @@ static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo
}
// Update the joystick with the combined serial numbers
if (joystick->serial) {
SDL_free(joystick->serial);
}
SDL_free(joystick->serial);
joystick->serial = serial;
return true;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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.
*/
// These are values used in the controller type byte of the controller GUID
typedef enum
{
SDL_FLYDIGI_UNKNOWN,
SDL_FLYDIGI_APEX2 = (1 << 0),
SDL_FLYDIGI_APEX3,
SDL_FLYDIGI_APEX4,
SDL_FLYDIGI_APEX5,
SDL_FLYDIGI_VADER2 = (1 << 4),
SDL_FLYDIGI_VADER2_PRO,
SDL_FLYDIGI_VADER3,
SDL_FLYDIGI_VADER3_PRO,
SDL_FLYDIGI_VADER4_PRO,
SDL_FLYDIGI_VADER5_PRO,
} SDL_FlyDigiControllerType;
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -23,6 +23,7 @@
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../SDL_hints_c.h"
#include "../../misc/SDL_libusb.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
@@ -76,7 +77,8 @@ static bool HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *device, c
}
if (vendor_id == USB_VENDOR_DRAGONRISE &&
(product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 ||
product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2)) {
product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 ||
product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3)) {
// EVORETRO GameCube Controller Adapter
return true;
}
@@ -101,6 +103,83 @@ static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, c
}
}
static bool HIDAPI_DriverGameCube_EnableAdapter(SDL_HIDAPI_Device *device)
{
#ifdef HAVE_LIBUSB
// Need to close the device while sending USB commands to it
SDL_hid_close(device->dev);
// This is needed to enable input for Nyko and EVORETRO GameCube adapters
SDL_LibUSBContext *libusb_ctx;
if (SDL_InitLibUSB(&libusb_ctx)) {
libusb_context *context = NULL;
libusb_device **devs = NULL;
libusb_device_handle *handle = NULL;
struct libusb_device_descriptor desc;
ssize_t i, num_devs;
bool kernel_detached = false;
if (libusb_ctx->init(&context) == 0) {
num_devs = libusb_ctx->get_device_list(context, &devs);
for (i = 0; i < num_devs; ++i) {
if (libusb_ctx->get_device_descriptor(devs[i], &desc) != 0) {
continue;
}
if (desc.idVendor != USB_VENDOR_NINTENDO ||
desc.idProduct != USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
continue;
}
if (libusb_ctx->open(devs[i], &handle) != 0) {
continue;
}
if (libusb_ctx->kernel_driver_active(handle, 0)) {
if (libusb_ctx->detach_kernel_driver(handle, 0) == 0) {
kernel_detached = true;
}
}
if (libusb_ctx->claim_interface(handle, 0) == 0) {
libusb_ctx->control_transfer(handle, 0x21, 11, 0x0001, 0, NULL, 0, 1000);
libusb_ctx->release_interface(handle, 0);
}
if (kernel_detached) {
libusb_ctx->attach_kernel_driver(handle, 0);
}
libusb_ctx->close(handle);
}
libusb_ctx->free_device_list(devs, 1);
libusb_ctx->exit(context);
}
SDL_QuitLibUSB();
}
// Reopen the device now that we're done
device->dev = SDL_hid_open_path(device->path);
if (!device->dev) {
return false;
}
#endif // HAVE_LIBUSB
Uint8 initMagic = 0x13;
if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028");
return false;
}
// Wait for the adapter to initialize
SDL_Delay(10);
return true;
}
static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverGameCube_Context *ctx;
@@ -108,13 +187,8 @@ static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
Uint8 *curSlot;
Uint8 i;
int size;
Uint8 initMagic = 0x13;
Uint8 rumbleMagic = 0x11;
#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS
SDL_EnableGameCubeAdaptors();
#endif
ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
if (!ctx) {
return false;
@@ -128,19 +202,21 @@ static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
}
if (ctx->pc_mode) {
#ifdef SDL_PLATFORM_WIN32
// We get a separate device for each slot
ResetAxisRange(ctx, 0);
HIDAPI_JoystickConnected(device, &ctx->joysticks[0]);
#else
for (i = 0; i < MAX_CONTROLLERS; ++i) {
ResetAxisRange(ctx, i);
HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
}
#endif
} else {
// This is all that's needed to initialize the device. Really!
if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028");
if (!HIDAPI_DriverGameCube_EnableAdapter(device)) {
return false;
}
// Wait for the adapter to initialize
SDL_Delay(10);
// Add all the applicable joysticks
while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
#ifdef DEBUG_GAMECUBE_PROTOCOL
@@ -199,14 +275,25 @@ static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device
{
}
static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, bool invert_c_stick)
static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 slot, const Uint8 *packet, bool invert_c_stick)
{
SDL_Joystick *joystick;
const Uint8 i = 0; // We have a separate context for each connected controller in PC mode, just use the first index
Uint8 i;
Uint8 v;
Sint16 axis_value;
Uint64 timestamp = SDL_GetTicksNS();
#ifdef SDL_PLATFORM_WIN32
// We get a separate device for each slot
i = 0;
#else
i = slot;
if (i >= MAX_CONTROLLERS) {
// Invalid packet?
return;
}
#endif
joystick = SDL_GetJoystickFromID(ctx->joysticks[i]);
if (!joystick) {
// Hasn't been opened yet, skip
@@ -356,11 +443,11 @@ static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device)
// This is the older firmware
// The first byte is the index of the connected controller
// The C stick has an inverted value range compared to the left stick
HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, &packet[1], true);
HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet[0] - 1, &packet[1], true);
} else if (size == 9) {
// This is the newer firmware (version 0x7)
// The C stick has the same value range compared to the left stick
HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, false);
HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, 0, packet, false);
} else {
// How do we handle this packet?
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -32,8 +32,6 @@
// Define this if you want to log all packets from the controller
// #define DEBUG_PS3_PROTOCOL
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
typedef enum
{
k_EPS3ReportIdState = 1,
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -44,12 +44,6 @@
#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \
(((Uint32)(B)) << 8) | \
(((Uint32)(C)) << 16) | \
(((Uint32)(D)) << 24))
enum
{
SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD = 11
+46 -28
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -43,12 +43,6 @@
#define ACCEL_RES_PER_G 8192.0f
#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \
(((Uint32)(B)) << 8) | \
(((Uint32)(C)) << 16) | \
(((Uint32)(D)) << 24))
enum
{
SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD = 11,
@@ -230,7 +224,7 @@ typedef struct
{
SDL_HIDAPI_Device *device;
SDL_Joystick *joystick;
bool is_nacon_dongle;
bool is_dongle;
bool use_alternate_report;
bool sensors_supported;
bool lightbar_supported;
@@ -251,6 +245,7 @@ typedef struct
Uint64 last_packet;
int player_index;
bool player_lights;
bool enhanced_rumble;
Uint8 rumble_left;
Uint8 rumble_right;
bool color_set;
@@ -432,6 +427,14 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)
}
}
if (device->vendor_id == USB_VENDOR_SONY) {
if (device->product_id == USB_PRODUCT_SONY_DS5_EDGE ||
ctx->firmware_version == 0 || // Assume that it's updated firmware over Bluetooth
ctx->firmware_version >= 0x0224) {
ctx->enhanced_rumble = true;
}
}
// Get the device capabilities
if (device->vendor_id == USB_VENDOR_SONY) {
ctx->sensors_supported = true;
@@ -506,8 +509,10 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)
ctx->touchpad_supported = true;
ctx->use_alternate_report = true;
} else if (device->vendor_id == USB_VENDOR_RAZER &&
device->product_id == USB_PRODUCT_RAZER_KITSUNE) {
// The Razer Kitsune doesn't respond to the detection protocol, but has a touchpad
(device->product_id == USB_PRODUCT_RAZER_KITSUNE ||
device->product_id == USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRED ||
device->product_id == USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRELESS)) {
// The Razer Kitsune and Raiju don't respond to the detection protocol, but have a touchpad
joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
ctx->touchpad_supported = true;
ctx->use_alternate_report = true;
@@ -515,9 +520,13 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)
}
ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported);
if (device->vendor_id == USB_VENDOR_NACON_ALT &&
device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) {
ctx->is_nacon_dongle = true;
if ((device->vendor_id == USB_VENDOR_NACON_ALT &&
device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) ||
(device->vendor_id == USB_VENDOR_RAZER &&
(device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS ||
device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS ||
device->product_id == USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRELESS))) {
ctx->is_dongle = true;
}
device->joystick_type = joystick_type;
@@ -531,7 +540,7 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)
}
HIDAPI_SetDeviceSerial(device, serial);
if (ctx->is_nacon_dongle) {
if (ctx->is_dongle) {
// We don't know if this is connected yet, wait for reports
return true;
}
@@ -684,17 +693,17 @@ static bool HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effec
if (ctx->vibration_supported) {
if (ctx->rumble_left || ctx->rumble_right) {
if (ctx->firmware_version < 0x0224) {
if (ctx->enhanced_rumble) {
effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer
effects.ucRumbleLeft = ctx->rumble_left;
effects.ucRumbleRight = ctx->rumble_right;
} else {
effects.ucEnableBits1 |= 0x01; // Enable rumble emulation
// Shift to reduce effective rumble strength to match Xbox controllers
effects.ucRumbleLeft = ctx->rumble_left >> 1;
effects.ucRumbleRight = ctx->rumble_right >> 1;
} else {
effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer
effects.ucRumbleLeft = ctx->rumble_left;
effects.ucRumbleRight = ctx->rumble_right;
}
effects.ucEnableBits1 |= 0x02; // Disable audio haptics
} else {
@@ -814,7 +823,7 @@ static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx
if (ctx->sensors_supported) {
// Standard DualSense sensor update rate is 250 Hz over USB
float update_rate = 250.0f;
if (ctx->device->is_bluetooth) {
// Bluetooth sensor update rate appears to be 1000 Hz
update_rate = 1000.0f;
@@ -955,6 +964,10 @@ static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic
joystick->nhats = 1;
joystick->firmware_version = ctx->firmware_version;
if (ctx->is_dongle) {
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
}
SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
SDL_PS5EnhancedReportsChanged, ctx);
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
@@ -1038,6 +1051,9 @@ static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *c
if (!ctx->enhanced_mode) {
if (application_usage) {
HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);
// Wait briefly before sending additional effects
SDL_Delay(10);
}
if (!ctx->enhanced_mode) {
@@ -1050,10 +1066,11 @@ static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *c
if (ctx->device->is_bluetooth) {
data[0] = k_EPS5ReportIdBluetoothEffects;
data[1] = 0x02; // Magic value
data[1] = 0x00; // Tag and sequence
data[2] = 0x10; // Magic value
report_size = 78;
offset = 2;
offset = 3;
} else {
data[0] = k_EPS5ReportIdUsbEffects;
@@ -1452,15 +1469,16 @@ static bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *da
{
switch (data[0]) {
case k_EPS5ReportIdState:
if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) {
if (ctx->is_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) {
// The report timestamp doesn't change when the controller isn't connected
PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1];
if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) {
return false;
}
if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 &&
ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 &&
ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) {
if (ctx->last_state.state.rgucPacketSequence[0] == 0 &&
ctx->last_state.state.rgucPacketSequence[1] == 0 &&
ctx->last_state.state.rgucPacketSequence[2] == 0 &&
ctx->last_state.state.rgucPacketSequence[3] == 0) {
// We don't have any state to compare yet, go ahead and copy it
SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t));
return false;
@@ -1559,7 +1577,7 @@ static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)
}
}
if (ctx->is_nacon_dongle) {
if (ctx->is_dongle) {
if (packet_count == 0) {
if (device->num_joysticks > 0) {
// Check to see if it looks like the device disconnected
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -28,7 +28,7 @@
#ifdef SDL_THREAD_SAFETY_ANALYSIS
extern SDL_Mutex *SDL_HIDAPI_rumble_lock;
#endif
bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(0, SDL_HIDAPI_rumble_lock);
bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(true, SDL_HIDAPI_rumble_lock);
bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size);
int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
typedef void (*SDL_HIDAPI_RumbleSentCallback)(void *userdata);
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -29,7 +29,9 @@
#ifdef SDL_JOYSTICK_HIDAPI_SHIELD
// Define this if you want to log all packets from the controller
// #define DEBUG_SHIELD_PROTOCOL
#if 0
#define DEBUG_SHIELD_PROTOCOL
#endif
#define CMD_BATTERY_STATE 0x07
#define CMD_RUMBLE 0x39
@@ -138,6 +140,11 @@ static void HIDAPI_DriverShield_SetDevicePlayerIndex(SDL_HIDAPI_Device *device,
static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd, const void *data, int size)
{
#ifdef SDL_PLATFORM_MACOS
// We hang for several seconds when trying to send output reports on macOS
return SDL_Unsupported();
#else
SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
ShieldCommandReport_t cmd_pkt;
@@ -166,6 +173,8 @@ static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd
}
return true;
#endif // MACOS
}
static bool HIDAPI_DriverShield_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
@@ -289,6 +298,7 @@ static bool HIDAPI_DriverShield_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *dev
static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size)
{
Uint64 timestamp = SDL_GetTicksNS();
Sint16 axis;
if (ctx->last_state[3] != data[3]) {
Uint8 hat;
@@ -346,14 +356,20 @@ static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SD
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x80) != 0));
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[4]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[6]) - 0x8000);
axis = LOAD16(data[4], data[5]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = LOAD16(data[6], data[7]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[8]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[10]) - 0x8000);
axis = LOAD16(data[8], data[9]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = LOAD16(data[10], data[11]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[12]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[14]) - 0x8000);
axis = LOAD16(data[12], data[13]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = LOAD16(data[14], data[15]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
@@ -379,6 +395,7 @@ static void HIDAPI_DriverShield_HandleTouchPacketV103(SDL_Joystick *joystick, SD
static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size)
{
Uint64 timestamp = SDL_GetTicksNS();
Sint16 axis;
if (size < 23) {
return;
@@ -434,14 +451,20 @@ static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SD
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[4] & 0x01) != 0));
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[9]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[11]) - 0x8000);
axis = LOAD16(data[9], data[10]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = LOAD16(data[11], data[12]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[13]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[15]) - 0x8000);
axis = LOAD16(data[13], data[14]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = LOAD16(data[15], data[16]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[19]) - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[21]) - 0x8000);
axis = LOAD16(data[19], data[20]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = LOAD16(data[21], data[22]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
if (ctx->last_state[17] != data[17]) {
//SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[17] & 0x01) != 0));
@@ -482,13 +505,13 @@ static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device)
#ifdef DEBUG_SHIELD_PROTOCOL
HIDAPI_DumpPacket("NVIDIA SHIELD packet: size = %d", data, size);
#endif
if (!joystick) {
continue;
}
// Byte 0 is HID report ID
switch (data[0]) {
case k_ShieldReportIdControllerState:
if (!joystick) {
break;
}
if (size == 16) {
HIDAPI_DriverShield_HandleStatePacketV103(joystick, ctx, data, size);
} else {
@@ -496,9 +519,6 @@ static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device)
}
break;
case k_ShieldReportIdControllerTouch:
if (!joystick) {
break;
}
HIDAPI_DriverShield_HandleTouchPacketV103(joystick, ctx, data, size);
break;
case k_ShieldReportIdCommandResponse:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,92 @@
/*
Simple DirectMedia Layer
Copyright (C) 2025 Mitchell Cairns <mitch.cairns@handheldlegend.com>
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.
*/
typedef enum
{
SINPUT_ANALOGSTYLE_NONE,
SINPUT_ANALOGSTYLE_LEFTONLY,
SINPUT_ANALOGSTYLE_RIGHTONLY,
SINPUT_ANALOGSTYLE_LEFTRIGHT,
SINPUT_ANALOGSTYLE_MAX,
} SInput_AnalogStyleType;
typedef enum
{
SINPUT_BUMPERSTYLE_NONE,
SINPUT_BUMPERSTYLE_ONE,
SINPUT_BUMPERSTYLE_TWO,
SINPUT_BUMPERSTYLE_MAX,
} SInput_BumperStyleType;
typedef enum
{
SINPUT_TRIGGERSTYLE_NONE,
SINPUT_TRIGGERSTYLE_ANALOG,
SINPUT_TRIGGERSTYLE_DIGITAL,
SINPUT_TRIGGERSTYLE_DUALSTAGE,
SINPUT_TRIGGERSTYLE_MAX,
} SInput_TriggerStyleType;
typedef enum
{
SINPUT_PADDLESTYLE_NONE,
SINPUT_PADDLESTYLE_TWO,
SINPUT_PADDLESTYLE_FOUR,
SINPUT_PADDLESTYLE_MAX,
} SInput_PaddleStyleType;
typedef enum
{
SINPUT_METASTYLE_NONE,
SINPUT_METASTYLE_BACK,
SINPUT_METASTYLE_BACKGUIDE,
SINPUT_METASTYLE_BACKGUIDESHARE,
SINPUT_METASTYLE_MAX,
} SInput_MetaStyleType;
typedef enum
{
SINPUT_TOUCHSTYLE_NONE,
SINPUT_TOUCHSTYLE_SINGLE,
SINPUT_TOUCHSTYLE_DOUBLE,
SINPUT_TOUCHSTYLE_MAX,
} SInput_TouchStyleType;
typedef enum
{
SINPUT_MISCSTYLE_NONE,
SINPUT_MISCSTYLE_1,
SINPUT_MISCSTYLE_2,
SINPUT_MISCSTYLE_3,
SINPUT_MISCSTYLE_4,
SINPUT_MISCSTYLE_MAX,
} SInput_MiscStyleType;
typedef struct
{
Uint16 analog_style;
Uint16 bumper_style;
Uint16 trigger_style;
Uint16 paddle_style;
Uint16 meta_style;
Uint16 touch_style;
Uint16 misc_style;
} SDL_SInputStyles_t;
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -221,7 +221,7 @@ static void hexdump(const uint8_t *ptr, int len)
static void ResetSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler)
{
SDL_memset(pAssembler->uBuffer, 0, sizeof(pAssembler->uBuffer));
SDL_zeroa(pAssembler->uBuffer);
pAssembler->nExpectedSegmentNumber = 0;
}
@@ -320,7 +320,7 @@ static int SetFeatureReport(SDL_HIDAPI_Device *dev, const unsigned char uBuffer[
nActualDataLen -= nBytesInPacket;
// Construct packet
SDL_memset(uPacketBuffer, 0, sizeof(uPacketBuffer));
SDL_zeroa(uPacketBuffer);
uPacketBuffer[0] = BLE_REPORT_NUMBER;
uPacketBuffer[1] = GetSegmentHeader(nSegmentNumber, nActualDataLen == 0);
SDL_memcpy(&uPacketBuffer[2], pBufferPtr, nBytesInPacket);
@@ -370,7 +370,7 @@ static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65])
#endif
while (nRetries < BLE_MAX_READ_RETRIES) {
SDL_memset(uSegmentBuffer, 0, sizeof(uSegmentBuffer));
SDL_zeroa(uSegmentBuffer);
uSegmentBuffer[0] = BLE_REPORT_NUMBER;
nRet = SDL_hid_get_feature_report(dev->dev, uSegmentBuffer, ucBytesToRead);
@@ -457,6 +457,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew
DPRINTF("ResetSteamController hid=%p\n", dev);
SDL_zero(buf);
buf[0] = 0;
buf[1] = ID_GET_ATTRIBUTES_VALUES;
res = SetFeatureReport(dev, buf, 2);
@@ -510,6 +511,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew
}
// Clear digital button mappings
SDL_zero(buf);
buf[0] = 0;
buf[1] = ID_CLEAR_DIGITAL_MAPPINGS;
res = SetFeatureReport(dev, buf, 2);
@@ -521,7 +523,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew
}
// Reset the default settings
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_LOAD_DEFAULT_SETTINGS;
buf[2] = 0;
res = SetFeatureReport(dev, buf, 3);
@@ -539,7 +541,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew
buf[3 + nSettings * 3 + 2] = ((uint16_t)VALUE) >> 8; \
++nSettings;
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_SET_SETTINGS_VALUES;
ADD_SETTING(SETTING_WIRELESS_PACKET_VERSION, 2);
ADD_SETTING(SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE);
@@ -567,7 +569,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew
bool bMappingsCleared = false;
int iRetry;
for (iRetry = 0; iRetry < 2; ++iRetry) {
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_GET_DIGITAL_MAPPINGS;
buf[2] = 1; // one byte - requesting from index 0
buf[3] = 0;
@@ -596,7 +598,7 @@ static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew
}
// Set our new mappings
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_SET_DIGITAL_MAPPINGS;
buf[2] = 6; // 2 settings x 3 bytes
buf[3] = IO_DIGITAL_BUTTON_RIGHT_TRIGGER;
@@ -634,7 +636,7 @@ static int ReadSteamController(SDL_hid_device *dev, uint8_t *pData, int nDataSiz
static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing)
{
unsigned char buf[65];
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_ENABLE_PAIRING;
buf[2] = 2; // 2 payload bytes: bool + timeout
buf[3] = bEnablePairing ? 1 : 0;
@@ -648,7 +650,7 @@ static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing)
static void CommitPairing(SDL_HIDAPI_Device *dev)
{
unsigned char buf[65];
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_DONGLE_COMMIT_DEVICE;
SetFeatureReport(dev, buf, 2);
}
@@ -663,18 +665,18 @@ static void CloseSteamController(SDL_HIDAPI_Device *dev)
int nSettings = 0;
// Reset digital button mappings
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_SET_DEFAULT_DIGITAL_MAPPINGS;
SetFeatureReport(dev, buf, 2);
// Reset the default settings
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_LOAD_DEFAULT_SETTINGS;
buf[2] = 0;
SetFeatureReport(dev, buf, 3);
// Reset mouse mode for lizard mode
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_SET_SETTINGS_VALUES;
ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE);
buf[2] = (unsigned char)(nSettings * 3);
@@ -706,13 +708,6 @@ static void RotatePad(int *pX, int *pY, float flAngleInRad)
*pX = (int)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY);
*pY = (int)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY);
}
static void RotatePadShort(short *pX, short *pY, float flAngleInRad)
{
int origX = *pX, origY = *pY;
*pX = (short)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY);
*pY = (short)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY);
}
//---------------------------------------------------------------------------
// Format the first part of the state packet
@@ -836,9 +831,16 @@ static void FormatStatePacketUntilGyro(SteamControllerStateInternal_t *pState, V
//---------------------------------------------------------------------------
static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState)
{
const float flRotationAngle = 0.261799f;
int nLeftPadX;
int nLeftPadY;
int nRightPadX;
int nRightPadY;
int nPadOffset;
uint32_t ucOptionDataMask;
// 15 degrees in rad
const float flRotationAngle = 0.261799f;
pState->unPacketNum++;
ucOptionDataMask = (*pData++ & 0xF0);
ucOptionDataMask |= (uint32_t)(*pData++) << 8;
@@ -867,7 +869,6 @@ static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, S
}
if (ucOptionDataMask & k_EBLELeftTrackpadChunk) {
int nLength = sizeof(pState->sLeftPadX) + sizeof(pState->sLeftPadY);
int nPadOffset;
SDL_memcpy(&pState->sLeftPadX, pData, nLength);
if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
nPadOffset = 1000;
@@ -875,14 +876,15 @@ static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, S
nPadOffset = 0;
}
RotatePadShort(&pState->sLeftPadX, &pState->sLeftPadY, -flRotationAngle);
pState->sLeftPadX = (short)clamp(pState->sLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pState->sLeftPadY = (short)clamp(pState->sLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
nLeftPadX = pState->sLeftPadX;
nLeftPadY = pState->sLeftPadY;
RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle);
pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pData += nLength;
}
if (ucOptionDataMask & k_EBLERightTrackpadChunk) {
int nLength = sizeof(pState->sRightPadX) + sizeof(pState->sRightPadY);
int nPadOffset = 0;
SDL_memcpy(&pState->sRightPadX, pData, nLength);
@@ -892,9 +894,11 @@ static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, S
nPadOffset = 0;
}
RotatePadShort(&pState->sRightPadX, &pState->sRightPadY, flRotationAngle);
pState->sRightPadX = (short)clamp(pState->sRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pState->sRightPadY = (short)clamp(pState->sRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
nRightPadX = pState->sRightPadX;
nRightPadY = pState->sRightPadY;
RotatePad(&nRightPadX, &nRightPadY, flRotationAngle);
pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
pData += nLength;
}
if (ucOptionDataMask & k_EBLEIMUAccelChunk) {
@@ -1038,6 +1042,11 @@ static bool HIDAPI_DriverSteam_IsSupportedDevice(SDL_HIDAPI_Device *device, cons
return false;
}
if (!device) {
// Might be supported by this driver, enumerate and find out
return true;
}
if (device->is_bluetooth) {
return true;
}
@@ -1133,6 +1142,7 @@ static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device)
unsigned char buf[65];
int res;
SDL_zero(buf);
buf[0] = 0;
buf[1] = ID_DONGLE_GET_WIRELESS_STATE;
res = SetFeatureReport(device, buf, 2);
@@ -1194,7 +1204,7 @@ static bool SetHomeLED(SDL_HIDAPI_Device *device, Uint8 value)
unsigned char buf[65];
int nSettings = 0;
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_SET_SETTINGS_VALUES;
ADD_SETTING(SETTING_LED_USER_BRIGHTNESS, value);
buf[2] = (unsigned char)(nSettings * 3);
@@ -1305,7 +1315,7 @@ static bool HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_
unsigned char buf[65];
int nSettings = 0;
SDL_memset(buf, 0, 65);
SDL_zero(buf);
buf[1] = ID_SET_SETTINGS_VALUES;
if (enabled) {
ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO);
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -32,8 +32,6 @@
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_HORI_PROTOCOL*/
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
enum
{
SDL_GAMEPAD_BUTTON_HORI_QAM = 11,
@@ -51,6 +49,8 @@ typedef struct
Uint8 last_state[USB_PACKET_LENGTH];
Uint64 sensor_ticks;
Uint32 last_tick;
Uint64 simulated_sensor_step_ns;
Uint64 simulated_sensor_time_stamp;
bool wireless;
bool serial_needs_init;
} SDL_DriverSteamHori_Context;
@@ -114,21 +114,25 @@ static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_J
/* Initialize the joystick capabilities */
joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS;
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
joystick->nhats = 1;
ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT;
ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT;
if (ctx->wireless && device->serial) {
joystick->serial = SDL_strdup(device->serial);
ctx->serial_needs_init = false;
} else if (!ctx->wireless) {
// Need to actual read from the device to init the serial
HIDAPI_DriverSteamHori_UpdateDevice(device);
}
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
if (ctx->wireless && device->serial) {
joystick->serial = SDL_strdup(device->serial);
ctx->serial_needs_init = false;
} else if (!ctx->wireless) {
// Need to actual read from the device to init the serial
HIDAPI_DriverSteamHori_UpdateDevice(device);
}
const float sensorupdaterate = ctx->wireless ? 120.0f : 250.0f;
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, sensorupdaterate);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, sensorupdaterate);
const Uint64 sensorupdatestep_ms = ctx->wireless ? 8333 : 4000; // Equivalent to 120hz / 250hz respectively
ctx->simulated_sensor_step_ns = SDL_US_TO_NS(sensorupdatestep_ms);
return true;
}
@@ -275,12 +279,13 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0));
}
if (!ctx->wireless && ctx->serial_needs_init) {
if (!ctx->wireless && ctx->serial_needs_init) {
char serial[18];
(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
data[38], data[39], data[40], data[41], data[42], data[43]);
(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
data[38], data[39], data[40], data[41], data[42], data[43]);
joystick->serial = SDL_strdup(serial);
SDL_AssertJoysticksLocked();
joystick->serial = SDL_strdup(serial);
ctx->serial_needs_init = false;
}
@@ -312,7 +317,11 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL
ctx->sensor_ticks += delta;
/* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */
sensor_timestamp = timestamp; // if the values were good we woudl call SDL_US_TO_NS(ctx->sensor_ticks);
// sensor_timestamp = timestamp; // if the values were good we would call SDL_US_TO_NS(ctx->sensor_ticks);
/* New approach - simulate a fixed rate of 250hz (from observation). This reduces stutter from dropped/racing bluetooth packets.*/
ctx->simulated_sensor_time_stamp += ctx->simulated_sensor_step_ns;
sensor_timestamp = ctx->simulated_sensor_time_stamp;
const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f;
const float gyroScale = DEG2RAD(2048);
@@ -320,7 +329,6 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL
imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3);
@@ -334,8 +342,9 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL
if (ctx->last_state[24] != data[24]) {
bool bCharging = (data[24] & 0x10) != 0;
int percent = (data[24] & 0xF) * 10;
SDL_PowerState state;
if (bCharging) {
SDL_PowerState state;
if (bCharging) {
state = SDL_POWERSTATE_CHARGING;
} else if (ctx->wireless) {
state = SDL_POWERSTATE_ON_BATTERY;
@@ -0,0 +1,540 @@
/*
Simple DirectMedia Layer
Copyright (C) 2023 Max Maisel <max.maisel@posteo.de>
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"
#ifdef SDL_JOYSTICK_HIDAPI_STEAM_TRITON
/*****************************************************************************************************/
#include "steam/controller_constants.h"
#include "steam/controller_structs.h"
// Always 1kHz according to USB descriptor, but actually about 4 ms.
#define TRITON_SENSOR_UPDATE_INTERVAL_US 4032
enum
{
SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11,
SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
SDL_GAMEPAD_NUM_TRITON_BUTTONS,
};
typedef enum
{
TRITON_LBUTTON_A = 0x00000001,
TRITON_LBUTTON_B = 0x00000002,
TRITON_LBUTTON_X = 0x00000004,
TRITON_LBUTTON_Y = 0x00000008,
TRITON_HBUTTON_QAM = 0x00000010,
TRITON_LBUTTON_R3 = 0x00000020,
TRITON_LBUTTON_VIEW = 0x00000040,
TRITON_HBUTTON_R4 = 0x00000080,
TRITON_LBUTTON_R5 = 0x00000100,
TRITON_LBUTTON_R = 0x00000200,
TRITON_LBUTTON_DPAD_DOWN = 0x00000400,
TRITON_LBUTTON_DPAD_RIGHT = 0x00000800,
TRITON_LBUTTON_DPAD_LEFT = 0x00001000,
TRITON_LBUTTON_DPAD_UP = 0x00002000,
TRITON_LBUTTON_MENU = 0x00004000,
TRITON_LBUTTON_L3 = 0x00008000,
TRITON_LBUTTON_STEAM = 0x00010000,
TRITON_HBUTTON_L4 = 0x00020000,
TRITON_LBUTTON_L5 = 0x00040000,
TRITON_LBUTTON_L = 0x00080000,
/*
STEAM_RIGHTSTICK_FINGERDOWN_MASK, // Right Stick Touch 0x00100000
STEAM_RIGHTPAD_FINGERDOWN_MASK, // Right Pad Touch 0x00200000
STEAM_BUTTON_RIGHTPAD_CLICKED_MASK, // Right Pressure Click 0x00400000
STEAM_RIGHT_TRIGGER_MASK, // Right Trigger Click 0x00800000
STEAM_LEFTSTICK_FINGERDOWN_MASK, // Left Stick Touch 0x01000000
STEAM_LEFTPAD_FINGERDOWN_MASK, // Left Pad Touch 0x02000000
STEAM_BUTTON_LEFTPAD_CLICKED_MASK, // Left Pressure Click 0x04000000
STEAM_LEFT_TRIGGER_MASK, // Left Trigger Click 0x08000000
STEAM_RIGHT_AUX_MASK, // Right Pinky Touch 0x10000000
STEAM_LEFT_AUX_MASK, // Left Pinky Touch 0x20000000
*/
} TritonButtons;
typedef struct
{
bool connected;
bool report_sensors;
Uint32 last_sensor_tick;
Uint64 sensor_timestamp_ns;
Uint64 last_button_state;
Uint64 last_lizard_update;
} SDL_DriverSteamTriton_Context;
static bool IsProteusDongle(Uint16 product_id)
{
return (product_id == USB_PRODUCT_VALVE_STEAM_PROTEUS_DONGLE ||
product_id == USB_PRODUCT_VALVE_STEAM_NEREID_DONGLE);
}
static bool DisableSteamTritonLizardMode(SDL_hid_device *dev)
{
int rc;
Uint8 buffer[HID_FEATURE_REPORT_BYTES] = { 1 };
FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
msg->header.type = ID_SET_SETTINGS_VALUES;
msg->header.length = 1 * sizeof(ControllerSetting);
msg->payload.setSettingsValues.settings[0].settingNum = SETTING_LIZARD_MODE;
msg->payload.setSettingsValues.settings[0].settingValue = LIZARD_MODE_OFF;
rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
if (rc != sizeof(buffer)) {
return false;
}
return true;
}
static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
SDL_Joystick *joystick,
TritonMTUFull_t *pTritonReport)
{
float values[3];
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
Uint64 timestamp = SDL_GetTicksNS();
if (pTritonReport->uButtons != ctx->last_button_state) {
Uint8 hat = 0;
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,
((pTritonReport->uButtons & TRITON_LBUTTON_A) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,
((pTritonReport->uButtons & TRITON_LBUTTON_B) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,
((pTritonReport->uButtons & TRITON_LBUTTON_X) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,
((pTritonReport->uButtons & TRITON_LBUTTON_Y) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
((pTritonReport->uButtons & TRITON_LBUTTON_L) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
((pTritonReport->uButtons & TRITON_LBUTTON_R) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,
((pTritonReport->uButtons & TRITON_LBUTTON_MENU) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,
((pTritonReport->uButtons & TRITON_LBUTTON_VIEW) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,
((pTritonReport->uButtons & TRITON_LBUTTON_STEAM) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM,
((pTritonReport->uButtons & TRITON_HBUTTON_QAM) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,
((pTritonReport->uButtons & TRITON_LBUTTON_L3) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,
((pTritonReport->uButtons & TRITON_LBUTTON_R3) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
((pTritonReport->uButtons & TRITON_HBUTTON_R4) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
((pTritonReport->uButtons & TRITON_HBUTTON_L4) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
((pTritonReport->uButtons & TRITON_LBUTTON_R5) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
((pTritonReport->uButtons & TRITON_LBUTTON_L5) != 0));
if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_UP) {
hat |= SDL_HAT_UP;
}
if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_DOWN) {
hat |= SDL_HAT_DOWN;
}
if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_LEFT) {
hat |= SDL_HAT_LEFT;
}
if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_RIGHT) {
hat |= SDL_HAT_RIGHT;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
ctx->last_button_state = pTritonReport->uButtons;
}
// RKRK There're button bits for this if you so choose.
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
(int)pTritonReport->sTriggerLeft * 2 - 32768);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
(int)pTritonReport->sTriggerRight * 2 - 32768);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX,
pTritonReport->sLeftStickX);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY,
-pTritonReport->sLeftStickY);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX,
pTritonReport->sRightStickX);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY,
-pTritonReport->sRightStickY);
if (ctx->report_sensors && pTritonReport->imu.uTimestamp != ctx->last_sensor_tick) {
Uint32 delta_us = (pTritonReport->imu.uTimestamp - ctx->last_sensor_tick);
ctx->sensor_timestamp_ns += SDL_US_TO_NS(delta_us);
values[0] = (pTritonReport->imu.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
values[1] = (pTritonReport->imu.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
values[2] = (-pTritonReport->imu.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_ns, values, 3);
values[0] = (pTritonReport->imu.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[1] = (pTritonReport->imu.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[2] = (-pTritonReport->imu.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_ns, values, 3);
ctx->last_sensor_tick = pTritonReport->imu.uTimestamp;
}
}
static void HIDAPI_DriverSteamTriton_HandleBatteryStatus(SDL_HIDAPI_Device *device,
SDL_Joystick *joystick,
TritonBatteryStatus_t *pTritonBatteryStatus)
{
SDL_PowerState state;
switch (pTritonBatteryStatus->ucChargeState) {
case k_EChargeStateDischarging:
state = SDL_POWERSTATE_ON_BATTERY;
break;
case k_EChargeStateCharging:
state = SDL_POWERSTATE_CHARGING;
break;
case k_EChargeStateChargingDone:
state = SDL_POWERSTATE_CHARGED;
break;
default:
// Error state?
state = SDL_POWERSTATE_UNKNOWN;
break;
}
SDL_SendJoystickPowerInfo(joystick, state, pTritonBatteryStatus->ucBatteryLevel);
}
static bool HIDAPI_DriverSteamTriton_SetControllerConnected(SDL_HIDAPI_Device *device, bool connected)
{
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
if (ctx->connected != connected) {
ctx->connected = connected;
if (connected) {
SDL_JoystickID joystickID;
if (!HIDAPI_JoystickConnected(device, &joystickID)) {
return false;
}
} else {
if (device->num_joysticks > 0) {
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
}
}
return true;
}
static void HIDAPI_DriverSteamTriton_HandleWirelessStatus(SDL_HIDAPI_Device *device,
TritonWirelessStatus_t *pTritonWirelessStatus)
{
switch (pTritonWirelessStatus->state) {
case k_ETritonWirelessStateConnect:
HIDAPI_DriverSteamTriton_SetControllerConnected(device, true);
break;
case k_ETritonWirelessStateDisconnect:
HIDAPI_DriverSteamTriton_SetControllerConnected(device, false);
break;
default:
break;
}
}
/*****************************************************************************************************/
static void HIDAPI_DriverSteamTriton_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
}
static void HIDAPI_DriverSteamTriton_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
}
static bool HIDAPI_DriverSteamTriton_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
}
static bool HIDAPI_DriverSteamTriton_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)
{
if (IsProteusDongle(product_id)) {
if (interface_number >= 2 && interface_number <= 5) {
// The set of controller interfaces for Proteus & Nereid...currently
return true;
}
} else if (SDL_IsJoystickSteamTriton(vendor_id, product_id)) {
return true;
}
return false;
}
static bool HIDAPI_DriverSteamTriton_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverSteamTriton_Context *ctx;
ctx = (SDL_DriverSteamTriton_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
return false;
}
device->context = ctx;
HIDAPI_SetDeviceName(device, "Steam Controller");
if (IsProteusDongle(device->product_id)) {
return true;
}
// Wired controller, connected!
return HIDAPI_DriverSteamTriton_SetControllerConnected(device, true);
}
static int HIDAPI_DriverSteamTriton_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverSteamTriton_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static bool HIDAPI_DriverSteamTriton_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
SDL_Joystick *joystick = NULL;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
}
if (ctx->connected && joystick) {
Uint64 now = SDL_GetTicks();
if (!ctx->last_lizard_update || (now - ctx->last_lizard_update) >= 3000) {
DisableSteamTritonLizardMode(device->dev);
ctx->last_lizard_update = now;
}
}
for (;;) {
uint8_t data[64];
int r = SDL_hid_read(device->dev, data, sizeof(data));
if (r == 0) {
return true;
}
if (r < 0) {
// Failed to read from controller
HIDAPI_DriverSteamTriton_SetControllerConnected(device, false);
return false;
}
switch (data[0]) {
case ID_TRITON_CONTROLLER_STATE:
case ID_TRITON_CONTROLLER_STATE_BLE:
if (!joystick) {
HIDAPI_DriverSteamTriton_SetControllerConnected(device, true);
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
}
}
if (joystick && r >= (1 + sizeof(TritonMTUFull_t))) {
TritonMTUFull_t *pTritonReport = (TritonMTUFull_t *)&data[1];
HIDAPI_DriverSteamTriton_HandleState(device, joystick, pTritonReport);
}
break;
case ID_TRITON_BATTERY_STATUS:
if (joystick && r >= (1 + sizeof(TritonBatteryStatus_t))) {
TritonBatteryStatus_t *pTritonBatteryStatus = (TritonBatteryStatus_t *)&data[1];
HIDAPI_DriverSteamTriton_HandleBatteryStatus(device, joystick, pTritonBatteryStatus);
}
break;
case ID_TRITON_WIRELESS_STATUS_X:
case ID_TRITON_WIRELESS_STATUS:
if (r >= (1 + sizeof(TritonWirelessStatus_t))) {
TritonWirelessStatus_t *pTritonWirelessStatus = (TritonWirelessStatus_t *)&data[1];
HIDAPI_DriverSteamTriton_HandleWirelessStatus(device, pTritonWirelessStatus);
}
break;
default:
break;
}
}
}
static bool HIDAPI_DriverSteamTriton_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
float update_rate_in_hz = 1000000.0f / TRITON_SENSOR_UPDATE_INTERVAL_US;
SDL_AssertJoysticksLocked();
// Initialize the joystick capabilities
joystick->nbuttons = SDL_GAMEPAD_NUM_TRITON_BUTTONS;
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);
return true;
}
static bool HIDAPI_DriverSteamTriton_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
int rc;
//RKRK Not sure about size. Probably 64+1 is OK for ORs
Uint8 buffer[HID_RUMBLE_OUTPUT_REPORT_BYTES];
OutputReportMsg *msg = (OutputReportMsg *)(buffer);
msg->report_id = ID_OUT_REPORT_HAPTIC_RUMBLE;
msg->payload.hapticRumble.type = 0;
msg->payload.hapticRumble.intensity = 0;
msg->payload.hapticRumble.left.speed = low_frequency_rumble;
msg->payload.hapticRumble.left.gain = 0;
msg->payload.hapticRumble.right.speed = high_frequency_rumble;
msg->payload.hapticRumble.right.gain = 0;
rc = SDL_hid_write(device->dev, buffer, sizeof(buffer));
if (rc != sizeof(buffer)) {
return false;
}
return true;
}
static bool HIDAPI_DriverSteamTriton_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverSteamTriton_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
return SDL_JOYSTICK_CAP_RUMBLE;
}
static bool HIDAPI_DriverSteamTriton_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverSteamTriton_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverSteamTriton_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
{
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
int rc;
Uint8 buffer[HID_FEATURE_REPORT_BYTES] = { 1 };
FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
msg->header.type = ID_SET_SETTINGS_VALUES;
msg->header.length = 1 * sizeof(ControllerSetting);
msg->payload.setSettingsValues.settings[0].settingNum = SETTING_IMU_MODE;
if (enabled) {
msg->payload.setSettingsValues.settings[0].settingValue = (SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO);
} else {
msg->payload.setSettingsValues.settings[0].settingValue = SETTING_GYRO_MODE_OFF;
}
rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer));
if (rc != sizeof(buffer)) {
return false;
}
ctx->report_sensors = enabled;
return true;
}
static void HIDAPI_DriverSteamTriton_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
// Lizard mode id automatically re-enabled by watchdog. Nothing to do here.
}
static void HIDAPI_DriverSteamTriton_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamTriton = {
SDL_HINT_JOYSTICK_HIDAPI_STEAM,
true,
HIDAPI_DriverSteamTriton_RegisterHints,
HIDAPI_DriverSteamTriton_UnregisterHints,
HIDAPI_DriverSteamTriton_IsEnabled,
HIDAPI_DriverSteamTriton_IsSupportedDevice,
HIDAPI_DriverSteamTriton_InitDevice,
HIDAPI_DriverSteamTriton_GetDevicePlayerIndex,
HIDAPI_DriverSteamTriton_SetDevicePlayerIndex,
HIDAPI_DriverSteamTriton_UpdateDevice,
HIDAPI_DriverSteamTriton_OpenJoystick,
HIDAPI_DriverSteamTriton_RumbleJoystick,
HIDAPI_DriverSteamTriton_RumbleJoystickTriggers,
HIDAPI_DriverSteamTriton_GetJoystickCapabilities,
HIDAPI_DriverSteamTriton_SetJoystickLED,
HIDAPI_DriverSteamTriton_SendJoystickEffect,
HIDAPI_DriverSteamTriton_SetSensorsEnabled,
HIDAPI_DriverSteamTriton_CloseJoystick,
HIDAPI_DriverSteamTriton_FreeDevice,
};
#endif // SDL_JOYSTICK_HIDAPI_STEAM_TRITON
#endif // SDL_JOYSTICK_HIDAPI
@@ -74,7 +74,7 @@ typedef enum
typedef struct
{
Uint32 update_rate_us;
Uint32 sensor_timestamp_us;
Uint64 sensor_timestamp_ns;
Uint64 last_button_state;
Uint8 watchdog_counter;
} SDL_DriverSteamDeck_Context;
@@ -222,17 +222,29 @@ static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device,
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY,
-pInReport->payload.deckState.sRightStickY);
ctx->sensor_timestamp_us += ctx->update_rate_us;
ctx->sensor_timestamp_ns += SDL_US_TO_NS(ctx->update_rate_us);
values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3);
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_ns, values, 3);
values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3);
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_ns, values, 3);
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0,
pInReport->payload.deckState.sPressurePadLeft > 0,
pInReport->payload.deckState.sLeftPadX / 65536.0f + 0.5f,
pInReport->payload.deckState.sLeftPadY / 65536.0f + 0.5f,
pInReport->payload.deckState.sPressurePadLeft / 32768.0f);
SDL_SendJoystickTouchpad(timestamp, joystick, 1, 0,
pInReport->payload.deckState.sPressurePadRight > 0,
pInReport->payload.deckState.sRightPadX / 65536.0f + 0.5f,
pInReport->payload.deckState.sRightPadY / 65536.0f + 0.5f,
pInReport->payload.deckState.sPressurePadRight / 32768.0f);
}
/*****************************************************************************************************/
@@ -331,7 +343,7 @@ static bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device)
return false;
}
SDL_memset(data, 0, sizeof(data));
SDL_zeroa(data);
do {
r = SDL_hid_read(device->dev, data, sizeof(data));
@@ -366,6 +378,9 @@ static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_J
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);
SDL_PrivateJoystickAddTouchpad(joystick, 1);
SDL_PrivateJoystickAddTouchpad(joystick, 1);
return true;
}
+134 -67
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -34,7 +34,9 @@
#ifdef SDL_JOYSTICK_HIDAPI_SWITCH
// Define this if you want to log all packets from the controller
// #define DEBUG_SWITCH_PROTOCOL
#if 0
#define DEBUG_SWITCH_PROTOCOL
#endif
// Define this to get log output for rumble logic
// #define DEBUG_RUMBLE
@@ -159,20 +161,21 @@ typedef struct
Uint8 ucVibrationCode;
} SwitchControllerStatePacket_t;
typedef struct
{
Sint16 sAccelX;
Sint16 sAccelY;
Sint16 sAccelZ;
Sint16 sGyroX;
Sint16 sGyroY;
Sint16 sGyroZ;
} SwitchControllerIMUState_t;
typedef struct
{
SwitchControllerStatePacket_t controllerState;
struct
{
Sint16 sAccelX;
Sint16 sAccelY;
Sint16 sAccelZ;
Sint16 sGyroX;
Sint16 sGyroY;
Sint16 sGyroZ;
} imuState[3];
SwitchControllerIMUState_t imuState[3];
} SwitchStatePacket_t;
typedef struct
@@ -313,6 +316,8 @@ typedef struct
Uint64 m_ulIMUUpdateIntervalNS;
Uint64 m_ulTimestampNS;
bool m_bVerticalMode;
SDL_PowerState m_ePowerState;
int m_nPowerPercent;
SwitchInputOnlyControllerStatePacket_t m_lastInputOnlyState;
SwitchSimpleStatePacket_t m_lastSimpleState;
@@ -388,7 +393,7 @@ static int WriteOutput(SDL_DriverSwitch_Context *ctx, const Uint8 *data, int siz
#endif // SWITCH_SYNCHRONOUS_WRITES
}
static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID)
static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID, const Uint8 *pBuf, Uint8 ucLen)
{
// Average response time for messages is ~30ms
Uint64 endTicks = SDL_GetTicks() + 100;
@@ -398,9 +403,17 @@ static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Conte
if (nRead > 0) {
if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) {
SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[1];
if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) {
return reply;
if (reply->ucSubcommandID != expectedID || !(reply->ucSubcommandAck & 0x80)) {
continue;
}
if (reply->ucSubcommandID == k_eSwitchSubcommandIDs_SPIFlashRead) {
SDL_assert(ucLen == sizeof(reply->spiReadData.opData));
if (SDL_memcmp(&reply->spiReadData.opData, pBuf, ucLen) != 0) {
// This was a reply for another SPI read command
continue;
}
}
return reply;
}
} else {
SDL_Delay(1);
@@ -437,7 +450,7 @@ static bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprieta
static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket)
{
SDL_memset(outPacket, 0, sizeof(*outPacket));
SDL_zerop(outPacket);
outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand;
outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber;
@@ -487,7 +500,7 @@ static bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs
continue;
}
reply = ReadSubcommandReply(ctx, ucCommandID);
reply = ReadSubcommandReply(ctx, ucCommandID, pBuf, ucLen);
}
if (ppReply) {
@@ -557,15 +570,33 @@ static Uint16 EncodeRumbleLowAmplitude(Uint16 amplitude)
return lfa[100][1];
}
static void SetNeutralRumble(SwitchRumbleData_t *pRumble)
static void SetNeutralRumble(SDL_HIDAPI_Device *device, SwitchRumbleData_t *pRumble)
{
pRumble->rgucData[0] = 0x00;
pRumble->rgucData[1] = 0x01;
pRumble->rgucData[2] = 0x40;
pRumble->rgucData[3] = 0x40;
bool bStandardNeutralValue;
if (device->vendor_id == USB_VENDOR_NINTENDO &&
device->product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) {
// The 8BitDo 64 Bluetooth Controller rumbles at startup with the standard neutral value,
// so we'll use a 0 amplitude value instead.
bStandardNeutralValue = false;
} else {
// The KingKong2 PRO Controller doesn't initialize correctly with a 0 amplitude value
// over Bluetooth, so we'll use the standard value in all other cases.
bStandardNeutralValue = true;
}
if (bStandardNeutralValue) {
pRumble->rgucData[0] = 0x00;
pRumble->rgucData[1] = 0x01;
pRumble->rgucData[2] = 0x40;
pRumble->rgucData[3] = 0x40;
} else {
pRumble->rgucData[0] = 0x00;
pRumble->rgucData[1] = 0x00;
pRumble->rgucData[2] = 0x01;
pRumble->rgucData[3] = 0x40;
}
}
static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp)
static void EncodeRumble(SDL_HIDAPI_Device *device, SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp)
{
if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) {
// High-band frequency and low-band amplitude are actually nine-bits each so they
@@ -582,7 +613,7 @@ static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 u
ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF);
#endif
} else {
SetNeutralRumble(pRumble);
SetNeutralRumble(device, pRumble);
}
}
@@ -747,9 +778,10 @@ static void UpdateSlotLED(SDL_DriverSwitch_Context *ctx)
{
if (!ctx->m_bInputOnly) {
Uint8 led_data = 0;
const Uint8 player_pattern[] = { 0x1, 0x3, 0x7, 0xf, 0x9, 0x5, 0xd, 0x6 };
if (ctx->m_bPlayerLights && ctx->m_nPlayerIndex >= 0) {
led_data = (1 << (ctx->m_nPlayerIndex % 4));
led_data = player_pattern[ctx->m_nPlayerIndex % 8];
}
WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL);
}
@@ -957,7 +989,7 @@ static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx)
if (user_reply && user_reply->stickUserCalibration.rgucRightMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucRightMagic[1] == 0xA1) {
userParamsReadSuccessCount += 1;
pRightStickCal = user_reply->stickUserCalibration.rgucRightCalibration;
}
}
// Only read the factory calibration info if we failed to receive the correct magic bytes
if (userParamsReadSuccessCount < 2) {
@@ -1386,7 +1418,15 @@ static bool HIDAPI_DriverSwitch_IsSupportedDevice(SDL_HIDAPI_Device *device, con
return false;
}
return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO);
if (type != SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO) {
return false;
}
// The Nintendo Switch 2 Pro uses another driver
if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH2_PRO) {
return false;
}
return true;
}
static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
@@ -1395,7 +1435,7 @@ static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
if (ctx->m_bInputOnly) {
if (SDL_IsJoystickGameCube(device->vendor_id, device->product_id)) {
device->type = SDL_GAMEPAD_TYPE_STANDARD;
device->type = SDL_GAMEPAD_TYPE_GAMECUBE;
}
} else {
char serial[18];
@@ -1507,8 +1547,8 @@ static bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id);
if (!ctx->m_bInputOnly) {
// Initialize rumble data, important for reading device info on the MOBAPAD M073
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[0]);
SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[1]);
BReadDeviceInfo(ctx);
}
@@ -1562,8 +1602,8 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
ctx->m_nCurrentInputMode = ctx->m_nInitialInputMode;
// Initialize rumble data
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[0]);
SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[1]);
if (!device->is_bluetooth) {
if (!BTrySetupUSB(ctx)) {
@@ -1583,7 +1623,8 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESRight &&
ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SNES &&
ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 &&
ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) {
ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis &&
!(device->vendor_id == USB_VENDOR_PDP && device->product_id == USB_PRODUCT_PDP_REALMZ_WIRELESS)) {
if (LoadIMUCalibration(ctx)) {
ctx->m_bSensorsSupported = true;
}
@@ -1660,11 +1701,11 @@ static bool HIDAPI_DriverSwitch_ActuallyRumbleJoystick(SDL_DriverSwitch_Context
const Uint16 k_usLowFreqAmp = EncodeRumbleLowAmplitude(low_frequency_rumble);
if (low_frequency_rumble || high_frequency_rumble) {
EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
EncodeRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
EncodeRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
} else {
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
SetNeutralRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[0]);
SetNeutralRumble(ctx->device, &ctx->m_RumblePacket.rumbleData[1]);
}
ctx->m_bRumbleActive = (low_frequency_rumble || high_frequency_rumble);
@@ -1771,16 +1812,29 @@ static Uint32 HIDAPI_DriverSwitch_GetJoystickCapabilities(SDL_HIDAPI_Device *dev
if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) {
// Doesn't have an RGB LED, so don't return SDL_JOYSTICK_CAP_RGB_LED here
result |= SDL_JOYSTICK_CAP_RUMBLE;
// But has the HOME LED, so treat it like a mono LED
result |= SDL_JOYSTICK_CAP_MONO_LED;
} else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||
ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
result |= SDL_JOYSTICK_CAP_RUMBLE;
if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
result |= SDL_JOYSTICK_CAP_MONO_LED; // Right JoyCon also have the HOME LED
}
}
return result;
}
static bool HIDAPI_DriverSwitch_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
if (!(ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) &&
ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_JoyConRight) {
return SDL_Unsupported();
}
int value = (int)((SDL_max(red, SDL_max(green, blue)) / 255.0f) * 100.0f); // The colors are received between 0-255 and we need them to be 0-100
return SetHomeLED(ctx, (Uint8)value);
}
static bool HIDAPI_DriverSwitch_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
@@ -2572,26 +2626,39 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C
* LSB of the battery nibble is used to report charging.
* The battery level is reported from 0(empty)-8(full)
*/
SDL_PowerState state;
int charging = (packet->controllerState.ucBatteryAndConnection & 0x10);
int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4;
int percent = (int)SDL_roundf((level / 8.0f) * 100.0f);
if (charging) {
if (level == 8) {
state = SDL_POWERSTATE_CHARGED;
ctx->m_ePowerState = SDL_POWERSTATE_CHARGED;
} else {
state = SDL_POWERSTATE_CHARGING;
ctx->m_ePowerState = SDL_POWERSTATE_CHARGING;
}
} else {
state = SDL_POWERSTATE_ON_BATTERY;
ctx->m_ePowerState = SDL_POWERSTATE_ON_BATTERY;
}
ctx->m_nPowerPercent = (int)SDL_roundf((level / 8.0f) * 100.0f);
if (!ctx->device->parent) {
SDL_PowerState state = ctx->m_ePowerState;
int percent = ctx->m_nPowerPercent;
SDL_SendJoystickPowerInfo(joystick, state, percent);
} else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
SDL_DriverSwitch_Context *other = (SDL_DriverSwitch_Context *)ctx->device->parent->children[0]->context;
SDL_PowerState state = (SDL_PowerState)SDL_min(ctx->m_ePowerState, other->m_ePowerState);
int percent = SDL_min(ctx->m_nPowerPercent, other->m_nPowerPercent);
SDL_SendJoystickPowerInfo(joystick, state, percent);
}
SDL_SendJoystickPowerInfo(joystick, state, percent);
if (ctx->m_bReportSensors) {
bool bHasSensorData = (packet->imuState[0].sAccelZ != 0 ||
packet->imuState[0].sAccelY != 0 ||
packet->imuState[0].sAccelX != 0);
// Need to copy the imuState to an aligned variable
SwitchControllerIMUState_t imuState[3];
SDL_COMPILE_TIME_ASSERT(imuState_size, sizeof(imuState) == sizeof(packet->imuState));
SDL_memcpy(imuState, packet->imuState, sizeof(packet->imuState));
bool bHasSensorData = (imuState[0].sAccelZ != 0 ||
imuState[0].sAccelY != 0 ||
imuState[0].sAccelX != 0);
if (bHasSensorData) {
const Uint32 IMU_UPDATE_RATE_SAMPLE_FREQUENCY = 1000;
Uint64 sensor_timestamp[3];
@@ -2620,37 +2687,37 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C
if (!ctx->device->parent ||
ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &packet->imuState[2].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &packet->imuState[2].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &imuState[2].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &imuState[2].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &packet->imuState[1].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &packet->imuState[1].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &imuState[1].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &imuState[1].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &packet->imuState[0].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &packet->imuState[0].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &imuState[0].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &imuState[0].sAccelX);
}
if (ctx->device->parent &&
ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &packet->imuState[2].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &packet->imuState[2].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &imuState[2].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &imuState[2].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &packet->imuState[1].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &packet->imuState[1].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &imuState[1].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &imuState[1].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &packet->imuState[0].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &packet->imuState[0].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &imuState[0].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &imuState[0].sAccelX);
}
if (ctx->device->parent &&
ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &packet->imuState[2].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &packet->imuState[2].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &imuState[2].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &imuState[2].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &packet->imuState[1].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &packet->imuState[1].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &imuState[1].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &imuState[1].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &packet->imuState[0].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &packet->imuState[0].sAccelX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &imuState[0].sGyroX);
SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &imuState[0].sAccelX);
}
} else if (ctx->m_bHasSensorData) {
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -512,6 +512,8 @@ static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryB
bool pluggedIn = !(extensionBatteryByte & 0x04);
Uint8 batteryLevel = extensionBatteryByte >> 4;
SDL_AssertJoysticksLocked();
if (pluggedIn) {
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
} else {
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -26,6 +26,7 @@
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#include "SDL_report_descriptor.h"
#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
@@ -33,7 +34,9 @@
// #define DEBUG_JOYSTICK
// Define this if you want to log all packets from the controller
// #define DEBUG_XBOX_PROTOCOL
#if 0
#define DEBUG_XBOX_PROTOCOL
#endif
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
#define XBOX_ONE_DRIVER_ACTIVE 1
@@ -132,6 +135,8 @@ typedef struct
bool has_unmapped_state;
bool has_trigger_rumble;
bool has_share_button;
bool has_separate_back_button;
bool has_separate_guide_button;
Uint8 last_paddle_state;
Uint8 low_frequency_rumble;
Uint8 high_frequency_rumble;
@@ -140,6 +145,8 @@ typedef struct
SDL_XboxOneRumbleState rumble_state;
Uint64 rumble_time;
bool rumble_pending;
SDL_ReportDescriptor *descriptor;
Uint32 last_buttons;
Uint8 last_state[USB_PACKET_LENGTH];
Uint8 *chunk_buffer;
Uint32 chunk_length;
@@ -266,8 +273,14 @@ static bool SendSerialRequest(SDL_DriverXboxOne_Context *ctx)
static bool ControllerSendsAnnouncement(Uint16 vendor_id, Uint16 product_id)
{
if (vendor_id == USB_VENDOR_PDP && product_id == 0x0246) {
// The PDP Rock Candy (PID 0x0246) doesn't send the announce packet on Linux for some reason
// The PDP Rock Candy (PID 0x0246) and PowerA Fusion Pro 4 (PID 0x400b)
// don't send the announce packet on Linux for some reason.
//
// Just to be safe and cover future products, we'll always send the startup
// protocol sequence for PDP and PowerA controllers
if (vendor_id == USB_VENDOR_PDP ||
vendor_id == USB_VENDOR_POWERA ||
vendor_id == USB_VENDOR_POWERA_ALT) {
return false;
}
return true;
@@ -350,6 +363,10 @@ static bool HIDAPI_DriverXboxOne_IsEnabled(void)
static bool HIDAPI_DriverXboxOne_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)
{
static const int LIBUSB_CLASS_VENDOR_SPEC = 0xFF;
static const int XBONE_IFACE_SUBCLASS = 71;
static const int XBONE_IFACE_PROTOCOL = 208;
#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI)
if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) {
// On macOS we get a shortened version of the real report and
@@ -358,6 +375,13 @@ static bool HIDAPI_DriverXboxOne_IsSupportedDevice(SDL_HIDAPI_Device *device, co
return false;
}
#endif
if (interface_class &&
(interface_class != LIBUSB_CLASS_VENDOR_SPEC ||
interface_subclass != XBONE_IFACE_SUBCLASS ||
interface_protocol != XBONE_IFACE_PROTOCOL)) {
// This isn't the Xbox gamepad interface
return false;
}
return (type == SDL_GAMEPAD_TYPE_XBOXONE);
}
@@ -373,6 +397,56 @@ static bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device)
device->context = ctx;
Uint8 descriptor[1024];
int descriptor_len = SDL_hid_get_report_descriptor(device->dev, descriptor, sizeof(descriptor));
if (descriptor_len > 0) {
HIDAPI_DumpPacket("Xbox One report descriptor: size = %d", descriptor, descriptor_len);
ctx->descriptor = SDL_ParseReportDescriptor(descriptor, descriptor_len);
if (ctx->descriptor) {
// Collapse the buttons into a single field read
int field_count = ctx->descriptor->field_count;
DescriptorInputField *fields = ctx->descriptor->fields;
int button_count = 0;
for (int i = 0; i < field_count; ++i) {
DescriptorInputField *field = &fields[i];
if (field->usage == MAKE_USAGE(USB_USAGEPAGE_BUTTON, 1)) {
Uint32 expected_usage = field->usage;
int expected_offset = field->bit_offset;
for (int j = i; j < field_count; ++j) {
DescriptorInputField *other = &fields[j];
if (other->usage != expected_usage ||
other->bit_offset != expected_offset) {
break;
}
++button_count;
++expected_usage;
++expected_offset;
}
field->bit_size = button_count;
int next_field = i + button_count;
int fields_left = (field_count - next_field);
SDL_memmove(&fields[i+1], &fields[next_field], (fields_left * sizeof(fields[0])));
ctx->descriptor->field_count -= (button_count - 1);
break;
}
}
if (!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_X) ||
!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Y) ||
(button_count != 12 && button_count != 15)) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Xbox report descriptor missing expected usages, ignoring");
SDL_DestroyDescriptor(ctx->descriptor);
ctx->descriptor = NULL;
}
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't parse Xbox report descriptor: %s", SDL_GetError());
}
} else {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Xbox report descriptor not available");
}
ctx->vendor_id = device->vendor_id;
ctx->product_id = device->product_id;
ctx->start_time = SDL_GetTicks();
@@ -581,6 +655,310 @@ static bool HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *de
return SDL_Unsupported();
}
static void HIDAPI_DriverXboxOne_HandleBatteryState(SDL_Joystick *joystick, Uint32 flags)
{
bool on_usb = (((flags & 0x0C) >> 2) == 0);
SDL_PowerState state;
int percent = 0;
// Mapped percentage value from:
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate
switch (flags & 0x03) {
case 0:
percent = 10;
break;
case 1:
percent = 40;
break;
case 2:
percent = 70;
break;
case 3:
percent = 100;
break;
}
if (on_usb) {
state = SDL_POWERSTATE_CHARGING;
} else {
state = SDL_POWERSTATE_ON_BATTERY;
}
SDL_SendJoystickPowerInfo(joystick, state, percent);
}
static void HandleDescriptorAxis(Uint64 timestamp, SDL_Joystick *joystick, SDL_GamepadAxis axis, Uint32 value)
{
Sint16 axis_value = (Sint16)((int)value - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, axis, axis_value);
}
static void HandleDescriptorTrigger(Uint64 timestamp, SDL_Joystick *joystick, SDL_GamepadAxis axis, Uint32 value)
{
Sint16 axis_value = (Sint16)(((int)value * 64) - 32768);
if (axis_value == 32704) {
axis_value = 32767;
}
SDL_SendJoystickAxis(timestamp, joystick, axis, axis_value);
}
static bool HIDAPI_DriverXboxOne_HandleDescriptorReport(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)
{
const SDL_ReportDescriptor *descriptor = ctx->descriptor;
Uint64 timestamp = SDL_GetTicksNS();
// Skip the report ID
const Uint8 report_id = *data;
++data;
--size;
for (int i = 0; i < descriptor->field_count; ++i) {
DescriptorInputField *field = &descriptor->fields[i];
if (field->report_id != report_id) {
continue;
}
Uint32 value;
if (!SDL_ReadReportData(data, size, field->bit_offset, field->bit_size, &value)) {
continue;
}
switch (field->usage) {
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_X):
{
HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, value);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Y):
{
HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, value);
break;
}
// Some controllers use Z+RZ for the right thumbstick and BRAKE and ACCEL for the left and right triggers
// and others use RX+RY for the right thumbstick and Z and RZ for the left and right triggers
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Z):
{
if (field->bit_size == 16) {
HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, value);
} else if (field->bit_size == 10) {
HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, value);
}
break;
}
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RX):
{
HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, value);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RY):
{
HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, value);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RZ):
{
if (field->bit_size == 16) {
HandleDescriptorAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, value);
} else if (field->bit_size == 10) {
HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, value);
}
break;
}
case MAKE_USAGE(USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_BRAKE):
{
HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, value);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_ACCELERATOR):
{
HandleDescriptorTrigger(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, value);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_HAT):
{
Uint8 hat;
switch (value) {
case 1:
hat = SDL_HAT_UP;
break;
case 2:
hat = SDL_HAT_RIGHTUP;
break;
case 3:
hat = SDL_HAT_RIGHT;
break;
case 4:
hat = SDL_HAT_RIGHTDOWN;
break;
case 5:
hat = SDL_HAT_DOWN;
break;
case 6:
hat = SDL_HAT_LEFTDOWN;
break;
case 7:
hat = SDL_HAT_LEFT;
break;
case 8:
hat = SDL_HAT_LEFTUP;
break;
default:
hat = SDL_HAT_CENTERED;
break;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 1):
{
static const SDL_GamepadButton button_map_12[] = {
// 0x0001
SDL_GAMEPAD_BUTTON_SOUTH,
// 0x0002
SDL_GAMEPAD_BUTTON_EAST,
// 0x0004
SDL_GAMEPAD_BUTTON_WEST,
// 0x0008
SDL_GAMEPAD_BUTTON_NORTH,
// 0x0010
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
// 0x0020
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
// 0x0040
SDL_GAMEPAD_BUTTON_BACK,
// 0x0080
SDL_GAMEPAD_BUTTON_START,
// 0x0100
SDL_GAMEPAD_BUTTON_LEFT_STICK,
// 0x0200
SDL_GAMEPAD_BUTTON_RIGHT_STICK,
// 0x0400
SDL_GAMEPAD_BUTTON_GUIDE,
// 0x0800
SDL_GAMEPAD_BUTTON_INVALID,
};
static const SDL_GamepadButton button_map_15[] = {
// 0x0001
SDL_GAMEPAD_BUTTON_SOUTH,
// 0x0002
SDL_GAMEPAD_BUTTON_EAST,
// 0x0004
SDL_GAMEPAD_BUTTON_INVALID,
// 0x0008
SDL_GAMEPAD_BUTTON_WEST,
// 0x0010
SDL_GAMEPAD_BUTTON_NORTH,
// 0x0020
SDL_GAMEPAD_BUTTON_INVALID,
// 0x0040
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
// 0x0080
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
// 0x0100
SDL_GAMEPAD_BUTTON_INVALID,
// 0x0200
SDL_GAMEPAD_BUTTON_INVALID,
// 0x0400
SDL_GAMEPAD_BUTTON_BACK,
// 0x0800
SDL_GAMEPAD_BUTTON_START,
// 0x1000
SDL_GAMEPAD_BUTTON_GUIDE,
// 0x2000
SDL_GAMEPAD_BUTTON_LEFT_STICK,
// 0x4000
SDL_GAMEPAD_BUTTON_RIGHT_STICK,
};
if (value == ctx->last_buttons) {
break;
}
ctx->last_buttons = value;
const SDL_GamepadButton *button_map;
if (field->bit_size == 12) {
button_map = button_map_12;
} else if (field->bit_size == 15) {
button_map = button_map_15;
} else {
// Should never happen
break;
}
for (int button_index = 0; button_index < field->bit_size; ++button_index, value >>= 1) {
SDL_GamepadButton button = button_map[button_index];
if (button == SDL_GAMEPAD_BUTTON_INVALID) {
continue;
}
if (button == SDL_GAMEPAD_BUTTON_BACK && ctx->has_separate_back_button) {
continue;
}
if (button == SDL_GAMEPAD_BUTTON_GUIDE && ctx->has_separate_guide_button) {
continue;
}
bool pressed = ((value & 1) != 0);
SDL_SendJoystickButton(timestamp, joystick, button, pressed);
}
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_AC_BACK):
{
bool pressed = (value != 0);
if (pressed) {
ctx->has_separate_back_button = true;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, pressed);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_AC_HOME):
{
bool pressed = (value != 0);
if (pressed) {
ctx->has_separate_guide_button = true;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, pressed);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_RECORD):
{
if (ctx->has_share_button) {
bool pressed = (value != 0);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, pressed);
}
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_ORDER_MOVIE):
{
// This value is the currently selected profile
ctx->has_unmapped_state = (value == 0);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_ASSIGN_SELECTION):
{
if (ctx->has_paddles) {
if (!ctx->has_unmapped_state) {
value = 0;
}
Uint8 button = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button
SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x1) != 0));
SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x2) != 0));
SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x4) != 0));
SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x8) != 0));
}
break;
}
case MAKE_USAGE(USB_USAGEPAGE_DEVICE_CONTROLS, USB_USAGE_DEVICE_CONTROLS_BATTERY_STRENGTH):
{
HIDAPI_DriverXboxOne_HandleBatteryState(joystick, value);
break;
}
default:
break;
}
}
return true;
}
/*
* The Xbox One Elite controller with 5.13+ firmware sends the unmapped state in a separate packet.
* We can use this to send the paddle state when they aren't mapped
@@ -810,7 +1188,7 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D
}
}
axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[2])) * 64) - 32768;
axis = ((int)LOAD16(data[2], data[3]) * 64) - 32768;
if (axis == 32704) {
axis = 32767;
}
@@ -819,7 +1197,7 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[4])) * 64) - 32768;
axis = ((int)LOAD16(data[4], data[5]) * 64) - 32768;
if (axis == -32768 && size == 26 && (data[18] & 0x40)) {
axis = 32767;
}
@@ -828,13 +1206,13 @@ static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_D
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));
axis = LOAD16(data[6], data[7]);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));
axis = LOAD16(data[8], data[9]);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);
axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));
axis = LOAD16(data[10], data[11]);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));
axis = LOAD16(data[12], data[13]);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
@@ -1030,25 +1408,25 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(SDL_Joystick *joysti
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
}
axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[9])) * 64) - 32768;
axis = ((int)LOAD16(data[9], data[10]) * 64) - 32768;
if (axis == 32704) {
axis = 32767;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[11])) * 64) - 32768;
axis = ((int)LOAD16(data[11], data[12]) * 64) - 32768;
if (axis == 32704) {
axis = 32767;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[1])) - 0x8000;
axis = (int)(Uint16)LOAD16(data[1], data[2]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[3])) - 0x8000;
axis = (int)(Uint16)LOAD16(data[3], data[4]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[5])) - 0x8000;
axis = (int)(Uint16)LOAD16(data[5], data[6]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[7])) - 0x8000;
axis = (int)(Uint16)LOAD16(data[7], data[8]) - 0x8000;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
@@ -1064,33 +1442,7 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(SDL_Joystick *joysti
static void HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
{
Uint8 flags = data[1];
bool on_usb = (((flags & 0x0C) >> 2) == 0);
SDL_PowerState state;
int percent = 0;
// Mapped percentage value from:
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate
switch (flags & 0x03) {
case 0:
percent = 10;
break;
case 1:
percent = 40;
break;
case 2:
percent = 70;
break;
case 3:
percent = 100;
break;
}
if (on_usb) {
state = SDL_POWERSTATE_CHARGING;
} else {
state = SDL_POWERSTATE_ON_BATTERY;
}
SDL_SendJoystickPowerInfo(joystick, state, percent);
HIDAPI_DriverXboxOne_HandleBatteryState(joystick, data[1]);
}
static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
@@ -1586,7 +1938,12 @@ static bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device)
#ifdef DEBUG_XBOX_PROTOCOL
HIDAPI_DumpPacket("Xbox One packet: size = %d", data, size);
#endif
if (device->is_bluetooth) {
if (ctx->descriptor) {
if (!joystick) {
break;
}
HIDAPI_DriverXboxOne_HandleDescriptorReport(joystick, ctx, data, size);
} else if (device->is_bluetooth) {
switch (data[0]) {
case 0x01:
if (!joystick) {
@@ -1645,6 +2002,8 @@ static void HIDAPI_DriverXboxOne_FreeDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
SDL_DestroyDescriptor(ctx->descriptor);
HIDAPI_GIP_DestroyChunkBuffer(ctx);
}
@@ -0,0 +1,297 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 2025 Zuiki Inc.
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"
#ifdef SDL_JOYSTICK_HIDAPI_ZUIKI
// Define this if you want to log all packets from the controller
#if 0
#define DEBUG_ZUIKI_PROTOCOL
#endif
typedef struct
{
Uint8 last_state[USB_PACKET_LENGTH];
} SDL_DriverZUIKI_Context;
static void HIDAPI_DriverZUIKI_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, callback, userdata);
}
static void HIDAPI_DriverZUIKI_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, callback, userdata);
}
static bool HIDAPI_DriverZUIKI_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_ZUIKI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
}
static bool HIDAPI_DriverZUIKI_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)
{
if (vendor_id == USB_VENDOR_ZUIKI) {
switch (product_id) {
case USB_PRODUCT_ZUIKI_MASCON_PRO:
return true;
default:
break;
}
}
return false;
}
static bool HIDAPI_DriverZUIKI_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_Context *)SDL_calloc(1, sizeof(*ctx));
if (!ctx) {
return false;
}
device->context = ctx;
if (device->product_id == USB_PRODUCT_ZUIKI_MASCON_PRO) {
HIDAPI_SetDeviceName(device, "ZUIKI MASCON PRO");
}
return HIDAPI_JoystickConnected(device, NULL);
}
static int HIDAPI_DriverZUIKI_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverZUIKI_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_DriverZUIKI_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_Context *)device->context;
SDL_AssertJoysticksLocked();
SDL_zeroa(ctx->last_state);
joystick->nbuttons = 11;
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
return true;
}
static bool HIDAPI_DriverZUIKI_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
Uint8 rumble_packet[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
rumble_packet[4] = low_frequency_rumble >> 8;
rumble_packet[5] = 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_DriverZUIKI_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverZUIKI_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
Uint32 caps = 0;
caps |= SDL_JOYSTICK_CAP_RUMBLE;
return caps;
}
static bool HIDAPI_DriverZUIKI_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverZUIKI_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
if (SDL_HIDAPI_SendRumble(device, data, size) != size) {
return SDL_SetError("Couldn't send rumble packet");
}
return true;
}
static bool HIDAPI_DriverZUIKI_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
{
return SDL_Unsupported();
}
static void HIDAPI_DriverZUIKI_HandleOldStatePacket(SDL_Joystick *joystick, SDL_DriverZUIKI_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS();
if (ctx->last_state[2] != data[2]) {
Uint8 hat;
switch (data[2]) {
case 0:
hat = SDL_HAT_UP;
break;
case 1:
hat = SDL_HAT_RIGHTUP;
break;
case 2:
hat = SDL_HAT_RIGHT;
break;
case 3:
hat = SDL_HAT_RIGHTDOWN;
break;
case 4:
hat = SDL_HAT_DOWN;
break;
case 5:
hat = SDL_HAT_LEFTDOWN;
break;
case 6:
hat = SDL_HAT_LEFT;
break;
case 7:
hat = SDL_HAT_LEFTUP;
break;
default:
hat = SDL_HAT_CENTERED;
break;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
}
if (ctx->last_state[0] != data[0]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x01) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x04) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x20) != 0));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[0] & 0x40) ? SDL_MAX_SINT16 : SDL_MIN_SINT16);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[0] & 0x80) ? SDL_MAX_SINT16 : SDL_MIN_SINT16);
}
if (ctx->last_state[1] != data[1]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x01) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x02) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x04) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1, ((data[1] & 0x20) != 0));
/* todo for switch C key */
}
#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(3);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = READ_STICK_AXIS(4);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = READ_STICK_AXIS(5);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = READ_STICK_AXIS(6);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
}
#undef READ_STICK_AXIS
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static bool HIDAPI_DriverZUIKI_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverZUIKI_Context *ctx = (SDL_DriverZUIKI_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_ZUIKI_PROTOCOL
HIDAPI_DumpPacket("ZUIKI packet: size = %d", data, size);
#endif
if (!joystick) {
continue;
}
if (size == 8) {
HIDAPI_DriverZUIKI_HandleOldStatePacket(joystick, ctx, data, size);
}
}
if (size < 0) {
// Read error, device is disconnected
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
return (size >= 0);
}
static void HIDAPI_DriverZUIKI_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
}
static void HIDAPI_DriverZUIKI_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverZUIKI = {
SDL_HINT_JOYSTICK_HIDAPI_ZUIKI,
true,
HIDAPI_DriverZUIKI_RegisterHints,
HIDAPI_DriverZUIKI_UnregisterHints,
HIDAPI_DriverZUIKI_IsEnabled,
HIDAPI_DriverZUIKI_IsSupportedDevice,
HIDAPI_DriverZUIKI_InitDevice,
HIDAPI_DriverZUIKI_GetDevicePlayerIndex,
HIDAPI_DriverZUIKI_SetDevicePlayerIndex,
HIDAPI_DriverZUIKI_UpdateDevice,
HIDAPI_DriverZUIKI_OpenJoystick,
HIDAPI_DriverZUIKI_RumbleJoystick,
HIDAPI_DriverZUIKI_RumbleJoystickTriggers,
HIDAPI_DriverZUIKI_GetJoystickCapabilities,
HIDAPI_DriverZUIKI_SetJoystickLED,
HIDAPI_DriverZUIKI_SendJoystickEffect,
HIDAPI_DriverZUIKI_SetJoystickSensorsEnabled,
HIDAPI_DriverZUIKI_CloseJoystick,
HIDAPI_DriverZUIKI_FreeDevice,
};
#endif // SDL_JOYSTICK_HIDAPI_ZUIKI
#endif // SDL_JOYSTICK_HIDAPI
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -70,11 +70,17 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = {
#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
&SDL_HIDAPI_DriverSteamDeck,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
&SDL_HIDAPI_DriverSteamTriton,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_SWITCH
&SDL_HIDAPI_DriverNintendoClassic,
&SDL_HIDAPI_DriverJoyCons,
&SDL_HIDAPI_DriverSwitch,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_SWITCH2
&SDL_HIDAPI_DriverSwitch2,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_WII
&SDL_HIDAPI_DriverWii,
#endif
@@ -82,9 +88,27 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = {
&SDL_HIDAPI_DriverXbox360,
&SDL_HIDAPI_DriverXbox360W,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_GIP
&SDL_HIDAPI_DriverGIP,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
&SDL_HIDAPI_DriverXboxOne,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_LG4FF
&SDL_HIDAPI_DriverLg4ff,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_8BITDO
&SDL_HIDAPI_Driver8BitDo,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI
&SDL_HIDAPI_DriverFlydigi,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_SINPUT
&SDL_HIDAPI_DriverSInput,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_ZUIKI
&SDL_HIDAPI_DriverZUIKI,
#endif
};
static int SDL_HIDAPI_numdrivers = 0;
static SDL_AtomicInt SDL_HIDAPI_updating_devices;
@@ -133,7 +157,7 @@ void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size)
current_len += SDL_snprintf(&buffer[current_len], length - current_len, " 0x%.2x", data[i]);
}
SDL_strlcat(buffer, "\n", length);
SDL_Log("%s", buffer);
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "%s", buffer);
SDL_free(buffer);
}
@@ -288,9 +312,13 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
0x1532, // Razer
0x20d6, // PowerA
0x24c6, // PowerA
0x294b, // Snakebyte
0x2dc8, // 8BitDo
0x2e24, // Hyperkin
0x2e95, // SCUF
0x3285, // Nacon
0x3537, // GameSir
0x366c, // ByoWave
};
int i;
@@ -338,7 +366,7 @@ static SDL_HIDAPI_DeviceDriver *HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device
return NULL;
}
if (device->vendor_id != USB_VENDOR_VALVE) {
if (device->vendor_id != USB_VENDOR_VALVE && device->vendor_id != USB_VENDOR_FLYDIGI_V1 && device->vendor_id != USB_VENDOR_FLYDIGI_V2) {
if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) {
return NULL;
}
@@ -430,7 +458,9 @@ static void HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device, bool *removed) S
if (device->driver) {
bool enabled;
if (device->vendor_id == USB_VENDOR_NINTENDO && device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) {
if (device->vendor_id == USB_VENDOR_NINTENDO &&
(device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR ||
device->product_id == USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR)) {
enabled = SDL_HIDAPI_combine_joycons;
} else {
enabled = device->driver->enabled;
@@ -1046,7 +1076,11 @@ static bool HIDAPI_CreateCombinedJoyCons(void)
SDL_zero(info);
info.path = "nintendo_joycons_combined";
info.vendor_id = USB_VENDOR_NINTENDO;
info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
if (joycons[0]->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) {
info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
} else {
info.product_id = USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR;
}
info.interface_number = -1;
info.usage_page = USB_USAGEPAGE_GENERIC_DESKTOP;
info.usage = USB_USAGE_GENERIC_GAMEPAD;
@@ -1274,6 +1308,16 @@ bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version,
*/
SDL_LockJoysticks();
for (device = SDL_HIDAPI_devices; device; device = device->next) {
// The HIDAPI functionality will be available when the FlyDigi Space Station app has
// enabled third party controller mapping, so the driver needs to be active to watch
// for that change. Since this is dynamic and we don't have a way to re-trigger device
// changes when that happens, we'll pretend the driver isn't available so the XInput
// interface will always show up (but won't have any input when the controller is in
// enhanced mode)
if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) {
continue;
}
if (device->driver &&
HIDAPI_IsEquivalentToDevice(vendor_id, product_id, device)) {
result = true;
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -35,11 +35,21 @@
#define SDL_JOYSTICK_HIDAPI_STEAM
#define SDL_JOYSTICK_HIDAPI_STEAMDECK
#define SDL_JOYSTICK_HIDAPI_SWITCH
#ifdef HAVE_LIBUSB
#define SDL_JOYSTICK_HIDAPI_SWITCH2
#endif
#define SDL_JOYSTICK_HIDAPI_WII
#define SDL_JOYSTICK_HIDAPI_XBOX360
#define SDL_JOYSTICK_HIDAPI_XBOXONE
#define SDL_JOYSTICK_HIDAPI_SHIELD
#define SDL_JOYSTICK_HIDAPI_STEAM_HORI
#define SDL_JOYSTICK_HIDAPI_STEAM_TRITON
#define SDL_JOYSTICK_HIDAPI_LG4FF
#define SDL_JOYSTICK_HIDAPI_8BITDO
#define SDL_JOYSTICK_HIDAPI_FLYDIGI
#define SDL_JOYSTICK_HIDAPI_GIP
#define SDL_JOYSTICK_HIDAPI_SINPUT
#define SDL_JOYSTICK_HIDAPI_ZUIKI
// Joystick capability definitions
#define SDL_JOYSTICK_CAP_MONO_LED 0x00000001
@@ -139,6 +149,7 @@ typedef struct SDL_HIDAPI_DeviceDriver
// HIDAPI device support
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic;
@@ -152,11 +163,24 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch2;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamTriton;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverZUIKI;
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \
(((Uint32)(B)) << 8) | \
(((Uint32)(C)) << 16) | \
(((Uint32)(D)) << 24))
// Return true if a HID device is present and supported as a joystick of the given type
extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type);
@@ -0,0 +1,616 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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"
#include "SDL_report_descriptor.h"
// This is a very simple (and non-compliant!) report descriptor parser
// used to quickly parse Xbox Bluetooth reports
typedef enum
{
DescriptorItemTypeMain = 0,
DescriptorItemTypeGlobal = 1,
DescriptorItemTypeLocal = 2,
DescriptorItemTypeReserved = 3,
} ItemType;
typedef enum
{
MainTagInput = 0x8,
MainTagOutput = 0x9,
MainTagFeature = 0xb,
MainTagCollection = 0xa,
MainTagEndCollection = 0xc,
} MainTag;
typedef enum
{
MainFlagConstant = 0x0001,
MainFlagVariable = 0x0002,
MainFlagRelative = 0x0004,
MainFlagWrap = 0x0008,
MainFlagNonLinear = 0x0010,
MainFlagNoPreferred = 0x0020,
MainFlagNullState = 0x0040,
MainFlagVolatile = 0x0080,
MainFlagBufferedBytes = 0x0100,
} MainFlag;
typedef enum
{
GlobalTagUsagePage = 0x0,
GlobalTagLogicalMinimum = 0x1,
GlobalTagLogicalMaximum = 0x2,
GlobalTagPhysicalMinimum = 0x3,
GlobalTagPhysicalMaximum = 0x4,
GlobalTagUnitExponent = 0x5,
GlobalTagUnit = 0x6,
GlobalTagReportSize = 0x7,
GlobalTagReportID = 0x8,
GlobalTagReportCount = 0x9,
GlobalTagPush = 0xa,
GlobalTagPop = 0xb,
} GlobalTag;
typedef enum
{
LocalTagUsage = 0x0,
LocalTagUsageMinimum = 0x1,
LocalTagUsageMaximum = 0x2,
LocalTagDesignatorIndex = 0x3,
LocalTagDesignatorMinimum = 0x4,
LocalTagDesignatorMaximum = 0x5,
LocalTagStringIndex = 0x7,
LocalTagStringMinimum = 0x8,
LocalTagStringMaximum = 0x9,
LocalTagDelimiter = 0xa,
} LocalTag;
typedef struct
{
Uint32 usage_page;
Uint32 report_size;
Uint32 report_count;
Uint32 report_id;
} DescriptorGlobalState;
typedef struct
{
Uint32 usage_minimum;
Uint32 usage_maximum;
int usage_maxcount;
int usage_count;
Uint32 *usages;
} DescriptorLocalState;
typedef struct
{
int collection_depth;
DescriptorGlobalState global;
DescriptorLocalState local;
int field_maxcount;
int field_count;
int field_offset;
DescriptorInputField *fields;
} DescriptorContext;
static void DebugDescriptor(DescriptorContext *ctx, const char *fmt, ...)
{
#ifdef DEBUG_DESCRIPTOR
va_list ap;
va_start(ap, fmt);
char *message = NULL;
SDL_vasprintf(&message, fmt, ap);
va_end(ap);
if (ctx->collection_depth > 0) {
size_t len = 4 * ctx->collection_depth + SDL_strlen(message) + 1;
char *output = (char *)SDL_malloc(len);
if (output) {
SDL_memset(output, ' ', 4 * ctx->collection_depth);
output[4 * ctx->collection_depth] = '\0';
SDL_strlcat(output, message, len);
SDL_free(message);
message = output;
}
}
SDL_Log("%s", message);
SDL_free(message);
#endif // DEBUG_DESCRIPTOR
}
static void DebugMainTag(DescriptorContext *ctx, const char *tag, Uint32 flags)
{
#ifdef DEBUG_DESCRIPTOR
char message[1024] = { 0 };
SDL_strlcat(message, tag, sizeof(message));
SDL_strlcat(message, "(", sizeof(message));
if (flags & MainFlagConstant) {
SDL_strlcat(message, " Constant", sizeof(message));
} else {
SDL_strlcat(message, " Data", sizeof(message));
}
if (flags & MainFlagVariable) {
SDL_strlcat(message, " Variable", sizeof(message));
} else {
SDL_strlcat(message, " Array", sizeof(message));
}
if (flags & MainFlagRelative) {
SDL_strlcat(message, " Relative", sizeof(message));
} else {
SDL_strlcat(message, " Absolute", sizeof(message));
}
if (flags & MainFlagWrap) {
SDL_strlcat(message, " Wrap", sizeof(message));
} else {
SDL_strlcat(message, " No Wrap", sizeof(message));
}
if (flags & MainFlagNonLinear) {
SDL_strlcat(message, " Non Linear", sizeof(message));
} else {
SDL_strlcat(message, " Linear", sizeof(message));
}
if (flags & MainFlagNoPreferred) {
SDL_strlcat(message, " No Preferred", sizeof(message));
} else {
SDL_strlcat(message, " Preferred State", sizeof(message));
}
if (flags & MainFlagNullState) {
SDL_strlcat(message, " Null State", sizeof(message));
} else {
SDL_strlcat(message, " No Null Position", sizeof(message));
}
if (flags & MainFlagVolatile) {
SDL_strlcat(message, " Volatile", sizeof(message));
} else {
SDL_strlcat(message, " Non Volatile", sizeof(message));
}
if (flags & MainFlagBufferedBytes) {
SDL_strlcat(message, " Buffered Bytes", sizeof(message));
} else {
SDL_strlcat(message, " Bit Field", sizeof(message));
}
SDL_strlcat(message, " )", sizeof(message));
DebugDescriptor(ctx, "%s", message);
#endif // DEBUG_DESCRIPTOR
}
static Uint32 ReadValue(const Uint8 *data, int size)
{
Uint32 value = 0;
int shift = 0;
while (size--) {
value |= ((Uint32)(*data++)) << shift;
shift += 8;
}
return value;
}
static void ResetLocalState(DescriptorContext *ctx)
{
ctx->local.usage_minimum = 0;
ctx->local.usage_maximum = 0;
ctx->local.usage_count = 0;
}
static bool AddUsage(DescriptorContext *ctx, Uint32 usage)
{
if (ctx->local.usage_count == ctx->local.usage_maxcount) {
int usage_maxcount = ctx->local.usage_maxcount + 4;
Uint32 *usages = (Uint32 *)SDL_realloc(ctx->local.usages, usage_maxcount * sizeof(*usages));
if (!usages) {
return false;
}
ctx->local.usages = usages;
ctx->local.usage_maxcount = usage_maxcount;
}
if (usage <= 0xFFFF) {
usage |= (ctx->global.usage_page << 16);
}
ctx->local.usages[ctx->local.usage_count++] = usage;
return true;
}
static bool AddInputField(DescriptorContext *ctx, Uint32 usage, int bit_size)
{
if (ctx->field_count == ctx->field_maxcount) {
int field_maxcount = ctx->field_maxcount + 4;
DescriptorInputField *fields = (DescriptorInputField *)SDL_realloc(ctx->fields, field_maxcount * sizeof(*fields));
if (!fields) {
return false;
}
ctx->fields = fields;
ctx->field_maxcount = field_maxcount;
}
DescriptorInputField *field = &ctx->fields[ctx->field_count++];
field->report_id = (Uint8)ctx->global.report_id;
field->usage = usage;
field->bit_offset = ctx->field_offset;
field->bit_size = bit_size;
DebugDescriptor(ctx, "Adding report %d field 0x%.8x size %d bits at bit offset %d", field->report_id, field->usage, field->bit_size, field->bit_offset);
return true;
}
static bool AddInputFields(DescriptorContext *ctx)
{
Uint32 usage = 0;
if (ctx->global.report_count == 0 || ctx->global.report_size == 0) {
return true;
}
if (ctx->local.usage_count == 0 &&
ctx->local.usage_minimum > 0 &&
ctx->local.usage_maximum >= ctx->local.usage_minimum) {
for (usage = ctx->local.usage_minimum; usage <= ctx->local.usage_maximum; ++usage) {
if (!AddUsage(ctx, usage)) {
return false;
}
}
}
int usage_index = 0;
for (Uint32 i = 0; i < ctx->global.report_count; ++i) {
if (usage_index < ctx->local.usage_count) {
usage = ctx->local.usages[usage_index];
if (usage_index < (ctx->local.usage_count - 1)) {
++usage_index;
}
}
int size = (int)ctx->global.report_size;
if (usage > 0) {
if (!AddInputField(ctx, usage, size)) {
return false;
}
}
ctx->field_offset += size;
}
return true;
}
static bool ParseMainItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data)
{
Uint32 flags;
switch (tag) {
case MainTagInput:
flags = ReadValue(data, size);
DebugMainTag(ctx, "MainTagInput", flags);
AddInputFields(ctx);
break;
case MainTagOutput:
flags = ReadValue(data, size);
DebugMainTag(ctx, "MainTagOutput", flags);
break;
case MainTagFeature:
flags = ReadValue(data, size);
DebugMainTag(ctx, "MainTagFeature", flags);
break;
case MainTagCollection:
DebugDescriptor(ctx, "MainTagCollection");
switch (*data) {
case 0x00:
DebugDescriptor(ctx, "Physical");
break;
case 0x01:
DebugDescriptor(ctx, "Application");
break;
case 0x02:
DebugDescriptor(ctx, "Logical");
break;
case 0x03:
DebugDescriptor(ctx, "Report");
break;
case 0x04:
DebugDescriptor(ctx, "Named Array");
break;
case 0x05:
DebugDescriptor(ctx, "Usage Switch");
break;
case 0x06:
DebugDescriptor(ctx, "Usage Modifier");
break;
default:
break;
}
++ctx->collection_depth;
break;
case MainTagEndCollection:
if (ctx->collection_depth > 0) {
--ctx->collection_depth;
}
DebugDescriptor(ctx, "MainTagEndCollection");
break;
default:
DebugDescriptor(ctx, "Unknown main tag: %d", tag);
break;
}
ResetLocalState(ctx);
return true;
}
static bool ParseGlobalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data)
{
Uint32 value;
switch (tag) {
case GlobalTagUsagePage:
ctx->global.usage_page = ReadValue(data, size);
DebugDescriptor(ctx, "GlobalTagUsagePage: 0x%.4x", ctx->global.usage_page);
break;
case GlobalTagLogicalMinimum:
value = ReadValue(data, size);
DebugDescriptor(ctx, "GlobalTagLogicalMinimum: %u", value);
break;
case GlobalTagLogicalMaximum:
value = ReadValue(data, size);
DebugDescriptor(ctx, "GlobalTagLogicalMaximum: %u", value);
break;
case GlobalTagPhysicalMinimum:
value = ReadValue(data, size);
DebugDescriptor(ctx, "GlobalTagPhysicalMinimum: %u", value);
break;
case GlobalTagPhysicalMaximum:
value = ReadValue(data, size);
DebugDescriptor(ctx, "GlobalTagPhysicalMaximum: %u", value);
break;
case GlobalTagUnitExponent:
DebugDescriptor(ctx, "GlobalTagUnitExponent");
break;
case GlobalTagUnit:
DebugDescriptor(ctx, "GlobalTagUnit");
break;
case GlobalTagReportSize:
ctx->global.report_size = ReadValue(data, size);
DebugDescriptor(ctx, "GlobalTagReportSize: %u", ctx->global.report_size);
break;
case GlobalTagReportID:
ctx->global.report_id = ReadValue(data, size);
ctx->field_offset = 0;
DebugDescriptor(ctx, "GlobalTagReportID: %u", ctx->global.report_id);
break;
case GlobalTagReportCount:
ctx->global.report_count = ReadValue(data, size);
DebugDescriptor(ctx, "GlobalTagReportCount: %u", ctx->global.report_count);
break;
case GlobalTagPush:
DebugDescriptor(ctx, "GlobalTagPush");
break;
case GlobalTagPop:
DebugDescriptor(ctx, "GlobalTagPop");
break;
default:
DebugDescriptor(ctx, "Unknown global tag");
break;
}
return true;
}
static bool ParseLocalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data)
{
Uint32 value;
switch (tag) {
case LocalTagUsage:
value = ReadValue(data, size);
AddUsage(ctx, value);
DebugDescriptor(ctx, "LocalTagUsage: 0x%.4x", value);
break;
case LocalTagUsageMinimum:
ctx->local.usage_minimum = ReadValue(data, size);
DebugDescriptor(ctx, "LocalTagUsageMinimum: 0x%.4x", ctx->local.usage_minimum);
break;
case LocalTagUsageMaximum:
ctx->local.usage_maximum = ReadValue(data, size);
DebugDescriptor(ctx, "LocalTagUsageMaximum: 0x%.4x", ctx->local.usage_maximum);
break;
case LocalTagDesignatorIndex:
DebugDescriptor(ctx, "LocalTagDesignatorIndex");
break;
case LocalTagDesignatorMinimum:
DebugDescriptor(ctx, "LocalTagDesignatorMinimum");
break;
case LocalTagDesignatorMaximum:
DebugDescriptor(ctx, "LocalTagDesignatorMaximum");
break;
case LocalTagStringIndex:
DebugDescriptor(ctx, "LocalTagStringIndex");
break;
case LocalTagStringMinimum:
DebugDescriptor(ctx, "LocalTagStringMinimum");
break;
case LocalTagStringMaximum:
DebugDescriptor(ctx, "LocalTagStringMaximum");
break;
case LocalTagDelimiter:
DebugDescriptor(ctx, "LocalTagDelimiter");
break;
default:
DebugDescriptor(ctx, "Unknown local tag");
break;
}
return true;
}
static bool ParseDescriptor(DescriptorContext *ctx, const Uint8 *descriptor, int descriptor_size)
{
SDL_zerop(ctx);
for (const Uint8 *here = descriptor; here < descriptor + descriptor_size; ) {
static const int sizes[4] = { 0, 1, 2, 4 };
Uint8 data = *here++;
int size = sizes[(data & 0x3)];
int type = ((data >> 2) & 0x3);
int tag = (data >> 4);
if ((here + size) > (descriptor + descriptor_size)) {
return SDL_SetError("Invalid descriptor");
}
#ifdef DEBUG_DESCRIPTOR
SDL_Log("Data: 0x%.2x, size: %d, type: %d, tag: %d", data, size, type, tag);
#endif
switch (type) {
case DescriptorItemTypeMain:
if (!ParseMainItem(ctx, tag, size, here)) {
return false;
}
break;
case DescriptorItemTypeGlobal:
if (!ParseGlobalItem(ctx, tag, size, here)) {
return false;
}
break;
case DescriptorItemTypeLocal:
if (!ParseLocalItem(ctx, tag, size, here)) {
return false;
}
break;
case DescriptorItemTypeReserved:
// Long items are currently unsupported
return SDL_Unsupported();
}
here += size;
}
return true;
}
static void CleanupContext(DescriptorContext *ctx)
{
SDL_free(ctx->local.usages);
SDL_free(ctx->fields);
}
SDL_ReportDescriptor *SDL_ParseReportDescriptor(const Uint8 *descriptor, int descriptor_size)
{
SDL_ReportDescriptor *result = NULL;
DescriptorContext ctx;
if (ParseDescriptor(&ctx, descriptor, descriptor_size)) {
result = (SDL_ReportDescriptor *)SDL_malloc(sizeof(*result));
if (result) {
result->field_count = ctx.field_count;
result->fields = ctx.fields;
ctx.fields = NULL;
}
}
CleanupContext(&ctx);
return result;
}
bool SDL_DescriptorHasUsage(SDL_ReportDescriptor *descriptor, Uint16 usage_page, Uint16 usage)
{
if (!descriptor) {
return false;
}
Uint32 full_usage = (((Uint32)usage_page << 16) | usage);
for (int i = 0; i < descriptor->field_count; ++i) {
if (descriptor->fields[i].usage == full_usage) {
return true;
}
}
return false;
}
void SDL_DestroyDescriptor(SDL_ReportDescriptor *descriptor)
{
if (descriptor) {
SDL_free(descriptor->fields);
SDL_free(descriptor);
}
}
bool SDL_ReadReportData(const Uint8 *data, int size, int bit_offset, int bit_size, Uint32 *value)
{
int offset = (bit_offset / 8);
if (offset >= size) {
*value = 0;
return SDL_SetError("Out of bounds reading report data");
}
*value = ReadValue(data + offset, (bit_size + 7) / 8);
int shift = (bit_offset % 8);
if (shift > 0) {
*value >>= shift;
}
switch (bit_size) {
case 1:
*value &= 0x1;
break;
case 4:
*value &= 0xf;
break;
case 10:
*value &= 0x3ff;
break;
case 15:
*value &= 0x7fff;
break;
default:
SDL_assert((bit_size % 8) == 0);
break;
}
return true;
}
#ifdef TEST_MAIN
#include <SDL3/SDL_main.h>
int main(int argc, char *argv[])
{
const char *file = argv[1];
if (argc < 2) {
SDL_Log("Usage: %s file", argv[0]);
return 1;
}
size_t descriptor_size = 0;
Uint8 *descriptor = SDL_LoadFile(argv[1], &descriptor_size);
if (!descriptor) {
SDL_Log("Couldn't load %s: %s", argv[1], SDL_GetError());
return 2;
}
DescriptorContext ctx;
if (!ParseDescriptor(&ctx, descriptor, descriptor_size)) {
SDL_Log("Couldn't parse %s: %s", argv[1], SDL_GetError());
return 3;
}
return 0;
}
#endif // TEST_MAIN
@@ -0,0 +1,40 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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"
typedef struct
{
Uint8 report_id;
Uint32 usage;
int bit_offset;
int bit_size;
} DescriptorInputField;
typedef struct
{
int field_count;
DescriptorInputField *fields;
} SDL_ReportDescriptor;
extern SDL_ReportDescriptor *SDL_ParseReportDescriptor(const Uint8 *descriptor, int descriptor_size);
extern bool SDL_DescriptorHasUsage(SDL_ReportDescriptor *descriptor, Uint16 usage_page, Uint16 usage);
extern void SDL_DestroyDescriptor(SDL_ReportDescriptor *descriptor);
extern bool SDL_ReadReportData(const Uint8 *data, int size, int bit_offset, int bit_size, Uint32 *value);
@@ -353,7 +353,7 @@ enum GamepadButtons
// Mode adjust
enum ModeAdjustModes
{
MODE_ADJUST_SENSITITY=1,
MODE_ADJUST_SENSITIVITY=1,
MODE_ADJUST_LEFT_PAD_SECONDARY_MODE,
MODE_ADJUST_RIGHT_PAD_SECONDARY_MODE,
MODE_ADJUST_COUNT
@@ -364,8 +364,8 @@ typedef enum
{
ATTRIB_UNIQUE_ID,
ATTRIB_PRODUCT_ID,
ATTRIB_PRODUCT_REVISON, // deprecated
ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing
ATTRIB_PRODUCT_REVISION, // deprecated
ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISION, // intentional aliasing
ATTRIB_FIRMWARE_VERSION, // deprecated
ATTRIB_FIRMWARE_BUILD_TIME,
ATTRIB_RADIO_FIRMWARE_BUILD_TIME,
@@ -411,6 +411,12 @@ typedef enum {
TRACKPAD_NUM_MODES
} TrackpadDPadMode;
typedef enum
{
LIZARD_MODE_OFF,
LIZARD_MODE_ON,
} LizardModeState_t;
// Read-write controller settings (only add to this enum and never change the order)
typedef enum
{
@@ -423,7 +429,7 @@ typedef enum
SETTING_USB_DEBUG_MODE,
SETTING_LEFT_TRACKPAD_MODE,
SETTING_RIGHT_TRACKPAD_MODE,
SETTING_MOUSE_POINTER_ENABLED,
SETTING_LIZARD_MODE,
// 10
SETTING_DPAD_DEADZONE,
@@ -112,7 +112,7 @@ typedef struct {
int16_t dur_ms; // Duration of tone / rumble (if applicable) (neg = infinite)
uint16_t noise_intensity;
uint16_t lfo_freq; // Drives both tone and rumble geneators
uint16_t lfo_freq; // Drives both tone and rumble generators
uint8_t lfo_depth; // percentage, typically 100
uint8_t rand_tone_gain; // Randomize each LFO cycle's gain
uint8_t script_id; // Used w/ dBgain for scripted haptics
@@ -154,6 +154,100 @@ typedef struct
} FeatureReportMsg;
// Triton and derivatives utilize output reports for haptic commands. This is a
// snapshot from Nov 2024 -- things may change.
// Triton Output Report Lengths #defs include +1 for the OR ID
// Output Report Haptic Messages for Triton
typedef struct
{
uint8_t type;
uint16_t intensity;
struct
{
uint16_t speed;
int8_t gain;
} left, right;
} MsgHapticRumble;
#define HID_RUMBLE_OUTPUT_REPORT_BYTES 10
typedef struct
{
uint8_t side;
uint16_t on_us;
uint16_t off_us;
uint16_t repeat_count;
uint16_t gain_db;
} MsgHapticPulse;
#define HID_HAPTIC_PULSE_OUTPUT_REPORT_BYTES 10
typedef struct
{
uint8_t side;
uint8_t command;
int8_t gain_db;
} MsgHapticCommand;
#define HID_HAPTIC_COMMAND_REPORT_BYTES 4
typedef struct
{
uint8_t side;
int8_t gain_db;
uint16_t frequency;
uint16_t duration_ms;
uint16_t lfo_freq;
uint8_t lfo_depth;
} MsgHapticLfoTone;
#define HID_HAPTIC_LFO_TONE_REPORT_BYTES 10
typedef struct
{
uint8_t side;
int8_t gain_db;
uint16_t duration_ms;
struct
{
uint16_t frequency;
} start, end;
} MsgHapticLogSweep;
#define HID_HAPTIC_LOG_SWEEP_REPORT_BYTES 9
typedef struct m
{
uint8_t side;
uint8_t script_id;
int8_t gain_db;
} MsgHapticScript;
#define HID_HAPTIC_SCRIPT_REPORT_BYTES 4
typedef enum
{
ID_OUT_REPORT_HAPTIC_RUMBLE = 0x80,
ID_OUT_REPORT_HAPTIC_PULSE = 0x81,
ID_OUT_REPORT_HAPTIC_COMMAND = 0x82,
ID_OUT_REPORT_HAPTIC_LFO_TONE = 0x83,
ID_OUT_REPORT_HAPTIC_LOG_SWEEP = 0x85,
ID_OUT_REPORT_HAPTIC_SCRIPT = 0x86,
} ValveTritonOutReportMessageIDs;
typedef struct
{
uint8_t report_id;
union
{
MsgHapticRumble hapticRumble;
MsgHapticPulse hapticPulse;
MsgHapticCommand hapticCommand;
MsgHapticLfoTone hapticLfoTone;
MsgHapticLogSweep hapticLogSweep;
MsgHapticScript hapticScript;
} payload;
} OutputReportMsg;
// Roll this version forward anytime that you are breaking compatibility of existing
// message types within ValveInReport_t or the header itself. Hopefully this should
// be super rare and instead you should just add new message payloads to the union,
@@ -261,7 +355,7 @@ typedef struct
short sRightPadX;
short sRightPadY;
//This mimcs how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode
//This mimics how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode
unsigned char ucGyroDataType; //TODO could maybe find some unused bits in the button field for this info (is only 2bits)
short sGyro[4];
@@ -413,51 +507,129 @@ typedef struct
unsigned short sPressurePadRight;
} SteamDeckStatePacket_t;
typedef struct
{
ValveInReportHeader_t header;
union
{
ValveControllerStatePacket_t controllerState;
ValveControllerBLEStatePacket_t controllerBLEState;
ValveControllerDebugPacket_t debugState;
ValveControllerTrackpadImage_t padImage;
ValveControllerRawTrackpadImage_t rawPadImage;
SteamControllerWirelessEvent_t wirelessEvent;
SteamControllerStatusEvent_t statusEvent;
SteamDeckStatePacket_t deckState;
} payload;
ValveInReportHeader_t header;
union
{
ValveControllerStatePacket_t controllerState;
ValveControllerBLEStatePacket_t controllerBLEState;
ValveControllerDebugPacket_t debugState;
ValveControllerTrackpadImage_t padImage;
ValveControllerRawTrackpadImage_t rawPadImage;
SteamControllerWirelessEvent_t wirelessEvent;
SteamControllerStatusEvent_t statusEvent;
SteamDeckStatePacket_t deckState;
} payload;
} ValveInReport_t;
// Enumeration for BLE packet protocol
enum EBLEPacketReportNums
{
// Skipping past 2-3 because they are escape characters in Uart protocol
k_EBLEReportState = 4,
k_EBLEReportStatus = 5,
k_EBLEReportState = 4,
k_EBLEReportStatus = 5,
};
// Enumeration of data chunks in BLE state packets
enum EBLEOptionDataChunksBitmask
{
// First byte upper nibble
k_EBLEButtonChunk1 = 0x10,
k_EBLEButtonChunk2 = 0x20,
k_EBLEButtonChunk3 = 0x40,
k_EBLELeftJoystickChunk = 0x80,
// First byte upper nibble
k_EBLEButtonChunk1 = 0x10,
k_EBLEButtonChunk2 = 0x20,
k_EBLEButtonChunk3 = 0x40,
k_EBLELeftJoystickChunk = 0x80,
// Second full byte
k_EBLELeftTrackpadChunk = 0x100,
k_EBLERightTrackpadChunk = 0x200,
k_EBLEIMUAccelChunk = 0x400,
k_EBLEIMUGyroChunk = 0x800,
k_EBLEIMUQuatChunk = 0x1000,
// Second full byte
k_EBLELeftTrackpadChunk = 0x100,
k_EBLERightTrackpadChunk = 0x200,
k_EBLEIMUAccelChunk = 0x400,
k_EBLEIMUGyroChunk = 0x800,
k_EBLEIMUQuatChunk = 0x1000,
};
// Triton and derivatives do not use the ValveInReport_t structure
enum ETritonReportIDTypes
{
ID_TRITON_CONTROLLER_STATE = 0x42,
ID_TRITON_BATTERY_STATUS = 0x43,
ID_TRITON_CONTROLLER_STATE_BLE = 0x45,
ID_TRITON_WIRELESS_STATUS_X = 0x46,
ID_TRITON_WIRELESS_STATUS = 0x79,
};
enum ETritonWirelessState
{
k_ETritonWirelessStateDisconnect = 1,
k_ETritonWirelessStateConnect = 2,
};
typedef struct
{
uint32_t uTimestamp;
short sAccelX;
short sAccelY;
short sAccelZ;
short sGyroX;
short sGyroY;
short sGyroZ;
short sGyroQuatW;
short sGyroQuatX;
short sGyroQuatY;
short sGyroQuatZ;
} TritonMTUIMU_t;
typedef struct
{
uint8_t cSeq_num;
uint32_t uButtons;
short sTriggerLeft;
short sTriggerRight;
short sLeftStickX;
short sLeftStickY;
short sRightStickX;
short sRightStickY;
short sLeftPadX;
short sLeftPadY;
unsigned short ucPressureLeft;
short sRightPadX;
short sRightPadY;
unsigned short ucPressureRight;
TritonMTUIMU_t imu;
} TritonMTUFull_t;
enum EChargeState
{
k_EChargeStateReset,
k_EChargeStateDischarging,
k_EChargeStateCharging,
k_EChargeStateSrcValidate,
k_EChargeStateChargingDone,
};
typedef struct
{
unsigned char ucChargeState; // EChargeState
unsigned char ucBatteryLevel;
unsigned short sBatteryVoltage;
unsigned short sSystemVoltage;
unsigned short sInputVoltage;
unsigned short sCurrent;
unsigned short sInputCurrent;
unsigned short sTemperature;
} TritonBatteryStatus_t;
typedef struct
{
unsigned char state;
} TritonWirelessStatus_t;
#pragma pack()
#endif // _CONTROLLER_STRUCTS
+151 -33
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -75,6 +75,18 @@
#ifndef BTN_DPAD_RIGHT
#define BTN_DPAD_RIGHT 0x223
#endif
#ifndef BTN_GRIPL
#define BTN_GRIPL 0x224
#endif
#ifndef BTN_GRIPR
#define BTN_GRIPR 0x225
#endif
#ifndef BTN_GRIPL2
#define BTN_GRIPL2 0x226
#endif
#ifndef BTN_GRIPR2
#define BTN_GRIPR2 0x227
#endif
#ifndef BTN_TRIGGER_HAPPY
#define BTN_TRIGGER_HAPPY 0x2c0
@@ -150,7 +162,10 @@ typedef struct SDL_joylist_item
{
SDL_JoystickID device_instance;
char *path; // "/dev/input/event2" or whatever
Uint16 vendor;
Uint16 product;
char *name; // "SideWinder 3D Pro" or whatever
char *driver; // "xpad" or whatever
SDL_GUID guid;
dev_t devnum;
int steam_virtual_gamepad_slot;
@@ -274,19 +289,20 @@ static bool GuessIsSensor(int fd)
return false;
}
static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid)
static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid, char **driver_return)
{
struct input_id inpid;
char *name;
char *name = NULL;
char *driver = NULL;
char product_string[128];
int class = 0;
SDL_zero(inpid);
#ifdef SDL_USE_LIBUDEV
// Opening input devices can generate synchronous device I/O, so avoid it if we can
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, &driver) &&
!(class & SDL_UDEV_DEVICE_JOYSTICK)) {
return false;
goto error;
}
#endif
@@ -294,34 +310,33 @@ static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *ve
*fd = open(path, O_RDONLY | O_CLOEXEC, 0);
}
if (!fd || *fd < 0) {
return false;
goto error;
}
if (ioctl(*fd, JSIOCGNAME(sizeof(product_string)), product_string) <= 0) {
// When udev enumeration or classification, we only got joysticks here, so no need to test
if (enumeration_method != ENUMERATION_LIBUDEV && !class && !GuessIsJoystick(*fd)) {
return false;
goto error;
}
// Could have vendor and product already from udev, but should agree with evdev
if (ioctl(*fd, EVIOCGID, &inpid) < 0) {
return false;
goto error;
}
if (ioctl(*fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) {
return false;
goto error;
}
}
name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string);
if (!name) {
return false;
goto error;
}
if (!IsVirtualJoystick(inpid.vendor, inpid.product, inpid.version, name) &&
SDL_JoystickHandledByAnotherDriver(&SDL_LINUX_JoystickDriver, inpid.vendor, inpid.product, inpid.version, name)) {
SDL_free(name);
return false;
goto error;
}
FixupDeviceInfoForMapping(*fd, &inpid);
@@ -331,14 +346,19 @@ static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *ve
#endif
if (SDL_ShouldIgnoreJoystick(inpid.vendor, inpid.product, inpid.version, name)) {
SDL_free(name);
return false;
goto error;
}
*name_return = name;
*driver_return = driver;
*vendor_return = inpid.vendor;
*product_return = inpid.product;
*guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, NULL, product_string, 0, 0);
return true;
error:
SDL_free(driver);
SDL_free(name);
return false;
}
static bool IsSensor(const char *path, int *fd)
@@ -349,7 +369,7 @@ static bool IsSensor(const char *path, int *fd)
SDL_zero(inpid);
#ifdef SDL_USE_LIBUDEV
// Opening input devices can generate synchronous device I/O, so avoid it if we can
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) &&
!(class & SDL_UDEV_DEVICE_ACCELEROMETER)) {
return false;
}
@@ -422,6 +442,7 @@ static void FreeJoylistItem(SDL_joylist_item *item)
SDL_free(item->mapping);
SDL_free(item->path);
SDL_free(item->name);
SDL_free(item->driver);
SDL_free(item);
}
@@ -436,6 +457,7 @@ static void MaybeAddDevice(const char *path)
struct stat sb;
int fd = -1;
char *name = NULL;
char *driver = NULL;
Uint16 vendor, product;
SDL_GUID guid;
SDL_joylist_item *item;
@@ -473,7 +495,7 @@ static void MaybeAddDevice(const char *path)
SDL_Log("Checking %s", path);
#endif
if (IsJoystick(path, &fd, &name, &vendor, &product, &guid)) {
if (IsJoystick(path, &fd, &name, &vendor, &product, &guid, &driver)) {
#ifdef DEBUG_INPUT_EVENTS
SDL_Log("found joystick: %s", path);
#endif
@@ -486,8 +508,11 @@ static void MaybeAddDevice(const char *path)
item->devnum = sb.st_rdev;
item->steam_virtual_gamepad_slot = -1;
item->path = SDL_strdup(path);
item->vendor = vendor;
item->product = product;
item->name = name;
item->guid = guid;
item->driver = driver;
if (vendor == USB_VENDOR_VALVE &&
product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
@@ -861,7 +886,7 @@ static void LINUX_ScanSteamVirtualGamepads(void)
// Opening input devices can generate synchronous device I/O, so avoid it if we can
class = 0;
SDL_zero(inpid);
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) &&
(inpid.vendor != USB_VENDOR_VALVE || inpid.product != USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) {
free(entries[i]); // This should NOT be SDL_free()
continue;
@@ -948,7 +973,7 @@ static void LINUX_JoystickDetect(void)
{
#ifdef SDL_USE_LIBUDEV
if (enumeration_method == ENUMERATION_LIBUDEV) {
SDL_UDEV_Poll();
// Polling will happen in the main event loop
} else
#endif
#ifdef HAVE_INOTIFY
@@ -1136,6 +1161,8 @@ static SDL_JoystickID LINUX_JoystickGetDeviceInstanceID(int device_index)
static bool allocate_balldata(SDL_Joystick *joystick)
{
SDL_AssertJoysticksLocked();
joystick->hwdata->balls =
(struct hwdata_ball *)SDL_calloc(joystick->nballs, sizeof(struct hwdata_ball));
if (joystick->hwdata->balls == NULL) {
@@ -1512,7 +1539,7 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item)
return NULL;
}
SDL_memset(uniq_item, 0, sizeof(uniq_item));
SDL_zeroa(uniq_item);
fd_item = open(item->path, O_RDONLY | O_CLOEXEC, 0);
if (fd_item < 0) {
return NULL;
@@ -1534,7 +1561,7 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item)
continue;
}
SDL_memset(uniq_sensor, 0, sizeof(uniq_sensor));
SDL_zeroa(uniq_sensor);
fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0);
if (fd_sensor < 0) {
continue;
@@ -1587,6 +1614,10 @@ static bool LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index)
item_sensor->hwdata = joystick->hwdata;
}
#ifdef SDL_USE_LIBUDEV
joystick->serial = SDL_UDEV_GetProductSerial(item->path);
#endif
// mark joystick as fresh and ready
joystick->hwdata->fresh = true;
@@ -1747,6 +1778,8 @@ static void HandleHat(Uint64 timestamp, SDL_Joystick *stick, int hatidx, int axi
static void HandleBall(SDL_Joystick *stick, Uint8 ball, int axis, int value)
{
SDL_AssertJoysticksLocked();
stick->hwdata->balls[ball].axis[axis] += value;
}
@@ -1844,6 +1877,20 @@ static void PollAllValues(Uint64 timestamp, SDL_Joystick *joystick)
// Joyballs are relative input, so there's no poll state. Events only!
}
static void CorrectSensorData(struct joystick_hwdata *hwdata, float *values, float *data)
{
if (hwdata->item->vendor == USB_VENDOR_NINTENDO) {
// The Nintendo driver uses a different axis order than SDL
data[0] = -values[1];
data[1] = values[2];
data[2] = -values[0];
} else {
data[0] = values[0];
data[1] = values[1];
data[2] = values[2];
}
}
static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick)
{
struct input_absinfo absinfo;
@@ -1854,27 +1901,31 @@ static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick)
SDL_assert(joystick->hwdata->fd_sensor >= 0);
if (joystick->hwdata->has_gyro) {
float data[3] = {0.0f, 0.0f, 0.0f};
float values[3] = {0.0f, 0.0f, 0.0f};
for (i = 0; i < 3; i++) {
if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) >= 0) {
data[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i];
values[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i];
#ifdef DEBUG_INPUT_EVENTS
SDL_Log("Joystick : Re-read Gyro (axis %d) val= %f", i, data[i]);
#endif
}
}
float data[3];
CorrectSensorData(joystick->hwdata, values, data);
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3);
}
if (joystick->hwdata->has_accelerometer) {
float data[3] = {0.0f, 0.0f, 0.0f};
float values[3] = {0.0f, 0.0f, 0.0f};
for (i = 0; i < 3; i++) {
if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) >= 0) {
data[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i];
values[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i];
#ifdef DEBUG_INPUT_EVENTS
SDL_Log("Joystick : Re-read Accelerometer (axis %d) val= %f", i, data[i]);
#endif
}
}
float data[3];
CorrectSensorData(joystick->hwdata, values, data);
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3);
}
}
@@ -2058,12 +2109,15 @@ static void HandleInputEvents(SDL_Joystick *joystick)
PollAllSensors(SDL_GetTicksNS(), joystick); // try to sync up to current state now
} else {
Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);
float data[3];
CorrectSensorData(joystick->hwdata, joystick->hwdata->gyro_data, data);
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO,
SDL_US_TO_NS(joystick->hwdata->sensor_tick),
joystick->hwdata->gyro_data, 3);
data, 3);
CorrectSensorData(joystick->hwdata, joystick->hwdata->accel_data, data);
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL,
SDL_US_TO_NS(joystick->hwdata->sensor_tick),
joystick->hwdata->accel_data, 3);
data, 3);
}
break;
default:
@@ -2244,6 +2298,12 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
MAPPED_DPAD_LEFT = 0x4,
MAPPED_DPAD_RIGHT = 0x8,
MAPPED_DPAD_ALL = 0xF,
MAPPED_LEFT_PADDLE1 = 0x1,
MAPPED_RIGHT_PADDLE1 = 0x2,
MAPPED_LEFT_PADDLE2 = 0x4,
MAPPED_RIGHT_PADDLE2 = 0x8,
MAPPED_PADDLE_ALL = 0xF,
};
unsigned int mapped;
bool result = false;
@@ -2609,6 +2669,27 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
SDL_Log("Mapped DPUP+DOWN to axis %d (ABS_HAT0Y)", out->dpup.target);
SDL_Log("Mapped DPLEFT+RIGHT to axis %d (ABS_HAT0X)", out->dpleft.target);
#endif
} else if (item->driver && SDL_strcmp(item->driver, "xpad") == 0) {
// xpad will sometimes map the D-Pad as BTN_TRIGGER_HAPPY1 - BTN_TRIGGER_HAPPY4
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY1] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY2] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY3] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY4]) {
out->dpleft.kind = EMappingKind_Button;
out->dpright.kind = EMappingKind_Button;
out->dpup.kind = EMappingKind_Button;
out->dpdown.kind = EMappingKind_Button;
out->dpleft.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY1];
out->dpright.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY2];
out->dpup.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY3];
out->dpdown.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY4];
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped DPLEFT to button %d (BTN_TRIGGER_HAPPY1)", out->dpleft.target);
SDL_Log("Mapped DPRIGHT to button %d (BTN_TRIGGER_HAPPY2)", out->dpright.target);
SDL_Log("Mapped DPUP to button %d (BTN_TRIGGER_HAPPY3)", out->dpup.target);
SDL_Log("Mapped DPDOWN to button %d (BTN_TRIGGER_HAPPY4)", out->dpdown.target);
#endif
}
}
}
@@ -2653,8 +2734,44 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
#endif
}
if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
mapped = 0;
if (joystick->hwdata->has_key[BTN_GRIPR]) {
out->right_paddle1.kind = EMappingKind_Button;
out->right_paddle1.target = joystick->hwdata->key_map[BTN_GRIPR];
mapped |= MAPPED_RIGHT_PADDLE1;
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_GRIPR)", out->right_paddle1.target);
#endif
}
if (joystick->hwdata->has_key[BTN_GRIPL]) {
out->left_paddle1.kind = EMappingKind_Button;
out->left_paddle1.target = joystick->hwdata->key_map[BTN_GRIPL];
mapped |= MAPPED_LEFT_PADDLE1;
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_GRIPL)", out->left_paddle1.target);
#endif
}
if (joystick->hwdata->has_key[BTN_GRIPR2]) {
out->right_paddle2.kind = EMappingKind_Button;
out->right_paddle2.target = joystick->hwdata->key_map[BTN_GRIPR2];
mapped |= MAPPED_RIGHT_PADDLE2;
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_GRIPR)", out->right_paddle2.target);
#endif
}
if (joystick->hwdata->has_key[BTN_GRIPL2]) {
out->left_paddle2.kind = EMappingKind_Button;
out->left_paddle2.target = joystick->hwdata->key_map[BTN_GRIPL2];
mapped |= MAPPED_LEFT_PADDLE2;
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_GRIPL2)", out->left_paddle2.target);
#endif
}
if (mapped != MAPPED_PADDLE_ALL && SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
// The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY5 - BTN_TRIGGER_HAPPY8
// in older drivers
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY5] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY6] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY7] &&
@@ -2667,6 +2784,7 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
out->right_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY6];
out->left_paddle2.kind = EMappingKind_Button;
out->left_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY8];
mapped = MAPPED_PADDLE_ALL;
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY5)", out->right_paddle1.target);
SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY7)", out->left_paddle1.target);
@@ -2674,15 +2792,15 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY8)", out->left_paddle2.target);
#endif
}
}
// The Xbox Series X controllers have the Share button as KEY_RECORD
if (joystick->hwdata->has_key[KEY_RECORD]) {
out->misc1.kind = EMappingKind_Button;
out->misc1.target = joystick->hwdata->key_map[KEY_RECORD];
// Xbox Series controllers have the Share button as KEY_RECORD
if (joystick->hwdata->has_key[KEY_RECORD]) {
out->misc1.kind = EMappingKind_Button;
out->misc1.target = joystick->hwdata->key_map[KEY_RECORD];
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
#endif
}
}
// Cache the mapping for later
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
+63 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -191,6 +191,66 @@ static SDL_JoystickID PS2_JoystickGetDeviceInstanceID(int device_index)
return device_index + 1;
}
static void PS2_WaitPadReady(int port, int slot)
{
int state = padGetState(port, slot);
while ((state != PAD_STATE_STABLE) && (state != PAD_STATE_FINDCTP1)) {
SDL_Delay(1);
state = padGetState(port, slot);
}
}
static void PS2_InitializePad(int port, int slot)
{
int modes;
int i;
char actAlign[6];
PS2_WaitPadReady(port, slot);
// How many different modes can this device operate in?
modes = padInfoMode(port, slot, PAD_MODETABLE, -1);
// Verify that the controller has a DUAL SHOCK mode
for (i = 0; i < modes; i++) {
if (padInfoMode(port, slot, PAD_MODETABLE, i) == PAD_TYPE_DUALSHOCK) {
break;
}
}
if (i >= modes) {
// This is no Dual Shock controller
return;
}
// If ExId != 0x0 => This controller has actuator engines
// This check should always pass if the Dual Shock test above passed
if (!padInfoMode(port, slot, PAD_MODECUREXID, 0)) {
// This is no Dual Shock controller??
return;
}
// When using MMODE_LOCK, user cant change mode with Select button
padSetMainMode(port, slot, PAD_MMODE_DUALSHOCK, PAD_MMODE_LOCK);
PS2_WaitPadReady(port, slot);
padEnterPressMode(port, slot);
PS2_WaitPadReady(port, slot);
if (padInfoAct(port, slot, -1, 0)) {
actAlign[0] = 0; // Enable small engine
actAlign[1] = 1; // Enable big engine
actAlign[2] = 0xff;
actAlign[3] = 0xff;
actAlign[4] = 0xff;
actAlign[5] = 0xff;
PS2_WaitPadReady(port, slot);
padSetActAlign(port, slot, actAlign);
}
PS2_WaitPadReady(port, slot);
}
/* Function to open a joystick for use.
The joystick to open is specified by the device index.
This should fill the nbuttons and naxes fields of the joystick structure.
@@ -208,6 +268,8 @@ static bool PS2_JoystickOpen(SDL_Joystick *joystick, int device_index)
return false;
}
}
PS2_InitializePad(info->port, info->slot);
joystick->nbuttons = PS2_BUTTONS;
joystick->naxes = PS2_TOTAL_AXIS;
joystick->nhats = 0;
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
+61 -23
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -32,6 +32,8 @@
#define USB_VENDOR_BACKBONE 0x358a
#define USB_VENDOR_GAMESIR 0x3537
#define USB_VENDOR_DRAGONRISE 0x0079
#define USB_VENDOR_FLYDIGI_V1 0x04b4
#define USB_VENDOR_FLYDIGI_V2 0x37d7
#define USB_VENDOR_GOOGLE 0x18d1
#define USB_VENDOR_HORI 0x0f0d
#define USB_VENDOR_HP 0x03f0
@@ -58,25 +60,36 @@
#define USB_VENDOR_SWITCH 0x2563
#define USB_VENDOR_VALVE 0x28de
#define USB_VENDOR_ZEROPLUS 0x0c12
#define USB_VENDOR_RASPBERRYPI 0x2e8a // Commercial hardware from various companies are registered under this VID
#define USB_VENDOR_ZUIKI 0x33dd
#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 0x2002 // Ultimate Wired Controller for Xbox
#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER2 0x3106 // Ultimate Wireless / Pro 2 Wired Controller
#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 // B + START
#define USB_PRODUCT_8BITDO_SF30_PRO_BT 0x6100 // B + START
#define USB_PRODUCT_8BITDO_SN30_PRO 0x6001 // B + START
#define USB_PRODUCT_8BITDO_SN30_PRO_BT 0x6101 // B + START
#define USB_PRODUCT_8BITDO_PRO_2 0x6003 // mode switch to D
#define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 // mode switch to D
#define USB_PRODUCT_8BITDO_PRO_3 0x6009 // mode switch to D
#define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 // mode switch to BT
#define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419
#define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024
#define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103
#define USB_PRODUCT_BACKBONE_ONE_IOS_PS5 0x0104
#define USB_PRODUCT_GAMESIR_G7 0x1001
#define USB_PRODUCT_BDA_XB1_CLASSIC 0x581a
#define USB_PRODUCT_BDA_XB1_FIGHTPAD 0x791a
#define USB_PRODUCT_BDA_XB1_SPECTRA_PRO 0x592a
#define USB_PRODUCT_GOOGLE_STADIA_CONTROLLER 0x9400
#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843
#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1846
#define USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X 0x0150
#define USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X 0x014f
#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1844
#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3 0x1846
#define USB_PRODUCT_FLYDIGI_V1_GAMEPAD 0x2412
#define USB_PRODUCT_FLYDIGI_V2_APEX 0x2501
#define USB_PRODUCT_FLYDIGI_V2_VADER 0x2401
#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS4 0x011c
#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184
#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184
#define USB_PRODUCT_HORI_STEAM_CONTROLLER 0x01AB
#define USB_PRODUCT_HORI_STEAM_CONTROLLER_BT 0x0196
#define USB_PRODUCT_HORI_TAIKO_DRUM_CONTROLLER 0x01b2
#define USB_PRODUCT_LOGITECH_F310 0xc216
#define USB_PRODUCT_LOGITECH_CHILLSTREAM 0xcad1
#define USB_PRODUCT_MADCATZ_SAITEK_SIDE_PANEL_CONTROL_DECK 0x2218
@@ -94,15 +107,25 @@
#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR 0x2008 // Used by joycond
#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT 0x2007
#define USB_PRODUCT_NINTENDO_SWITCH_PRO 0x2009
#define USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER 0x2073
#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_LEFT 0x2067
#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_PAIR 0x2068
#define USB_PRODUCT_NINTENDO_SWITCH2_JOYCON_RIGHT 0x2066
#define USB_PRODUCT_NINTENDO_SWITCH2_PRO 0x2069
#define USB_PRODUCT_NINTENDO_WII_REMOTE 0x0306
#define USB_PRODUCT_NINTENDO_WII_REMOTE2 0x0330
#define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 0x7210
#define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104 0x7214
#define USB_PRODUCT_PDP_ROCK_CANDY 0x0246
#define USB_PRODUCT_PDP_REALMZ_WIRELESS 0x018c
#define USB_PRODUCT_POWERA_MINI 0x541a
#define USB_PRODUCT_RAZER_ATROX 0x0a00
#define USB_PRODUCT_RAZER_KITSUNE 0x1012
#define USB_PRODUCT_RAZER_PANTHERA 0x0401
#define USB_PRODUCT_RAZER_PANTHERA_EVO 0x1008
#define USB_PRODUCT_RAZER_RAIJU 0x1000
#define USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRED 0x1024
#define USB_PRODUCT_RAZER_RAIJU_V3_PRO_PS5_WIRELESS 0x1026
#define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_USB 0x1007
#define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_BLUETOOTH 0x100a
#define USB_PRODUCT_RAZER_ULTIMATE_EDITION_USB 0x1004
@@ -114,7 +137,6 @@
#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRED 0x1010
#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRELESS 0x1011
#define USB_PRODUCT_RAZER_WOLVERINE_V3_PRO 0x0a3f
#define USB_PRODUCT_ROG_RAIKIRI 0x1a38
#define USB_PRODUCT_SAITEK_CYBORG_V3 0xf622
#define USB_PRODUCT_SHANWAN_DS3 0x0523
#define USB_PRODUCT_SONY_DS3 0x0268
@@ -124,12 +146,13 @@
#define USB_PRODUCT_SONY_DS4_STRIKEPAD 0x05c5
#define USB_PRODUCT_SONY_DS5 0x0ce6
#define USB_PRODUCT_SONY_DS5_EDGE 0x0df2
#define USB_PRODUCT_STEALTH_ULTRA_WIRED 0x7073
#define USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER 0x0575
#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_PS4 0xd00e
#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X 0xd012
#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R 0x7013
#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON 0x7009
#define USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE 0xb68c
#define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE 0x1142
#define USB_PRODUCT_VALVE_STEAM_PROTEUS_DONGLE 0x1304
#define USB_PRODUCT_VALVE_STEAM_NEREID_DONGLE 0x1305
#define USB_PRODUCT_VICTRIX_FS_PRO 0x0203
#define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207
#define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 // XUSB driver software PID
@@ -150,23 +173,22 @@
#define USB_PRODUCT_XBOX_ONE_S_REV2_BLE 0x0b20
#define USB_PRODUCT_XBOX_SERIES_X 0x0b12
#define USB_PRODUCT_XBOX_SERIES_X_BLE 0x0b13
#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX 0x08b6
#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB 0x07a0
#define USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW 0x02da
#define USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE 0x02d9
#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 0x4001
#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 0x400b
#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB 0x4014
#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE 0x4016
#define USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA 0x890b
#define USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA 0x4002
#define USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT 0x02d6
#define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER 0x02ff // XBOXGIP driver software PID
#define USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD 0x11ff
#define USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC 0x10c6
#define USB_PRODUCT_HANDHELDLEGEND_PROGCC 0x10df
#define USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE 0x10dd
#define USB_PRODUCT_BONZIRICHANNEL_FIREBIRD 0x10e0
#define USB_PRODUCT_ZUIKI_MASCON_PRO 0x0006
#define USB_PRODUCT_VOIDGAMING_PS4FIREBIRD 0x10e5
// USB usage pages
#define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001
#define USB_USAGEPAGE_SIMULATION 0x0002
#define USB_USAGEPAGE_DEVICE_CONTROLS 0x0006
#define USB_USAGEPAGE_BUTTON 0x0009
#define USB_USAGEPAGE_CONSUMER 0x000C
#define USB_USAGEPAGE_VENDOR_FLYDIGI 0xFFA0
// USB usages for USAGE_PAGE_GENERIC_DESKTOP
#define USB_USAGE_GENERIC_POINTER 0x0001
@@ -187,6 +209,22 @@
#define USB_USAGE_GENERIC_WHEEL 0x0038
#define USB_USAGE_GENERIC_HAT 0x0039
// USB usages for USB_USAGEPAGE_SIMULATION
#define USB_USAGE_SIMULATION_ACCELERATOR 0x00C4
#define USB_USAGE_SIMULATION_BRAKE 0x00C5
// USB usages for USB_USAGEPAGE_DEVICE_CONTROLS
#define USB_USAGE_DEVICE_CONTROLS_BATTERY_STRENGTH 0x0020
// USB usages for USB_USAGEPAGE_CONSUMER
#define USB_USAGE_CONSUMER_ASSIGN_SELECTION 0x0081
#define USB_USAGE_CONSUMER_ORDER_MOVIE 0x0085
#define USB_USAGE_CONSUMER_RECORD 0x00B2
#define USB_USAGE_CONSUMER_AC_HOME 0x0223
#define USB_USAGE_CONSUMER_AC_BACK 0x0224
#define MAKE_USAGE(PAGE, USAGE) (((Uint32)PAGE) << 16 | USAGE)
/* Bluetooth SIG assigned Company Identifiers
https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/ */
#define BLUETOOTH_VENDOR_AMAZON 0x0171
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -138,11 +138,11 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des
SDL_AssertJoysticksLocked();
if (!desc) {
CHECK_PARAM(!desc) {
SDL_InvalidParamError("desc");
return 0;
}
if (desc->version < sizeof(*desc)) {
CHECK_PARAM(desc->version < sizeof(*desc)) {
// Update this to handle older versions of this interface
SDL_SetError("Invalid desc, should be initialized with SDL_INIT_INTERFACE()");
return 0;
@@ -471,7 +471,7 @@ bool SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorTy
return false;
}
hwdata->sensor_events = sensor_events;
hwdata->max_sensor_events = hwdata->max_sensor_events;
hwdata->max_sensor_events = new_max_sensor_events;
}
VirtualSensorEvent *event = &hwdata->sensor_events[hwdata->num_sensor_events++];
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -45,6 +45,7 @@ extern HWND SDL_HelperWindow;
// local variables
static bool coinitialized = false;
static LPDIRECTINPUT8 dinput = NULL;
static bool has_broken_EZFRD64DLL = false;
// Taken from Wine - Thanks!
static DIOBJECTDATAFORMAT dfDIJoystick2[] = {
@@ -289,7 +290,7 @@ static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, Uint16 vendor_id, Uint1
}
*manufacturer_string = NULL;
*product_string = WIN_StringToUTF8(dipstr.wsz);
*product_string = WIN_StringToUTF8W(dipstr.wsz);
return true;
}
@@ -437,6 +438,29 @@ bool SDL_DINPUT_JoystickInit(void)
dinput = NULL;
return SetDIerror("IDirectInput::Initialize", result);
}
#ifdef _WIN64
if (SDL_GetHintBoolean("SDL_JOYSTICK_CHECK_EZFRD64", true)) {
// The 64-bit version of EZFRD64.DLL crashes after being loaded,
// which happens implicitly when querying the device capabilities,
// so make sure we don't do that if there's a possibility of crashing
static const char *directories[] = {
"C:/Windows/USB_Vibration",
"C:/Windows/USB Vibration"
};
for (int i = 0; i < SDL_arraysize(directories) && !has_broken_EZFRD64DLL; ++i) {
int count = 0;
char **files = SDL_GlobDirectory(directories[i], "*/EZFRD64.DLL", SDL_GLOB_CASEINSENSITIVE, &count);
if (count > 0) {
has_broken_EZFRD64DLL = true;
}
SDL_free(files);
}
if (has_broken_EZFRD64DLL) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Broken EZFRD64.DLL detected, disabling DirectInput force feedback");
}
}
#endif
return true;
}
@@ -778,12 +802,14 @@ bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joysti
return SetDIerror("IDirectInputDevice8::SetDataFormat", result);
}
// Get device capabilities
result =
IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice,
&joystick->hwdata->Capabilities);
if (FAILED(result)) {
return SetDIerror("IDirectInputDevice8::GetCapabilities", result);
if (!has_broken_EZFRD64DLL) {
// Get device capabilities to see if we are force feedback capable
result =
IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice,
&joystick->hwdata->Capabilities);
if (FAILED(result)) {
return SetDIerror("IDirectInputDevice8::GetCapabilities", result);
}
}
// Force capable?
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 2026 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
@@ -923,12 +923,8 @@ static void RAWINPUT_AddDevice(HANDLE hDevice)
device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string);
device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, manufacturer_string, product_string, 'r', 0);
if (manufacturer_string) {
SDL_free(manufacturer_string);
}
if (product_string) {
SDL_free(product_string);
}
SDL_free(manufacturer_string);
SDL_free(product_string);
}
device->path = SDL_strdup(dev_name);
@@ -963,12 +959,8 @@ err:
CloseHandle(hFile);
}
if (device) {
if (device->name) {
SDL_free(device->name);
}
if (device->path) {
SDL_free(device->path);
}
SDL_free(device->name);
SDL_free(device->path);
SDL_free(device);
}
#undef CHECK
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -25,8 +25,8 @@
extern bool RAWINPUT_IsEnabled(void);
// Registers for input events
extern int RAWINPUT_RegisterNotifications(HWND hWnd);
extern int RAWINPUT_UnregisterNotifications(void);
extern bool RAWINPUT_RegisterNotifications(HWND hWnd);
extern bool RAWINPUT_UnregisterNotifications(void);
// Returns 0 if message was handled
extern LRESULT CALLBACK RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -69,6 +69,7 @@ typedef PCWSTR(WINAPI *WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 *leng
static struct
{
bool ro_initialized;
CoIncrementMTAUsage_t CoIncrementMTAUsage;
RoGetActivationFactory_t RoGetActivationFactory;
WindowsCreateStringReference_t WindowsCreateStringReference;
@@ -585,13 +586,14 @@ static bool WGI_JoystickInit(void)
{
HRESULT hr;
if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) {
if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, false)) {
return true;
}
if (FAILED(WIN_RoInitialize())) {
return SDL_SetError("RoInitialize() failed");
}
wgi.ro_initialized = true;
#define RESOLVE(x) wgi.x = (x##_t)WIN_LoadComBaseFunction(#x); if (!wgi.x) return WIN_SetError("GetProcAddress failed for " #x);
RESOLVE(CoIncrementMTAUsage);
@@ -971,9 +973,7 @@ static void WGI_JoystickQuit(void)
while (wgi.controller_count > 0) {
IEventHandler_CRawGameControllerVtbl_InvokeRemoved(&controller_removed.iface, NULL, wgi.controllers[wgi.controller_count - 1].controller);
}
if (wgi.controllers) {
SDL_free(wgi.controllers);
}
SDL_free(wgi.controllers);
if (wgi.arcade_stick_statics) {
__x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_Release(wgi.arcade_stick_statics);
@@ -1002,7 +1002,9 @@ static void WGI_JoystickQuit(void)
__x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_Release(wgi.controller_statics);
}
WIN_RoUninitialize();
if (wgi.ro_initialized) {
WIN_RoUninitialize();
}
SDL_zero(wgi);
}
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -416,11 +416,15 @@ void WINDOWS_JoystickDetect(void)
while (pCurList) {
JoyStick_DeviceData *pListNext = NULL;
if (!pCurList->bXInputDevice) {
#ifdef SDL_HAPTIC_DINPUT
#ifdef SDL_JOYSTICK_XINPUT
if (!pCurList->bXInputDevice) {
SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice);
#endif
}
#else
SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice);
#endif
#endif
SDL_PrivateJoystickRemoved(pCurList->nInstanceID);
@@ -432,11 +436,15 @@ void WINDOWS_JoystickDetect(void)
for (pCurList = SYS_Joystick; pCurList; pCurList = pCurList->pNext) {
if (pCurList->send_add_event) {
if (!pCurList->bXInputDevice) {
#ifdef SDL_HAPTIC_DINPUT
#ifdef SDL_JOYSTICK_XINPUT
if (!pCurList->bXInputDevice) {
SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice);
#endif
}
#else
SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice);
#endif
#endif
SDL_PrivateJoystickAdded(pCurList->nInstanceID);
@@ -489,16 +497,18 @@ static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
device = device->pNext;
}
#ifdef SDL_JOYSTICK_XINPUT
if (device->bXInputDevice) {
// The slot for XInput devices can change as controllers are seated
return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId);
} else {
return device->steam_virtual_gamepad_slot;
}
#endif
return device->steam_virtual_gamepad_slot;
}
static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index)
{
#ifdef SDL_JOYSTICK_XINPUT
JoyStick_DeviceData *device = SYS_Joystick;
int index;
@@ -507,6 +517,9 @@ static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index)
}
return device->bXInputDevice ? (int)device->XInputUserId : -1;
#else
return -1;
#endif
}
static void WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
@@ -560,20 +573,22 @@ static bool WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index)
}
joystick->hwdata->guid = device->guid;
#ifdef SDL_JOYSTICK_XINPUT
if (device->bXInputDevice) {
return SDL_XINPUT_JoystickOpen(joystick, device);
} else {
return SDL_DINPUT_JoystickOpen(joystick, device);
}
#endif
return SDL_DINPUT_JoystickOpen(joystick, device);
}
static bool WINDOWS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
#ifdef SDL_JOYSTICK_XINPUT
if (joystick->hwdata->bXInputDevice) {
return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
} else {
return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
}
#endif
return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
}
static bool WINDOWS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
@@ -602,21 +617,27 @@ static void WINDOWS_JoystickUpdate(SDL_Joystick *joystick)
return;
}
#ifdef SDL_JOYSTICK_XINPUT
if (joystick->hwdata->bXInputDevice) {
SDL_XINPUT_JoystickUpdate(joystick);
} else {
SDL_DINPUT_JoystickUpdate(joystick);
return;
}
#endif
SDL_DINPUT_JoystickUpdate(joystick);
}
// Function to close a joystick after use
static void WINDOWS_JoystickClose(SDL_Joystick *joystick)
{
#ifdef SDL_JOYSTICK_XINPUT
if (joystick->hwdata->bXInputDevice) {
SDL_XINPUT_JoystickClose(joystick);
} else {
SDL_DINPUT_JoystickClose(joystick);
}
#else
SDL_DINPUT_JoystickClose(joystick);
#endif
SDL_free(joystick->hwdata);
}
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -37,9 +37,11 @@ typedef struct JoyStick_DeviceData
char *joystickname;
Uint8 send_add_event;
SDL_JoystickID nInstanceID;
#ifdef SDL_JOYSTICK_XINPUT
bool bXInputDevice;
BYTE SubType;
Uint8 XInputUserId;
#endif
DIDEVICEINSTANCE dxdevice;
char path[MAX_PATH];
int steam_virtual_gamepad_slot;
@@ -85,10 +87,12 @@ struct joystick_hwdata
LPDIRECTINPUTEFFECT ffeffect_ref;
#endif
#ifdef SDL_JOYSTICK_XINPUT
bool bXInputDevice; // true if this device supports using the xinput API rather than DirectInput
bool bXInputHaptic; // Supports force feedback via XInput.
Uint8 userid; // XInput userid index for this joystick
DWORD dwPacketNumber;
#endif
};
#ifdef SDL_JOYSTICK_DINPUT
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 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