0

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.

ElijaRock
  • 3
  • 1

1 Answers1

0

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?).)

Oka
  • 23,367
  • 6
  • 42
  • 53