#hook #mouse-input #keyboard #keyboard-input #mouse #keyboard-events #user-input

willhook

捕获所有键盘和鼠标输入,无论在活动窗口桌面上应用程序是否具有焦点

10个版本

0.6.3 2023年12月1日
0.6.2 2023年11月25日
0.6.1 2023年2月13日
0.5.0 2023年2月12日
0.2.3 2023年2月5日

#24 in Windows API

每月47次下载

MIT许可证

60KB
990

Willhook logo

Build Docs.rs issues

捕获所有键盘和鼠标输入,无论在活动窗口桌面上应用程序是否具有焦点

此包提供的内容

此仅限Windows的包提供了一种安全、正确的方法来监听键盘和鼠标事件,无论应用程序是否具有焦点。应用程序可以是命令行界面或带有窗口的。

在底层,此包利用Windows低级钩子。您可以在MSDN上了解更多关于这个主题的信息。[链接](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-hooks?redirectedfrom=MSDN)。此包主要是为了学习和我的个人项目而创建的,但我们将看看它将走向何方。

此包的设计目标是:正确、不易误用和不易出错。考虑到这一点,实现尽可能地避免任何恐慌。在最坏的情况下,它应该只是返回不完整的事件(例如,缺少键盘键码)。

此包不提供的内容

此包旨在为钩子提供“只读”访问。它不支持注入输入事件或修改它们。如果您正在寻找此类功能,可以尝试mki。与mki包相比,它也支持Linux,但不会清理低级钩子及其线程(通过取消钩子)以及它们(通过与它们连接)。这可能不会成为您的问题。向[willhook]添加“注入”和“修改”输入事件的可能性是存在的,尽管它不是首要任务。

警告:当前状态

目前它支持监听鼠标和键盘操作,详细信息请参阅[事件]模块。没有复杂的逻辑来解释事件 - 使用这个crate,您只需接收它们并对这些信息做您想做的事情。在这方面,我认为它功能完善。有一些集成测试应该覆盖所有现实场景。还有一些单元测试覆盖了一些不太现实的案例,例如Windows OS发送无效输入的情况。我认为这个crate的测试相当充分,但请注意,这个crate也是“年轻的”。注意:集成测试注入鼠标和键盘事件,而且它们需要按顺序运行(没有多线程)。有一些测试在GitHub Actions上无法通过,已被忽略。考虑到这一点,请使用以下命令运行测试:cargo test --tests -- --test-threads=1 --include-ignored强烈建议在使用此crate进行任何非业余项目之前,至少快速审查一下代码,至少在当前状态下是这样。

待办事项

  • 在我忘记所有怪癖之前,先记录不安全代码 :-)
  • 可能编写更多单元测试
  • 可能改进crate的模块化,而不会破坏API
  • 可能重新设计底层通道,以便它们在钩子中被丢弃(现在它们只是被排空)
  • 可能添加事件注入
  • 可能添加阻塞事件,如果可能的话
  • 可能添加操纵事件,如果可能的话

它是如何工作的

简而言之,有几个方便的函数可以请求钩子:[keyboard_hook]、[mouse_hook]和[wilhook]。当它们被调用时

  • 为每个低级钩子启动后台线程,在这些线程中
    • 注册鼠标和/或键盘低级钩子
    • 启动Windows消息队列并等待消息结束执行
  • 创建(如果尚未创建),用于将事件传递给“客户端”线程的通道
  • 返回底层低级钩子的句柄作为hook::Hook

hook::Hook超出作用域时,支持低级钩子的底层资源将被丢弃

  • 每个底层低级钩子都将从Windows内核中取消挂钩
  • 每个后台线程都将适当地加入
  • 所有挂起的事件都将被丢弃(后台通道将被排空)

hook::Hook处于活动状态(在作用域内/未被丢弃)时,可以通过hook::Hook::try_recv接收记录的event::InputEvent。它的工作方式类似于std::sync::mpsc::Receiver::try_recv

快速示例

use willhook::willhook;
use std::sync::{Arc, atomic::{Ordering, AtomicBool}};

fn main() {
    let is_running = Arc::new(AtomicBool::new(true));
    let set_running = is_running.clone();

    let h = willhook().unwrap();

    ctrlc::set_handler(move || {
        set_running.store(false, Ordering::SeqCst);
    })
    .expect("Error setting Ctrl-C handler");

    while is_running.load(Ordering::SeqCst) {
        if let Ok(ie) = h.try_recv() {
            match ie {
                willhook::InputEvent::Keyboard(ke) => println!("{:?}", ke),
                willhook::InputEvent::Mouse(me) => println!("{:?}", me),
                _ => println!("Input event: {:?}", ie),
            }
        } else {
            std::thread::yield_now();   
        }
    };
}

示例输出

PS ~ cargo run --example showcase
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target\debug\examples\showcase.exe`
KeyboardEvent { pressed: Down(Normal), key: Some(A), is_injected: Some(NotInjected) }
KeyboardEvent { pressed: Up(Normal), key: Some(A), is_injected: Some(NotInjected) }
KeyboardEvent { pressed: Down(Normal), key: Some(Q), is_injected: Some(NotInjected) }
KeyboardEvent { pressed: Up(Normal), key: Some(Q), is_injected: Some(NotInjected) }
KeyboardEvent { pressed: Down(System), key: Some(LeftAlt), is_injected: Some(NotInjected) }
KeyboardEvent { pressed: Down(System), key: Some(A), is_injected: Some(NotInjected) }
KeyboardEvent { pressed: Up(System), key: Some(A), is_injected: Some(NotInjected) }
MouseEvent { event: Press(MousePressEvent { pressed: Down, button: Left(SingleClick) }), is_injected: Some(NotInjected) }
MouseEvent { event: Press(MousePressEvent { pressed: Up, button: Left(SingleClick) }), is_injected: Some(NotInjected) }
MouseEvent { event: Move(MouseMoveEvent { point: Some(Point { x: 1010, y: 1188 }) }), is_injected: Some(NotInjected) }
MouseEvent { event: Move(MouseMoveEvent { point: Some(Point { x: 1013, y: 1188 }) }), is_injected: Some(NotInjected) }
MouseEvent { event: Wheel(MouseWheelEvent { wheel: Vertical, direction: Some(Backward) }), is_injected: Some(NotInjected) }
MouseEvent { event: Wheel(MouseWheelEvent { wheel: Vertical, direction: Some(Forward) }), is_injected: Some(NotInjected) }
MouseEvent { event: Move(MouseMoveEvent { point: Some(Point { x: 1068, y: 1189 }) }), is_injected: Some(NotInjected) }
MouseEvent { event: Move(MouseMoveEvent { point: Some(Point { x: 1067, y: 1189 }) }), is_injected: Some(NotInjected) }
MouseEvent { event: Press(MousePressEvent { pressed: Down, button: Middle(SingleClick) }), is_injected: Some(NotInjected) }

依赖关系

~26–400KB