Created
November 4, 2024 15:29
-
-
Save lyc8503/34e16db4bbfb1c4cf48b3a92fbd4c592 to your computer and use it in GitHub Desktop.
Android absolute mouse
This file contains 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
/* | |
* Android (and Linux X11) can't handle absolute position HID events from mouse | |
* https://www.codeproject.com/Articles/1001891/A-USB-HID-Keyboard-Mouse-Touchscreen-emulator-with | |
* | |
* This program read these ABS_X & ABS_Y events from /dev/input/eventX, and emulate a touchscreen/stylus via linux uinput, so an "absolute" mouse can work. | |
* Example of "absolute" mouse: PiKVM, or some other mouse-emulating hardware (e.g. https://detail.tmall.com/item.htm?id=684263217981) | |
*/ | |
#include <linux/uinput.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
// An "absolute" mouse is sending events to "/dev/input/event12" | |
#define EVENT_SRC "/dev/input/event12" | |
// My device is Sony Xperia 5 II which has a 2520x1080 display | |
#define ABS_X_MAX 2520 | |
#define ABS_Y_MAX 1080 | |
// My absolute mouse assumes a different aspect ratio, so we need a manual transform here | |
#define TRANSFORM_ABS_X(x) (x * 1920 / 0x1fff + 300) | |
#define TRANSFORM_ABS_Y(y) (y * 1080 / 0xfff) | |
void emit(int fd, int type, int code, int val) | |
{ | |
struct input_event ie; | |
ie.type = type; | |
ie.code = code; | |
ie.value = val; | |
/* timestamp values below are ignored */ | |
ie.time.tv_sec = 0; | |
ie.time.tv_usec = 0; | |
write(fd, &ie, sizeof(ie)); | |
} | |
int main(void) | |
{ | |
struct uinput_setup usetup; | |
struct uinput_abs_setup uabs; | |
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); | |
ioctl(fd, UI_SET_EVBIT, EV_KEY); | |
/* | |
* BTN_TOUCH is neccessary for Android to recognize it as a touchscreen device. | |
* https://source.android.com/docs/core/interaction/input/touch-devices | |
*/ | |
ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); | |
ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN); | |
// Regular android buttons | |
ioctl(fd, UI_SET_KEYBIT, BTN_LEFT); | |
ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT); | |
ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE); | |
ioctl(fd, UI_SET_KEYBIT, BTN_SIDE); | |
ioctl(fd, UI_SET_KEYBIT, BTN_EXTRA); | |
/* | |
* Only touchscreen devices (instead of mouse) support EV_ABS (ABS_X/ABS_Y) | |
*/ | |
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_EVBIT, EV_REL); | |
ioctl(fd, UI_SET_RELBIT, REL_WHEEL); | |
// Still need a pointer on the screen | |
ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_POINTER); | |
uabs.code = ABS_X; | |
uabs.absinfo.minimum = 0; | |
uabs.absinfo.maximum = ABS_X_MAX; | |
ioctl(fd, UI_ABS_SETUP, &uabs); | |
uabs.code = ABS_Y; | |
uabs.absinfo.minimum = 0; | |
uabs.absinfo.maximum = ABS_Y_MAX; | |
ioctl(fd, UI_ABS_SETUP, &uabs); | |
memset(&usetup, 0, sizeof(usetup)); | |
usetup.id.bustype = BUS_USB; | |
usetup.id.vendor = 0x1234; | |
usetup.id.product = 0x5678; | |
strcpy(usetup.name, "Virtual device"); | |
ioctl(fd, UI_DEV_SETUP, &usetup); | |
ioctl(fd, UI_DEV_CREATE); | |
int src_fd = open(EVENT_SRC, O_RDONLY); | |
struct input_event src_ev; | |
int n; | |
while ((n = read(src_fd, &src_ev, sizeof(src_ev)))) | |
{ | |
if (n != sizeof(src_ev)) | |
{ | |
fprintf(stderr, "error reading events from src: %d", n); | |
close(src_fd); | |
sleep(1); | |
// Try again | |
src_fd = open(EVENT_SRC, O_RDONLY); | |
} | |
// Always send BTN_TOOL_PEN to indicate the virtual "pen" is hovering near to screen | |
emit(fd, EV_KEY, BTN_TOOL_PEN, 1); | |
if (src_ev.type == EV_KEY && | |
(src_ev.code == BTN_LEFT || | |
src_ev.code == BTN_RIGHT || | |
src_ev.code == BTN_MIDDLE)) | |
{ | |
// Send BTN_TOUCH to indicate the virtual "pen" is touching the screen | |
emit(fd, EV_KEY, src_ev.value ? src_ev.code : BTN_TOUCH, src_ev.value); | |
emit(fd, EV_KEY, src_ev.value ? BTN_TOUCH : src_ev.code, src_ev.value); | |
} | |
else if (src_ev.type == EV_KEY && (src_ev.code == BTN_SIDE || src_ev.code == BTN_EXTRA)) | |
{ | |
emit(fd, EV_KEY, src_ev.code, src_ev.value); | |
} | |
else if (src_ev.type == EV_ABS && (src_ev.code == ABS_X || src_ev.code == ABS_Y)) | |
{ | |
if (src_ev.code == ABS_X) | |
emit(fd, EV_ABS, ABS_X, TRANSFORM_ABS_X(src_ev.value)); | |
if (src_ev.code == ABS_Y) | |
emit(fd, EV_ABS, ABS_Y, TRANSFORM_ABS_Y(src_ev.value)); | |
} | |
else if (src_ev.type == EV_SYN) | |
{ | |
emit(fd, EV_SYN, SYN_REPORT, 0); | |
} | |
else if (src_ev.type == EV_REL && src_ev.code == REL_WHEEL) | |
{ | |
emit(fd, EV_REL, REL_WHEEL, src_ev.value); | |
} | |
else | |
{ | |
fprintf(stderr, "unrecognized event: %d %d %d\n", src_ev.type, src_ev.code, src_ev.value); | |
} | |
} | |
// int i = 0; | |
// while (i++ < 200) | |
// { | |
// printf("i: %d\n", i); | |
// emit(fd, EV_KEY, BTN_TOOL_PEN, 1); | |
// emit(fd, EV_ABS, ABS_X, 300 + i); | |
// emit(fd, EV_ABS, ABS_Y, i); | |
// emit(fd, EV_SYN, SYN_REPORT, 0); | |
// usleep(15 * 1000); | |
// } | |
// emit(fd, EV_KEY, BTN_LEFT, 1); | |
// emit(fd, EV_KEY, BTN_TOUCH, 1); | |
// emit(fd, EV_SYN, SYN_REPORT, 0); | |
// usleep(15 * 1000); | |
// emit(fd, EV_KEY, BTN_TOUCH, 0); | |
// emit(fd, EV_KEY, BTN_LEFT, 0); | |
// emit(fd, EV_SYN, SYN_REPORT, 0); | |
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