Skip to content

Commit f96177c

Browse files
authored
Merge pull request #4035 from cmitu/sdl-mappings
Input auto-configuration for SDL2 gamepads
2 parents 69cb744 + ba2092d commit f96177c

File tree

3 files changed

+196
-1
lines changed

3 files changed

+196
-1
lines changed

scriptmodules/supplementary/emulationstation.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ function depends_emulationstation() {
132132
local depends=(
133133
libfreeimage-dev libfreetype6-dev
134134
libcurl4-openssl-dev libasound2-dev cmake libsdl2-dev libsm-dev
135-
libvlc-dev libvlccore-dev vlc
135+
libvlc-dev libvlccore-dev python3-sdl2 vlc
136136
)
137137

138138
[[ "$__os_debian_ver" -gt 8 ]] && depends+=(rapidjson-dev)
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env bash
2+
3+
# This file is part of The RetroPie Project
4+
#
5+
# The RetroPie Project is the legal property of its developers, whose names are
6+
# too numerous to list here. Please refer to the COPYRIGHT.md file distributed with this source.
7+
#
8+
# See the LICENSE.md file at the top-level directory of this distribution and
9+
# at https://raw.githubusercontent.com/RetroPie/RetroPie-Setup/master/LICENSE.md
10+
#
11+
12+
# This input configuration script will create a SDL(2) GameController mapping string based on the user's choices (see [1] for the format).
13+
# The mapping is a comma (,) separated string of 'parameter:value' used by SDL to use the GameController.
14+
# SDL2 comes with it's own list of built-in mappings, defined in [2], which covers most of the well-known gamepads, but offers the ability
15+
# to:
16+
# - load additional mappings programmatically from a file using [3].
17+
# This is the approach used by emulators that include an additional `gamecontrollerdb.txt` to allow extending the default SDL gamecontroller mapping database.
18+
# - load additional mappings from the `SDL_GAMECONTROLLERCONFIG` environment variable, which should contain newline delimited list of mappings (see [4])
19+
# - load additional mappings from the file pointed with `SDL_GAMECONTROLLERCONFIG_FILE` environment variable, containing newline delimited list of mapping (see [5]). Available only from SDL 2.0.22.
20+
#
21+
# The script will produce a mapping string for a configured joystick in EmulationStation, then store the result in:
22+
# /opt/retropie/configs/all/sdl2_gamecontrollerdb.txt
23+
# This file can then be consulted by `runcommand` and the new mappings referenced via SDL2 hints.
24+
#
25+
# Notes:
26+
# - this script will not replace the default/built-in SDL mappings
27+
# - gamecontroller naming sanitization is not identical to SDL name mapping routines, but that doesn't affect functionality
28+
#
29+
# Ref:
30+
# [1] https://wiki.libsdl.org/SDL2/SDL_GameControllerAddMapping
31+
# [2] https://github.com/libsdl-org/SDL/blob/SDL2/src/joystick/SDL_gamecontrollerdb.h
32+
# [3] https://wiki.libsdl.org/SDL2/SDL_GameControllerAddMappingsFromFile
33+
# [4] https://wiki.libsdl.org/SDL2/SDL_HINT_GAMECONTROLLERCONFIG
34+
# [5] https://wiki.libsdl.org/SDL2/SDL_HINT_GAMECONTROLLERCONFIG_FILE
35+
36+
function onstart_sdl2_joystick() {
37+
# save the intermediary mappings into a temporary file
38+
local temp_file
39+
temp_file="$(_temp_file_sdl2)"
40+
: > "$temp_file"
41+
}
42+
43+
function map_sdl2_joystick() {
44+
local input_name="$1"
45+
local input_type="$2"
46+
local input_id="$3"
47+
local input_value="$4"
48+
local input_temp_map="$(_temp_file_sdl2)"
49+
50+
# map ES input name => SDL Gamecontroller mapping label
51+
declare -A input_map=(
52+
[up]="dpup"
53+
[down]="dpdown"
54+
[left]="dpleft"
55+
[right]="dpright"
56+
[a]="a"
57+
[b]="b"
58+
[x]="x"
59+
[y]="y"
60+
[start]="start"
61+
[select]="back"
62+
63+
[hotkeyenable]="guide"
64+
65+
[leftshoulder]="leftshoulder"
66+
[lefttrigger]="lefttrigger"
67+
[rightshoulder]="rightshoulder"
68+
[righttrigger]="righttrigger"
69+
70+
[leftthumb]="leftstick"
71+
[leftanalogright]="leftx"
72+
[leftanalogdown]="lefty"
73+
74+
[rightthumb]="rightstick"
75+
[rightanalogright]="rightx"
76+
[rightanalogdown]="righty"
77+
78+
[pageup]="rightshoulder"
79+
[pagedown]="leftshoulder"
80+
)
81+
local sdl2_mapped_input
82+
83+
sdl2_mapped_input=${input_map[$input_name]}
84+
85+
# when the SDL mapped action/input is not defined skip the mapping
86+
[[ -z "$sdl2_mapped_input" ]] && return
87+
88+
case "$input_type" in
89+
axis)
90+
if [[ "$input_value" == "-1" ]]; then
91+
echo "$sdl2_mapped_input:-a${input_id}" >> "$input_temp_map"
92+
else
93+
echo "$sdl2_mapped_input:+a${input_id}" >> "$input_temp_map"
94+
fi
95+
;;
96+
hat)
97+
echo "$sdl2_mapped_input:hat${input_id}.${input_value}" >> "$input_temp_map"
98+
;;
99+
button)
100+
echo "$sdl2_mapped_input:b${input_id}" >> "$input_temp_map"
101+
;;
102+
*)
103+
;;
104+
esac
105+
}
106+
107+
function onend_sdl2_joystick() {
108+
# check whether SDL2 already has a mapping for this GUID
109+
if [[ "$(_check_gamepad_sdl2)" == "MAPPED" ]]; then
110+
echo "W: Device \"$DEVICE_NAME\" (guid: $DEVICE_GUID) is already known by SDL2, gamecontroller mapping was not created"
111+
return
112+
fi
113+
114+
# gamecontroller name sanitization:
115+
# - replace unexpected chars with '-'
116+
# - trim blanks from the name (beginning/end)
117+
# - replace ',' with space, since ',' is a delimiter in the mapping string
118+
local joyname="$(echo ${DEVICE_NAME//[:><?\"\/\\|*]/-} | tr -s '[:blank:]' | tr ',' ' ')"
119+
local select_value
120+
local hotkey_value
121+
local mapping
122+
local input_temp_map
123+
input_temp_map="$(_temp_file_sdl2)"
124+
125+
# add each mapping value and save the values for Select/Back Hotkey/Guide
126+
# don't add the Hotkey/Guide mapping yet
127+
while read m; do
128+
if [[ "$m" == "guide:*" ]]; then
129+
hotkey_value="$m";
130+
continue;
131+
fi
132+
133+
if [[ "$m" == "back:*" ]]; then
134+
select_value="$m";
135+
fi
136+
137+
mapping+="$m,"
138+
done < <(sort "$input_temp_map")
139+
140+
# add the mapping for Hotkey as Guide IIF it's different than Select
141+
if [[ $select_value != $hotkey_value && -n "$hotkey_value" ]]; then
142+
mapping+="$hotkey_value,"
143+
fi
144+
local sdl_configs="$configdir/all/sdl2_gamecontrollerdb.txt"
145+
mapping="${DEVICE_GUID},${joyname},platform:Linux,${mapping}"
146+
147+
[[ -n $__debug ]] && \
148+
echo "Mapping is $mapping"
149+
150+
# update the mapping when it's present, otherwise append it to the file
151+
if grep --silent --no-messages ${DEVICE_GUID} "$sdl_configs"; then
152+
sed -i "s/^${DEVICE_GUID}.*$/$mapping/" "$sdl_configs"
153+
else
154+
echo "$mapping" >> "$sdl_configs"
155+
fi
156+
rm -f "$input_temp_map"
157+
}
158+
159+
# function to check whether a gamepad GUID is already in SDL2's mapping list
160+
# we need to load SDL2's GameController subsystem and query by GUID
161+
function _check_gamepad_sdl2() {
162+
163+
cat << EOF > "/tmp/guid_check.py"
164+
from sdl2 import SDL_GameControllerMappingForGUID,\
165+
SDL_Init,SDL_Quit,SDL_Error,\
166+
SDL_INIT_GAMECONTROLLER
167+
from sdl2.joystick import SDL_JoystickGetGUIDFromString
168+
169+
if SDL_Init(SDL_INIT_GAMECONTROLLER) < 0:
170+
exit(2)
171+
172+
guid_str=b"${DEVICE_GUID}"
173+
guid = SDL_JoystickGetGUIDFromString(guid_str)
174+
map = SDL_GameControllerMappingForGUID(guid)
175+
SDL_Quit()
176+
177+
if map is not None:
178+
exit(0)
179+
180+
exit(1)
181+
EOF
182+
python3 /tmp/guid_check.py 1>/dev/null 2>&1 && echo "MAPPED"
183+
rm -f "/tmp/guid_check.py"
184+
}
185+
186+
function _temp_file_sdl2() {
187+
echo "/tmp/sdl2temp.txt"
188+
}

