Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect keydown?

I would like to detect a keydown event in Rust and then check if a combination of keys is pressed, in order to do further actions based on that. So basically support keyboard shortcuts in my Rust application.

I've looked at some crates for example ncurses but they did not match my requirements...

like image 820
frankenapps Avatar asked Feb 08 '20 19:02

frankenapps


People also ask

How do you check if a key is pressed JS?

In plain JavaScript, you can use the EventTarget. addEventListener() method to listen for keyup event. When it occurs, check the keyCode 's value to see if an Enter key is pressed.

What does Keydown mean?

The keydown event is fired when a key is pressed. Unlike the keypress event, the keydown event is fired for all keys, regardless of whether they produce a character value. The keydown and keyup events provide a code indicating which key is pressed, while keypress indicates which character was entered.


2 Answers

Best solution for ANSI terminals (Linux, macOS)

If you don't need support for Windows then the best is termion.

It's a library for manipulating the terminal. In which you can detect key events and even keyboard shortcuts. And it's also really lightweight! Only 22.78 kB (as of version 1.5.5).

Here's a quick program I put together to showcase few shortcuts.

Add this code to main.rs, add termion = "1.5.5" to Cargo.toml and start it with 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();
    }
}

Cross Platform Solution

If you need to support Windows and all other platforms, then you can use crossterm. It's a pretty decent library and quite heavier than termion. It's 98.06 kB (as of version 0.16.0).

Here's the same program as above but written using crossterm.

Add this code to main.rs, add crossterm = "0.16.0" to Cargo.toml and try it with 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();
}

I'm not going to lie, this is a bit harder to read than the termion solution, but it does the same job. I have no prior experience with crossterm so this code may actually not be the best but it's decent.

Looking for a way to detect only key press without any modifier (Shift, Control, Alt)? Check this simplified code:

//-- code --

loop {
    //--code--

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

    //--code--
}

//--code--

The important part here is the use of KeyModifiers::NONE.

like image 71
Ejdrien Avatar answered Sep 28 '22 10:09

Ejdrien


You could use console as a simple cross-platform solution.

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,
            }
        }
    }
}

The snippet above shows a basic example for matching common movement characters for a platform.

like image 33
Evan Schwartzentruber Avatar answered Sep 28 '22 11:09

Evan Schwartzentruber