123 lines
3.7 KiB
Python
Executable File
123 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
find_codes.py - print real evdev BTN_*/ABS_* codes for the Sol-R1
|
|
grip and base, so they can replace the TODO placeholders in any
|
|
game's joyful-config.yml / daemon config.
|
|
|
|
Requirements:
|
|
pip install evdev --break-system-packages
|
|
|
|
Permissions:
|
|
sudo usermod -aG input $USER # for reading the joystick
|
|
(re-login after this)
|
|
|
|
Usage:
|
|
# Step 1: find the exact device name(s) - grip and base usually
|
|
# show up as two separate evdev devices.
|
|
python3 find_codes.py --list
|
|
|
|
# Step 2: watch a device and exercise every physical control on it
|
|
# (move every axis through its full range, press every button,
|
|
# flip every switch both ways, run every rotary knob both ways).
|
|
python3 find_codes.py --device "Sol-R"
|
|
"""
|
|
|
|
import argparse
|
|
import selectors
|
|
import sys
|
|
|
|
try:
|
|
from evdev import InputDevice, ecodes, list_devices
|
|
except ImportError:
|
|
sys.exit("Missing deps. Run:\n pip install evdev --break-system-packages")
|
|
|
|
|
|
def list_all_devices():
|
|
devices = [InputDevice(path) for path in list_devices()]
|
|
if not devices:
|
|
print("No input devices found (check permissions: groups $USER).")
|
|
return
|
|
for dev in devices:
|
|
print(f"{dev.path}\t{dev.name}")
|
|
|
|
|
|
def find_devices(name_substr: str):
|
|
matches = [
|
|
dev for dev in (InputDevice(path) for path in list_devices())
|
|
if name_substr.lower() in dev.name.lower()
|
|
]
|
|
if not matches:
|
|
sys.exit(
|
|
f"No input device matching '{name_substr}' found. "
|
|
f"Run --list to see available device names."
|
|
)
|
|
if len(matches) > 1:
|
|
print(f"Multiple devices match '{name_substr}', watching all:")
|
|
for d in matches:
|
|
print(f" {d.path}\t{d.name}")
|
|
print()
|
|
return matches
|
|
|
|
|
|
def code_name(ev_type: int, code: int) -> str:
|
|
"""Resolve a numeric code back to its BTN_*/ABS_*/KEY_* name(s)."""
|
|
names = ecodes.bytype.get(ev_type, {}).get(code)
|
|
if names is None:
|
|
return str(code)
|
|
return names if isinstance(names, str) else "/".join(names)
|
|
|
|
|
|
def watch(devices):
|
|
print("Exercise every physical control now (axes through full "
|
|
"range, buttons pressed, switches flipped both ways, knobs "
|
|
"turned both ways). Ctrl+C to stop.\n")
|
|
|
|
sel = selectors.DefaultSelector()
|
|
for dev in devices:
|
|
sel.register(dev, selectors.EVENT_READ, data=dev)
|
|
|
|
try:
|
|
while True:
|
|
for key, _ in sel.select():
|
|
dev = key.data
|
|
for event in dev.read():
|
|
if event.type == ecodes.EV_KEY:
|
|
name = code_name(ecodes.EV_KEY, event.code)
|
|
state = {0: "UP", 1: "DOWN", 2: "HOLD"}.get(
|
|
event.value, f"value={event.value}"
|
|
)
|
|
print(f"[{dev.name}] {name} ({event.code}) {state}")
|
|
elif event.type == ecodes.EV_ABS:
|
|
name = code_name(ecodes.EV_ABS, event.code)
|
|
print(f"[{dev.name}] {name} ({event.code}) = {event.value}")
|
|
except KeyboardInterrupt:
|
|
print("\nStopped.")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
|
|
)
|
|
parser.add_argument(
|
|
"--list", action="store_true", help="list all evdev input devices and exit"
|
|
)
|
|
parser.add_argument(
|
|
"--device", metavar="NAME",
|
|
help="substring match against evdev device name(s) to watch",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.list:
|
|
list_all_devices()
|
|
return
|
|
|
|
if args.device:
|
|
watch(find_devices(args.device))
|
|
return
|
|
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|