Skip to content

Instantly share code, notes, and snippets.

@bhupiister
Forked from lyc8503/absmouse.c
Last active January 29, 2026 01:03
Show Gist options
  • Select an option

  • Save bhupiister/0e66c25177a847bbdb9f49e66510d1f7 to your computer and use it in GitHub Desktop.

Select an option

Save bhupiister/0e66c25177a847bbdb9f49e66510d1f7 to your computer and use it in GitHub Desktop.
eGalaxTouch absolute mouse
#include <linux/uinput.h>
#include <linux/input.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
// Source device (your working ABS mouse-like interface)
//#define EVENT_SRC "/dev/input/event3"
#define EVENT_SRC "/dev/input/by-id/usb-eGalax_Inc._eGalaxTouch_P80H84_0232_v00_M4_R6_k4.10.143-event-mouse"
// Your display resolution (1920x1080 => max indices 1919/1079)
#define ABS_X_MAX 1919
#define ABS_Y_MAX 1079
// Source device range (from evtest: 0..4095)
#define SRC_ABS_X_MAX 4095
#define SRC_ABS_Y_MAX 4095
// Coordinate transform summary:
//
// The source device reports coordinates rotated relative to the display.
// Observed behavior required:
//
// - Swap axes (X ↔ Y)
// - Invert screen X only
// - Screen Y is non-inverted
//
// Mapping implemented below:
//
// screen_x = MAX_X - scale(src_y)
// screen_y = scale(src_x)
//
// This corrects the rotation and aligns touch movement with the display.
#define TRANSFORM_TO_SCREEN_X_FROM_SRC_Y(src_y) (ABS_X_MAX - ((src_y) * ABS_X_MAX / SRC_ABS_Y_MAX))
#define TRANSFORM_TO_SCREEN_Y_FROM_SRC_X(src_x) (((src_x) * ABS_Y_MAX / SRC_ABS_X_MAX))
static void emit(int fd, int type, int code, int val)
{
struct input_event ie;
memset(&ie, 0, sizeof(ie));
ie.type = type;
ie.code = code;
ie.value = val;
// timestamps ignored by kernel for uinput
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;
if (write(fd, &ie, sizeof(ie)) != (ssize_t)sizeof(ie)) {
perror("write(uinput)");
}
}
static int must_ioctl(int fd, unsigned long req, void *arg, const char *what)
{
if (ioctl(fd, req, arg) < 0) {
fprintf(stderr, "ioctl failed (%s): %s\n", what, strerror(errno));
return -1;
}
return 0;
}
static int must_ioctl0(int fd, unsigned long req, unsigned long arg, const char *what)
{
if (ioctl(fd, req, arg) < 0) {
fprintf(stderr, "ioctl failed (%s): %s\n", what, strerror(errno));
return -1;
}
return 0;
}
int main(void)
{
// Open uinput
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0) {
perror("open(/dev/uinput)");
fprintf(stderr, "Tip: try `sudo modprobe uinput` and ensure /dev/uinput exists.\n");
return 1;
}
// Declare event types we will emit
if (must_ioctl0(fd, UI_SET_EVBIT, EV_SYN, "UI_SET_EVBIT EV_SYN") < 0) return 1;
if (must_ioctl0(fd, UI_SET_EVBIT, EV_KEY, "UI_SET_EVBIT EV_KEY") < 0) return 1;
if (must_ioctl0(fd, UI_SET_EVBIT, EV_ABS, "UI_SET_EVBIT EV_ABS") < 0) return 1;
// Keys: keep it touch-like (avoid exposing mouse buttons on the virtual device)
if (must_ioctl0(fd, UI_SET_KEYBIT, BTN_TOUCH, "UI_SET_KEYBIT BTN_TOUCH") < 0) return 1;
if (must_ioctl0(fd, UI_SET_KEYBIT, BTN_TOOL_FINGER, "UI_SET_KEYBIT BTN_TOOL_FINGER") < 0) return 1;
// ABS axes
if (must_ioctl0(fd, UI_SET_ABSBIT, ABS_X, "UI_SET_ABSBIT ABS_X") < 0) return 1;
if (must_ioctl0(fd, UI_SET_ABSBIT, ABS_Y, "UI_SET_ABSBIT ABS_Y") < 0) return 1;
// Tell libinput/Wayland this is a direct-touch device
if (must_ioctl0(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT, "UI_SET_PROPBIT INPUT_PROP_DIRECT") < 0) return 1;
// Properly initialize ABS setup (prevents "min == max" warning)
struct uinput_abs_setup uabs;
memset(&uabs, 0, sizeof(uabs));
uabs.code = ABS_X;
uabs.absinfo.minimum = 0;
uabs.absinfo.maximum = ABS_X_MAX;
uabs.absinfo.fuzz = 0;
uabs.absinfo.flat = 0;
uabs.absinfo.resolution = 0;
if (must_ioctl(fd, UI_ABS_SETUP, &uabs, "UI_ABS_SETUP ABS_X") < 0) return 1;
memset(&uabs, 0, sizeof(uabs));
uabs.code = ABS_Y;
uabs.absinfo.minimum = 0;
uabs.absinfo.maximum = ABS_Y_MAX;
uabs.absinfo.fuzz = 0;
uabs.absinfo.flat = 0;
uabs.absinfo.resolution = 0;
if (must_ioctl(fd, UI_ABS_SETUP, &uabs, "UI_ABS_SETUP ABS_Y") < 0) return 1;
// Create virtual device
struct uinput_setup usetup;
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234;
usetup.id.product = 0x5678;
strncpy(usetup.name, "Virtual Touchscreen (uinput)", sizeof(usetup.name) - 1);
if (must_ioctl(fd, UI_DEV_SETUP, &usetup, "UI_DEV_SETUP") < 0) return 1;
if (must_ioctl0(fd, UI_DEV_CREATE, 0, "UI_DEV_CREATE") < 0) return 1;
// Open source input device
int src_fd = open(EVENT_SRC, O_RDONLY);
if (src_fd < 0) {
perror("open(EVENT_SRC)");
fprintf(stderr, "Check EVENT_SRC (%s) exists and is readable.\n", EVENT_SRC);
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 1;
}
// Optional but very useful: grab the source device so it doesn't interfere
// (prevents compositor also seeing event3 as a pointer)
int grab = 1;
if (ioctl(src_fd, EVIOCGRAB, grab) < 0) {
perror("EVIOCGRAB (continuing without grab)");
}
fprintf(stderr, "Forwarding %s -> virtual touchscreen. Ctrl+C to stop.\n", EVENT_SRC);
// Main loop: forward events
struct input_event src_ev;
int n;
// Cache latest src coords (needed for axis swap mapping)
int src_x = 0;
int src_y = 0;
while ((n = read(src_fd, &src_ev, sizeof(src_ev))) > 0) {
if (n != (int)sizeof(src_ev)) {
fprintf(stderr, "partial read from src: %d\n", n);
continue;
}
// Ignore scan codes
if (src_ev.type == EV_MSC) {
continue;
}
// Keep "finger present" state set
emit(fd, EV_KEY, BTN_TOOL_FINGER, 1);
// Map left-click to touch down/up
if (src_ev.type == EV_KEY && src_ev.code == BTN_LEFT) {
emit(fd, EV_KEY, BTN_TOUCH, src_ev.value); // 1=down, 0=up
}
// Axis swap + rotation fix:
// source ABS_X affects screen Y (inverted)
else if (src_ev.type == EV_ABS && src_ev.code == ABS_X) {
src_x = src_ev.value;
emit(fd, EV_ABS, ABS_Y, TRANSFORM_TO_SCREEN_Y_FROM_SRC_X(src_x));
}
// source ABS_Y affects screen X
else if (src_ev.type == EV_ABS && src_ev.code == ABS_Y) {
src_y = src_ev.value;
emit(fd, EV_ABS, ABS_X, TRANSFORM_TO_SCREEN_X_FROM_SRC_Y(src_y));
}
// End of event frame
else if (src_ev.type == EV_SYN) {
emit(fd, EV_SYN, SYN_REPORT, 0);
}
// Ignore anything else
}
if (n < 0) {
perror("read(src_fd)");
}
// Release grab (if it was set)
grab = 0;
ioctl(src_fd, EVIOCGRAB, grab);
close(src_fd);
// Destroy virtual device
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment