You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Wayland环境下uinput虚拟设备如何设置光标绝对位置?

问题描述

我参考Linux uinput文档中的代码示例,实现了一个虚拟指针设备,现有代码可以成功让光标向右下方做相对移动。但我想把光标绝对定位到屏幕x=500、y=500的位置,仅将代码中的REL相关常量替换为ABS后并未生效。我没找到相关功能的详细文档,想了解在Wayland(Gnome Mutter)环境下,是否可以通过uinput实现该功能,或者有没有其他替代方案。我注意到libinput replay具备绝对定位的能力,希望能复现这个行为。

原相对移动代码如下:

#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;
}
解决方案

一、修正uinput绝对定位的代码实现

仅替换REL为ABS无法生效的原因是:绝对坐标设备需要告知系统轴的取值范围,并且需要正确初始化设备的事件掩码。以下是修正后的代码:

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

   ie.type = type;
   ie.code = code;
   ie.value = val;
   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 abs_setup;
   int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);

   if (fd < 0) {
       perror("Failed to open /dev/uinput");
       return EXIT_FAILURE;
   }

   /* 启用按键事件(保留左键) */
   ioctl(fd, UI_SET_EVBIT, EV_KEY);
   ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);

   /* 启用绝对坐标事件 */
   ioctl(fd, UI_SET_EVBIT, EV_ABS);
   ioctl(fd, UI_SET_ABSBIT, ABS_X);
   ioctl(fd, UI_SET_ABSBIT, ABS_Y);

   /* 设置X轴范围(根据你的屏幕分辨率调整,这里示例为1920x1080) */
   memset(&abs_setup, 0, sizeof(abs_setup));
   abs_setup.code = ABS_X;
   abs_setup.absinfo.min = 0;
   abs_setup.absinfo.max = 1919;
   abs_setup.absinfo.fuzz = 0;
   abs_setup.absinfo.flat = 0;
   ioctl(fd, UI_ABS_SETUP, &abs_setup);

   /* 设置Y轴范围 */
   abs_setup.code = ABS_Y;
   abs_setup.absinfo.max = 1079;
   ioctl(fd, UI_ABS_SETUP, &abs_setup);

   /* 配置设备信息 */
   memset(&usetup, 0, sizeof(usetup));
   usetup.id.bustype = BUS_USB;
   usetup.id.vendor = 0x1234;
   usetup.id.product = 0x5678;
   strcpy(usetup.name, "Absolute Pointer Device");

   ioctl(fd, UI_DEV_SETUP, &usetup);
   ioctl(fd, UI_DEV_CREATE);

   sleep(1); /* 等待系统识别设备 */

   /* 发送绝对定位到(500,500)的事件 */
   emit(fd, EV_ABS, ABS_X, 500);
   emit(fd, EV_ABS, ABS_Y, 500);
   emit(fd, EV_SYN, SYN_REPORT, 0); /* 提交事件 */

   sleep(1); /* 等待事件被处理 */

   ioctl(fd, UI_DEV_DESTROY);
   close(fd);

   return 0;
}

关键修改点:

  • 删除了重复定义的emit函数
  • 替换EV_REL为EV_ABS,并启用ABS_X、ABS_Y事件位
  • 通过UI_ABS_SETUP设置X/Y轴的最小、最大值(必须和屏幕分辨率匹配,否则坐标会被缩放)
  • 直接发送目标绝对坐标,最后通过SYN_REPORT提交事件

二、Wayland(Gnome Mutter)下的适配注意事项

Mutter等Wayland compositor对输入设备的识别有以下要求:

  • 确保设备被标记为绝对定位设备(示例中用BUS_USB即可)
  • 轴范围必须准确,否则Mutter会自动缩放坐标导致定位偏移
  • 确保当前用户有权限访问/dev/uinput(可将用户加入input组)

三、替代方案:基于libinput实现

如果uinput方式在Wayland下有兼容性问题,可以参考libinput replay的实现逻辑:

  • libinput提供了更上层的输入设备抽象,能自动适配X11和Wayland环境
  • 可以使用libinput的API创建虚拟绝对指针设备,直接发送绝对坐标事件
  • 也可以直接调用libinput-record和libinput-replay工具,录制一次绝对定位事件后重放,无需自己编写代码

内容的提问来源于stack exchange,提问作者ElijaRock

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 15:27:06