检测按键按下?

19
我想在Rust中检测键盘按下事件,然后检查是否按下了一组键,以便根据此执行进一步的操作。所以基本上在我的Rust应用程序中支持快捷键。 我已经查看了一些crate,例如ncurses,但它们不符合我的要求...

3
键盘处理是系统特定的,它取决于您正在编写什么类型的程序:如果它是控制台或GUI,如果它是Windows、Linux、Mac、WebAssembly或其他任何系统,如果您使用工具包或本地绑定... - rodrigo
我想支持Windows,Linux和Mac。我目前使用Web视图作为我的GUI,并希望在系统级别上检测按键。 - frankenapps
2个回答

37

最佳的ANSI终端解决方案(Linux、macOS)

如果您不需要支持Windows,则最好选择termion

这是一个用于操作终端的库,您可以检测键盘事件,甚至键盘快捷键。而且它非常轻巧!仅有22.78 kB(截至版本1.5.5)。

以下是我编写的一个快速程序,展示了一些快捷键。

将此代码添加到main.rs中,将termion = "1.5.5"添加到Cargo.toml中,并使用cargo run启动它!

use std::io::{stdin, stdout, Write};
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;

fn main() {
    let stdin = stdin();
    //setting up stdout and going into raw mode
    let mut stdout = stdout().into_raw_mode().unwrap();
    //printing welcoming message, clearing the screen and going to left top corner with the cursor
    write!(stdout, r#"{}{}ctrl + q to exit, ctrl + h to print "Hello world!", alt + t to print "termion is cool""#, termion::cursor::Goto(1, 1), termion::clear::All)
            .unwrap();
    stdout.flush().unwrap();

    //detecting keydown events
    for c in stdin.keys() {
        //clearing the screen and going to top left corner
        write!(
            stdout,
            "{}{}",
            termion::cursor::Goto(1, 1),
            termion::clear::All
        )
        .unwrap();

        //i reckon this speaks for itself
        match c.unwrap() {
            Key::Ctrl('h') => println!("Hello world!"),
            Key::Ctrl('q') => break,
            Key::Alt('t') => println!("termion is cool"),
            _ => (),
        }

        stdout.flush().unwrap();
    }
}

跨平台解决方案

如果您需要支持 Windows 和其他所有平台,则可以使用 crossterm。它是一个相当不错的库,比 termion 更重。它的大小为 98.06 kB(截至版本 0.16.0)。

以下是使用 crossterm 编写的与上文相同的程序。

将此代码添加到 main.rs,将 crossterm = "0.16.0" 添加到 Cargo.toml 中,并使用 cargo run 运行它!

//importing in execute! macro
#[macro_use]
extern crate crossterm;

use crossterm::cursor;
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::style::Print;
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType};
use std::io::{stdout, Write};

fn main() {
    let mut stdout = stdout();
    //going into raw mode
    enable_raw_mode().unwrap();

    //clearing the screen, going to top left corner and printing welcoming message
    execute!(stdout, Clear(ClearType::All), cursor::MoveTo(0, 0), Print(r#"ctrl + q to exit, ctrl + h to print "Hello world", alt + t to print "crossterm is cool""#))
            .unwrap();

    //key detection
    loop {
        //going to top left corner
        execute!(stdout, cursor::MoveTo(0, 0)).unwrap();

        //matching the key
        match read().unwrap() {
            //i think this speaks for itself
            Event::Key(KeyEvent {
                code: KeyCode::Char('h'),
                modifiers: KeyModifiers::CONTROL,
                //clearing the screen and printing our message
            }) => execute!(stdout, Clear(ClearType::All), Print("Hello world!")).unwrap(),
            Event::Key(KeyEvent {
                code: KeyCode::Char('t'),
                modifiers: KeyModifiers::ALT,
            }) => execute!(stdout, Clear(ClearType::All), Print("crossterm is cool")).unwrap(),
            Event::Key(KeyEvent {
                code: KeyCode::Char('q'),
                modifiers: KeyModifiers::CONTROL,
            }) => break,
            _ => (),
        }
    }

    //disabling raw mode
    disable_raw_mode().unwrap();
}

说实话,这比termion的解决方案更难阅读,但它能完成同样的工作。我没有使用过crossterm,所以这段代码可能不是最好的,但还可以。

想找到一种检测按键时不带任何修改键(ShiftControlAlt)的方法吗?请查看下面的简化代码:

//-- code --

loop {
    //--code--

    match read().unwrap() {
        Event::Key(KeyEvent {
            code: KeyCode::Char('a'),
            modifiers: KeyModifiers::NONE,
        }) => //--code--
    }

    //--code--
}

//--code--

这里重要的部分是使用 KeyModifiers::NONE

1
@SephReed,请查看回答底部的编辑,应该有解决您问题的方法。 - Ejdrien
4
我对Rust非常陌生。在我的C/C++时代,我可以使用getch()getchar()和其他几个内置函数来实现这个目的。但是在Rust中,我必须使用第三方库才能实现这个功能?这是因为Rust仍处于起步阶段还是它就是这样构建的(具有非常少的功能)? - James Poulose
2
@JamesPoulose 你仍然可以在Rust中使用getchar(),像这样:extern "C" { fn getchar() -> std::os::raw::c_int; }并省略任何第三方库,但这显然是不安全的。 - frankenapps
1
不要使用 KeyModifiers::empty(),因为这两个 no_modifiers 实际上是两个不同的变量。第一个被声明但从未使用,而第二个则像通配符一样用于解构枚举。请改用 KeyModifiers::NONE - Cl00e9ment
1
请注意,这 不会 全局捕获按键。窗口/终端需要处于活动状态。 - Sancarn
显示剩余7条评论

7

你可以使用console作为简单的跨平台解决方案。

use console::Term;

fn main() {
    let stdout = Term::buffered_stdout();

    'game_loop: loop {
        if let Ok(character) = stdout.read_char() {
            match character {
                'w' => todo!("Up"),
                'a' => todo!("Left"),
                's' => todo!("Down"),
                'd' => todo!("Right"),
                _ => break 'game_loop,
            }
        }
    }
}

上面的代码片段展示了一个基本例子,用于匹配平台上常见的运动角色。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接