# enigo ## 项目概述 跨平台键盘鼠标输入模拟库,支持 Linux (X11/Wayland)、Windows、macOS 和 BSD。 **GitHub**: https://github.com/enigo-rs/enigo **本地路径**: `vendors/enigo` ## 目录结构 ``` enigo/ ├── src/ │ ├── lib.rs # 公共 API 和 Trait 定义 │ ├── platform.rs # 平台抽象 │ ├── keycodes.rs # 键码定义 │ ├── agent.rs # Token/Agent 模式 │ ├── errors.rs # 错误定义 │ ├── tests/ # 测试用例 │ │ ├── keyboard.rs │ │ ├── mouse.rs │ │ └── text.rs │ ├── linux/ # Linux 实现 │ │ ├── mod.rs # 模块入口 │ │ ├── x11.rs # X11 实现 (x11rb) │ │ ├── xdo.rs # xdo 实现 │ │ ├── wayland.rs # Wayland 实现 │ │ ├── libei.rs # libei 实现 │ │ ├── keymap2.rs # 键码映射 │ │ └── keyboard.rs # 键盘布局 │ ├── macos/ # macOS 实现 │ │ ├── mod.rs │ │ ├── macos_impl.rs # CoreGraphics 实现 │ │ └── key_layout.rs # 键盘布局 │ ├── win/ # Windows 实现 │ │ ├── mod.rs │ │ ├── win_impl.rs # SendInput 实现 │ │ └── key_codes.rs # 虚拟键码 │ └── freebsd/ # FreeBSD 实现 │ └── mod.rs ├── Cargo.toml └── README.md ``` ## 核心依赖 ```toml [dependencies] # 平台 API windows = "0.48" core-foundation = "0.9" core-graphics = "0.22" objc2 = "0.5" # Linux 平台 x11rb = { version = "0.13", optional = true } wayland-client = { version = "0.31", optional = true } ashpd = { version = "0.4", optional = true } reis = { version = "0.3", optional = true } libei = { version = "0.3", optional = true } # 运行时支持 smol = { version = "2.0", optional = true } tokio = { version = "1.0", optional = true, features = ["rt", "rt-multi-thread", "time"] } [features] default = ["smol-runtime"] smol-runtime = ["smol", "reis"] tokio-runtime = ["tokio", "reis"] x11rb = ["x11rb", "reis"] xdo = ["xdo"] wayland = ["wayland-client", "reis", "ashpd"] libei = ["libei", "reis"] ``` ## 核心 Trait 和结构体 ### Keyboard Trait ```rust // src/lib.rs /// 键盘操作 Trait pub trait Keyboard { /// 发送文本(Unicode 支持) fn text(&mut self, text: &str) -> InputResult<()>; /// 发送单个按键 fn key(&mut self, key: Key, direction: Direction) -> InputResult<()>; /// 发送原始键码 fn raw(&mut self, keycode: u16, direction: Direction) -> InputResult<()>; /// 获取可打印字符 fn get_char(&self, key: Key, direction: Direction) -> Option; } ``` ### Mouse Trait ```rust /// 鼠标操作 Trait pub trait Mouse { /// 鼠标按钮点击 fn button(&mut self, button: Button, direction: Direction) -> InputResult<()>; /// 移动鼠标到绝对位置 fn move_mouse(&mut self, x: i32, y: i32, coordinate: Coordinate) -> InputResult<()>; /// 移动鼠标(相对位移) fn move_mouse_relative(&mut self, x: i32, y: i32) -> InputResult<()>; /// 滚动 fn scroll(&mut self, length: i32, axis: Axis) -> InputResult<()>; /// 水平滚动 fn scroll_x(&mut self, length: i32) -> InputResult<()> { self.scroll(length, Axis::Horizontal) } /// 垂直滚动 fn scroll_y(&mut self, length: i32) -> InputResult<()> { self.scroll(length, Axis::Vertical) } /// 获取主显示器尺寸 fn main_display(&self) -> InputResult<(i32, i32)>; /// 获取鼠标当前位置 fn location(&self) -> InputResult<(i32, i32)>; } ``` ### 主结构体 ```rust /// 主入口结构体 pub struct Enigo { keyboard: Box, mouse: Box, } impl Enigo { /// 创建新的 Enigo 实例 pub fn new(settings: &Settings) -> Self { let keyboard = create_keyboard(settings); let mouse = create_mouse(settings); Self { keyboard, mouse } } } pub struct Settings { /// 跳过的初始化检查 pub skip_init: bool, /// 键盘设置 pub keyboard: KeyboardSettings, /// 鼠标设置 pub mouse: MouseSettings, } impl Default for Settings { fn default() -> Self { Self { skip_init: false, keyboard: KeyboardSettings::default(), mouse: MouseSettings::default(), } } } ``` ## Key 键码定义 ```rust // src/keycodes.rs /// 特殊键枚举 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Key { // 修饰键 Control, Alt, Shift, AltGr, Super, // Windows/Cmd // 功能键 Function(Fn), // F1-F24 // 布局相关字符 Layout(char), // 基于当前键盘布局的字符 // Unicode 字符 Unicode(char), // 特殊键 Space, Enter, Tab, Escape, Backspace, Delete, Insert, Home, End, PageUp, PageDown, // 方向键 ArrowUp, ArrowDown, ArrowLeft, ArrowRight, // 数字行 F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, // 音频键 VolumeUp, VolumeDown, VolumeMute, // 媒体键 MediaNextTrack, MediaPreviousTrack, MediaPlay, MediaStop, // 小键盘 NumLock, Numpad0, Numpad1, Numpad2, Numpad3, Numpad4, Numpad5, Numpad6, Numpad7, Numpad8, Numpad9, NumpadAdd, NumpadSubtract, NumpadMultiply, NumpadDivide, NumpadEnter, NumpadDecimal, } pub enum Fn { F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, } /// 转换为虚拟键码 impl From for KeyCode<'_> { fn from(key: Key) -> Self { match key { Key::Control => Self::Control, Key::Alt => Self::Alt, Key::Shift => Self::Shift, Key::Super => Self::Super, // ... } } } ``` ## Direction 和 Button ```rust /// 按键方向 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { Click, // 点击(按下 + 释放) Down, // 按下 Up, // 释放 } /// 鼠标按钮 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Button { Left, Right, Middle, Back, // 浏览器后退 Forward, // 浏览器前进 } /// 滚动轴 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Axis { Horizontal, Vertical, } /// 坐标系统 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Coordinate { Absolute, // 绝对坐标(屏幕位置) Relative, // 相对坐标(位移) } ``` ## 平台实现详情 ### Windows 实现 ```rust // src/win/win_impl.rs use windows::Win32::UI::Input::KeyboardAndMouse::{ SendInput, INPUT, INPUT_KEYBOARD, KEYBDEVENTFLAGS, VkKeyScanW, MapVirtualKeyExW, ToUnicodeEx, }; pub struct EnigoWindows { // 键盘布局 layout: HKL, // 状态 state: Vec, } impl Keyboard for EnigoWindows { fn key(&mut self, key: Key, direction: Direction) -> InputResult<()> { let vk = key_to_virtual_key(key, self.layout)?; let flags = match direction { Direction::Down => KEYBDEVENTFLAGS::empty(), Direction::Up => KEYBDEVENTFLAGS::KEYEVENTF_KEYUP, Direction::Click => { self.key(key, Direction::Down)?; self.key(key, Direction::Up)?; return Ok(()); } }; let input = INPUT { type_: INPUT_TYPE::INPUT_KEYBOARD, Anonymous: input_ki!(vk, 0, flags), }; unsafe { SendInput(&[input], std::mem::size_of::() as i32); } Ok(()) } fn text(&mut self, text: &str) -> InputResult<()> { // 使用 ToUnicodeEx 处理 Unicode 输入 let context = 0; // 不获取上下文 let flags = 0; for c in text.chars() { let mut buffer = [0u16; 5]; let len = unsafe { ToUnicodeEx( VkKeyScanW(c as u16), 0, // 扫描码 &mut [0u8; 256], buffer.as_mut_ptr(), buffer.len() as i32, flags, self.layout, ) }; if len > 0 { for i in 0..len as usize { let vk = buffer[i]; self.key(Key::Unicode(c), Direction::Down)?; self.key(Key::Unicode(c), Direction::Up)?; } } } Ok(()) } } impl Mouse for EnigoWindows { fn move_mouse(&mut self, x: i32, y: i32, coordinate: Coordinate) -> InputResult<()> { match coordinate { Coordinate::Absolute => { // 绝对位置 - 使用 MOUSEINPUT let input = INPUT { type_: INPUT_TYPE::INPUT_MOUSE, Anonymous: input_mi!(x, y, 0, MOUSEEVENTF_MOVE, 0), }; unsafe { SendInput(&[input], std::mem::size_of::() as i32); } } Coordinate::Relative => { // 相对位移 let input = INPUT { type_: INPUT_TYPE::INPUT_MOUSE, Anonymous: input_mi!(x, y, 0, MOUSEEVENTF_MOVE, 0), }; unsafe { SendInput(&[input], std::mem::size_of::() as i32); } } } Ok(()) } fn button(&mut self, button: Button, direction: Direction) -> InputResult<()> { let (flags, data) = match (button, direction) { (Button::Left, Direction::Down) => (MOUSEEVENTF_LEFTDOWN, 0), (Button::Left, Direction::Up) => (MOUSEEVENTF_LEFTUP, 0), (Button::Right, Direction::Down) => (MOUSEEVENTF_RIGHTDOWN, 0), (Button::Right, Direction::Up) => (MOUSEEVENTF_RIGHTUP, 0), (Button::Middle, Direction::Down) => (MOUSEEVENTF_MIDDLEDOWN, 0), (Button::Middle, Direction::Up) => (MOUSEEVENTF_MIDDLEUP, 0), _ => return Err(Error::Unimplemented), }; let input = INPUT { type_: INPUT_TYPE::INPUT_MOUSE, Anonymous: input_mi!(0, 0, 0, flags, data), }; unsafe { SendInput(&[input], std::mem::size_of::() as i32); } Ok(()) } fn scroll(&mut self, length: i32, axis: Axis) -> InputResult<()> { let flags = match axis { Axis::Vertical => MOUSEEVENTF_WHEEL, Axis::Horizontal => MOUSEEVENTF_HWHEEL, }; let input = INPUT { type_: INPUT_TYPE::INPUT_MOUSE, Anonymous: input_mi!(0, 0, length as i16, flags, 0), }; unsafe { SendInput(&[input], std::mem::size_of::() as i32); } Ok(()) } } ``` ### macOS 实现 ```rust // src/macos/macos_impl.rs use core_foundation::base::{TCFType, CFTypeRef}; use core_graphics::display::{CGDisplay, CGPoint, CGRect}; use objc2::rc::Retained; use objc2::runtime::{AnyClass, ProtocolObject}; use objc2_foundation::{NSInteger, NSUInteger}; pub struct EnigoMac { // CGEventSource source: Retained>, // 键盘布局 layout: TISInputSourceRef, } impl Keyboard for EnigoMac { fn key(&mut self, key: Key, direction: Direction) -> InputResult<()> { let (keycode, flags) = key_to_keycode_and_flags(key, self.layout)?; let event_type = match direction { Direction::Down => CGEventType::KeyDown, Direction::Up => CGEventType::KeyUp, Direction::Click => { self.key(key, Direction::Down)?; self.key(key, Direction::Up)?; return Ok(()); } }; let event = CGEvent::new_keyboard_event(self.source.as_ref(), event_type, keycode, true)?; event.set_flags(flags); unsafe { CGEventPost(CGEventSourceStateID::HIDState, event.as_ref()); } Ok(()) } fn text(&mut self, text: &str) -> InputResult<()> { for c in text.chars() { let event = CGEvent::new_keyboard_event( self.source.as_ref(), CGEventType::KeyDown, c as u64, true, )?; // 设置 Unicode 内容 event.set_keyboard_type(0); // 使用当前键盘类型 event.set_string(c.to_string()); unsafe { CGEventPost(CGEventSourceStateID::HIDState, event.as_ref()); } } Ok(()) } } impl Mouse for EnigoMac { fn button(&mut self, button: Button, direction: Direction) -> InputResult<()> { let (button_num, event_type) = match (button, direction) { (Button::Left, Direction::Down) => (0, CGEventType::LeftMouseDown), (Button::Left, Direction::Up) => (0, CGEventType::LeftMouseUp), (Button::Right, Direction::Down) => (1, CGEventType::RightMouseDown), (Button::Right, Direction::Up) => (1, CGEventType::RightMouseUp), (Button::Middle, Direction::Down) => (2, CGEventType::OtherMouseDown), (Button::Middle, Direction::Up) => (2, CGEventType::OtherMouseUp), _ => return Err(Error::Unimplemented), }; let point = CGEvent::get_location(self.mouse_event.as_ref().unwrap()); let event = CGEvent::new_mouse_event( self.source.as_ref(), event_type, point, button_num, )?; unsafe { CGEventPost(CGEventSourceStateID::HIDState, event.as_ref()); } Ok(()) } fn move_mouse(&mut self, x: i32, y: i32, coordinate: Coordinate) -> InputResult<()> { let point = CGPoint::new(x as f64, y as f64); let event_type = match coordinate { Coordinate::Absolute => CGEventType::MouseMoved, Coordinate::Relative => CGEventType::MouseMoved, }; let event = CGEvent::new_mouse_event(self.source.as_ref(), event_type, point, 0)?; unsafe { CGEventPost(CGEventSourceStateID::HIDState, event.as_ref()); } Ok(()) } fn scroll(&mut self, length: i32, axis: Axis) -> InputResult<()> { let (scroll_type, wheel_count) = match axis { Axis::Vertical => (CGEventType::Wheel, 1), Axis::Horizontal => (CGEventType::Wheel, 2), }; let point = CGEvent::get_location(self.mouse_event.as_ref().unwrap()); let event = CGEvent::new_scroll_event( self.source.as_ref(), scroll_type, CGScrollEventUnit::Pixel, wheel_count as i32, length as i32, 0, )?; unsafe { CGEventPost(CGEventSourceStateID::HIDState, event.as_ref()); } Ok(()) } } ``` ### Linux X11 实现 ```rust // src/linux/x11.rs use x11rb::connection::Connection; use x11rb::protocol::xproto::*; use x11rb::wrapper::ConnectionExt as _; pub struct EnigoX11<'a, C: Connection> { conn: &'a C, screen: usize, root: Window, // 键盘状态 keyboard_state: Vec, // 修饰键状态 modifiers: ModifiersState, } impl Keyboard for EnigoX11<'_, C> { fn key(&mut self, key: Key, direction: Direction) -> InputResult<()> { let keycode = self.key_to_keycode(key)?; let event_detail = match direction { Direction::Down => keycode, Direction::Up => keycode + 8, // X11: keyup = keycode + 8 Direction::Click => { self.key(key, Direction::Down)?; self.key(key, Direction::Up)?; return Ok(()); } }; let event = InputEvent::new( self.conn.get_input_focus().reply()?.focus, event_detail, self.root, state: self.get_current_state(), event_type: match direction { Direction::Down => EventType::KEY_PRESS, Direction::Up => EventType::KEY_RELEASE, _ => unreachable!(), }, ); self.conn.send_event(false, event, EventType::KEY_PRESS as u8)?; Ok(()) } } impl Mouse for EnigoX11<'_, C> { fn move_mouse(&mut self, x: i32, y: i32, coordinate: Coordinate) -> InputResult<()> { let (root_x, root_y) = match coordinate { Coordinate::Absolute => { // 转换屏幕坐标 (x, y) } Coordinate::Relative => { // 相对位移需要获取当前位置 let (current_x, _) = self.query_pointer()?; (current_x + x, y) } }; let event = InputEvent::new( self.root, EventType::MOTION_NOTIFY, self.root, root_x as u16, root_y as u16, state: self.get_current_state(), ); self.conn.send_event(false, event, EventType::MOTION_NOTIFY as u8)?; Ok(()) } fn button(&mut self, button: Button, direction: Direction) -> InputResult<()> { let button_code = button_to_x11(button)?; let event = InputEvent::new( self.root, match direction { Direction::Down => EventType::BUTTON_PRESS, Direction::Up => EventType::BUTTON_RELEASE, Direction::Click => { self.button(button, Direction::Down)?; self.button(button, Direction::Up)?; return Ok(()); } }, self.root, 0, 0, state: self.get_current_state(), detail: button_code, ); self.conn.send_event(false, event, EventType::BUTTON_PRESS as u8)?; Ok(()) } fn scroll(&mut self, length: i32, axis: Axis) -> InputResult<()> { let button = match axis { Axis::Vertical => { if length > 0 { ButtonCode::4 } else { ButtonCode::5 } } Axis::Horizontal => { if length > 0 { ButtonCode::6 } else { ButtonCode::7 } } }; let event = InputEvent::new( self.root, EventType::BUTTON_PRESS, self.root, 0, 0, state: self.get_current_state(), detail: button as u16, ); self.conn.send_event(false, event, EventType::BUTTON_PRESS as u8)?; Ok(()) } } ``` ## 使用示例 ```rust use enigo::{Enigo, Key, Button, Direction, Coordinate, KeyboardControllable, MouseControllable}; fn main() { let mut enigo = Enigo::new(&enigo::Settings::default()); // 键盘模拟 enigo.key_click(Key::Space); enigo.key_down(Key::Control); enigo.key_click(Key::C); enigo.key_up(Key::Control); // 文本输入 enigo.text("Hello, World!"); // 鼠标移动(绝对位置) enigo.move_mouse(100, 200, Coordinate::Absolute); // 鼠标移动(相对位移) enigo.move_mouse_relative(10, 10); // 鼠标点击 enigo.click(Button::Left); enigo.click(Button::Right); // 鼠标按下/释放 enigo.button(Button::Left, Direction::Down); enigo.button(Button::Left, Direction::Up); // 滚动 enigo.scroll(5, Axis::Vertical); // 向下滚动 enigo.scroll(-3, Axis::Vertical); // 向上滚动 enigo.scroll(2, Axis::Horizontal); // 向右滚动 // 组合键 enigo.key_down(Key::Shift); enigo.text("a"); enigo.key_up(Key::Shift); // 输入 "A" // 快捷键:Ctrl+S 保存 enigo.key_down(Key::Control); enigo.key_click(Key::S); enigo.key_up(Key::Control); } ``` ## 可复用代码 | 模块 | 文件 | 用途 | |------|------|------| | **平台抽象** | `src/platform.rs` | 统一的平台接口 | | **键码映射** | `src/keycodes.rs` | 跨平台键码转换 | | **Windows 实现** | `src/win/` | Windows API | | **macOS 实现** | `src/macos/` | Core Graphics | | **Linux 实现** | `src/linux/` | X11/Wayland | ## 在本项目中的使用 用于实现远程桌面控制时的键盘鼠标模拟: ``` agent-server-admin (远程控制端) │ ├── 输入事件 --> hbb_common (Protocol Buffers) │ │ └── 屏幕画面 <─── qiming-rustdesk (scrap) │ └── enigo (输入模拟) │ └── 客户端本地执行输入 ``` ## 与 agent-client 集成场景 ### 场景1:远程桌面输入模拟 ```rust // agent-client 接收远程输入事件并执行 use enigo::{Enigo, Key, Button, Direction, Coordinate}; use std::sync::{Arc, Mutex}; pub struct RemoteInputHandler { enigo: Mutex, // 输入队列(处理高频输入) input_queue: Arc>>, // 处理任务 _task: JoinHandle<()>, } pub enum InputEvent { Keyboard { key: Key, direction: Direction, }, Text { text: String, }, Mouse { button: Option