资讯详情

资讯详情

rdev 跨平台全局输入事件库:从监听、模拟到按键拦截的完整实战指南

桌面应用交互助手【免费下载链接】keyvizKeyviz is a free and open-source tool to visualize your keystrokes ⌨️ and ️ mouse actions in real-time.项目地址https://gitcode.com/gh_mirrors/ke/keyviz点击查看免费下载导读rdev是一个用 Rust 编写的跨平台库可在Windows、macOS 和 LinuxX11上全局监听listen并模拟simulate键盘与鼠标事件还提供可选的全局事件拦截能力grab需要unstable_grabfeature。它正是开源按键可视化工具 keyviz 的后端输入引擎——keyviz 通过rdev捕获真实的按键与鼠标动作并实时渲染到屏幕上。读完本文你将掌握rdev的全部公开 API 用法、三大平台的系统级实现原理与权限/兼容性约束并能直接在自己的 Rust 项目里落地全局输入监听、自动化输入模拟与快捷键拦截。说明本文以仓库内 rdev/README.md 为主线结合仓库中的源码实现进行佐证与扩充所有相对路径均以仓库根目录为起点。rdev 是什么keyviz 背后的全局输入引擎rdev的定位非常明确一个简单的库用于在 macOS、Windows、Linux (x11) 上全局监听和发送键盘/鼠标事件。它不依赖 GUI 框架直接对接操作系统底层的输入事件机制。在 keyviz 项目中rdev以本地路径依赖的形式被引入# src-tauri/Cargo.toml rdev { path crates/rdev }keyviz 在 src-tauri/src/app/event.rs 中通过rdev::listen启动一个后台线程把系统级输入事件按键、鼠标按键、移动、滚轮转换为自己的InputEvent并发射给前端渲染实现实时可视化。这是理解rdev价值的最佳实例它的核心使用场景就是全局——无论焦点在哪个应用事件都能被捕获。rdev的完整能力分为四块全局监听listen(callback)阻塞式地接收所有键盘/鼠标事件事件模拟simulate(EventType)向系统注入按键、鼠标移动、点击与滚轮屏幕尺寸查询display_size()返回主屏像素宽高按键状态机Keyboard::new()KeyboardStatetrait把物理按键事件转换为实际字符可选事件拦截unstable_grabfeature 下的grab()可在事件到达应用之前消费或放行。全局事件监听listen 的完整用法监听是rdev最常用的功能。核心 API 是一个阻塞函数listen接受一个回调闭包use rdev::{listen, Event}; // 这会阻塞当前线程。 if let Err(error) listen(callback) { println!(Error: {:?}, error) } fn callback(event: Event) { println!(My callback {:?}, event); match event.name { Some(string) println!(User wrote {:?}, string), None (), } }从 lib.rs 的实现 可以看到listen的签名是pub fn listenT(callback: T) - Result(), ListenError where T: FnMut(Event) static,注意两点回调必须是FnMut(Event) static即可以持有可变状态、且不能借用栈上生命周期不足的变量回调在线程上下文中被反复调用因此若要在回调里做耗时操作请自行排队或使用 channel。在 keyviz 中监听被放入独立线程event.rs并利用回调中的EventType匹配来维护当前按下的键集合pressed_keys从而实现按下/释放状态跟踪与切换快捷键判定——这是实时可视化类应用的典型模式。listen 的系统级原理三个平台各有门道listen在每个平台调用的是不同的系统 API源码分别位于Linuxsrc-tauri/crates/rdev/src/linux/listen.rs —— 通过 X11 的RECORD 扩展XRecordCreateContext/XRecordEnableContext注册一个记录全部客户端事件的上下文回调里把 X11 的原始数据KeyPress/KeyRelease/MotionNotify/ButtonPress等转换成Event。因此它只工作在 X11 会话Wayland 与内核虚拟控制台VT下无法工作macOSsrc-tauri/crates/rdev/src/macos/listen.rs —— 创建CGEventTapCGEventTapCreate位置HID模式ListenOnly挂到主 RunLoop监听全部事件掩码kCGEventMaskForAllEvents。若设置KEYBOARD_ONLY环境变量则只监听 KeyDown/KeyUp/FlagsChangedWindowssrc-tauri/crates/rdev/src/windows/listen.rs —— 使用 Win32 的低级键盘/鼠标钩子WH_KEYBOARD_LL/WH_MOUSE_LLraw_callback解析KBDLLHOOKSTRUCT/MOUSEHOOKSTRUCT后构造Event最后CallNextHookEx放行事件。listen 的 OS 注意事项务必逐条核对平台注意事项macOS运行阻塞listen循环的进程必须是父进程不能先 fork进程必须被授予辅助功能AccessibilityAPI 权限例如在 Terminal.app 内运行则需把 Terminal.app 加入「系统设置 → 隐私与安全性 → 辅助功能」。权限缺失时 macOS 会静默忽略listen回调不产生任何错误Linuxlisten使用 X11 API在 Wayland 或内核虚拟控制台下不可用无 DISPLAY 时返回ListenError::MissingDisplayError从 rdev.rs 的错误定义 可以看到ListenError包含平台相关的全部错误变体macOS 的EventTapError/LoopSourceErrorLinux 的MissingDisplayError/KeyboardError/RecordContextError/XRecordExtensionError等Windows 的KeyHookError(u32)/MouseHookError(u32)。在 macOS 上权限不足并不走这些错误路径而是无提示地不触发回调这是最容易踩的坑。事件模拟simulate 与 EventTyperdev同时支持向系统注入事件API 为simulate(EventType) - Result(), SimulateError见 lib.rs。README 给出的完整示例use rdev::{simulate, Button, EventType, Key, SimulateError}; use std::{thread, time}; fn send(event_type: EventType) { let delay time::Duration::from_millis(20); match simulate(event_type) { Ok(()) (), Err(SimulateError) { println!(We could not send {:?}, event_type); } } // 给操作系统一点追赶时间至少 macOS 需要 thread::sleep(delay); } send(EventType::KeyPress(Key::KeyS)); send(EventType::KeyRelease(Key::KeyS)); send(EventType::MouseMove { x: 0.0, y: 0.0 }); send(EventType::MouseMove { x: 400.0, y: 400.0 }); send(EventType::ButtonPress(Button::Left)); send(EventType::ButtonRelease(Button::Right)); send(EventType::Wheel { delta_x: 0, delta_y: 1, });关键细节连续模拟多个事件时每发送一个后建议thread::sleep约 20msREADME 注释指出 macOS 尤其需要否则系统可能来不及处理。keyviz 本身只用listen不做模拟但该 API 对自动化测试、演示工具非常有用。EventType 枚举的完整语义EventType定义在 rdev.rs其设计是混合各家 OS 的取并集pub enum EventType { KeyPress(Key), KeyRelease(Key), ButtonPress(Button), ButtonRelease(Button), /// 单位为像素(0,0) 对应屏幕左上角 MouseMove { x: f64, y: f64 }, /// delta_y 表示纵向滚动、delta_x 表示横向滚动 /// 正值向上/向右负值向下/向左 Wheel { delta_x: i64, delta_y: i64 }, }各变体的语义与平台差异KeyPress(Key)/KeyRelease(Key)Key枚举rdev.rs 定义基于QWERTY 布局的物理位置命名KeyS、KeyQ……不随布局变化包含修饰键ShiftLeft/ControlRight/Alt/MetaLeft等、功能键 F1-F24、数字小键盘Kp0-Kp9、多媒体键VolumeUp等以及Unknown(u32)兜底变体。ButtonPress(Button)/ButtonRelease(Button)Button只定义标准三键Left/Right/Middle超过三个按键的鼠标在不同 OS 会映射为Button::Unknown(u8)且数值各不相同。MouseMove { x, y }坐标单位为像素(0, 0)为屏幕左上角。注意 rdev.rs 的注释 写明x向右增大、y向下增大。Wheel { delta_x, delta_y }delta_x横向、delta_y纵向。Linux 下不支持横向滚动模拟时delta_x的实际值被忽略只根据delta_y的正负号决定滚轮向上button 4或向下button 5见 simulate.rs 的实现且 X11 监听端也把 button 4/5 直接转换为delta_y ±1见 common.rs。simulate 的底层实现Linuxsimulate.rs基于XTest 扩展——XTestFakeKeyEvent、XTestFakeButtonEvent、XTestFakeMotionEvent滚轮通过模拟 button 4/5 按下释放实现。源码中还额外提供simulate_char把 Unicode 字符临时重映射到一个未使用的 keycode 后模拟输入等 Linux 特有函数导出见 lib.rs。macOSsimulate.rs基于CoreGraphics 的 CGEventCGEvent::new_keyboard_event、鼠标事件等并针对 F 键、方向键、Home/End 等做了workaround_fn清理粘滞 flag如CGEventFlagSecondaryFn的兼容处理——这正是给 OS 留追赶时间注释背后的真实原因之一。Windows使用SendInput系列 API见 windows/simulate.rs。映射不完整的风险EventType::KeyPress(Key)里的Key并非所有值都能映射到 OS 键码——README 明确警告不是所有键都映射了 OS code模拟一个未映射的键可能失败返回SimulateError而发送Unknown()变体始终会尝试OS 仍可能拒绝。模拟失败时统一返回SimulateErrorrdev.rs可通过Display输出 Could not simulate event。核心数据结构Event 与 EventType 的区别这是理解rdev的关键概念README 用了专门一节说明EventType是物理事件对应 QWERTY 布局下的物理键位不随系统布局变化Event是真实收到的事件Event.name反映那一刻操作系统按当前布局解释出的按键含义比如在 AZERTY 布局下按物理KeyS位置name会是别的字符。Event结构定义见 rdev.rs比 README 中展示的版本增加了unicode、platform_code等字段是仓库实际代码pub struct Event { pub time: SystemTime, // 事件被接收的时间 pub unicode: OptionUnicodeInfo, pub event_type: EventType, // 物理事件 pub platform_code: u32, // Linux: keysymWindows: vkcode pub position_code: u32, pub usb_hid: u32, }UnicodeInfordev.rs包含name: OptionString、unicode: Vecu16与is_dead: bool用于描述按键产生的字符与死键dead key状态。使用Event.name前必须做健全性检查README 明确警告name可能是None也可能是空字符串String::from()可能包含不可显示的 Unicode 字符rdev 只是原样转发 OS 给出的内容Linux 上死键机制尚未实现——死键会返回None重音字符会退化为原始字母。keyviz 在事件处理里就做了类似防御它把按键名格式化后跳过任何包含(的名字如RawKey(123)、Unknown()避免把无法识别的键当作用户按键渲染见 event.rs。同时 keyviz 使用EventType而非name来判断哪个键被按下从而不受键盘布局影响。查询主屏幕尺寸display_size模拟MouseMove时往往需要知道屏幕范围。rdev提供use rdev::{display_size}; let (w, h) display_size().unwrap(); assert!(w 0); assert!(h 0);实现上Linux 端直接读取默认 X11 screen 的width/heightlinux/display.rs返回(u64, u64)错误类型为DisplayErrorNoDisplay/ConversionError见 rdev.rs。按键状态机Keyboard 与 KeyboardState如果要把物理按键事件变成用户实际输入的字符串就需要跟踪键盘状态Shift、Ctrl 等修饰键、死键。rdev为此提供了Keyboard与KeyboardStatetraituse rdev::{Keyboard, EventType, Key, KeyboardState}; let mut keyboard Keyboard::new().unwrap(); let string keyboard.add(EventType::KeyPress(Key::KeyS)); // string Some(s)KeyboardState::add(mut self, event_type: EventType) - OptionUnicodeInfordev.rs只改变内部状态、不真正触碰 OS因此可离线模拟如果按了这个键会输出什么。Linux 实现linux/keyboard.rs通过 XIM/XICXOpenIM、XCreateIC与 XKeysym 查询把键码翻译为字符。README 给出的三条重要 Caveat布局相关它返回的是当前布局的解释结果。如果你的应用需要支持布局切换不要使用它Linux 上死键机制未实现只实现了 Shift 和死键Windows 下Altunicode 码的输入方式不生效。全局事件拦截unstable_grab 与 grablisten只能看grab可以拦。安装时启用unstable_grabfeature 后grab会钩入全局输入设备事件流在事件送达应用/窗口管理器之前拦截它们。回调中返回None表示吞掉事件返回Some(event)表示放行当前不支持修改事件no modification of the event possible (yet)。#[cfg(feature unstable_grab)] use rdev::{grab, Event, EventType, Key}; #[cfg(feature unstable_grab)] let callback |event: Event| - OptionEvent { if let EventType::KeyPress(Key::CapsLock) event.event_type { println!(Consuming and cancelling CapsLock); None // CapsLock 现在被彻底禁用 } else { Some(event) } }; // 这会阻塞。 #[cfg(feature unstable_grab)] if let Err(error) grab(callback) { println!(Error: {:?}, error) }命名说明unstable特指grab 的 API 不稳定、未来可能变化并非指功能不稳定。仓库 Cargo.toml 中该 feature 默认处于注释关闭状态# unstable_grab [...]需要显式启用。grab 的平台差异与权限要求平台机制与权限要求macOS基于CGEventTap位置Session模式Default回调返回None时把事件类型置为Null实现吞掉见 macos/grab.rs。运行进程需为父进程并拥有辅助功能权限权限缺失时grab会直接返回EventTapError至少在 macOS 10.15 上如此其他版本可能类似Linux基于 X11 的XGrabKeyboard配合 mio 轮询 X 连接 fd、独立控制线程见 linux/grab.rs。注意这与 README 提到的 evdev 方案不同——当前仓库的 Linux grab 实现实际走 X11 抓取需要 X 会话Wayland 下不可用并依赖KEYBOARD全局状态与mio事件循环Windows使用低级钩子见 windows/grab.rs并配套提供exit_grab、is_grabbed等控制函数导出见 lib.rsREADME 针对 grab 的补充说明源自上游文档供参考macOS 权限缺失时grab会失败并返回EventTapErrorLinux 传统上要求进程以 root 运行不推荐或属于input组推荐部分发行版该组名为plugdev两者都可能存在时建议都加入。仓库 Linux 实现还导出了enable_grab/disable_grab/start_grab_listen/exit_grab_listen/is_grabbed等细粒度控制见 linux/mod.rs。GrabError枚举rdev.rs覆盖了各平台失败场景包括EventTapError、MissingDisplayError、KeyHookError(u32)、ExitGrabError(String)等。序列化支持serialize featurelisten和grab返回的Event数据可通过 Serde 序列化/反序列化前提是启用serializefeature# Cargo.toml 中启用 rdev { version 0.5.0-2, features [serialize] }源码中Event、EventType、Key、Button等类型都通过#[cfg_attr(feature serialize, derive(Serialize, Deserialize))]条件派生见 rdev.rsfeature 定义见 Cargo.tomlserialize [serde]。这使得事件可以跨进程传输、落盘回放或用于自动化测试脚本。键码映射体系跨平台转换与 RawKey为支撑混合各 OS 事件rdev 内置了多套键码映射表keycodes 模块linux.rs/macos.rs含virtual_keycodes.rs/windows.rs/android.rs/usb_hid.rs/chrome.rs通过codes_conv.rs提供平台间转换函数如linux_code_to_win_scancode、win_scancode_to_macos_code、macos_code_to_linux_code、usb_hid_code_to_win_scancode等codes_conv.rs用于在 Linux scancode、Windows 虚拟键码/扫描码、macOS 虚拟键码、Android 键码与 USB HID 键码之间互转。此外Key枚举还包含RawKey(RawKey)变体RawKey可携带ScanCode、WinVirtualKeycode、LinuxXorgKeycode、LinuxConsoleKeycode、MacVirtualKeycode等原始码rdev.rs允许直接按平台原生键码收发事件。在 keyviz 中的实际落地一个完整的 listen 应用示例最后回到 keyviz 的实战用法展示如何在 Rust 桌面应用中把 rdev 用于实时输入可视化。核心逻辑在 src-tauri/src/app/event.rsuse std::{sync::Mutex, thread}; use rdev::{listen, Button, EventType}; pub fn start_listener(app_handle: AppHandle, toggle_menu_item: MenuItemWry) { thread::spawn(move || { if let Err(err) listen(move |event| { let state app_handle.state::MutexAppState(); let mut app_state state.lock().unwrap(); // 1. 跟踪按下/释放的键集合 if let EventType::KeyPress(key) event.event_type { let key_name format!({:?}, key); if key_name.contains(() { return; } // 过滤 RawKey(..)/Unknown(..) if app_state.pressed_keys.contains(key_name) { return; } // 忽略重复 app_state.pressed_keys.push(key_name); // 2. 检测切换快捷键 if app_state.toggle_shortcut app_state.pressed_keys { app_state.toggle_listener(app_handle, toggle_menu_item); } } else if let EventType::KeyRelease(key) event.event_type { // 从 pressed_keys 中移除 } // 3. 监听关闭时不再发射 if !app_state.listening { return; } // 4. 转换并发射给前端 let input_event match event.event_type { EventType::KeyPress(key) Some(InputEvent::KeyEvent { pressed: true, name: format!({:?}, key) }), EventType::KeyRelease(key) Some(InputEvent::KeyEvent { pressed: false, name: format!({:?}, key) }), EventType::ButtonPress(button) Some(InputEvent::MouseButtonEvent { pressed: true, button: map_mouse_button(button) }), EventType::ButtonRelease(button) Some(InputEvent::MouseButtonEvent { pressed: false, button: map_mouse_button(button) }), EventType::MouseMove { x, y } { // 物理坐标 → 逻辑坐标减去显示器偏移 let (logical_x, logical_y) (x - offset_x, y - offset_y); Some(InputEvent::MouseMoveEvent { x: logical_x, y: logical_y }) } EventType::Wheel { delta_x, delta_y } { Some(InputEvent::MouseWheelEvent { delta_x, delta_y }) } }; app_handle.emit(input-event, input_event).unwrap(); }) { eprintln!(rdev listen failed: {:?}, err); } }); }这段代码几乎用到了 README 的全部要点可作为参考模板线程化阻塞监听、用 EventType 做状态跟踪、过滤不可识别键、坐标换算、错误兜底打印。小结与工程建议平台差异是第一优先级的功课Linux 的listen只支持 X11macOS 必须授权辅助功能且权限缺失时静默失败Windows 使用低级钩子。生产环境需在启动时检测ListenError/GrabError并给用户明确提示。区分 EventType 与 Event.name跨布局场景用EventType判断物理键用name时先做空值与不可打印字符的健全性检查。模拟事件要有节奏每个事件之间留 20ms 左右的延迟且不要模拟未映射的Key变体。grab 属于 unstable APIAPI 签名可能随版本演进且权限要求macOS 辅助功能、Linux 组权限比listen更严格拦截功能请谨慎依赖。如需深入源码建议按以下顺序阅读rdev.rs核心类型→ lib.rs公开 API 与平台重导出→ linux/listen.rs、macos/listen.rs、windows/listen.rs平台监听实现→ codes_conv.rs跨平台键码转换→ src-tauri/src/app/event.rskeyviz 的实际集成范例。参考文件索引本文全部技术要点均可追溯到仓库源码rdev/README.md、Cargo.tomlfeature/依赖、rdev.rs核心类型与错误枚举、lib.rs公开 API、src-tauri/src/app/event.rskeyviz 集成示例。赞分享桌面应用交互助手【免费下载链接】keyvizKeyviz is a free and open-source tool to visualize your keystrokes ⌨️ and ️ mouse actions in real-time.项目地址https://gitcode.com/gh_mirrors/ke/keyviz点击查看免费下载相关推荐Vibora 事件与 Hooks监听器机制完全指南从全局响应头注入到请求拦截Vibora 事件与 Hooks监听器机制完全指南从全局响应头注入到请求拦截 Vibora 是一个异步、基于 asyncio 的 Python Web 框后端如何使用Python keyboard库轻松实现全局键盘事件监听与模拟的完整指南如何使用Python keyboard库轻松实现全局键盘事件监听与模拟的完整指南 keyboard 是一款强大的Python库能够帮助开发者轻松实现全局键盘react-router路由事件监听路由变化的回调机制react router路由事件监听路由变化的回调机制 你是否曾在开发React应用时遇到这样的困扰用户在表单填写到一半时意外跳转页面导致数据丢失或者需要前端路由上一篇Figma导入Unity实用指南3步用UnityFigmaBridge做出可交互原型下一篇显卡驱动清理终极指南一次性彻底卸载NVIDIA/AMD/Intel驱动创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →