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硬件支持 分类中

Download history 881/week @ 2024-04-21 848/week @ 2024-04-28 839/week @ 2024-05-05 838/week @ 2024-05-12 773/week @ 2024-05-19 939/week @ 2024-05-26 1020/week @ 2024-06-02 771/week @ 2024-06-09 1189/week @ 2024-06-16 960/week @ 2024-06-23 805/week @ 2024-06-30 514/week @ 2024-07-07 684/week @ 2024-07-14 753/week @ 2024-07-21 807/week @ 2024-07-28 707/week @ 2024-08-04

3,010 每月下载量
24 个Crates (21 直接) 使用

MIT 许可证

125KB
3K SLoC

Crate API

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 一起工作。为了使此功能正常工作,运行 listengrab 循环的进程需要作为 root 运行(不推荐),或者作为属于 input 组的用户运行(推荐)。注意:在某些发行版中,evdev 访问的组名为 plugdev,在某些系统中,这两个组都存在。如果有疑问,如果存在,请将您的用户添加到这两个组中。

序列化

如果使用带有 serialize 功能安装此库,则 listengrab 函数返回的事件数据可以序列化和反序列化。

依赖关系

~0–0.8MB
~14K SLoC