diff --git a/emulator.c b/emulator.c index 439684e..dfd83bc 100644 --- a/emulator.c +++ b/emulator.c @@ -6,399 +6,645 @@ #include #include #include +#include #include #include #include #include -#include #include -#include "keys.h" - -#define GAMEPAD_NAME "Virtual XPAD" +#include +#include + +#define GAMEPAD_NAME "Virtual XPAD" +#define VENDOR_ID 0x045e /* Microsoft */ +#define PRODUCT_ID 0x028e /* Xbox 360 Controller */ + +/* Right stick: clamp range and auto-center timeout */ +#define RSTICK_MAX 512 +#define RSTICK_MIN -512 +#define RSTICK_CENTER_MS 80 /* ms without mouse movement → center stick */ + +/* Left stick multiplier range */ +#define ABS_MULTIPLIER_MIN 1 +#define ABS_MULTIPLIER_MAX 15 +#define ABS_MULTIPLIER_DEF 8 +#define ABS_STEP 2000 + +/* Trigger full-press value */ +#define TRIGGER_MAX 255 + +/* ── globals (needed by signal handler) ─────────────────────────── */ +static int keyboard_fd = -1; +static int mouse_fd = -1; +static int touchpad_fd = -1; +static int gamepad_fd = -1; +static int epoll_fd = -1; + +static bool verbose = false; +static int absMultiplier = ABS_MULTIPLIER_DEF; + +static char pathKeyboard[256] = "???"; +static char pathMouse[256] = "???"; +static char pathTouchpad[256] = ""; +static bool mouse_is_touchpad = false; + +/* Multitouch state: track up to 10 slots */ +#define MT_MAX_SLOTS 10 +static struct { + int tracking_id; + int x, y; + bool active; +} mt_slots[MT_MAX_SLOTS]; +static int mt_current_slot = 0; +static int mt_prev_x = -1, mt_prev_y = -1; +static int mt_smooth_rx = 0, mt_smooth_ry = 0; + +/* ── helpers ─────────────────────────────────────────────────────── */ +static long now_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; +} -#define MOUSE_SENSITIVITY 512 -#define MOUSE_SENSITIVITY_NEGATIVE -512 +static void send_event(int fd, int type, int code, int value) +{ + struct input_event ev; + memset(&ev, 0, sizeof(ev)); + ev.type = type; + ev.code = code; + ev.value = value; + if (write(fd, &ev, sizeof(ev)) < 0) + perror("write event"); + else if (verbose) + printf("-> Gamepad: type=%d code=%d value=%d\n", type, code, value); +} -char pathKeyboard[256] = "???"; -char pathMouse[256] = "???"; +static void send_sync(int fd) +{ + send_event(fd, EV_SYN, SYN_REPORT, 0); +} -bool verbose = false; +static void send_event_sync(int fd, int type, int code, int value) +{ + send_event(fd, type, code, value); + send_sync(fd); +} -bool q_pressed = false; +/* ── cleanup ─────────────────────────────────────────────────────── */ +static void cleanup(void) +{ + if (keyboard_fd >= 0) { + close(keyboard_fd); + keyboard_fd = -1; + } + if (mouse_fd >= 0) { + close(mouse_fd); + mouse_fd = -1; + } + if (touchpad_fd >= 0) { + close(touchpad_fd); + touchpad_fd = -1; + } + if (gamepad_fd >= 0) { + if (ioctl(gamepad_fd, UI_DEV_DESTROY) < 0) + perror("UI_DEV_DESTROY"); + close(gamepad_fd); + gamepad_fd = -1; + } + if (epoll_fd >= 0) { + close(epoll_fd); + epoll_fd = -1; + } +} -int absMultiplier = 8; +static void signal_handler(int sig) +{ + (void)sig; + printf("\nExiting.\n"); + cleanup(); + exit(0); +} +/* ── argp ────────────────────────────────────────────────────────── */ static int parse_opt(int key, char *arg, struct argp_state *state) { - switch(key) - { + (void)state; + switch (key) { + case 't': + if (strlen(arg) < sizeof(pathTouchpad)) + strcpy(pathTouchpad, arg); + break; case 'm': - if(strlen(arg)<256) strcpy(pathMouse,arg); + if (strlen(arg) < sizeof(pathMouse)) + strcpy(pathMouse, arg); break; case 'k': - if(strlen(arg)<256) strcpy(pathKeyboard,arg); + if (strlen(arg) < sizeof(pathKeyboard)) + strcpy(pathKeyboard, arg); break; case 'v': verbose = true; break; } - return 0; } -int parse_arguments(int argc, char**argv) +static void parse_arguments(int argc, char **argv) { - struct argp_option options[] = - { - { "mouse", 'm', "PATH", 0, "The path to mouse (should look something like /dev/input/eventX)" }, - { "keyboard", 'k', "PATH", 0, "The path to keyboard (should look something like /dev/input/eventX)" }, - { "verbose", 'v', 0, OPTION_ARG_OPTIONAL, "Show more info" }, + struct argp_option options[] = { + { "mouse", 'm', "PATH", 0, "Path to mouse (/dev/input/eventX)" }, + { "keyboard", 'k', "PATH", 0, "Path to keyboard (/dev/input/eventX)" }, + { "touchpad", 't', "PATH", 0, "Path to touchpad (/dev/input/eventX) - 2 fingers = right stick" }, + { "verbose", 'v', 0, OPTION_ARG_OPTIONAL, "Show events" }, { 0 } }; - struct argp argp = { options, parse_opt }; - - return argp_parse (&argp, argc, argv, 0, 0, 0); + struct argp argp = { options, parse_opt, 0, 0 }; + argp_parse(&argp, argc, argv, 0, 0, 0); } -void exitFunc(int keyboard_fd, int mouse_fd, int gamepad_fd) +/* ── keymap printout ─────────────────────────────────────────────── */ +static void print_keymap(void) { - close(keyboard_fd); - close(mouse_fd); - if (ioctl(gamepad_fd, UI_DEV_DESTROY) < 0) - { - printf("Error destroying gamepad! \n"); - } - close(gamepad_fd); + printf("\n=== Virtual XPAD key map ===\n"); + printf(" Face buttons :\n"); + printf(" Space -> A\n"); + printf(" Left Alt -> B\n"); + printf(" X -> X\n"); + printf(" C -> Y\n"); + printf(" Bumpers/Triggers:\n"); + printf(" Left Shift -> LB\n"); + printf(" Right Shift-> RB\n"); + printf(" Q -> LT (analog)\n"); + printf(" E -> RT (analog)\n"); + printf(" Menu:\n"); + printf(" Enter -> Start\n"); + printf(" Tab -> Back/Select\n"); + printf(" F -> Xbox/Guide\n"); + printf(" Left stick : WASD\n"); + printf(" Right stick : Mouse move OR 2-finger touchpad\n"); + printf(" Mouse L -> L3\n"); + printf(" Mouse R -> R3\n"); + printf(" Mouse Mid -> Reset right stick\n"); + printf(" D-Pad : Arrow keys\n"); + printf(" Sensitivity : PgUp/PgDn or scroll wheel\n"); + printf(" Left Ctrl -> Reset right stick\n"); + printf(" Q + Enter -> Quit\n"); + printf("============================\n\n"); } -void send_sync_event(int gamepad_fd, struct input_event gamepad_event) +/* ── uinput gamepad setup ────────────────────────────────────────── */ +static int create_gamepad(void) { - memset(&gamepad_event, 0, sizeof(struct input_event)); - gamepad_event.type = EV_SYN; - gamepad_event.code = 0; - gamepad_event.value = 0; - - if(write(gamepad_fd, &gamepad_event, sizeof(struct input_event)) < 0) - { - printf("Error writing sync event\n"); + int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (fd < 0) { + perror("open /dev/uinput"); + return -1; } -} -// TYPE Is The event to write to the gamepad and CODE is an integer value for the button on the gamepad -void send_event(int gamepad_fd, struct input_event gamepad_event, int TYPE, int CODE, int VALUE) -{ - memset(&gamepad_event, 0, sizeof(struct input_event)); - gamepad_event.type = TYPE; - gamepad_event.code = CODE; - gamepad_event.value = VALUE; - - if(write(gamepad_fd, &gamepad_event, sizeof(struct input_event)) < 0) - { - printf("Error writing event to gamepad!\n"); + /* Keys */ + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_KEYBIT, BTN_A); + ioctl(fd, UI_SET_KEYBIT, BTN_B); + ioctl(fd, UI_SET_KEYBIT, BTN_X); + ioctl(fd, UI_SET_KEYBIT, BTN_Y); + ioctl(fd, UI_SET_KEYBIT, BTN_TL); + ioctl(fd, UI_SET_KEYBIT, BTN_TR); + ioctl(fd, UI_SET_KEYBIT, BTN_TL2); + ioctl(fd, UI_SET_KEYBIT, BTN_TR2); + ioctl(fd, UI_SET_KEYBIT, BTN_START); + ioctl(fd, UI_SET_KEYBIT, BTN_SELECT); + ioctl(fd, UI_SET_KEYBIT, BTN_MODE); + ioctl(fd, UI_SET_KEYBIT, BTN_THUMBL); + ioctl(fd, UI_SET_KEYBIT, BTN_THUMBR); + ioctl(fd, UI_SET_KEYBIT, BTN_DPAD_UP); + ioctl(fd, UI_SET_KEYBIT, BTN_DPAD_DOWN); + ioctl(fd, UI_SET_KEYBIT, BTN_DPAD_LEFT); + ioctl(fd, UI_SET_KEYBIT, BTN_DPAD_RIGHT); + + /* Absolute axes */ + ioctl(fd, UI_SET_EVBIT, EV_ABS); + ioctl(fd, UI_SET_ABSBIT, ABS_X); + ioctl(fd, UI_SET_ABSBIT, ABS_Y); + ioctl(fd, UI_SET_ABSBIT, ABS_RX); + ioctl(fd, UI_SET_ABSBIT, ABS_RY); + ioctl(fd, UI_SET_ABSBIT, ABS_Z); /* LT */ + ioctl(fd, UI_SET_ABSBIT, ABS_RZ); /* RT */ + + struct uinput_user_dev uidev; + memset(&uidev, 0, sizeof(uidev)); + snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, GAMEPAD_NAME); + uidev.id.bustype = BUS_USB; + uidev.id.vendor = VENDOR_ID; + uidev.id.product = PRODUCT_ID; + uidev.id.version = 0x0110; + + /* Left stick */ + uidev.absmax[ABS_X] = 32767; uidev.absmin[ABS_X] = -32768; + uidev.absflat[ABS_X] = 15; + uidev.absmax[ABS_Y] = 32767; uidev.absmin[ABS_Y] = -32768; + uidev.absflat[ABS_Y] = 15; + + /* Right stick */ + uidev.absmax[ABS_RX] = RSTICK_MAX; uidev.absmin[ABS_RX] = RSTICK_MIN; + uidev.absflat[ABS_RX] = 16; + uidev.absmax[ABS_RY] = RSTICK_MAX; uidev.absmin[ABS_RY] = RSTICK_MIN; + uidev.absflat[ABS_RY] = 16; + + /* Triggers (LT=ABS_Z, RT=ABS_RZ) */ + uidev.absmax[ABS_Z] = TRIGGER_MAX; uidev.absmin[ABS_Z] = 0; + uidev.absmax[ABS_RZ] = TRIGGER_MAX; uidev.absmin[ABS_RZ] = 0; + + if (write(fd, &uidev, sizeof(uidev)) < 0) { + perror("write uinput_user_dev"); + close(fd); + return -1; } - else if(verbose) - { - printf("-> Gamepad: type %d code %d value %d ", gamepad_event.type, gamepad_event.code, gamepad_event.value); + if (ioctl(fd, UI_DEV_CREATE) < 0) { + perror("UI_DEV_CREATE"); + close(fd); + return -1; } -} -void send_event_and_sync(int gamepad_fd, struct input_event gamepad_event, int TYPE, int CODE, int VALUE) -{ - send_event(gamepad_fd, gamepad_event, TYPE, CODE, VALUE); - send_sync_event(gamepad_fd, gamepad_event); + return fd; } -int main(int argc, char *argv[]) +/* ── event handlers ──────────────────────────────────────────────── */ +static bool q_pressed = false; + +static void handle_keyboard(struct input_event *ev) { - parse_arguments(argc, argv); + if (ev->type == EV_SYN || ev->type == EV_MSC) + return; - sleep(1); - int rcode = 0; + if (verbose) + printf("Keyboard: type=%d code=%d value=%d\n", + ev->type, ev->code, ev->value); - char keyboard_name[256] = "Unknown"; - int keyboard_fd = open(pathKeyboard, O_RDONLY | O_NONBLOCK); - if (keyboard_fd == -1) - { - printf("Failed to open keyboard -> %s\n",pathKeyboard); - exit(1); - } - rcode = ioctl(keyboard_fd, EVIOCGNAME(sizeof(keyboard_name)), keyboard_name); - printf("Reading from : %s \n", keyboard_name); + if (ev->type != EV_KEY) + return; - // printf("Getting exclusive access: "); - // rcode = ioctl(keyboard_fd, EVIOCGRAB, 1); - // printf("%s\n", (rcode == 0) ? "SUCCESS" : "FAILURE");>> - struct input_event keyboard_event; + int v = ev->value; /* 0=release 1=press 2=repeat */ - char mouse_name[256] = "Unknown"; - int mouse_fd = open(pathMouse, O_RDWR | O_NOCTTY | O_NONBLOCK); - if (mouse_fd == -1) - { - printf("Failed to open mouse -> %s\n",pathMouse); - exit(1); - } + /* quit combo: Q then Enter */ + if (ev->code == KEY_Q && v == 1) + q_pressed = !q_pressed; - rcode = ioctl(mouse_fd, EVIOCGNAME(sizeof(mouse_name)), mouse_name); - printf("Reading from : %s \n", mouse_name); + if (ev->code == KEY_ENTER && v == 1 && q_pressed) { + printf("Quit combo pressed.\n"); + cleanup(); + exit(0); + } - struct input_event mouse_event; + /* ── one-shot buttons (no repeat) ── */ + if (v != 2) { + switch (ev->code) { + case KEY_SPACE: send_event_sync(gamepad_fd, EV_KEY, BTN_A, v); break; + case KEY_LEFTALT: send_event_sync(gamepad_fd, EV_KEY, BTN_B, v); break; + case KEY_X: send_event_sync(gamepad_fd, EV_KEY, BTN_X, v); break; + case KEY_C: send_event_sync(gamepad_fd, EV_KEY, BTN_Y, v); break; + + case KEY_LEFTSHIFT: send_event_sync(gamepad_fd, EV_KEY, BTN_TL, v); break; + case KEY_RIGHTSHIFT:send_event_sync(gamepad_fd, EV_KEY, BTN_TR, v); break; + + /* LT / RT as analog triggers */ + case KEY_Q: + send_event_sync(gamepad_fd, EV_ABS, ABS_Z, v ? TRIGGER_MAX : 0); + break; + case KEY_E: + send_event_sync(gamepad_fd, EV_ABS, ABS_RZ, v ? TRIGGER_MAX : 0); + break; + + case KEY_ENTER: send_event_sync(gamepad_fd, EV_KEY, BTN_START, v); break; + case KEY_TAB: send_event_sync(gamepad_fd, EV_KEY, BTN_SELECT, v); break; + case KEY_F: send_event_sync(gamepad_fd, EV_KEY, BTN_MODE, v); break; + + case KEY_UP: send_event_sync(gamepad_fd, EV_KEY, BTN_DPAD_UP, v); break; + case KEY_DOWN: send_event_sync(gamepad_fd, EV_KEY, BTN_DPAD_DOWN, v); break; + case KEY_LEFT: send_event_sync(gamepad_fd, EV_KEY, BTN_DPAD_LEFT, v); break; + case KEY_RIGHT: send_event_sync(gamepad_fd, EV_KEY, BTN_DPAD_RIGHT, v); break; + } + } - // printf("Getting exclusive access: "); - // rcode = ioctl(mouse_fd, EVIOCGRAB, 1); - // printf("%s\n", (rcode == 0) ? "SUCCESS" : "FAILURE"); + /* ── left stick: press + hold (1 and 2) ── */ + bool held = (v == 1 || v == 2); + int mag = ABS_STEP * absMultiplier; + switch (ev->code) { + case KEY_W: send_event_sync(gamepad_fd, EV_ABS, ABS_Y, held ? -mag : 0); break; + case KEY_S: send_event_sync(gamepad_fd, EV_ABS, ABS_Y, held ? mag : 0); break; + case KEY_A: send_event_sync(gamepad_fd, EV_ABS, ABS_X, held ? -mag : 0); break; + case KEY_D: send_event_sync(gamepad_fd, EV_ABS, ABS_X, held ? mag : 0); break; + } - // Now, create gamepad + /* ── sensitivity ── */ + if (v == 1) { + if (ev->code == KEY_PAGEUP) + absMultiplier = absMultiplier < ABS_MULTIPLIER_MAX ? absMultiplier + 1 : ABS_MULTIPLIER_MAX; + if (ev->code == KEY_PAGEDOWN) + absMultiplier = absMultiplier > ABS_MULTIPLIER_MIN ? absMultiplier - 1 : ABS_MULTIPLIER_MIN; + if (ev->code == KEY_LEFTCTRL) { + send_event(gamepad_fd, EV_ABS, ABS_RX, 0); + send_event(gamepad_fd, EV_ABS, ABS_RY, 0); + send_sync(gamepad_fd); + } + } +} - int gamepad_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); - if (gamepad_fd < 0) - { - printf("Opening of input failed! \n"); - return 1; +static void handle_mouse(struct input_event *ev) +{ + if (ev->type == EV_SYN || ev->type == EV_MSC) + return; + + if (verbose) + printf("Mouse: type=%d code=%d value=%d\n", + ev->type, ev->code, ev->value); + + switch (ev->type) { + case EV_REL: { + int raw = ev->value * 32; + if (raw > RSTICK_MAX) raw = RSTICK_MAX; + if (raw < -RSTICK_MAX) raw = -RSTICK_MAX; /* use RSTICK_MAX as limit */ + + if (ev->code == REL_X) + send_event_sync(gamepad_fd, EV_ABS, ABS_RX, raw); + else if (ev->code == REL_Y) + send_event_sync(gamepad_fd, EV_ABS, ABS_RY, raw); + else if (ev->code == REL_WHEEL) { + absMultiplier += ev->value; + if (absMultiplier > ABS_MULTIPLIER_MAX) absMultiplier = ABS_MULTIPLIER_MAX; + if (absMultiplier < ABS_MULTIPLIER_MIN) absMultiplier = ABS_MULTIPLIER_MIN; + } + break; + } + case EV_KEY: + if (ev->code == BTN_LEFT) + send_event_sync(gamepad_fd, EV_KEY, BTN_THUMBL, ev->value); + else if (ev->code == BTN_RIGHT) + send_event_sync(gamepad_fd, EV_KEY, BTN_THUMBR, ev->value); + else if (ev->code == BTN_MIDDLE && ev->value == 1) { + send_event(gamepad_fd, EV_ABS, ABS_RX, 0); + send_event(gamepad_fd, EV_ABS, ABS_RY, 0); + send_sync(gamepad_fd); + } + break; } +} - ioctl(gamepad_fd, UI_SET_EVBIT, EV_KEY); // setting Gamepad keys - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_A); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_B); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_X); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_Y); +static int mt_active_count(void) +{ + int count = 0; + for (int i = 0; i < MT_MAX_SLOTS; i++) + if (mt_slots[i].active) count++; + return count; +} - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_TL); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_TR); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_TL2); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_TR2); +static void handle_touchpad(struct input_event *ev) +{ + if (ev->type == EV_SYN) { + if (ev->code == SYN_REPORT && mt_active_count() == 2) { + int sum_x = 0, sum_y = 0, n = 0; + for (int i = 0; i < MT_MAX_SLOTS; i++) { + if (mt_slots[i].active) { + sum_x += mt_slots[i].x; + sum_y += mt_slots[i].y; + n++; + } + } + int cx = sum_x / n; + int cy = sum_y / n; + + if (mt_prev_x >= 0 && mt_prev_y >= 0) { + int raw_dx = cx - mt_prev_x; + int raw_dy = cy - mt_prev_y; + + /* dead zone: ignore micro-tremors */ + if (abs(raw_dx) < 2 && abs(raw_dy) < 2) + goto update_prev; + + /* scale to stick range */ + int dx = raw_dx * 8; + int dy = raw_dy * 8; + + /* power curve: gentle start, stronger finish */ + float fx = dx / (float)RSTICK_MAX; + float fy = dy / (float)RSTICK_MAX; + fx = (fx > 0 ? 1 : -1) * pow(fabs(fx), 0.65f); + fy = (fy > 0 ? 1 : -1) * pow(fabs(fy), 0.65f); + + /* exponential smoothing: 70% prev, 30% new */ + mt_smooth_rx = (int)(mt_smooth_rx * 0.7f + fx * RSTICK_MAX * 0.3f); + mt_smooth_ry = (int)(mt_smooth_ry * 0.7f + fy * RSTICK_MAX * 0.3f); + + int out_x = mt_smooth_rx; + int out_y = mt_smooth_ry; + + if (out_x > RSTICK_MAX) out_x = RSTICK_MAX; + if (out_x < -RSTICK_MAX) out_x = -RSTICK_MAX; + if (out_y > RSTICK_MAX) out_y = RSTICK_MAX; + if (out_y < -RSTICK_MAX) out_y = -RSTICK_MAX; + + if (abs(out_x) > 2 || abs(out_y) > 2) { + send_event(gamepad_fd, EV_ABS, ABS_RX, out_x); + send_event(gamepad_fd, EV_ABS, ABS_RY, out_y); + send_sync(gamepad_fd); + if (verbose) + printf("Touchpad 2F: raw=(%d,%d) smooth=(%d,%d)\n", + raw_dx, raw_dy, out_x, out_y); + } + } +update_prev: + mt_prev_x = cx; + mt_prev_y = cy; + } + if (ev->code == SYN_REPORT && mt_active_count() != 2) { + mt_prev_x = -1; + mt_prev_y = -1; + mt_smooth_rx = 0; + mt_smooth_ry = 0; + /* center stick on lift */ + send_event(gamepad_fd, EV_ABS, ABS_RX, 0); + send_event(gamepad_fd, EV_ABS, ABS_RY, 0); + send_sync(gamepad_fd); + } + return; + } + + if (ev->type == EV_ABS) { + switch (ev->code) { + case ABS_MT_SLOT: + if (ev->value >= 0 && ev->value < MT_MAX_SLOTS) + mt_current_slot = ev->value; + break; + case ABS_MT_TRACKING_ID: + if (ev->value == -1) { + mt_slots[mt_current_slot].active = false; + mt_slots[mt_current_slot].tracking_id = -1; + } else { + mt_slots[mt_current_slot].active = true; + mt_slots[mt_current_slot].tracking_id = ev->value; + } + break; + case ABS_MT_POSITION_X: + mt_slots[mt_current_slot].x = ev->value; + break; + case ABS_MT_POSITION_Y: + mt_slots[mt_current_slot].y = ev->value; + break; + } + } +} - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_START); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_SELECT); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_THUMBL); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_THUMBR); +int main(int argc, char *argv[]) +{ + parse_arguments(argc, argv); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_DPAD_UP); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_DPAD_DOWN); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_DPAD_LEFT); - ioctl(gamepad_fd, UI_SET_KEYBIT, BTN_DPAD_RIGHT); + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); - ioctl(gamepad_fd, UI_SET_EVBIT, EV_ABS); // setting Gamepad thumbsticks - ioctl(gamepad_fd, UI_SET_ABSBIT, ABS_X); - ioctl(gamepad_fd, UI_SET_ABSBIT, ABS_Y); - ioctl(gamepad_fd, UI_SET_ABSBIT, ABS_RX); - ioctl(gamepad_fd, UI_SET_ABSBIT, ABS_RY); - ioctl(gamepad_fd, UI_SET_ABSBIT, ABS_TILT_X); - ioctl(gamepad_fd, UI_SET_ABSBIT, ABS_TILT_Y); + /* open keyboard */ + keyboard_fd = open(pathKeyboard, O_RDONLY | O_NONBLOCK); + if (keyboard_fd < 0) { + fprintf(stderr, "Failed to open keyboard: %s\n", pathKeyboard); + return 1; + } + char kbd_name[256] = "Unknown"; + ioctl(keyboard_fd, EVIOCGNAME(sizeof(kbd_name)), kbd_name); + printf("Keyboard : %s (%s)\n", kbd_name, pathKeyboard); - struct uinput_user_dev uidev; - memset(&uidev, 0, sizeof(uidev)); - snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, GAMEPAD_NAME); // Name of Gamepad - uidev.id.bustype = BUS_USB; - uidev.id.vendor = 0x3; - uidev.id.product = 0x3; - uidev.id.version = 2; - uidev.absmax[ABS_X] = 32767; // Parameters of thumbsticks - uidev.absmin[ABS_X] = -32768; - uidev.absfuzz[ABS_X] = 0; - uidev.absflat[ABS_X] = 15; - uidev.absmax[ABS_Y] = 32767; - uidev.absmin[ABS_Y] = -32768; - uidev.absfuzz[ABS_Y] = 0; - uidev.absflat[ABS_Y] = 15; - uidev.absmax[ABS_RX] = 512; - uidev.absmin[ABS_RX] = -512; - uidev.absfuzz[ABS_RX] = 0; - uidev.absflat[ABS_RX] = 16; - uidev.absmax[ABS_RY] = 512; - uidev.absmin[ABS_RY] = -512; - uidev.absfuzz[ABS_RY] = 0; - uidev.absflat[ABS_RY] = 16; - - if (write(gamepad_fd, &uidev, sizeof(uidev)) < 0) - { - printf("Failed to write! \n"); + /* open mouse */ + mouse_fd = open(pathMouse, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (mouse_fd < 0) { + fprintf(stderr, "Failed to open mouse: %s\n", pathMouse); + cleanup(); return 1; } - if (ioctl(gamepad_fd, UI_DEV_CREATE) < 0) - { - printf("Failed to create gamepad! \n"); - return 1; + char mouse_name[256] = "Unknown"; + ioctl(mouse_fd, EVIOCGNAME(sizeof(mouse_name)), mouse_name); + printf("Mouse : %s (%s)\n", mouse_name, pathMouse); + + unsigned long abs_bits = 0; + ioctl(mouse_fd, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), &abs_bits); + if (abs_bits & (1 << ABS_X)) { + mouse_is_touchpad = true; + printf(" (has ABS - will use for 2-finger right stick)\n"); + for (int i = 0; i < MT_MAX_SLOTS; i++) { + mt_slots[i].active = false; + mt_slots[i].tracking_id = -1; + } } - sleep(0.6); - struct input_event gamepad_ev; - while (1) - { - sleep(0.001); - - if (read(keyboard_fd, &keyboard_event, sizeof(keyboard_event)) != -1) - { - if(verbose) - { - printf("Event: Keyboard: type %d code %d value %d ", keyboard_event.type, keyboard_event.code, keyboard_event.value); - } + /* create virtual gamepad */ + gamepad_fd = create_gamepad(); + if (gamepad_fd < 0) { + cleanup(); + return 1; + } + printf("Gamepad : %s created (045e:028e)\n", GAMEPAD_NAME); + + if (pathTouchpad[0] != '\0') { + touchpad_fd = open(pathTouchpad, O_RDONLY | O_NONBLOCK); + if (touchpad_fd < 0) { + fprintf(stderr, "Failed to open touchpad: %s\n", pathTouchpad); + cleanup(); + return 1; + } + char tp_name[256] = "Unknown"; + ioctl(touchpad_fd, EVIOCGNAME(sizeof(tp_name)), tp_name); + printf("Touchpad : %s (%s)\n", tp_name, pathTouchpad); + for (int i = 0; i < MT_MAX_SLOTS; i++) { + mt_slots[i].active = false; + mt_slots[i].tracking_id = -1; + } + } - if (keyboard_event.code == KEY_Q && keyboard_event.value == 1) - { - q_pressed = !q_pressed; - } + sleep(1); /* let udev settle */ - if (keyboard_event.code == KEY_ENTER && keyboard_event.value == 1 && q_pressed == true) - { - exitFunc(keyboard_fd, mouse_fd, gamepad_fd); - break; - } + print_keymap(); - if (keyboard_event.value != 2) // only care about button press and not hold - { - switch (keyboard_event.code) - { - case KEY_C: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_Y, keyboard_event.value); - break; - case KEY_X: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_X, keyboard_event.value); - break; - case KEY_SPACE: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_A, keyboard_event.value); - break; - case KEY_LEFTALT: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_B, keyboard_event.value); - break; - case KEY_ENTER: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_START, keyboard_event.value); - break; - case KEY_TAB: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_SELECT, keyboard_event.value); - break; - } - } + /* epoll setup */ + epoll_fd = epoll_create1(0); + if (epoll_fd < 0) { + perror("epoll_create1"); + cleanup(); + return 1; + } - bool pressedOrHold = keyboard_event.value == 2 || keyboard_event.value == 1; - switch (keyboard_event.code) - { - case KEY_W: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_ABS, ABS_Y, pressedOrHold ? -2000 * absMultiplier : 0); - break; - case KEY_S: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_ABS, ABS_Y, pressedOrHold ? 2000 * absMultiplier : 0); - break; - case KEY_A: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_ABS, ABS_X, pressedOrHold ? -2000 * absMultiplier : 0); - break; - case KEY_D: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_ABS, ABS_X, pressedOrHold ? 2000 * absMultiplier : 0); - break; - } + struct epoll_event eev; + eev.events = EPOLLIN; + eev.data.fd = keyboard_fd; + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, keyboard_fd, &eev); + eev.data.fd = mouse_fd; + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mouse_fd, &eev); + if (touchpad_fd >= 0) { + eev.data.fd = touchpad_fd; + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, touchpad_fd, &eev); + } - switch (keyboard_event.code) - { - case KEY_LEFTSHIFT: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_TL, keyboard_event.value); - break; - case KEY_RIGHTSHIFT: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_TR, keyboard_event.value); - break; - case KEY_Q: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_TL2, keyboard_event.value); - break; - case KEY_E: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_TR2, keyboard_event.value); - break; - - case KEY_UP: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_DPAD_UP, keyboard_event.value); - break; - case KEY_DOWN: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_DPAD_DOWN, keyboard_event.value); - break; - case KEY_LEFT: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_DPAD_LEFT, keyboard_event.value); - break; - case KEY_RIGHT: - send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_DPAD_RIGHT, keyboard_event.value); - break; - - case KEY_PAGEUP: - absMultiplier >= 15 ? absMultiplier = 15 : absMultiplier++; - break; - case KEY_PAGEDOWN: - absMultiplier <= 1 ? absMultiplier = 1 : absMultiplier--; - break; - - // reset view joystick on left control - case KEY_LEFTCTRL: - { - send_event(gamepad_fd, gamepad_ev, EV_ABS, ABS_RX, 0); - send_event(gamepad_fd, gamepad_ev, EV_ABS, ABS_RY, 0); - - // send one sync event for both axes - send_sync_event(gamepad_fd, gamepad_ev); - } + /* right-stick auto-center state */ + long last_mouse_ms = now_ms(); + bool rstick_centered = true; + + struct epoll_event events[8]; + struct input_event ev; + + while (1) { + /* timeout = time left before we should center the stick */ + long elapsed = now_ms() - last_mouse_ms; + int timeout = rstick_centered ? -1 : + (int)(RSTICK_CENTER_MS - elapsed); + if (timeout < 0 && !rstick_centered) timeout = 0; + + int n = epoll_wait(epoll_fd, events, 8, timeout); + + if (n == 0) { + /* timeout expired → center right stick */ + if (!rstick_centered) { + send_event(gamepad_fd, EV_ABS, ABS_RX, 0); + send_event(gamepad_fd, EV_ABS, ABS_RY, 0); + send_sync(gamepad_fd); + rstick_centered = true; + if (verbose) printf("Right stick centered\n"); } - - if(verbose) printf("\n"); + continue; } - if (read(mouse_fd, &mouse_event, sizeof(struct input_event)) != -1) - { - if(verbose) - { - printf("Event: Mouse: type %d code %d value %d ", mouse_event.type, mouse_event.code, mouse_event.value); - } + if (n < 0) { + if (errno == EINTR) continue; + perror("epoll_wait"); + break; + } - switch (mouse_event.type) - { - case EV_REL: - if (mouse_event.code == REL_X) - { - int toWrite = mouse_event.value * 32; - if (mouse_event.value > 0 && toWrite > MOUSE_SENSITIVITY) toWrite = MOUSE_SENSITIVITY; - if (mouse_event.value < 0 && toWrite < MOUSE_SENSITIVITY_NEGATIVE) toWrite = MOUSE_SENSITIVITY_NEGATIVE; - if (mouse_event.value == 0) toWrite = 0; - - send_event_and_sync(gamepad_fd, gamepad_ev, EV_ABS, ABS_RX, toWrite); - } - if (mouse_event.code == REL_Y) - { - int toWrite = mouse_event.value * 32; - if (mouse_event.value > 0 && toWrite > MOUSE_SENSITIVITY) toWrite = MOUSE_SENSITIVITY; - if (mouse_event.value < 0 && toWrite < MOUSE_SENSITIVITY_NEGATIVE) toWrite = MOUSE_SENSITIVITY_NEGATIVE; - if (mouse_event.value == 0) toWrite = 0; - - send_event_and_sync(gamepad_fd, gamepad_ev, EV_ABS, ABS_RY, toWrite); + for (int i = 0; i < n; i++) { + int fd = events[i].data.fd; + + /* drain all pending events from this fd */ + while (read(fd, &ev, sizeof(ev)) > 0) { + if (fd == keyboard_fd) { + handle_keyboard(&ev); + } else if (fd == mouse_fd && !mouse_is_touchpad) { + handle_mouse(&ev); + if (ev.type == EV_REL && + (ev.code == REL_X || ev.code == REL_Y)) { + last_mouse_ms = now_ms(); + rstick_centered = false; + } + } else if (fd == touchpad_fd || (fd == mouse_fd && mouse_is_touchpad)) { + handle_touchpad(&ev); + /* auto-center right stick on 2-finger movement */ + if (ev.type == EV_SYN && ev.code == SYN_REPORT && mt_active_count() == 2) { + last_mouse_ms = now_ms(); + rstick_centered = false; + } } - if (mouse_event.code == REL_WHEEL) - { - absMultiplier += mouse_event.value; - if (absMultiplier >= 15) absMultiplier = 15; - if (absMultiplier <= 1) absMultiplier = 1; - } - break; - case EV_KEY: - if (mouse_event.code == BTN_LEFT) send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_THUMBL, mouse_event.value); - if (mouse_event.code == BTN_RIGHT) send_event_and_sync(gamepad_fd, gamepad_ev, EV_KEY, BTN_THUMBR, mouse_event.value); - - // reset controller state - if (mouse_event.code == BTN_MIDDLE) - { - send_event(gamepad_fd, gamepad_ev, EV_ABS, ABS_RX, 0); - send_event(gamepad_fd, gamepad_ev, EV_ABS, ABS_RY, 0); - - // send one sync event for both axes - send_sync_event(gamepad_fd, gamepad_ev); - } - break; } - - if(verbose) printf("\n"); } } - printf("Exiting.\n"); - rcode = ioctl(keyboard_fd, EVIOCGRAB, 1); - close(keyboard_fd); - rcode = ioctl(mouse_fd, EVIOCGRAB, 1); - close(mouse_fd); + cleanup(); return 0; }