20个版本
0.5.3 | 2023年6月26日 |
---|---|
0.5.2 | 2022年11月11日 |
0.5.1 | 2021年4月21日 |
0.5.0 | 2021年3月8日 |
0.3.5 | 2020年3月30日 |
#25 在 硬件支持 分类中
3,010 每月下载量
被 24 个Crates (21 直接) 使用
125KB
3K SLoC
rdev
一个用于在macOS、Windows和Linux (x11) 上全局监听和发送键盘和鼠标事件的简单库
您还可以查看 Enigo,这是另一个帮助我编写这个库的crate。
这个crate到目前为止是我为了了解Rust生态系统的一个宠物项目。
监听全局事件
use rdev::{listen, Event};
// This will block.
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 => (),
}
}
操作系统注意事项
当使用 listen
函数时,以下注意事项适用
macOS
运行阻塞 listen
函数(循环)的过程需要是父进程(不允许在fork之前)。进程需要获得对Accessibility API的访问权限(即如果您在Terminal.app中运行进程,则Terminal.app需要在系统偏好设置 > 安全 & 隐私 > 隐私 > 访问 > 权限中添加)。如果进程未获得对Accessibility API的访问权限,macOS将静默忽略rdev的 listen
回调,并且不会触发它的事件。不会生成任何错误。
Linux
listen
函数使用X11 API,因此不会在Wayland或Linux内核虚拟控制台中工作
发送一些事件
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);
}
}
// Let ths OS catchup (at least 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,
});
主要结构体
事件
为了检测用户输入的内容,我们需要连接到操作系统级别的键盘状态管理(例如shift、CTRL等修饰键,以及可能存在的死键)。
EventType
对应于一个 物理 事件,对应于 QWERTY 布局的 Event
对应于实际接收到的事件,而 Event.name
反映了操作系统当时解释了哪个键,它将尊重布局。
/// When events arrive from the system we can add some information
/// time is when the event was received.
#[derive(Debug)]
pub struct Event {
pub time: SystemTime,
pub name: Option<String>,
pub event_type: EventType,
}
请注意,Event::name 可能是 None,也可能是 String::from(""),并且可能包含不可显示的 Unicode 字符。我们发送操作系统发送给我们的确切内容,因此在使用之前请做一些合理性检查。注意:在 Linux 上,死键功能尚未实现。
EventType
为了管理不同的操作系统,当前的 EventType 选择是混合搭配的,以涵盖所有可能的事件。有一个安全的机制可以检测任何事件,这是枚举中的 Unknown() 变体,它将包含一些操作系统特定的值。请注意,并非所有键都映射到操作系统代码,因此如果尝试发送未映射的键,模拟可能会失败。发送 Unknown() 变体始终有效(操作系统仍然可能拒绝它)。
/// In order to manage different OS, the current EventType choices is a mix&match
/// to account for all possible events.
#[derive(Debug)]
pub enum EventType {
/// The keys correspond to a standard qwerty layout, they don't correspond
/// To the actual letter a user would use, that requires some layout logic to be added.
KeyPress(Key),
KeyRelease(Key),
/// Some mouse will have more than 3 buttons, these are not defined, and different OS will
/// give different Unknown code.
ButtonPress(Button),
ButtonRelease(Button),
/// Values in pixels
MouseMove {
x: f64,
y: f64,
},
/// Note: On Linux, there is no actual delta the actual values are ignored for delta_x
/// and we only look at the sign of delta_y to simulate wheelup or wheeldown.
Wheel {
delta_x: i64,
delta_y: i64,
},
}
获取主屏幕尺寸
use rdev::{display_size};
let (w, h) = display_size().unwrap();
assert!(w > 0);
assert!(h > 0);
键盘状态
我们可以定义一个虚拟键盘,我们将使用它来检测哪种 EventType 触发某些 String。现在我们获取当前使用的布局!注意:这取决于布局。如果你的应用程序需要支持布局切换,请不要使用此功能!注意:在 Linux 上,死键机制尚未实现。注意:仅实现了 Shift 和死键,Windows 上的 Alt+Unicode 代码无法工作。
use rdev::{Keyboard, EventType, Key, KeyboardState};
let mut keyboard = Keyboard::new().unwrap();
let string = keyboard.add(&EventType::KeyPress(Key::KeyS));
// string == Some("s")
抓取全局事件。(需要 unstable_grab
功能)
使用带有 unstable_grab
功能的此库安装库添加了 grab
函数,该函数钩入全局输入设备事件流。通过向该函数提供回调,您可以在事件传递到应用程序/窗口管理器之前拦截所有键盘和鼠标事件。在回调中,返回 None 忽略事件,返回事件则允许其通过。在这里(目前)无法修改事件。
注意:此处使用“不稳定”一词特指 grab
API 不稳定且可能发生变化。
#[cfg(feature = "unstable_grab")]
use rdev::{grab, Event, EventType, Key};
#[cfg(feature = "unstable_grab")]
let callback = |event: Event| -> Option<Event> {
if let EventType::KeyPress(Key::CapsLock) = event.event_type {
println!("Consuming and cancelling CapsLock");
None // CapsLock is now effectively disabled
}
else { Some(event) }
};
// This will block.
#[cfg(feature = "unstable_grab")]
if let Err(error) = grab(callback) {
println!("Error: {:?}", error)
}
操作系统注意事项
当使用 listen
和/或 grab
函数时,以下注意事项适用
macOS
运行阻塞 grab
函数(循环)的进程需要是父进程(没有 fork)。进程需要获得访问 Accessibility API 的权限(即如果您在 Terminal.app 中运行进程,那么 Terminal.app 需要在系统偏好设置 > 安全 & 隐私 > 隐私 > 无障碍中添加)。如果进程未获得访问 Accessibility API 的权限,则 grab
调用将因 EventTapError 失败(至少在 macOS 10.15 中,可能还有其他版本)
Linux
grab
函数使用 evdev
库来拦截事件,因此它们将与 X11 和 Wayland 一起工作。为了使此功能正常工作,运行 listen
或 grab
循环的进程需要作为 root 运行(不推荐),或者作为属于 input
组的用户运行(推荐)。注意:在某些发行版中,evdev 访问的组名为 plugdev
,在某些系统中,这两个组都存在。如果有疑问,如果存在,请将您的用户添加到这两个组中。
序列化
如果使用带有 serialize
功能安装此库,则 listen
和 grab
函数返回的事件数据可以序列化和反序列化。
依赖关系
~0–0.8MB
~14K SLoC