windows/linux 模拟鼠标键盘输入
windows 使用 SendInput (用户空间 API)
#include <Windows.h>// UINT SendInput(
// UINT nInputs, // 输入结构体数量
// LPINPUT pInputs, // 指向输入结构体数组的指针
// int cbSize // 每个结构体的大小(单位:字节)
// );void SimulateMouseClick(int x, int y) {// 设置鼠标位置(绝对坐标)SetCursorPos(x, y);// 创建鼠标事件结构体INPUT input = {0}input.type = INPUT_MOUSE;input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; // 鼠标按下SendInput(1, &input, sizeof(INPUT));Sleep(50); // 等待 50msinput.mi.dwFlags = MOUSEEVENTF_LEFTUP; // 鼠标释放SendInput(1, &input, sizeof(INPUT));
}void SimulateKeyPress(WORD keyCode) {INPUT input = {0};input.type = INPUT_KEYBOARD;input.ki.wVk = keyCode; // 虚拟键码(如 VK_RETURN)input.ki.dwFlags = 0; // 按下SendInput(1, &input, sizeof(INPUT));Sleep(50); // 等待 50msinput.ki.dwFlags = KEYEVENTF_KEYUP; // 释放SendInput(1, &input, sizeof(INPUT));
}// 模拟 Ctrl + C(复制)操作
void ControlC() {INPUT inputs[4] = {0};// Ctrl downinputs[0].type = INPUT_KEYBOARD;inputs[0].ki.wVk = VK_CONTROL;// C downinputs[1].type = INPUT_KEYBOARD;inputs[1].ki.wVk = 'C';// C upinputs[2].type = INPUT_KEYBOARD;inputs[2].ki.wVk = 'C';inputs[2].ki.dwFlags = KEYEVENTF_KEYUP;// Ctrl upinputs[3].type = INPUT_KEYBOARD;inputs[3].ki.wVk = VK_CONTROL;inputs[3].ki.dwFlags = KEYEVENTF_KEYUP;SendInput(4, inputs, sizeof(INPUT));}int main() {SimulateMouseClick(500, 300);Sleep(100);SimulateKeyPress(VK_RETURN);return 0;
}
linux 使用 X11 库 (用户空间 API)
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <unistd.h>
#include <X11/keysym.h>void SimulateKeyPress(Display* display, KeySym keysym) {KeyCode keycode = XKeysymToKeycode(display, keysym);XTestFakeKeyEvent(display, keycode, True, 0); // True 按下XTestFakeKeyEvent(display, keycode, False, 0); // False 释放XFlush(display); // 强制刷新事件队列,把事件立即发送给 X Server。
}void SimulateMouseClick(Display* display, int button) {XTestFakeButtonEvent(display, button, True, 0); // True 按下XTestFakeButtonEvent(display, button, False, 0); // False 释放XFlush(display);
}int main() {Display* display = XOpenDisplay(nullptr);if (!display) return -1;SimulateMouseClick(display, 1); // 1:鼠标左键,2:中键,3:右键sleep(1);SimulateKeyPress(display, XK_Return); // 模拟回车键XCloseDisplay(display);return 0;
}
以上方式都是调用了用户空间的 API 来实现。
接下来介绍驱动层模拟的方式
输入子系统(Input Subsystem)结构
[用户层] → /dev/uinput → 内核 input 子系统 → 事件发送至 X Server/Wayland↓应用程序收到输入
[用户层] → DeviceIoControl → HID 驱动层 → 输入堆栈(Kbdclass/Mouclass) → Win32 消息
Linux 驱动层:使用 /dev/uinput
创建虚拟输入设备
Linux 提供了 uinput 接口,可在用户空间模拟底层输入事件,但从内核角度看,是通过驱动层完成,在用户空间创建一个虚拟输入设备。通过 ioctl()
设置设备能力,然后通过写 input_event
结构体模拟键盘事件(比如按下 A 键),最后发送同步事件使其生效。这种方式运行在驱动层之上,绕过常规用户空间 API,常用于自动化测试或底层输入模拟。
/dev 文件夹的作用
-
/dev/tty
:终端设备 -
/dev/console
:系统控制台 -
/dev/input/eventX
:键盘鼠标等输入设备 -
/dev/uinput
:用户空间模拟输入设备
实现步骤:
-
打开
/dev/uinput
-
配置一个虚拟设备(设置支持的事件、按键)
-
写入
input_event
结构体发送事件 -
内核将其当作真实输入设备处理
伪代码如下:
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);// 设置设备支持按键
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, KEY_A);// 配置虚拟设备信息,在 /proc/bus/input/devices 或 evtest 工具中能看到这个“虚拟键盘”。
struct uinput_user_dev uidev = {};
strcpy(uidev.name, "virtual-keyboard");
write(fd, &uidev, sizeof(uidev));
ioctl(fd, UI_DEV_CREATE);// 模拟按下 'A'
struct input_event ev = {};
ev.type = EV_KEY; // 键盘事件
ev.code = KEY_A; // 对应 'A' 键
ev.value = 1; // 按下
write(fd, &ev, sizeof(ev));// 释放 'A'
ev.value = 0; // 0 = 松开
write(fd, &ev, sizeof(ev));// 同步事件,告诉内核:“我一组输入事件已经发完了,请立即处理它们”。
ev.type = EV_SYN;
ev.code = SYN_REPORT;
ev.value = 0;
write(fd, &ev, sizeof(ev));
Windows 驱动层:写 HID 模拟驱动或使用驱动框架
在 Windows 中,驱动层模拟键盘鼠标通常通过以下方式实现:
方式 1:HID 模拟驱动
-
编写一个虚拟 HID 设备(Human Interface Device)
-
使用 KMDF/UMDF(内核/用户模式驱动框架)
-
模拟输入数据包送入 HID Class 驱动堆栈
-
类似于:软件伪造一个“插入的 USB 键盘”