mirror of
https://github.com/RetroPie/RetroPie-Setup.git
synced 2025-04-02 10:51:41 -04:00
Move much of the helpers.sh start/stop logic and default parameters to a joy2key wrapper script. Switch runcommand.sh to use new wrapper script Add tab button to "y" key for use in edit dialogs Remove runcommand $md_inst on update. If old joy2key is present in runcommand install, trigger joy2key module install. Remove system.sh python3-sdl2 dependency check - moved to joy2key_depends
297 lines
8.3 KiB
Python
Executable file
297 lines
8.3 KiB
Python
Executable file
#!/usr/bin/python3
|
|
|
|
# This file is part of The RetroPie Project
|
|
#
|
|
# The RetroPie Project is the legal property of its developers, whose names are
|
|
# too numerous to list here. Please refer to the COPYRIGHT.md file distributed with this source.
|
|
#
|
|
# See the LICENSE.md file at the top-level directory of this distribution and
|
|
# at https://raw.githubusercontent.com/RetroPie/RetroPie-Setup/master/LICENSE.md
|
|
#
|
|
|
|
import os, sys, struct, time, fcntl, termios, signal
|
|
import curses, errno, re
|
|
from pyudev import Context
|
|
|
|
|
|
# struct js_event {
|
|
# __u32 time; /* event timestamp in milliseconds */
|
|
# __s16 value; /* value */
|
|
# __u8 type; /* event type */
|
|
# __u8 number; /* axis/button number */
|
|
# };
|
|
|
|
JS_MIN = -32768
|
|
JS_MAX = 32768
|
|
JS_REP = 0.20
|
|
|
|
JS_THRESH = 0.75
|
|
|
|
JS_EVENT_BUTTON = 0x01
|
|
JS_EVENT_AXIS = 0x02
|
|
JS_EVENT_INIT = 0x80
|
|
|
|
CONFIG_DIR = '/opt/retropie/configs/'
|
|
RETROARCH_CFG = CONFIG_DIR + 'all/retroarch.cfg'
|
|
|
|
def ini_get(key, cfg_file):
|
|
pattern = r'[ |\t]*' + key + r'[ |\t]*=[ |\t]*'
|
|
value_m = r'"*([^"\|\r]*)"*'
|
|
value = ''
|
|
with open(cfg_file, 'r') as ini_file:
|
|
for line in ini_file:
|
|
if re.match(pattern, line):
|
|
value = re.sub(pattern + value_m + '.*\n', r'\1', line)
|
|
break
|
|
return value
|
|
|
|
def get_btn_num(btn, cfg):
|
|
num = ini_get('input_' + btn + '_btn', cfg)
|
|
if num: return num
|
|
num = ini_get('input_player1_' + btn + '_btn', cfg)
|
|
if num: return num
|
|
return ''
|
|
|
|
def sysdev_get(key, sysdev_path):
|
|
value = ''
|
|
for line in open(sysdev_path + key, 'r'):
|
|
value = line.rstrip('\n')
|
|
break
|
|
return value
|
|
|
|
def get_button_codes(dev_path):
|
|
js_cfg_dir = CONFIG_DIR + 'all/retroarch-joypads/'
|
|
js_cfg = ''
|
|
dev_name = ''
|
|
dev_button_codes = list(default_button_codes)
|
|
|
|
for device in Context().list_devices(DEVNAME=dev_path):
|
|
sysdev_path = os.path.normpath('/sys' + device.get('DEVPATH')) + '/'
|
|
if not os.path.isfile(sysdev_path + 'name'):
|
|
sysdev_path = os.path.normpath(sysdev_path + '/../') + '/'
|
|
# getting joystick name
|
|
dev_name = sysdev_get('name', sysdev_path)
|
|
# getting joystick vendor ID
|
|
dev_vendor_id = int(sysdev_get('id/vendor', sysdev_path), 16)
|
|
# getting joystick product ID
|
|
dev_product_id = int(sysdev_get('id/product', sysdev_path), 16)
|
|
if not dev_name:
|
|
return dev_button_codes
|
|
|
|
# getting retroarch config file for joystick
|
|
for f in os.listdir(js_cfg_dir):
|
|
if f.endswith('.cfg'):
|
|
input_device = ini_get('input_device', js_cfg_dir + f)
|
|
input_vendor_id = ini_get('input_vendor_id', js_cfg_dir + f)
|
|
input_product_id = ini_get('input_product_id', js_cfg_dir + f)
|
|
if (input_device == dev_name and
|
|
(input_vendor_id == '' or int(input_vendor_id) == dev_vendor_id) and
|
|
(input_product_id == '' or int(input_product_id) == dev_product_id)):
|
|
js_cfg = js_cfg_dir + f
|
|
break
|
|
if not js_cfg:
|
|
js_cfg = RETROARCH_CFG
|
|
|
|
# getting configs for dpad, buttons A, B, X and Y
|
|
btn_map = [ 'left', 'right', 'up', 'down', 'a', 'b', 'x', 'y' ]
|
|
btn_num = {}
|
|
biggest_num = 0
|
|
i = 0
|
|
for btn in list(btn_map):
|
|
if i >= len(dev_button_codes):
|
|
break
|
|
try:
|
|
btn_num[btn] = int(get_btn_num(btn, js_cfg))
|
|
except ValueError:
|
|
btn_map.pop(i)
|
|
dev_button_codes.pop(i)
|
|
btn_num.pop(btn, None)
|
|
continue
|
|
if btn_num[btn] > biggest_num:
|
|
biggest_num = btn_num[btn]
|
|
i += 1
|
|
|
|
# building the button codes list
|
|
btn_codes = [''] * (biggest_num + 1)
|
|
i = 0
|
|
for btn in btn_map:
|
|
if i >= len(dev_button_codes):
|
|
break
|
|
btn_codes[btn_num[btn]] = dev_button_codes[i]
|
|
i += 1
|
|
try:
|
|
# if button A is <enter> and menu_swap_ok_cancel_buttons is true, swap buttons A and B functions
|
|
if (ini_get('menu_swap_ok_cancel_buttons', RETROARCH_CFG) == 'true' and
|
|
'a' in btn_num and 'b' in btn_num and btn_codes[btn_num['a']] == '\n'):
|
|
btn_codes[btn_num['a']] = btn_codes[btn_num['b']]
|
|
btn_codes[btn_num['b']] = '\n'
|
|
except (IOError, ValueError):
|
|
pass
|
|
|
|
return btn_codes
|
|
|
|
def signal_handler(signum, frame):
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
signal.signal(signal.SIGTERM, signal.SIG_IGN)
|
|
if (js_fds):
|
|
close_fds(js_fds)
|
|
if (tty_fd):
|
|
os.close(tty_fd)
|
|
sys.exit(0)
|
|
|
|
def get_hex_chars(key_str):
|
|
if (key_str.startswith("0x")):
|
|
out = bytes.fromhex(key_str[2:])
|
|
else:
|
|
out = curses.tigetstr(key_str)
|
|
return out.decode('utf-8')
|
|
|
|
def get_devices():
|
|
devs = []
|
|
if sys.argv[1] == '/dev/input/jsX':
|
|
for dev in os.listdir('/dev/input'):
|
|
if dev.startswith('js'):
|
|
devs.append('/dev/input/' + dev)
|
|
else:
|
|
devs.append(sys.argv[1])
|
|
|
|
return devs
|
|
|
|
def open_devices():
|
|
devs = get_devices()
|
|
|
|
fds = []
|
|
for dev in devs:
|
|
try:
|
|
fds.append(os.open(dev, os.O_RDONLY | os.O_NONBLOCK ))
|
|
js_button_codes[fds[-1]] = get_button_codes(dev)
|
|
except (OSError, ValueError):
|
|
pass
|
|
|
|
return devs, fds
|
|
|
|
def close_fds(fds):
|
|
for fd in fds:
|
|
os.close(fd)
|
|
|
|
def read_event(fd):
|
|
while True:
|
|
try:
|
|
event = os.read(fd, event_size)
|
|
except OSError as e:
|
|
if e.errno == errno.EWOULDBLOCK:
|
|
return None
|
|
return False
|
|
|
|
else:
|
|
return event
|
|
|
|
def process_event(event):
|
|
|
|
(js_time, js_value, js_type, js_number) = struct.unpack(event_format, event)
|
|
|
|
# ignore init events
|
|
if js_type & JS_EVENT_INIT:
|
|
return False
|
|
|
|
hex_chars = ""
|
|
|
|
if js_type == JS_EVENT_BUTTON:
|
|
if js_number < len(button_codes) and js_value == 1:
|
|
hex_chars = button_codes[js_number]
|
|
|
|
if js_type == JS_EVENT_AXIS and js_number <= 7:
|
|
if js_number % 2 == 0:
|
|
if js_value <= JS_MIN * JS_THRESH:
|
|
hex_chars = axis_codes[0]
|
|
if js_value >= JS_MAX * JS_THRESH:
|
|
hex_chars = axis_codes[1]
|
|
if js_number % 2 == 1:
|
|
if js_value <= JS_MIN * JS_THRESH:
|
|
hex_chars = axis_codes[2]
|
|
if js_value >= JS_MAX * JS_THRESH:
|
|
hex_chars = axis_codes[3]
|
|
|
|
if hex_chars:
|
|
for c in hex_chars:
|
|
fcntl.ioctl(tty_fd, termios.TIOCSTI, c)
|
|
return True
|
|
|
|
return False
|
|
|
|
js_fds = []
|
|
tty_fd = []
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
# daemonize when signal handlers are registered
|
|
if os.fork():
|
|
os._exit(0)
|
|
|
|
js_button_codes = {}
|
|
button_codes = []
|
|
default_button_codes = []
|
|
axis_codes = []
|
|
|
|
curses.setupterm()
|
|
|
|
i = 0
|
|
for arg in sys.argv[2:]:
|
|
chars = get_hex_chars(arg)
|
|
if i < 4:
|
|
axis_codes.append(chars)
|
|
|
|
default_button_codes.append(chars)
|
|
i += 1
|
|
|
|
event_format = 'IhBB'
|
|
event_size = struct.calcsize(event_format)
|
|
|
|
try:
|
|
tty_fd = os.open('/dev/tty', os.O_WRONLY)
|
|
except IOError:
|
|
print('Unable to open /dev/tty', file = sys.stderr)
|
|
sys.exit(1)
|
|
|
|
rescan_time = time.time()
|
|
while True:
|
|
do_sleep = True
|
|
if not js_fds:
|
|
js_devs, js_fds = open_devices()
|
|
if js_fds:
|
|
i = 0
|
|
current = time.time()
|
|
js_last = [None] * len(js_fds)
|
|
for js in js_fds:
|
|
js_last[i] = current
|
|
i += 1
|
|
else:
|
|
time.sleep(1)
|
|
else:
|
|
i = 0
|
|
for fd in js_fds:
|
|
event = read_event(fd)
|
|
if event:
|
|
do_sleep = False
|
|
if time.time() - js_last[i] > JS_REP:
|
|
if fd in js_button_codes:
|
|
button_codes = js_button_codes[fd]
|
|
else:
|
|
button_codes = default_button_codes
|
|
if process_event(event):
|
|
js_last[i] = time.time()
|
|
elif event == False:
|
|
close_fds(js_fds)
|
|
js_fds = []
|
|
break
|
|
i += 1
|
|
|
|
if time.time() - rescan_time > 2:
|
|
rescan_time = time.time()
|
|
if js_devs != get_devices():
|
|
close_fds(js_fds)
|
|
js_fds = []
|
|
|
|
if do_sleep:
|
|
time.sleep(0.01)
|