Skip to content

Instantly share code, notes, and snippets.

@lyc8503
Created November 4, 2024 15:29
Show Gist options
  • Save lyc8503/34e16db4bbfb1c4cf48b3a92fbd4c592 to your computer and use it in GitHub Desktop.
Save lyc8503/34e16db4bbfb1c4cf48b3a92fbd4c592 to your computer and use it in GitHub Desktop.
Android absolute mouse
/*
* 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