Skip to content

Commit 325df5f

Browse files
committed
emulationstation: SDL gamecontroller mapping suppport
Added an input configuration script which will produce a SDL(2) gamecontroller mapping ([1]) based on the user's choices in the EmulationStation input configuration. The result of the mapping is saved in `/opt/retropie/configs/all/sdl2_gamecontrollerdb.txt`, which can be referenced by `runcommand` to load the mappings through SDL hints ([2] or [3]). Since we're not trying to overwrite the existing built-in mappings, I've added a query method which needs `python3-sdl2` as dependency. The mapping produced should follow the user's choices as far as inputs are concerned. The only 'outlier' is the 'hotkey enable' button, which is mapped to the 'Guide' SDL gamepad button IF its value is different than the _Select_ input. This way the extra button present on various gamepads (Xbox/PS) or a dedicated button chosen by the user can be mapped separately. Ref: [1] https://wiki.libsdl.org/SDL2/SDL_GameControllerAddMapping [2] https://wiki.libsdl.org/SDL2/SDL_HINT_GAMECONTROLLERCONFIG [3] https://wiki.libsdl.org/SDL2/SDL_HINT_GAMECONTROLLERCONFIG_FILE
1 parent 3810202 commit 325df5f

File tree

2 files changed

+189
-1
lines changed

2 files changed

+189
-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+
}

0 commit comments

Comments
 (0)