-
-
Save bhupiister/0e66c25177a847bbdb9f49e66510d1f7 to your computer and use it in GitHub Desktop.
eGalaxTouch absolute mouse
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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