-
Notifications
You must be signed in to change notification settings - Fork 13
/
read-sdl-mappings.py
135 lines (116 loc) · 4.52 KB
/
read-sdl-mappings.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import json
import re
import sys
# We're gonna output a map from the standard gamepad to the actual buttons, just like SDL does
# The gamepad api's "standard gamepad", in SDL terms, is:
STANDARD_ORDER = [
# buttons
'a',
'b',
'x',
'y',
'leftshoulder',
'rightshoulder',
'lefttrigger',
'righttrigger',
'back',
'start',
'leftstick',
'rightstick',
'dpup',
'dpdown',
'dpleft',
'dpright',
'guide',
# axes
'-leftx', '+leftx',
'-lefty', '+lefty',
'-rightx', '+rightx',
'-righty', '+righty',
]
def main(path):
web_mappings = {}
with open(path) as f:
for line in f:
line = re.sub('#.*', '', line)
line = line.strip()
if not line:
continue
ident, name, *mapping_strings = line.split(',')
if ident == 'xinput':
continue
vendor1 = ident[10:12] + ident[8:10]
vendor2 = ident[18:20] + ident[16:18]
key = f"{vendor1}:{vendor2}"
# There are a lot of duplicates for different OSes that I assume are largely the same,
# and I don't have a good idea for reconciling them, so just take the first of each
if key in web_mappings:
continue
mappings = {}
max_axis_id = -1
for mapping_string in mapping_strings:
k, _, v = mapping_string.partition(':')
if k in ('', 'platform'):
continue
if m := re.fullmatch('b(\d+)', v):
v = 'button', int(m.group(1))
elif m := re.fullmatch('a(\d+)', v):
v = 'axis', int(m.group(1)), False
elif m := re.fullmatch('a(\d+)[~]', v):
v = 'axis', int(m.group(1)), True
elif m := re.fullmatch('[-]a(\d+)', v):
v = 'axis', int(m.group(1)), -1
elif m := re.fullmatch('[+]a(\d+)', v):
v = 'axis', int(m.group(1)), +1
elif m := re.fullmatch('h(\d+)[.](\d+)', v):
v = 'hat', int(m.group(1)), int(m.group(2))
else:
v = '???'
if v[0] == 'axis':
max_axis_id = max(max_axis_id, v[1])
if k in ('leftx', 'lefty', 'rightx', 'righty'):
assert v[0] == 'axis' and isinstance(v[2], bool)
if v[2]:
# swap
mappings['-' + k] = v[0], v[1], +1
mappings['+' + k] = v[0], v[1], -1
else:
mappings['-' + k] = v[0], v[1], -1
mappings['+' + k] = v[0], v[1], +1
else:
mappings[k] = v
# Now we know the highest numbered axis, so change hats into axes, praying that there
# weren't any axes skipped in the mapping
for control, mapping in mappings.items():
if mapping[0] == 'hat':
h = mapping[1]
direction = mapping[2]
axis_id = max_axis_id + 1 + h * 2
# assume a hat is a combination of two axes, x and y, with x coming first
if direction == 1: # up
mappings[control] = 'axis', axis_id + 1, -1
elif direction == 4: # down
mappings[control] = 'axis', axis_id + 1, +1
elif direction == 8: # left
mappings[control] = 'axis', axis_id, -1
elif direction == 2: # right
mappings[control] = 'axis', axis_id, +1
else:
raise RuntimeError(f"Don't know what to do with hat: {mapping}")
# And now put them in gamepad api order, in a compact form
out = []
for control in STANDARD_ORDER:
mapping = mappings.get(control)
if mapping is None:
out.append('x')
continue
if mapping[0] == 'button':
out.append(f"b{mapping[1]}")
elif mapping[0] == 'axis':
out.append(f"a{mapping[1]}{'-' if mapping[2] < 0 else '+'}")
else:
raise
web_mappings[key] = ' '.join(out)
print(json.dumps(web_mappings, indent=4, sort_keys=True))
if __name__ == '__main__':
main(*sys.argv[1:])