mirror of
https://github.com/archr-linux/Arch-R.git
synced 2026-03-31 14:41:55 -07:00
- input-merge daemon: combines gpio-keys + adc-joystick into single virtual "Arch R Gamepad" via uinput so RetroArch sees one device (fixes analog sticks ignored due to max_users=1) - retroarch-launch.sh: start/stop input-merge, VT blank on launch - retroarch.cfg: ROCKNIX-based tuning (48kHz audio, triple buffer, late input polling, autosave, core options path) - retroarch-core-options.cfg: per-core tuning from ROCKNIX RK3326 (mupen64plus, pcsx_rearmed, flycast, mame2003-plus, melonds, etc.) - archr-gamepad.cfg: autoconfig for merged virtual device - panel-detect.py: panels in numerical order (beep count = position), default_panel searches by empty dtb_name instead of panels[0] - build-rootfs.sh: compile input-merge, install core options and autoconfig, add input-merge and kill to sudoers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
233 lines
6.6 KiB
C
233 lines
6.6 KiB
C
/*
|
|
* input-merge — Merge gpio-keys + adc-joystick into single virtual gamepad
|
|
*
|
|
* The R36S has buttons (gpio-keys) and analog sticks (adc-joystick) as
|
|
* separate evdev devices. RetroArch's udev driver assigns each to a
|
|
* different player port, so analog sticks end up on "player 2" and are
|
|
* ignored when max_users=1.
|
|
*
|
|
* This daemon:
|
|
* 1. Finds both devices by name in /dev/input/
|
|
* 2. Grabs them (EVIOCGRAB) so only we read their events
|
|
* 3. Creates a virtual "Arch R Gamepad" via uinput with all capabilities
|
|
* 4. Forwards all events from both sources to the virtual device
|
|
*
|
|
* Started by retroarch-launch.sh before RetroArch, killed on RA exit.
|
|
* While grabbed, EmulationStation and other programs won't see the
|
|
* original devices — that's fine because RA is the only thing running.
|
|
*
|
|
* Build: aarch64-linux-gnu-gcc -static -O2 -o input-merge input-merge.c
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/input.h>
|
|
#include <linux/uinput.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
|
|
#define DEVICE_NAME "Arch R Gamepad"
|
|
#define MAX_EVENTS 16
|
|
#define RETRY_DELAY_US 200000 /* 200ms between retries */
|
|
#define MAX_RETRIES 50 /* 10 seconds max wait */
|
|
|
|
static volatile int running = 1;
|
|
|
|
static void signal_handler(int sig)
|
|
{
|
|
(void)sig;
|
|
running = 0;
|
|
}
|
|
|
|
/* Find /dev/input/eventX by device name, return fd or -1 */
|
|
static int find_device(const char *name)
|
|
{
|
|
char path[64];
|
|
char dev_name[256];
|
|
int i;
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
int fd;
|
|
snprintf(path, sizeof(path), "/dev/input/event%d", i);
|
|
fd = open(path, O_RDONLY | O_NONBLOCK);
|
|
if (fd < 0)
|
|
continue;
|
|
|
|
if (ioctl(fd, EVIOCGNAME(sizeof(dev_name)), dev_name) >= 0) {
|
|
if (strcmp(dev_name, name) == 0)
|
|
return fd;
|
|
}
|
|
close(fd);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* Create uinput virtual device with capabilities from both sources */
|
|
static int setup_uinput(int gpio_fd, int adc_fd)
|
|
{
|
|
int ufd;
|
|
struct uinput_setup setup;
|
|
unsigned long bits[(KEY_MAX + 1) / (8 * sizeof(unsigned long)) + 1];
|
|
int i;
|
|
|
|
ufd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
|
|
if (ufd < 0) {
|
|
perror("open /dev/uinput");
|
|
return -1;
|
|
}
|
|
|
|
/* Enable EV_KEY + copy key bits from gpio-keys */
|
|
ioctl(ufd, UI_SET_EVBIT, EV_KEY);
|
|
memset(bits, 0, sizeof(bits));
|
|
if (ioctl(gpio_fd, EVIOCGBIT(EV_KEY, sizeof(bits)), bits) >= 0) {
|
|
for (i = 0; i <= KEY_MAX; i++) {
|
|
if (bits[i / (8 * sizeof(unsigned long))] &
|
|
(1UL << (i % (8 * sizeof(unsigned long))))) {
|
|
ioctl(ufd, UI_SET_KEYBIT, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Enable EV_ABS + copy abs bits from adc-joystick (with absinfo) */
|
|
ioctl(ufd, UI_SET_EVBIT, EV_ABS);
|
|
memset(bits, 0, sizeof(bits));
|
|
if (ioctl(adc_fd, EVIOCGBIT(EV_ABS, sizeof(bits)), bits) >= 0) {
|
|
for (i = 0; i <= ABS_MAX; i++) {
|
|
if (bits[i / (8 * sizeof(unsigned long))] &
|
|
(1UL << (i % (8 * sizeof(unsigned long))))) {
|
|
struct uinput_abs_setup abs_setup;
|
|
memset(&abs_setup, 0, sizeof(abs_setup));
|
|
abs_setup.code = i;
|
|
if (ioctl(adc_fd, EVIOCGABS(i), &abs_setup.absinfo) >= 0)
|
|
ioctl(ufd, UI_ABS_SETUP, &abs_setup);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Device identity */
|
|
memset(&setup, 0, sizeof(setup));
|
|
strncpy(setup.name, DEVICE_NAME, UINPUT_MAX_NAME_SIZE - 1);
|
|
setup.id.bustype = BUS_VIRTUAL;
|
|
setup.id.vendor = 0x4152; /* "AR" */
|
|
setup.id.product = 0x3336; /* "36" */
|
|
setup.id.version = 1;
|
|
|
|
if (ioctl(ufd, UI_DEV_SETUP, &setup) < 0) {
|
|
perror("UI_DEV_SETUP");
|
|
close(ufd);
|
|
return -1;
|
|
}
|
|
if (ioctl(ufd, UI_DEV_CREATE) < 0) {
|
|
perror("UI_DEV_CREATE");
|
|
close(ufd);
|
|
return -1;
|
|
}
|
|
|
|
return ufd;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int gpio_fd = -1, adc_fd = -1, ufd, epfd;
|
|
int retries = 0;
|
|
struct epoll_event ev, events[MAX_EVENTS];
|
|
struct input_event ie;
|
|
|
|
signal(SIGTERM, signal_handler);
|
|
signal(SIGINT, signal_handler);
|
|
|
|
/* Wait for both devices to appear (kernel may still be probing) */
|
|
while (running && retries < MAX_RETRIES) {
|
|
if (gpio_fd < 0)
|
|
gpio_fd = find_device("gpio-keys");
|
|
if (adc_fd < 0)
|
|
adc_fd = find_device("adc-joystick");
|
|
if (gpio_fd >= 0 && adc_fd >= 0)
|
|
break;
|
|
usleep(RETRY_DELAY_US);
|
|
retries++;
|
|
}
|
|
|
|
if (gpio_fd < 0 || adc_fd < 0) {
|
|
fprintf(stderr, "input-merge: devices not found (gpio=%d adc=%d)\n",
|
|
gpio_fd, adc_fd);
|
|
if (gpio_fd >= 0) close(gpio_fd);
|
|
if (adc_fd >= 0) close(adc_fd);
|
|
return 1;
|
|
}
|
|
|
|
/* Grab both devices (exclusive access) */
|
|
if (ioctl(gpio_fd, EVIOCGRAB, 1) < 0)
|
|
perror("EVIOCGRAB gpio-keys");
|
|
if (ioctl(adc_fd, EVIOCGRAB, 1) < 0)
|
|
perror("EVIOCGRAB adc-joystick");
|
|
|
|
/* Create virtual combined device */
|
|
ufd = setup_uinput(gpio_fd, adc_fd);
|
|
if (ufd < 0) {
|
|
ioctl(gpio_fd, EVIOCGRAB, 0);
|
|
ioctl(adc_fd, EVIOCGRAB, 0);
|
|
close(gpio_fd);
|
|
close(adc_fd);
|
|
return 1;
|
|
}
|
|
|
|
/* Small delay for uinput device to settle */
|
|
usleep(50000);
|
|
|
|
/* Signal ready: write PID file so launcher knows we're up */
|
|
{
|
|
int pfd = open("/run/input-merge.pid", O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
if (pfd >= 0) {
|
|
char buf[16];
|
|
int len = snprintf(buf, sizeof(buf), "%d\n", getpid());
|
|
write(pfd, buf, len);
|
|
close(pfd);
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "input-merge: active (gpio=%d adc=%d -> %s)\n",
|
|
gpio_fd, adc_fd, DEVICE_NAME);
|
|
|
|
/* epoll on both source devices */
|
|
epfd = epoll_create1(0);
|
|
|
|
ev.events = EPOLLIN;
|
|
ev.data.fd = gpio_fd;
|
|
epoll_ctl(epfd, EPOLL_CTL_ADD, gpio_fd, &ev);
|
|
|
|
ev.data.fd = adc_fd;
|
|
epoll_ctl(epfd, EPOLL_CTL_ADD, adc_fd, &ev);
|
|
|
|
/* Event forwarding loop */
|
|
while (running) {
|
|
int n = epoll_wait(epfd, events, MAX_EVENTS, 1000);
|
|
int i;
|
|
for (i = 0; i < n; i++) {
|
|
while (read(events[i].data.fd, &ie, sizeof(ie)) == sizeof(ie)) {
|
|
write(ufd, &ie, sizeof(ie));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Cleanup */
|
|
ioctl(ufd, UI_DEV_DESTROY);
|
|
close(ufd);
|
|
ioctl(gpio_fd, EVIOCGRAB, 0);
|
|
ioctl(adc_fd, EVIOCGRAB, 0);
|
|
close(gpio_fd);
|
|
close(adc_fd);
|
|
close(epfd);
|
|
unlink("/run/input-merge.pid");
|
|
|
|
fprintf(stderr, "input-merge: stopped\n");
|
|
return 0;
|
|
}
|