7. uinput 模組¶
7.1. 介紹¶
uinput 是一個核心模組,它使得從使用者空間模擬輸入裝置成為可能。透過寫入 /dev/uinput (或 /dev/input/uinput) 裝置,一個程序可以建立一個具有特定功能的虛擬輸入裝置。 一旦建立了這個虛擬裝置,程序就可以透過它傳送事件,這些事件將被傳遞給使用者空間和核心消費者。
7.2. 介面¶
linux/uinput.h
uinput 標頭檔案定義了用於建立、設定和銷燬虛擬裝置的 ioctl。
7.3. libevdev¶
libevdev 是 evdev 裝置的包裝庫,它提供了建立 uinput 裝置和傳送事件的介面。 libevdev 比直接訪問 uinput 更不容易出錯,對於新的軟體應該考慮使用它。
有關 libevdev 的示例和更多資訊:https://www.freedesktop.org/software/libevdev/doc/latest/
7.4. 示例¶
7.4.1. 鍵盤事件¶
第一個例子展示瞭如何建立一個新的虛擬裝置,以及如何傳送一個按鍵事件。為了簡單起見,所有預設匯入和錯誤處理程式都被移除。
#include <linux/uinput.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 fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
/*
* The ioctls below will enable the device that is about to be
* created, to pass key events, in this case the space key.
*/
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);
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);
/* Key press, report the event, send key release, and report again */
emit(fd, EV_KEY, KEY_SPACE, 1);
emit(fd, EV_SYN, SYN_REPORT, 0);
emit(fd, EV_KEY, KEY_SPACE, 0);
emit(fd, EV_SYN, SYN_REPORT, 0);
/*
* 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;
}
7.4.2. 滑鼠移動¶
這個例子展示瞭如何建立一個行為類似於物理滑鼠的虛擬裝置。
#include <linux/uinput.h>
/* emit function is identical to of the first example */
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;
}
7.4.3. uinput 舊介面¶
在 uinput 版本 5 之前,沒有專門的 ioctl 來設定虛擬裝置。 支援舊版本 uinput 介面的程式需要填充一個 uinput_user_dev 結構,並將其寫入 uinput 檔案描述符來配置新的 uinput 裝置。 新的程式碼不應該使用舊介面,而是透過 ioctl 呼叫與 uinput 互動,或者使用 libevdev。
#include <linux/uinput.h>
/* emit function is identical to of the first example */
int main(void)
{
struct uinput_user_dev uud;
int version, rc, fd;
fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
rc = ioctl(fd, UI_GET_VERSION, &version);
if (rc == 0 && version >= 5) {
/* use UI_DEV_SETUP */
return 0;
}
/*
* The ioctls below will enable the device that is about to be
* created, to pass key events, in this case the space key.
*/
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);
memset(&uud, 0, sizeof(uud));
snprintf(uud.name, UINPUT_MAX_NAME_SIZE, "uinput old interface");
write(fd, &uud, sizeof(uud));
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);
/* Key press, report the event, send key release, and report again */
emit(fd, EV_KEY, KEY_SPACE, 1);
emit(fd, EV_SYN, SYN_REPORT, 0);
emit(fd, EV_KEY, KEY_SPACE, 0);
emit(fd, EV_SYN, SYN_REPORT, 0);
/*
* 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;
}