哈嘍,大家好,我是程序員秘書LittleG。
前言
uinput
即Userspace Input
,uinput 的實(shí)現(xiàn)是基于 Linux input子系統(tǒng)(Input Subsystem),允許用戶空間程序創(chuàng)建虛擬的輸入設(shè)備并向內(nèi)核發(fā)送輸入事件,比如鍵盤敲擊、鼠標(biāo)移動(dòng)等,就像這些事件來(lái)自真實(shí)的物理設(shè)備一樣。對(duì)于開發(fā)自定義輸入設(shè)備驅(qū)動(dòng)、自動(dòng)化測(cè)試、游戲控制模擬以及各種人機(jī)交互實(shí)驗(yàn)等場(chǎng)景非常有用。
可能的使用場(chǎng)景有:
自動(dòng)化測(cè)試:模擬用戶操作,如點(diǎn)擊、滑動(dòng)等,進(jìn)行UI自動(dòng)化測(cè)試。在自動(dòng)化測(cè)試過程中,可以模擬用戶的輸入操作來(lái)測(cè)試軟件的功能。
自定義輸入設(shè)備:為沒有驅(qū)動(dòng)支持的新奇設(shè)備創(chuàng)建虛擬驅(qū)動(dòng)。
游戲控制模擬:模擬游戲手柄或其他特殊控制器。一些游戲玩家可能會(huì)使用腳本來(lái)自動(dòng)執(zhí)行一些復(fù)雜的操作。
輔助工具開發(fā):如屏幕鍵盤、宏命令工具等。實(shí)現(xiàn)一些輔助工具(如屏幕閱讀器、殘疾人輔助工具等)可能需要模擬輸入設(shè)備來(lái)與操作系統(tǒng)或其他應(yīng)用程序進(jìn)行交互。
uinput的使用場(chǎng)景非常廣泛,包括但不限于以上場(chǎng)景。下面就學(xué)習(xí)看下原理和如何使用。
實(shí)現(xiàn)原理
uinput
?通過在內(nèi)核和用戶空間之間創(chuàng)建一個(gè)特殊的設(shè)備文件(通常是?/dev/uinput
)來(lái)工作。用戶空間程序可以打開這個(gè)文件,并向其中寫入數(shù)據(jù)來(lái)模擬輸入事件。這些數(shù)據(jù)會(huì)被內(nèi)核的輸入子系統(tǒng)接收,并像真實(shí)的輸入設(shè)備產(chǎn)生的事件一樣進(jìn)行處理。
具體來(lái)說,uinput
?的實(shí)現(xiàn)包括以下幾個(gè)步驟:
創(chuàng)建 uinput 設(shè)備:用戶空間程序通過?ioctl
?調(diào)用?UINPUT_CREATE_DEVICE
?請(qǐng)求來(lái)創(chuàng)建一個(gè)?uinput
?設(shè)備。這個(gè)請(qǐng)求需要指定模擬的輸入設(shè)備的類型和屬性。
設(shè)置輸入事件:用戶空間程序通過寫入?/dev/uinput
?設(shè)備文件來(lái)設(shè)置要模擬的輸入事件。這些事件可以是鍵盤按鍵、鼠標(biāo)移動(dòng)、觸摸屏觸摸等。
啟用 uinput 設(shè)備:在設(shè)置完所有需要的輸入事件后,用戶空間程序通過?ioctl
?調(diào)用?UINPUT_START_DEVICE
?請(qǐng)求來(lái)啟用?uinput
?設(shè)備。此時(shí),內(nèi)核的輸入子系統(tǒng)會(huì)開始處理這些模擬的輸入事件。
發(fā)送輸入事件:一旦?uinput
?設(shè)備被啟用,用戶空間程序就可以通過寫入?/dev/uinput
?設(shè)備文件來(lái)發(fā)送輸入事件了。這些事件會(huì)被內(nèi)核的輸入子系統(tǒng)接收,并像真實(shí)的輸入設(shè)備產(chǎn)生的事件一樣進(jìn)行處理。
銷毀 uinput 設(shè)備:當(dāng)用戶空間程序不再需要模擬輸入設(shè)備時(shí),它可以通過?ioctl
?調(diào)用?UINPUT_DESTROY_DEVICE
?請(qǐng)求來(lái)銷毀?uinput
?設(shè)備。
補(bǔ)充說明
關(guān)于配置設(shè)備屬性
在創(chuàng)建虛擬設(shè)備之前,需要通過ioctl
調(diào)用配置設(shè)備的各種屬性,比如設(shè)備名稱、ID(廠商ID、產(chǎn)品ID、版本等)、支持的事件類型(EV_KEY、EV_REL、EV_ABS等)和具體的事件代碼(如BTN_LEFT、KEY_A、REL_X等)。
關(guān)于發(fā)送事件
一旦設(shè)備被創(chuàng)建,就可以通過寫入input_event
結(jié)構(gòu)體到文件描述符來(lái)模擬輸入事件。每個(gè)事件包括類型(EV_KEY、EV_REL等)、代碼(按鍵、軸等)、值(按鍵狀態(tài)、移動(dòng)距離等)以及一個(gè)同步事件(EV_SYN)來(lái)標(biāo)記事件包的結(jié)束。
使用舉例
下面是一個(gè)簡(jiǎn)單的使用例子,通過uinput
創(chuàng)建虛擬鍵盤并發(fā)送一次鍵擊事件:
#include <fcntl.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int fd;
struct uinput_user_dev uidev;
struct input_event ev;
/* 打開uinput設(shè)備 */
fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0) {
perror("Error opening /dev/uinput");
return 1;
}
/* 配置虛擬設(shè)備 */
memset(&uidev, 0, sizeof(uidev));
strncpy(uidev.name, "Virtual Keyboard", UINPUT_MAX_NAME_SIZE);
uidev.id.bustype = BUS_USB;
uidev.id.vendor = 0x1234;
uidev.id.product = 0x5678;
uidev.id.version = 1;
/* 設(shè)置支持的事件類型 */
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, KEY_A);
/* 創(chuàng)建虛擬設(shè)備 */
write(fd, &uidev, sizeof(uidev));
ioctl(fd, UI_DEV_CREATE);
/* 發(fā)送按鍵事件 */
memset(&ev, 0, sizeof(ev));
ev.type = EV_KEY;
ev.code = KEY_A;
ev.value = 1; // 按下
write(fd, &ev, sizeof(ev));
ev.value = 0; // 抬起
write(fd, &ev, sizeof(ev));
/* 發(fā)送同步事件 */
memset(&ev, 0, sizeof(ev));
ev.type = EV_SYN;
ev.code = SYN_REPORT;
ev.value = 0;
write(fd, &ev, sizeof(ev));
/* 銷毀設(shè)備 */
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
說明:以上代碼中,創(chuàng)建了一個(gè)名為"Virtual Keyboard"的虛擬鍵盤設(shè)備,發(fā)送了一個(gè)"A"鍵的按下與釋放事件,并在最后銷毀了虛擬設(shè)備。
注意,調(diào)試運(yùn)行時(shí),可能會(huì)存在相應(yīng)的權(quán)限問題,可以使用root登錄運(yùn)行或chmod修改/dev/uinput
權(quán)限。
下期見~