Создайте группу для пользователей, которым разрешен доступ к устройству, и правило udev, чтобы установить право собственности на это устройство ввода событий для этой группы.
Я использую teensy
(система)group:
sudo groupadd -r teensy
и добавьте в него каждого пользователя, используя, например,
sudo usermod -a -g teensy my-user-name
или любой другой графический интерфейс пользователя, который у меня есть.
Управляя какими пользователями и демонами службыпринадлежа к группе teensy
, вы можете легко управлять доступом к устройствам.
Для моих микроконтроллеров Teensy (которые имеют собственный USB, и я использую для тестирования HID), у меня есть следующее /lib/udev/rules.d/49-teensy.rules
:
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", ENV{ID_MM_DEVICE_IGNORE}="1"
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789A]?", ENV{MTP_NO_PROBE}="1"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789ABCD]?", GROUP:="teensy", MODE:="0660"
KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", GROUP:="teensy", MODE:="0660"
Третья строка (SUBSYSTEMS=="usb",
one) вам нужна только для устройств HID.Убедитесь, что idVendor
и idProduct
соответствуют вашему HID-устройству USB.Вы можете использовать lsusb
для отображения списка подключенных в данный момент USB-устройств и номеров продуктов.При сопоставлении используются шаблоны глобуса, как и имена файлов.
После добавления вышеупомянутого не забудьте запустить sudo udevadm control --reload-rules && sudo udevadm trigger
для перезагрузки правил.При следующем подключении устройства USB HID все члены вашей группы (teensy
в приведенном выше) могут получить к нему прямой доступ.
Обратите внимание, что по умолчанию в большинстве дистрибутивов udev также создает постоянные символические ссылки.в /dev/input/by-id/
с использованием типа устройства USB и серийного номера.В моем случае один из моих Teensy LC (серийный 4298820) с комбинированным устройством клавиатура-мышь-джойстик обеспечивает /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-event-kbd
для устройства события клавиатуры, /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-if01-event-mouse
для устройства события мыши и /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-if03-event-joystick
и /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-if04-event-joystick
для устройства события.два интерфейса джойстика.
(Под «постоянным» я не подразумеваю, что эти символические ссылки всегда существуют; я имею в виду, что всякий раз, когда к этому конкретному устройству подключается, символическая ссылка именно этого имени существует, и указывает на фактическоеСимвольное устройство ввода события Linux.)
Устройство Linux uinput может использоваться для реализации виртуального устройства ввода события с использованием простого привилегированного демона.
ПроцессСоздание нового виртуального устройства ввода USB-событий происходит следующим образом.
Открыть /dev/uinput
для записи (или чтения и записи):
fd = open("/dev/uinput", O_RDWR);
if (fd == -1) {
fprintf(stderr, "Cannot open /dev/uinput: %s.\n", strerror(errno));
exit(EXIT_FAILURE);
}
Вышеуказанное требуетпривилегии суперпользователя.Однако сразу же после открытия устройства вы можете отказаться от всех привилегий и вместо этого запустить свой демон / службу в качестве выделенного пользователя.
Используйте ioctl UI_SET_EVBIT
для каждогоразрешен тип события.
Вы хотите разрешить хотя бы EV_SYN
;и EV_KEY
для клавиатур и кнопок мыши, EV_REL
для движения мыши и т. д.
if (ioctl(fd, UI_SET_EVBIT, EV_SYN) == -1 ||
ioctl(fd, UI_SET_EVBIT, EV_KEY) == -1 ||
ioctl(fd, UI_SET_EVBIT, EV_REL) == -1) {
fprintf(stderr, "Uinput event types not allowed: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
Лично я использую массив статических констант с кодами для упрощения управления.
Используйте ioctl UI_SET_KEYBIT
для каждого кода клавиши, который может выдавать устройство, и ioctl UI_SET_RELBIT
для каждого кода относительного движения (кода мыши).Например, чтобы оставить пробел, левая кнопка мыши, горизонтальное и вертикальное движение мыши и колесо мыши:
if (ioctl(fd, UI_SET_KEYBIT, KEY_SPACE) == -1 ||
ioctl(fd, UI_SET_KEYBIT, BTN_LEFT) == -1 ||
ioctl(fd, UI_SET_RELBIT, REL_X) == -1 ||
ioctl(fd, UI_SET_RELBIT, REL_Y) == -1 ||
ioctl(fd, UI_SET_RELBIT, REL_WHEEL) == -1) {
fprintf(stderr, "Uinput event types not allowed: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
Опять же, статические массивы const (один для UI_SET_KEYBIT
и один для UI_SET_RELBIT
кодов)намного проще в обслуживании.
Определите struct uinput_user_dev
и запишите его на устройство.
Если у вас есть name
, содержащий строку имени устройства, vendor
и product
с указанием идентификатора производителя USB и идентификатора продукта, version
с номером версии (нормально 0), используйте
struct uinput_user_dev dev;
memset(&dev, 0, sizeof dev);
strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
dev.id.bustype = BUS_USB;
dev.id.vendor = vendor;
dev.id.product = product;
dev.id.version = version;
if (write(fd, &dev, sizeof dev) != sizeof dev) {
fprintf(stderr, "Cannot write an uinput device description: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
В более поздних ядрах ioctl используется для того же действия(очевидно, что участие в разработке systemd приводит к такому «сливному повреждению»);
struct uinput_setup dev;
memset(&dev, 0, sizeof dev);
strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
dev.id.bustype = BUS_USB;
dev.id.vendor = vendor;
dev.id.product = product;
dev.id.version = version;
if (ioctl(fd, UI_DEV_SETUP, &dev) == -1) {
fprintf(stderr, "Cannot write an uinput device description: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
Идея состоит в том, что вместо использования первого вы можете сначала попробовать второе, а в случае неудачи выполнитебывший вместоВы знаете, потому что одного интерфейса может быть недостаточно.(Это то, о чем говорят документация и .)
Может показаться немного капризным, но это только потому, что я поддерживаю и Unix философия , и KISS принцип (или минималистский подход), и видеть такие бородавки совершенно не нужно.И слишком часто из той же слабо связанной группы разработчиков.Гм.Личное оскорбление не предусмотрено;Я просто думаю, что они плохо выполняют свою работу.
Создайте виртуальное устройство, выполнив UI_DEV_CREATE
ioctl:
if (ioctl(fd, UI_DEV_CREATE) == -1) {
fprintf(stderr, "Cannot create the virtual uinput device: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
На этом этапеядро создаст устройство, предоставит соответствующее событие демону udev, а демон udev создаст узел устройства и символическую ссылку (и) в соответствии с его конфигурацией.Все это займет немного времени - доли секунды в реальном мире, но этого достаточно, чтобы попытка немедленно вызвать события могла привести к потере некоторых из них.
Излучение входных событий (struct input_event
) путем записи на устройство uinput.
Вы можете записывать по одному или нескольким struct input_event
с одновременно, и никогда не должны видеть короткие записи (если вы не пытаетесь писатьчастичная структура события).Частичные структуры событий полностью игнорируются.(См. drivers / input / misc / uinput.c: uinput_write () uinput_inject_events () , чтобы узнать, как ядро обрабатывает такие записи.)
Многие действия состоят из нескольких struct input_event
.Например, вы можете перемещать мышь по диагонали (испуская { .type == EV_REL, .code == REL_X, .value = xdelta }
и { .type == EV_REL, .code == REL_Y, .value = ydelta }
для этого единственного движения).События синхронизации ({ .type == EV_SYN, .code == 0, .value == 0 }
) используются в качестве часового или разделителя, обозначая конец связанных событий.
Из-за этого вам необходимо добавлять входное событие { .type == EV_SYN, .code == 0, .value == 0 }
после каждого отдельного действия (движение мыши, нажатие клавиши, отпускание клавиши и т. д.).Думайте об этом как о эквиваленте новой строки для буферизованного ввода.
Например, следующий код перемещает мышь по диагонали вниз на один пиксель вправо.
struct input_event event[3];
memset(event, 0, sizeof event);
event[0].type = EV_REL;
event[0].code = REL_X;
event[0].value = +1; /* Right */
event[1].type = EV_REL;
event[1].code = REL_Y;
event[1].value = +1; /* Down */
event[2].type = EV_SYN;
event[2].code = 0;
event[2].value = 0;
if (write(fd, event, sizeof event) != sizeof event)
fprintf(stderr, "Failed to inject mouse movement event.\n");
Ошибкадело не смертельно;это только означает, что события не были введены (хотя я не понимаю, как это могло бы произойти в современных ядрах; на всякий случай лучше защититься).Вы можете просто повторить то же самое снова или проигнорировать сбой (но сообщите об этом пользователю, чтобы он мог провести расследование, если это когда-нибудь произойдет).Поэтому зарегистрируйте его или выведите предупреждение, но не нужно, чтобы демон / служба завершали работу.
Удалите устройство:
ioctl(fd, UI_DEV_DESTROY);
close(fd);
Устройство автоматически уничтожается, когда закрывается последний дубликат исходного открытого дескриптора, но я рекомендую сделать это явно, как указано выше.
Помещение шагов 1-5 в функциювы получаете что-то вроде
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/uinput.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
static const unsigned int allow_event_type[] = {
EV_KEY,
EV_SYN,
EV_REL,
};
#define ALLOWED_EVENT_TYPES (sizeof allow_event_type / sizeof allow_event_type[0])
static const unsigned int allow_key_code[] = {
KEY_SPACE,
BTN_LEFT,
BTN_MIDDLE,
BTN_RIGHT,
};
#define ALLOWED_KEY_CODES (sizeof allow_key_code / sizeof allow_key_code[0])
static const unsigned int allow_rel_code[] = {
REL_X,
REL_Y,
REL_WHEEL,
};
#define ALLOWED_REL_CODES (sizeof allow_rel_code / sizeof allow_rel_code[0])
static int uinput_open(const char *name, const unsigned int vendor, const unsigned int product, const unsigned int version)
{
struct uinput_user_dev dev;
int fd;
size_t i;
if (!name || strlen(name) < 1 || strlen(name) >= UINPUT_MAX_NAME_SIZE) {
errno = EINVAL;
return -1;
}
fd = open("/dev/uinput", O_RDWR);
if (fd == -1)
return -1;
memset(&dev, 0, sizeof dev);
strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
dev.id.bustype = BUS_USB;
dev.id.vendor = vendor;
dev.id.product = product;
dev.id.version = version;
do {
for (i = 0; i < ALLOWED_EVENT_TYPES; i++)
if (ioctl(fd, UI_SET_EVBIT, allow_event_type[i]) == -1)
break;
if (i < ALLOWED_EVENT_TYPES)
break;
for (i = 0; i < ALLOWED_KEY_CODES; i++)
if (ioctl(fd, UI_SET_KEYBIT, allow_key_code[i]) == -1)
break;
if (i < ALLOWED_KEY_CODES)
break;
for (i = 0; i < ALLOWED_REL_CODES; i++)
if (ioctl(fd, UI_SET_RELBIT, allow_rel_code[i]) == -1)
break;
if (i < ALLOWED_REL_CODES)
break;
if (write(fd, &dev, sizeof dev) != sizeof dev)
break;
if (ioctl(fd, UI_DEV_CREATE) == -1)
break;
/* Success. */
return fd;
} while (0);
/* FAILED: */
{
const int saved_errno = errno;
close(fd);
errno = saved_errno;
return -1;
}
}
static void uinput_close(const int fd)
{
ioctl(fd, UI_DEV_DESTROY);
close(fd);
}
, которое, кажется, работает нормально и не требует никаких библиотек (кроме стандартной библиотеки C).
Важно понимать, что подсистема ввода Linux,включая uinput и struct input_event
, являются двоичными интерфейсами для ядра Linux и, следовательно, будут обратно совместимыми (за исключением неотложных технических причин, таких как проблемы безопасности или серьезные конфликты с другими частями ядра).(Желание обернуть все под зонтик freedesktop.org или systemd не одно).