I followed the code example on the linux uinput documentation in order to simulate a virtual pointing device. I have the following lines of code which can successfully move the cursor down and to the right.
#include <linux/uinput.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
void emit(int fd, int type, int code, int val)
{
struct input_event ie;
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));
}
/* emit function is identical to of the first example */
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;
int i = 50;
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
/* enable mouse button left and relative events */
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
ioctl(fd, UI_SET_EVBIT, EV_REL);
ioctl(fd, UI_SET_RELBIT, REL_X);
ioctl(fd, UI_SET_RELBIT, REL_Y);
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; /* sample vendor */
usetup.id.product = 0x5678; /* sample product */
strcpy(usetup.name, "Example device");
ioctl(fd, UI_DEV_SETUP, &usetup);
ioctl(fd, UI_DEV_CREATE);
/*
* On UI_DEV_CREATE the kernel will create the device node for this
* device. We are inserting a pause here so that userspace has time
* to detect, initialize the new device, and can start listening to
* the event, otherwise it will not notice the event we are about
* to send. This pause is only needed in our example code!
*/
sleep(1);
/* Move the mouse diagonally, 5 units per axis */
while (i--) {
emit(fd, EV_REL, REL_X, 5);
emit(fd, EV_REL, REL_Y, 5);
emit(fd, EV_SYN, SYN_REPORT, 0);
usleep(15000);
}
/*
* Give userspace some time to read the events before we destroy the
* device with UI_DEV_DESTROY.
*/
sleep(1);
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
Now, I would like to set the cursor's position absolutely to x=500,y=500 on the screen, for example.
Changing the instances of "REL" to "ABS" in the code doesn't seem to have done the trick (still moved relatively). Since I couldn't find the documentation for this capability, I was wondering where to find it, or if it is possible to set absolute cursor position with uinput on Wayland (Gnome mutter), or if there is something else I can use.
I have seen that behavior (setting absolute position of the cursor) in "libinput replay", and am wondering if I can replicate it. Cheers.
For absolute movement, the ioctl
requests to use are
ioctl(fd, UI_SET_EVBIT, EV_ABS)
and (one or more)
ioctl(fd, UI_ABS_SETUP, &abs)
where abs
is of type struct uinput_abs_setup
(wraps struct input_absinfo
).
Here is a modified example, that should work.
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/uinput.h>
#include <sys/ioctl.h>
static void fatal(const char *msg)
{
fprintf(stderr, "fatal: ");
if (errno)
perror(msg);
else
fprintf(stderr, "%s\n", msg);
exit(EXIT_FAILURE);
}
static void setup_abs(int fd, int type, int min, int max, int res)
{
struct uinput_abs_setup abs = {
.code = type,
.absinfo = {
.minimum = min,
.maximum = max,
.resolution = res
}
};
if (-1 == ioctl(fd, UI_ABS_SETUP, &abs))
fatal("ioctl UI_ABS_SETUP");
}
static void init(int fd, int width, int height, int dpi)
{
if (-1 == ioctl(fd, UI_SET_EVBIT, EV_SYN))
fatal("ioctl UI_SET_EVBIT EV_SYN");
if (-1 == ioctl(fd, UI_SET_EVBIT, EV_KEY))
fatal("ioctl UI_SET_EVBIT EV_KEY");
if (-1 == ioctl(fd, UI_SET_KEYBIT, BTN_LEFT))
fatal("ioctl UI_SET_KEYBIT BTN_LEFT");
if (-1 == ioctl(fd, UI_SET_EVBIT, EV_ABS))
fatal("ioctl UI_SET_EVBIT EV_ABS");
/* the ioctl UI_ABS_SETUP enables these automatically, when appropriate:
ioctl(fd, UI_SET_ABSBIT, ABS_X);
ioctl(fd, UI_SET_ABSBIT, ABS_Y);
*/
struct uinput_setup device = {
.id = {
.bustype = BUS_USB
},
.name = "Emulated Absolute Positioning Device"
};
if (-1 == ioctl(fd, UI_DEV_SETUP, &device))
fatal("ioctl UI_DEV_SETUP");
setup_abs(fd, ABS_X, 0, width, dpi);
setup_abs(fd, ABS_Y, 0, height, dpi);
if (-1 == ioctl(fd, UI_DEV_CREATE))
fatal("ioctl UI_DEV_CREATE");
/* give time for device creation */
sleep(1);
}
static void emit(int fd, int type, int code, int value)
{
struct input_event ie = {
.type = type,
.code = code,
.value = value
};
write(fd, &ie, sizeof ie);
}
int main(int argc, char **argv)
{
/* These values are very device specific */
int w = argc > 1 ? atoi(argv[1]) : 1920;
int h = argc > 2 ? atoi(argv[2]) : 1080;
int d = argc > 3 ? atoi(argv[3]) : 96;
if (w < 1 || h < 1 || d < 1)
fatal("Bad initial value(s).");
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (-1 == fd)
fatal("open");
printf("Initializing device screen map as %dx%d @ %ddpi\n", w, h, d);
init(fd, w, h, d);
while (1) {
printf("Enter x & y: ");
fflush(stdout);
char input[128];
if (!fgets(input, sizeof input, stdin) || 0 == strncmp(".exit", input, 5))
break;
int x, y;
if (2 != sscanf(input, "%d%d", &x, &y) || x < 0 || y < 0) {
fprintf(stderr, "Invalid input.\n");
continue;
}
printf("Moving cursor to %d,%d\n", x, y);
/* input is zero-based, but event positions are one-based */
emit(fd, EV_ABS, ABS_X, 1 + x);
emit(fd, EV_ABS, ABS_Y, 1 + y);
emit(fd, EV_SYN, SYN_REPORT, 0);
}
puts("Cleaning up...");
/* give time for events to finish */
sleep(1);
if (-1 == ioctl(fd, UI_DEV_DESTROY))
fatal("ioctl UI_DEV_DESTROY");
close(fd);
puts("Goodbye.");
}
./a.out 4640 1080 96
Initializing device screen map as 4640x1080 @ 96dpi
Enter x & y: 10 10
Moving cursor to 10,10
Enter x & y: 123 321
Moving cursor to 123,321
Enter x & y: .exit
Cleaning up...
Goodbye
If the above does not work, one uinput
work-around (hack) that I have come across is to relatively move the mouse an extremely negative amount in both axes, which should place the cursor at 0,0
. Then you make a second relative movement to the intended position. The problem here is that mouse acceleration can influence the final position.
This is the same hack used by ydotool
.
void move_cursor_to_xy(int fd, int x, int y)
{
emit(fd, EV_REL, REL_X, INT_MIN);
emit(fd, EV_REL, REL_Y, INT_MIN);
emit(fd, EV_SYN, SYN_REPORT, 0);
emit(fd, EV_REL, REL_X, x);
emit(fd, EV_REL, REL_Y, y);
emit(fd, EV_SYN, SYN_REPORT, 0);
}
See also: Simulating mouse and keyboard input on Wayland and X11
(Note: Under X11, there is XWarpPointer, but Wayland does not appear to support directly controlling the mouse from user code (potential security risk?).)