Files
qiming/qimingclaw/specs/agent-client/vendorsdoc/enigo.md

1194 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<char>;
}
```
### 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<dyn Keyboard + Send>,
mouse: Box<dyn Mouse + Send>,
}
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<Key> 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<u8>,
}
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::<INPUT>() 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::<INPUT>() 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::<INPUT>() 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::<INPUT>() 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::<INPUT>() 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<ProtocolObject<dyn CGEventSourceProtocol>>,
// 键盘布局
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<u8>,
// 修饰键状态
modifiers: ModifiersState,
}
impl<C: Connection> 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<C: Connection> 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<Enigo>,
// 输入队列(处理高频输入)
input_queue: Arc<Mutex<Vec<InputEvent>>>,
// 处理任务
_task: JoinHandle<()>,
}
pub enum InputEvent {
Keyboard {
key: Key,
direction: Direction,
},
Text {
text: String,
},
Mouse {
button: Option<Button>,
direction: Option<Direction>,
x: Option<i32>,
y: Option<i32>,
scroll_x: Option<i32>,
scroll_y: Option<i32>,
},
}
impl RemoteInputHandler {
pub fn new() -> Result<Self> {
let enigo = Enigo::new(&enigo::Settings::default());
let input_queue = Arc::new(Mutex::new(Vec::new()));
let this = Self {
enigo: Mutex::new(enigo),
input_queue: input_queue.clone(),
_task: tokio::spawn(Self::process_loop(input_queue)),
};
Ok(this)
}
/// 处理远程输入事件
pub fn handle_input(&self, event: InputEvent) {
let mut queue = self.input_queue.lock().unwrap();
queue.push(event);
}
/// 执行键盘事件
pub fn key_event(&self, key: Key, direction: Direction) {
let mut enigo = self.enigo.lock().unwrap();
enigo.key(key, direction).ok();
}
/// 执行文本输入
pub fn text_input(&self, text: &str) {
let mut enigo = self.enigo.lock().unwrap();
enigo.text(text).ok();
}
/// 执行鼠标事件
pub fn mouse_event(
&self,
button: Option<Button>,
direction: Option<Direction>,
x: Option<i32>,
y: Option<i32>,
scroll_x: Option<i32>,
scroll_y: Option<i32>,
) {
let mut enigo = self.enigo.lock().unwrap();
// 鼠标移动
if let (Some(x), Some(y)) = (x, y) {
enigo.move_mouse(x, y, Coordinate::Absolute).ok();
}
// 鼠标点击
if let (Some(btn), Some(dir)) = (button, direction) {
enigo.button(btn, dir).ok();
}
// 滚动
if let Some(y) = scroll_y {
enigo.scroll(y, enigo::Axis::Vertical).ok();
}
if let Some(x) = scroll_x {
enigo.scroll(x, enigo::Axis::Horizontal).ok();
}
}
/// 组合键处理Ctrl+C, Ctrl+V 等)
pub fn shortcut(&self, modifier: Key, key: Key) {
let mut enigo = self.enigo.lock().unwrap();
enigo.key(modifier, Direction::Down).ok();
enigo.key(key, Direction::Click).ok();
enigo.key(modifier, Direction::Up).ok();
}
/// 获取鼠标位置
pub fn get_mouse_position(&self) -> (i32, i32) {
let enigo = self.enigo.lock().unwrap();
enigo.location().unwrap_or((0, 0))
}
/// 获取主显示器尺寸
pub fn get_display_size(&self) -> (i32, i32) {
let enigo = self.enigo.lock().unwrap();
enigo.main_display().unwrap_or((1920, 1080))
}
/// 输入处理循环
async fn process_loop(input_queue: Arc<Mutex<Vec<InputEvent>>>) {
let mut interval = tokio::time::interval(Duration::from_millis(16)); // ~60fps
loop {
interval.tick().await;
let events = {
let mut queue = input_queue.lock().unwrap();
std::mem::take(&mut *queue)
};
for event in events {
// 根据事件类型执行输入
match event {
InputEvent::Keyboard { key, direction } => {
// 执行键盘输入
}
InputEvent::Text { text } => {
// 执行文本输入
}
InputEvent::Mouse { button, direction, x, y, scroll_x, scroll_y } => {
// 执行鼠标输入
}
}
}
}
}
}
```
### 场景2从 Protocol Buffers 消息解析输入
```rust
// 解析来自 qiming-rustdesk 的 protobuf 输入消息
use hbb_common::protobuf::RemoteInput;
// 从 protobuf 消息转换为 enigo 输入事件
impl From<RemoteInput> for InputEvent {
fn from(msg: RemoteInput) -> Self {
match msg.input_type {
Some(InputType::Key(key)) => InputEvent::Keyboard {
key: convert_key(key.name()),
direction: convert_direction(key.down()),
},
Some(InputType::Text(text)) => InputEvent::Text {
text: text.content(),
},
Some(InputType::Mouse(mouse)) => InputEvent::Mouse {
button: mouse.button().map(convert_button),
direction: mouse.down().map(convert_direction),
x: if mouse.x() != 0 { Some(mouse.x() as i32) } else { None },
y: if mouse.y() != 0 { Some(mouse.y() as i32) } else { None },
scroll_x: None, // 需要从 protobuf 定义中添加
scroll_y: None,
},
Some(InputType::Scroll(scroll)) => InputEvent::Mouse {
button: None,
direction: None,
x: None,
y: None,
scroll_x: Some(scroll.x() as i32),
scroll_y: Some(scroll.y() as i32),
},
_ => unreachable!(),
}
}
}
/// 转换键码
fn convert_key(name: &str) -> Key {
match name {
"Control" => Key::Control,
"Alt" => Key::Alt,
"Shift" => Key::Shift,
"Super" => Key::Super,
"Enter" => Key::Enter,
"Tab" => Key::Tab,
"Escape" => Key::Escape,
"Backspace" => Key::Backspace,
"Delete" => Key::Delete,
"ArrowUp" => Key::ArrowUp,
"ArrowDown" => Key::ArrowDown,
"ArrowLeft" => Key::ArrowLeft,
"ArrowRight" => Key::ArrowRight,
_ => Key::Layout(name.chars().next().unwrap_or(' ')),
}
}
/// 转换鼠标按钮
fn convert_button(btn: MouseButton) -> Button {
match btn {
MouseButton::Left => Button::Left,
MouseButton::Right => Button::Right,
MouseButton::Middle => Button::Middle,
MouseButton::Back => Button::Back,
MouseButton::Forward => Button::Forward,
_ => Button::Left,
}
}
/// 转换按键方向
fn convert_direction(down: bool) -> Direction {
if down { Direction::Down } else { Direction::Up }
}
```
### 场景3输入事件序列化与传输
```rust
// 输入事件序列化为 protobuf 用于网络传输
#[derive(Debug, Clone)]
pub struct SerializedInput {
pub event_type: String,
pub key: Option<String>,
pub text: Option<String>,
pub button: Option<String>,
pub direction: Option<String>,
pub x: Option<i32>,
pub y: Option<i32>,
pub scroll_x: Option<i32>,
pub scroll_y: Option<i32>,
}
impl SerializedInput {
pub fn from_input_event(event: &InputEvent) -> Self {
match event {
InputEvent::Keyboard { key, direction } => Self {
event_type: "key".to_string(),
key: Some(format!("{:?}", key)),
text: None,
button: None,
direction: Some(format!("{:?}", direction)),
x: None,
y: None,
scroll_x: None,
scroll_y: None,
},
InputEvent::Text { text } => Self {
event_type: "text".to_string(),
key: None,
text: Some(text.clone()),
button: None,
direction: None,
x: None,
y: None,
scroll_x: None,
scroll_y: None,
},
InputEvent::Mouse { button, direction, x, y, scroll_x, scroll_y } => Self {
event_type: "mouse".to_string(),
key: None,
text: None,
button: button.map(|b| format!("{:?}", b)),
direction: direction.map(|d| format!("{:?}", d)),
x: *x,
y: *y,
scroll_x: *scroll_x,
scroll_y: *scroll_y,
},
}
}
}
```
### 场景4平台特定的权限处理
```rust
// 不同平台的输入权限获取
#[cfg(target_os = "macos")]
mod macos_permissions {
use crate::Result;
pub fn request_accessibility() -> bool {
// macOS 需要用户授权辅助功能权限
// 可以通过 AXIsProcessTrustedWithOptions() 检查
// 需要引导用户到系统设置中启用
unsafe {
let options: CFDictionaryRef = core_foundation::base::CFDictionaryCreateMutable(
core_foundation::base::kCFAllocatorDefault,
0,
&core_foundation::base::kCFTypeDictionaryKeyCallBacks,
&core_foundation::base::kCFTypeDictionaryValueCallBacks,
);
let key = core_foundation::string::CFStringCreateWithCString(
core_foundation::base::kCFAllocatorDefault,
"AXTrustedCheckPurposeOptionKey\0".as_ptr() as *const _,
core_foundation::base::kCFStringEncodingUTF8,
);
let value = core_foundation::boolean::kCFBooleanTrue;
CFDictionarySetValue(options, key as *const _, value as *const _);
let trusted = AXIsProcessTrustedWithOptions(options);
CFRelease(options as *const _);
trusted
}
}
}
#[cfg(target_os = "windows")]
mod windows_permissions {
pub fn check_accessibility() -> bool {
// Windows 需要启用"允许应用控制鼠标键盘"
// 通过 OpenInputDesktop 检查
true
}
}
#[cfg(target_os = "linux")]
mod linux_permissions {
pub fn check_accessibility() -> bool {
// Linux 需要 X11/Wayland 权限
// 可以通过 WAYLAND_DISPLAY 环境变量检查
std::env::var("WAYLAND_DISPLAY").is_ok()
}
}
```
### 场景5输入频率限制与平滑
```rust
// 防止高频输入导致的问题
pub struct InputRateLimiter {
// 上一条输入的时间戳
last_input: AtomicU64,
// 最小输入间隔(毫秒)
min_interval: u64,
// 输入平滑因子
smoothing: f32,
// 累积的位移
pending_movement: AtomicI32,
}
impl InputRateLimiter {
pub fn new(min_interval_ms: u64) -> Self {
Self {
last_input: AtomicU64::new(0),
min_interval: min_interval_ms,
smoothing: 0.8,
pending_movement: AtomicI32::new(0),
}
}
/// 检查是否可以执行输入
pub fn can_input(&self) -> bool {
let now = current_time_millis();
let last = self.last_input.load(Ordering::SeqCst);
(now - last) >= self.min_interval
}
/// 执行带速率限制的输入
pub fn execute_with_rate_limit<F, T>(&self, f: F) -> Option<T>
where
F: FnOnce() -> T,
{
if self.can_input() {
self.last_input.store(current_time_millis(), Ordering::SeqCst);
Some(f())
} else {
None
}
}
/// 平滑鼠标移动
pub fn smooth_mouse_move(&self, target_x: i32, target_y: i32, current_x: i32, current_y: i32) -> (i32, i32) {
let dx = target_x - current_x;
let dy = target_y - current_y;
// 应用平滑因子
let smooth_x = (dx as f32 * self.smoothing) as i32;
let smooth_y = (dy as f32 * self.smoothing) as i32;
// 累积未移动的距离
let pending = self.pending_movement.load(Ordering::SeqCst);
self.pending_movement.store(pending + dx - smooth_y, Ordering::SeqCst);
(smooth_x, smooth_y)
}
}
```