Имитация событий мыши в Linux

На пути к задаче "трекинга головы" посредством видеокамеры лежит задача научиться имитировать события мыши из пользовательского пространства.

В ядре Linux есть замечательный интерфейс, который позволяет пользовательскому приложению создавать устройства ввода и отправлять от него события. Нужно открыть устройство /dev/uinput, настроить его через ioctl, и записывать события напрямую. Собственно ничего изобретать не требуется, так как все прекрасно описано в документации к ядру (https://www.kernel.org/doc/html/v4.15/input/uinput.html).

Приведенный ниже код (из примеров к документации ядра) имитирует движение мыши. Программе требуется разрешение на запись в файл /dev/uinput (поэтому sudo либо chmod /dev/uinput).

#include <fcntl.h>
#include <linux/uinput.h>
#include <unistd.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;
   /* 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_DESTOY.
    */
   sleep(1);
 
   ioctl(fd, UI_DEV_DESTROY);
   close(fd);
 
   return 0;
}
 

2018-10-24