有没有办法为 uinput 虚拟设备 (Wayland) 设置绝对光标位置?

Is there a way to set absolute cursor position for uinput virtual device (Wayland)?

提问人:ElijaRock 提问时间:8/27/2023 更新时间:8/28/2023 访问量:142

问:

我按照 linux uinput 文档中的代码示例来模拟虚拟指针设备。我有以下几行代码,可以成功地将光标向下和向右移动。

#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;
}

现在,我想在屏幕上将光标的位置绝对设置为 x=500,y=500。

在代码中将“REL”的实例更改为“ABS”似乎并没有解决问题(仍然相对移动)。由于我找不到此功能的文档,我想知道在哪里可以找到它,或者是否可以在 Wayland (Gnome mutter) 上使用 uinput 设置绝对光标位置,或者是否有其他可以使用的东西。

我已经在“libinput replay”中看到了这种行为(设置光标的绝对位置),我想知道我是否可以复制它。干杯。

C Linux IO 驱动程序 Wayland

评论


答:

0赞 Oka 8/28/2023 #1

对于绝对移动,要使用的请求是ioctl

ioctl(fd, UI_SET_EVBIT, EV_ABS)

和(一项或多项)

ioctl(fd, UI_ABS_SETUP, &abs)

其中 的类型为 struct uinput_abs_setup(包装 struct input_absinfo)。abs

这是一个修改后的示例,应该可以工作。

#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

如果上述方法不起作用,我遇到的一种解决方法(hack)是在两个轴上相对地移动鼠标极负的量,这应该将光标放在 .然后,您向预期位置进行第二次相对移动。这里的问题是鼠标加速度会影响最终位置。uinput0,0

这与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);
}

Смотритетакже: 在 Wayland 和 X11 上模拟鼠标和键盘输入

(注意:在 X11 下,有 XWarpPointer,但 Wayland 似乎不支持从用户代码直接控制鼠标(潜在的安全风险?)