Skip to content

Commit

Permalink
Recalbox 8 support
Browse files Browse the repository at this point in the history
-Upgraded uinput-mapper to Python 3.x support in addition to old Python 2.7 support
-Saved game data/states from settings and DB extensions should work with new launch instead of /tmp on Recalbox 8
-Small fixes/improvements to launCharc
-Automatic installation script, systemlist.xml and alsa hack for Recalbox 8 coming soon
-Porting to comined support for Python 2.7 and 3.x in uinput-mapper is not tested exetensively and might have
introduced instability/regressions. Please report in a new issue here if you experience problems!
-When running McAirpos with uinput-mapper for the first time on Recalbox 8, remember
to set / writable(mount -o remount,rw /), so *.pyc files can be written for faster Python execution.
  • Loading branch information
Vegz78 committed Dec 16, 2021
1 parent 19619c9 commit b3caca0
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 62 deletions.
Binary file modified McAirpos/launCharc/launCharc
Binary file not shown.
37 changes: 28 additions & 9 deletions McAirpos/launCharc/launCharc.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,14 @@ int main(int argc, char** argv) {
// Variables for whole main function scope
char* path = "/dev/tty";
int fd;
char basename[200];
memset (basename, 0, sizeof(basename));
char copyCmd[300];
memset (copyCmd, 0, sizeof(copyCmd));


// Read game file argument to execute
// Note: Should be updated to allow for combinations of more than one argument, e.g. "nomap verbose" and future additions
char game[200];
memset (game, 0, sizeof(game));
char options[10];
Expand All @@ -98,15 +103,22 @@ int main(int argc, char** argv) {
}


// Check if run on Recalbox
// Find rom's basename
snprintf(copyCmd, 300, "basename %s", game);
strcat(basename, getSystemOutput(copyCmd));


// Clear Linux console screen
system("clear");


// Check if run on Recalbox
int isRecalbox = 0;
if (!strcmp("RECALBOX", getSystemOutput("uname -a | tr ' ' '\\n' | grep RECALBOX | tr -d [:cntrl:]"))) {
isRecalbox = 1;
// Copy game_file.elf to /tmp(.../roms folder mount exFAT and cannot execute files)
char copyCmd[300];
memset (copyCmd, 0, sizeof(copyCmd));
snprintf(copyCmd, 300, "cp %s /tmp/arcade.elf&&chmod +x /tmp/arcade.elf", game);
snprintf(copyCmd, 300, "rsync %s /recalbox/share/bootvideos/makecode/&&chmod +x /recalbox/share/bootvideos/makecode/%s", game, basename);
system(copyCmd);
system("/usr/bin/fbv2 /home/pi/McAirpos/McAirpos/MakeCode/MakeCode_Arcade.png >>/dev/null 2>&1");
}
Expand Down Expand Up @@ -150,7 +162,11 @@ int main(int argc, char** argv) {
if (numberOfPads < 2) {
char padCommand[150];
memset (padCommand, 0, sizeof(padCommand));
snprintf(padCommand, 150, "/home/pi/McAirpos/McAirpos/uinput-mapper/input-read -vp /dev/input/event%d 2>&1 | grep -e BTN_START -e BTN_SOUTH -e BTN_PINKIE", i);
if (isRecalbox == 1) {
snprintf(padCommand, 150, "/home/pi/McAirpos/McAirpos/uinput-mapper/input-read -vp /dev/input/event%d 2>&1 | grep -e BTN_START -e BTN_SOUTH -e BTN_PINKIE", i);
}else {
snprintf(padCommand, 150, "/home/pi/McAirpos/McAirpos/uinput-mapper/input-read -vp /dev/input/event%d 2>&1 | grep -e BTN_START -e BTN_SOUTH -e BTN_PINKIE", i);
}
char* event = getSystemOutput(padCommand);
if (strcmp(event, "")) {
if (numberOfPads == 0) {
Expand Down Expand Up @@ -294,9 +310,12 @@ int main(int argc, char** argv) {
}
if (strcmp("", getSystemOutput("ps -A | grep pulse"))) {
if (isRecalbox == 1) {
system("killall pulseaudio >>/dev/null 2>&1"); //Kill PulseAudio if running, can sometimes halt game looking for ALSA
if (strcmp("RECALBOX 5", getSystemOutput("uname -a | tr ' ' '\\n' | grep RECALBOX | tr -d [:cntrl:]"))) {
system("killall pulseaudio >>/dev/null 2>&1"); //Kill PulseAudio if running below kernel 5, can sometimes halt game looking for ALSA
}
} else {
system("sudo killall pulseaudio >>/dev/null 2>&1"); //Kill PulseAudio if running, can sometimes halt game looking for ALSA
system("sudo killall pulseaudio >>/dev/null 2>&1"); //Kill PulseAudio if running on RPi OS/RetroPie, can sometimes halt game looking for ALSA
// Note: Pulseaudio used to restart automatically on kernels below 5, keep an eye on how this is handled > 5 on RPi OS/RetroPie
}
}
fflush(stdout);
Expand All @@ -319,13 +338,13 @@ int main(int argc, char** argv) {
close(fd);
}

// Run copy of game to circumvent Recalbox' read-only file system
// Run copy of game to circumvent Recalbox' read-only(/) and/or non-executablel(.../share/roms exFAT) file systems
if (isRecalbox == 1) {
memset (game, 0, sizeof(game));
strcat(game, "/tmp/arcade.elf");
snprintf(game, 200, "/recalbox/share/bootvideos/makecode/%s", basename); //New location instead of /tmp allows for saving game states in settings and DB extensions etc.
}

// Silence the game launch
// Silence the game launch information to Linux console if verbose option is not given
char gameString[200];
memcpy(gameString, game, strlen(game)+1);
if (strcmp("verbose", options)) {
Expand Down
23 changes: 19 additions & 4 deletions McAirpos/uinput-mapper/input-create
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ try:
except ImportError:
import pickle

import imp
#import imp #Deprecated
#import importlib.machinery as imp #Deprecated and not backwards compatible with 2.7
import optparse

_usage = 'python create.py /path/to/config1 ... /path/to/configN'
Expand All @@ -31,7 +32,12 @@ parser.add_option('-v', '--verbose', action='store_true',
args, cfg = parser.parse_args()

# Unpickle from stdin ; currently this is the default and only way
in_f = pickle.Unpickler(sys.stdin)
# Python 3 changed os.write() to require to specify bytes vs strings,
# since strings=bytes in Python 2.7...
if sys.version_info.major == 3:
in_f = pickle.Unpickler(sys.stdin.buffer)
else:
in_f = pickle.Unpickler(sys.stdin)

# Read input device count
nifd = in_f.load()
Expand All @@ -47,7 +53,15 @@ if args.verbose:

# Allow configurations to change our current configuration
for path in cfg:
config_merge = imp.load_source('', path).config_merge
# config_merge = imp.load_source('', path).config_merge #Deprecated
# config_merge = imp.SourceFileLoader('', path).load_module() #Deprecated in 3.5, not backwards compatible to 2.7

#Should work to dynamically load modules in 2.7-3.x
config_file = path
with open(config_file) as f:
code = compile(f.read(), config_file, 'exec')
exec(code, globals(), locals())

config_merge(conf, names)

if args.verbose:
Expand All @@ -60,7 +74,8 @@ nofd = get_exported_device_count(conf)

# Create and expose uinput devices
ofs = []
for f in xrange(nofd):
#for f in xrange(nofd):
for f in range(nofd):
name = names[f]
d = UInputDevice()
m.expose(d, f)
Expand Down
22 changes: 14 additions & 8 deletions McAirpos/uinput-mapper/input-read
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ if len(input_file) + len(args.grab) == 0:
exit(0)

# Open input devices
fs = map(InputDevice, input_file)
fs = list(map(InputDevice, input_file))

# Open devices in grab mode
fsg = map(InputDevice, args.grab)
fsg = list(map(InputDevice, args.grab))

# Grab devices
for _ in fsg:
Expand Down Expand Up @@ -73,16 +73,21 @@ for f in fs:
# Human readable info
if args.dump:
for f in fs:
print 'Version:', f.get_version()
print f.get_name()
print ('Version:', f.get_version())
print (f.get_name())

d = f.get_exposed_events()
for k, v in d.iteritems():
print k + ':', ', '.join(v)
print (k + ':', ', '.join(v))

else:
# Dump initial information over pickle to stdout
p = pickle.Pickler(sys.stdout)
# Python 3.x requires to specify bytes vs strings,
# since in 2.7 strings=bytes...
if sys.version_info.major == 3:
p = pickle.Pickler(sys.stdout.buffer)
else:
p = pickle.Pickler(sys.stdout)

p.dump(len(fs))

Expand All @@ -91,6 +96,7 @@ else:

sys.stdout.flush()


while True:
events = pp.poll()

Expand All @@ -110,10 +116,10 @@ while True:

if args.dump:
try:
print i, ev.time.tv_sec, ev.time.tv_usec
print (i, ev.time.tv_sec, ev.time.tv_usec)
s = '%s %s %d' % (rev_events[ev.type],
rev_event_keys[ev.type][ev.code], ev.value)
print 'Event type:', s
print ('Event type:', s)
except KeyError:
pass

Expand Down
42 changes: 29 additions & 13 deletions McAirpos/uinput-mapper/uinputmapper/cinput.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from linux_input import *
from linux_uinput import *
from uinputmapper.linux_input import *
from uinputmapper.linux_uinput import *

import array, struct, fcntl, os, sys

Expand All @@ -16,9 +16,15 @@ def get_input_name(f, l=256):
"""
Returns the name of a specified fd of a device
"""
buf = array.array('c', ' ' * l)
# buf = array.array('c', ' ' * l)
buf = array.array('B', [0] * l)
r = fcntl.ioctl(f, EVIOCGNAME(l), buf)
return ''.join(buf.tolist()[:r])
buflist = buf.tolist()
buflistchar = []
for i in range(0, r):
buflistchar.append(chr(buflist[i]))
# return ''.join(buf.tolist()[:r])
return ''.join(buflistchar)

def read_abs_values(f, abs_ev):
buf = array.array('i', [0] * 6)
Expand All @@ -30,17 +36,19 @@ def read_abs_values(f, abs_ev):
_bpl = struct.calcsize('@L') * 8
_nbits = lambda x: ((x-1) / _bpl) + 1
_ll = _nbits(KEY_MAX)
test_bit = lambda j, v: (v[j / _bpl] >> (j % _bpl)) & 1
#test_bit = lambda j, v: (v[j / _bpl] >> (j % _bpl)) & 1
test_bit = lambda j, v: (v[int(j / _bpl)] >> (j % _bpl)) & 1

def get_keys(f, ev):
"""
Get keys of type *f* from a specific input device *f*.
"""
buf = array.array('L', [0L] * _ll)
# buf = array.array('L', [0L] * _ll)
buf = array.array('L', [0] * int(_ll))
try:
fcntl.ioctl(f, EVIOCGBIT(ev, KEY_MAX), buf)
except IOError:
#print >>sys.stderr, 'Whoops!', rev_events[ev]
#print >>sys.stderr, 'Whoopso!', rev_events[ev]
return None

v = struct.unpack('@%dL' % _ll, buf)
Expand Down Expand Up @@ -89,7 +97,8 @@ def get_exposed_events(self):
Returns all the keys exposed by this input device.
"""
d = dict()
for k, v in events.iteritems():
# for k, v in events.iteritems():
for k, v in events.items():
l = get_keys(self._f, v)
if l:
d[k] = []
Expand Down Expand Up @@ -142,7 +151,7 @@ def open_uinput():
try:
f = os.open('/dev/input/uinput', os.O_WRONLY | os.O_NONBLOCK)
except OSError:
print 'FAIL MUCH?'
print ('FAIL MUCH?')
return None
return f

Expand All @@ -157,13 +166,17 @@ def write_uinput_device_info(uidev, f, name):
# Allocate other info

# TODO: Get from specs
uidev.name = name
# uidev.name = name
uidev.name = name.encode()
uidev._id.bustype = 0x03 # BUS_USB (TODO)
uidev._id.vendor = 0x42
uidev._id.product = 0xbebe
uidev._id.version = 1

buf = buffer(uidev)[:]
if sys.version_info.major == 3:
buf = bytes(memoryview(uidev))[:]
else:
buf = buffer(uidev)[:]

# Write dev info
os.write(f, buf)
Expand All @@ -185,7 +198,7 @@ class UInputDevice(object):
def __init__(self):
self._f = open_uinput()
if not self._f:
print 'Failed to open uinput'
print ('Failed to open uinput')
raise OSError
self.uidev = uinput_user_dev()

Expand Down Expand Up @@ -218,7 +231,10 @@ def fire_event(self, ev):
"""
Fire a new input event.
"""
os.write(self._f, buffer(ev)[:])
if sys.version_info.major == 3:
os.write(self._f, bytes(memoryview(ev))[:])
else:
os.write(self._f, buffer(ev)[:])

def __del__(self):
if hasattr(self, '_f'):
Expand Down
3 changes: 2 additions & 1 deletion McAirpos/uinput-mapper/uinputmapper/ioctlhelp.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@


def IOC(_dir, _type, nr, size):
if type(size) in (str, unicode):
# if type(size) in (str, unicode):
if type(size) in (str, u''.__class__):
size = struct.calcsize(size)
return _dir << _IOC_DIRSHIFT | _type << _IOC_TYPESHIFT | \
nr << _IOC_NRSHIFT | size << _IOC_SIZESHIFT
Expand Down
24 changes: 16 additions & 8 deletions McAirpos/uinput-mapper/uinputmapper/linux_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@

import struct

from uinput_gen import input_constants_dict as icd
from uinputmapper.uinput_gen import input_constants_dict as icd

for k, v in icd.iteritems():
locals()[k] = v

rdict = lambda x: dict(map(lambda (k, v): (v, k), x.iteritems()))
#for k, v in icd.iteritems():
# locals()[k] = v
for k, v in icd.items():
locals()[k] = v

events = dict(filter(lambda (k, v): k in ["EV_SYN", "EV_KEY", "EV_REL",
#rdict = lambda x: dict(map(lambda (k, v): (v, k), x.iteritems()))
rdict = lambda x: dict(map(lambda kv: (kv[1], kv[0]), x.items()))

#events = dict(filter(lambda (k, v): k in ["EV_SYN", "EV_KEY", "EV_REL",
# "EV_ABS", "EV_MSC", "EV_SW", "EV_LED", "EV_SND", "EV_REP",
# "EV_FF", "EV_PWR", "EV_FF_STATUS"], icd.iteritems()))
events = dict(filter(lambda kv: kv[0] in ["EV_SYN", "EV_KEY", "EV_REL",
"EV_ABS", "EV_MSC", "EV_SW", "EV_LED", "EV_SND", "EV_REP",
"EV_FF", "EV_PWR", "EV_FF_STATUS"], icd.iteritems()))
"EV_FF", "EV_PWR", "EV_FF_STATUS"], icd.items()))
rev_events = rdict(events)

filter_event = lambda c: dict(filter(lambda (k, v): c(k), icd.iteritems()))
#filter_event = lambda c: dict(filter(lambda (k, v): c(k), icd.iteritems()))
filter_event = lambda c: dict(filter(lambda kv: c(kv[0]), icd.items()))

keys = filter_event(lambda x: x.startswith("KEY_") or x.startswith("BTN_"))
rev_keys = rdict(keys)
Expand Down Expand Up @@ -108,7 +116,7 @@ class input_absinfo(ctypes.Structure):
]


from ioctlhelp import IOR, IOW, IOC, IO, _IOC_READ
from uinputmapper.ioctlhelp import IOR, IOW, IOC, IO, _IOC_READ

# Get driver version
EVIOCGVERSION = IOR(ord('E'), 0x01, '@i')
Expand Down
8 changes: 5 additions & 3 deletions McAirpos/uinput-mapper/uinputmapper/linux_uinput.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from ioctlhelp import *
from uinputmapper.ioctlhelp import *
import sys

import uinputmapper.linux_input as linux_input

import linux_input

# For uinput version 3

Expand Down Expand Up @@ -48,7 +50,7 @@
UI_FF_ERASE = 2

import ctypes
import linux_input
import uinputmapper.linux_input as linux_input

UINPUT_MAX_NAME_SIZE = 80
class uinput_user_dev(ctypes.Structure):
Expand Down

0 comments on commit b3caca0

Please sign in to comment.