scriptmodules/supplementary/runcommand/runcommand.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ EMU_CONF="$CONFIGDIR/all/emulators.cfg"
8787
BACKENDS_CONF="$CONFIGDIR/all/backends.cfg"
8888
RETRONETPLAY_CONF="$CONFIGDIR/all/retronetplay.cfg"
8989
JOY2KEY="$ROOTDIR/admin/joy2key/joy2key"
90+
SDL2_MAPPINGS="$CONFIGDIR/all/sdl2_gamecontrollerdb.txt"
9091

9192
# modesetting tools
9293
TVSERVICE="/opt/vc/bin/tvservice"
@@ -1395,6 +1396,12 @@ function runcommand() {
13951396
13961397
user_script "runcommand-onlaunch.sh"
13971398
1399+
# include our SDL gamecontroller mappings in the environment
1400+
# but check if the environment doesn't already have the SDL GameController mapping hint
1401+
if [[ -z $SDL_GAMECONTROLLERCONFIG && -f "$SDL2_MAPPINGS" ]]; then
1402+
export SDL_GAMECONTROLLERCONFIG="$(cat "$SDL2_MAPPINGS")"
1403+
fi
1404+
13981405
local ret
13991406
launch_command
14001407
ret=$?

0 commit comments

Comments
 (0)