Rust Termion detect key release - rust

Im trying to detect a key-release event with termion (crossterm 0.25.0 didnt work for me).
Im running ubuntu 20.04 and Termion 2.0.1
Ive been looking through the documentation and the crate code but i cant find any code on how to do that.
Ive tried detecting key releases via crossterm but that didnt work with the gnome-terminal.
what i require
I need to detect all currently pressed keys. Thats all i require.
What i want to do is trigger an action as long as a key is pressed.
I Want to write a game and its kinda inconvenient if i have to stop the player movement to shoot or do something else and start the player movement afterwards. It would be a lot nicer if i could keep the keys pressed.
What i tried
Ive tried crossterm
match key_event.kind {
KeyEventKind::Release => {
stdout.queue(MoveTo(128, 6)).unwrap();
print!("Released: {:?}", key_event.code);
},
KeyEventKind::Press => {
stdout.queue(MoveTo(128, 7)).unwrap();
print!("Press: {:?}", key_event.code);
},
KeyEventKind::Repeat => {
stdout.queue(MoveTo(128, 8)).unwrap();
print!("Repeat: {:?}", key_event.code);
},
}
that only showed key presses but not releases.
Ive tried Termion
match stdin.keys().enumerate().unwrap() {
Key::Esc => break,
Key::Char(c) => println!("{}", c),
Key::Alt(c) => println!("^{}", c),
Key::Ctrl(c) => println!("*{}", c),
Key::Left => println!("←"),
Key::Right => println!("→"),
Key::Up => println!("↑"),
Key::Down => println!("↓"),
Key::Backspace => println!("×"),
_ => {}
}
but it also only shows a single key press and not the release of the key.

Related

what's the easy way to make a simple window and catch key presses in Rust?

I am a blind Rust learner and i'm trying to make a simple audiogame engine for blind people but in rust.
Now I'm trying to make a simple window. A game for blind uses only audio and keyboard, so i have a question.
I'm trying to create an empty window and catch key presses to be able to make a self voiced user interface and game interaction using keyboard only, without mouce. what's the best solution for that?
I tryed to use winit and winit input helper, but maybe there is a better way to do that?
use winit::event::VirtualKeyCode;
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
use winit_input_helper::WinitInputHelper;
pub fn show_window() {
let mut input = WinitInputHelper::new();
let event_loop = EventLoop::new();
let _window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, _, control_flow| {
// Pass every event to the WindowInputHelper.
// It will return true when the last event has been processed and it is time to run your application logic.
if input.update(&event) {
// query keypresses this update
if input.key_pressed_os(VirtualKeyCode::A) {
println!("The 'A' key was pressed on the keyboard (OS repeating)");
}
if input.key_pressed(VirtualKeyCode::A) {
println!("The 'A' key was pressed on the keyboard");
}
if input.key_released(VirtualKeyCode::Q) || input.quit() {
*control_flow = ControlFlow::Exit;
return;
}
}
});
}

Reduce nested match statements with unwrap_or_else in Rust

I have a rust program that has multiple nested match statements as shown below.
match client.get(url).send() {
Ok(mut res) => {
match res.read_to_string(&mut s) {
Ok(m) => {
match get_auth(m) {
Ok(k) => k,
Err(_) => return Err(“a”);
}
},
Err(_) => {
return Err(“b”);
}
}
},
Err(_) => {
return Err(“c”);
},
};
All the variables k and m are of type String.I am looking for a way to make the code more readable by removing excessive nested match statements keeping the error handling intact since both the output and the error types are important for the problem.Is it possible to achieve this by unwrap_or_else?
The .map_err() utility converts a Result to have a new error type, leaving the success type alone. It accepts a closure that consumes the existing error value and returns the new one.
The ? operator will early-return the error in the Err case, and unwrap in the Ok case.
Combining these two allows you to express this same flow succinctly:
get_auth(
client.get(url).send().map_err(|_| "c")?
.read_to_string(&mut s).map_err(|_| "b")?
).map_err(|_| "a")?
(I suspect that you actually want to pass s to get_auth() but that's not what the code in your question does, so I'm choosing to represent the code you posted instead of imaginary code that I'm guessing about.)

How to switch screens in a rust tui app (termion + tui-rs)

I have a tui app where a user is presented with some choices through a list. Once they navigate to the choice they want and hit enter I'd like to take them to the "next" screen.
It's more complicated than just clearning existing text and printing new one because I also need to replace keybindings and basically start a new tui-rs loop. More below.
Code for Screen 1:
pub fn draw_screen() -> Result<(), Box<dyn Error>> {
// Terminal initialization
let stdout = io::stdout().into_raw_mode()?;
let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout);
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let events = Events::new();
loop {
terminal.draw(|f| {
// user shown a list they can navigate through using arrow keys
});
match events.next()? {
Event::Input(input) => match input {
Key::Char('q') => {
break;
}
Key::Char('\n') => {
// this is where I need to "send" them to a new screen
}
Key::Down => {
// my_list won't exist on next screen
my_list.items.next();
}
Key::Up => {
my_list.items.previous();
}
_ => {}
},
_ => {}
}
}
Ok(())
}
As can be seen the keybindings at the bottom are specific to this screen. Eg on the next screen there's not going to be a my_list and instead there might be a my_another_list or my_box or nothing at all.
So if all I did was clear the text, I'd still be left inside the same loop with the same keybindings - doesn't work.
What's the right way to initiate a new loop with fresh keybindings?

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...
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.
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.

Logstash-filter-rest sent field references incorrectly it always reference first field value it had referened

I recently use logstash-filter-rest, and configure it like below:
rest {
url => "http://example.com/api"
sprintf => true
method => "post"
params => {
"post_key" => "%{a_field_in_log}"
}
response_key => "my_key"
}
after this, logstash make a post request to my api, but something is wrong, the value of a_field_in_log
is identical in every request ( I check api access log, all of the value is the first field value sent to api ) it seems like there have caches for referenced field.
Does someone had encountered same problem, would thank you for your help!
As it happens, I'm the author of logstash-filter-rest and I'm glad to hear that someone is actually using it.
I was able to reproduce your issue. It was a bug and (good news) I fixed it. Thank you for reporting!
You can update now to the new version 0.1.5.
../logstash/bin/plugin update logstash-filter-rest
Test config:
input { stdin {} }
filter {
grok { match => [ "message", "Hello %{WORD:who}" ] }
rest {
url => "http://requestb.in/1f7s1sg1?test=%{who}"
method => "post"
sprintf => true
params => {
"hello" => "%{who}"
}
}
}
output { stdout{ codec => "rubydebug" } }
Test data:
Hello John
Hello Doe
Hello foo
Hello bar
Result:
http://requestb.in/1f7s1sg1?inspect (looks good)
Many thanks for contributing! I hope everything works as expected now.

Resources