Adds support for mouse and keyboard events. (#111)

Added support and expansion for:
- Keyboard Input
- Mouse Input
This commit is contained in:
Timon 2019-04-02 22:11:26 +02:00 committed by GitHub
commit 6ff20aadda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1783 additions and 644 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
target/
.idea/
.vscode/
**/*.rs.bk
Cargo.lock

View File

@ -1,6 +1,6 @@
[package]
name = "crossterm"
version = "0.7.0"
version = "0.8.0"
authors = ["T. Post"]
description = "An crossplatform terminal library for manipulating terminals."
repository = "https://github.com/TimonPost/crossterm"
@ -36,7 +36,7 @@ crossterm_screen = { optional = true, version = "0.1.0" }
crossterm_cursor = { optional = true, version = "0.1.0" }
crossterm_terminal = { optional = true, version = "0.1.0" }
crossterm_style = { optional = true, version = "0.2.0" }
crossterm_input = { optional = true, version = "0.1.0" }
crossterm_input = { optional = true, version = "0.2.0" }
crossterm_utils = { version = "0.1.0" }
[lib]

View File

@ -98,34 +98,34 @@ These are the features from this crate:
- Input
- Read character
- Read line
- Read async
- Read async until
- Wait for key event (terminal pause)
- Read key input events async / sync (ALT + Key, CTRL + Key, FN, Arrows, ESC, BackSpace, HOME, DELETE. INSERT, PAGEUP/DOWN, and more)
- Read mouse input events (Press, Release, Position, Button)
## Examples
These are some basic examples demonstrating how to use this crate. See [examples](https://github.com/TimonPost/crossterm/blob/master/examples/) for more.
### Crossterm Type | [see more](https://github.com/TimonPost/crossterm/blob/master/examples/crossterm.rs)
### Crossterm Type
This is a wrapper for all the modules crossterm provides like terminal, cursor, styling and input.
Good documentation could be found on the following places: [docs](https://docs.rs/crossterm/), [examples](https://github.com/TimonPost/crossterm/blob/master/examples/crossterm.rs).
```rust
// screen wheron the `Crossterm` methods will be executed.
// screen whereon the `Crossterm` methods will be executed.
let crossterm = Crossterm::new();
// get instance of the modules, whereafter you can use the methods the particulary module provides.
// get instance of the modules, whereafter you can use the methods the particularly module provides.
let color = crossterm.color();
let cursor = crossterm.cursor();
let terminal = crossterm.terminal();
// styling
println!("{}", crossterm.style("Black font on Green background color").with(Color::Black).on(Color::Green));
let input = crossterm.input();
```
### Styled Font | [see more](http://atcentra.com/crossterm/styling.html)
### Styled Font
This module provides the functionalities to style the terminal.
First include those types:
Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_style/), [book](http://atcentra.com/crossterm/styling.html), [examples](https://github.com/TimonPost/crossterm/tree/master/examples/key_events.rs)
_imports_
```rust
use crossterm::{Colored, Color, Colorize, Styler, Attribute};
```
@ -168,9 +168,11 @@ println!("{} some colored text", Colored::Fg(Color::AnsiValue(10)));
```
### Cursor | [see more](https://github.com/TimonPost/crossterm/blob/master/examples/cursor.rs)
### Cursor
This module provides the functionalities to work with the terminal cursor.
Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_cursor/), [examples](https://github.com/TimonPost/crossterm/tree/master/examples/cursor.rs)
```rust
use crossterm::cursor;
@ -209,9 +211,11 @@ cursor.blink(true)
```
### Terminal | [see more](https://github.com/TimonPost/crossterm/blob/master/examples/terminal.rs)
### Terminal
This module provides the functionalities to work with the terminal in general.
Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_terminal/), [examples](https://github.com/TimonPost/crossterm/tree/master/examples/terminal.rs).
```rust
use crossterm::{terminal,ClearType};
@ -246,8 +250,67 @@ terminal.exit();
terminal.write("Some text\n Some text on new line");
```
### Input Reading
This module provides the functionalities to read user input events.
Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_input/), [book](http://atcentra.com/crossterm/input.html), [examples](https://github.com/TimonPost/crossterm/tree/master/examples/key_events.rs)
_available imports_
```rust
use crossterm_input::{
input, InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput, AsyncReader, SyncReader, Screen
};
```
_Simple Readings_
```rust
let mut input = input();
match input.read_char() {
Ok(s) => println!("char typed: {}", s),
Err(e) => println!("char error : {}", e),
}
match input.read_line() {
Ok(s) => println!("string typed: {}", s),
Err(e) => println!("error: {}", e),
}
```
_Read input events synchronously or asynchronously._
```rust
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let mut input = input();
// either read the input synchronously
let stdin = input.read_sync();
// or asynchronously
let stdin = input.read_async();
if let Some(key_event) = stdin.next() {
match key_event {
InputEvent::Keyboard(event: KeyEvent) => match event { /* check key event */ }
InputEvent::Mouse(event: MouseEvent) => match event { /* check mouse event */ }
}
}
```
_Enable mouse input events._
```rust
let input = input();
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
```
### Alternate and Raw Screen
These concepts are a little more complex, please checkout the [book](http://atcentra.com/crossterm/screen.html) topics about these subjects.
These concepts are a little more complex and would take over the README, please checkout the [docs](https://docs.rs/crossterm_screen/), [book](http://atcentra.com/crossterm/screen.html), and [examples](https://github.com/TimonPost/crossterm/tree/master/examples).
## Tested terminals
@ -269,12 +332,6 @@ This library is average stable now but I don't expect it to not to change that m
If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade.
## Todo
I still have some things in mind to implement.
- Handling mouse events
I want to be able to do something based on the clicks the user has done with its mouse.
- Handling key events
I want to be able to read key combination inputs.
- Tests
Find a way to test: color, alternate screen, rawscreen

View File

@ -13,7 +13,7 @@ edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.5", features = ["wincon","winnt","minwindef"] }
crossterm_winapi = "0.1.1"
crossterm_winapi = "0.1.2"
[dependencies]
crossterm_utils = "0.1.0"

View File

@ -1,5 +1,5 @@
# Crossterm Cursor | cross-platform cursor movement.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
[s1]: https://img.shields.io/crates/v/crossterm_cursor.svg
[l1]: https://crates.io/crates/crossterm_cursor
@ -13,8 +13,7 @@
[s3]: https://docs.rs/crossterm_cursor/badge.svg
[l3]: https://docs.rs/crossterm_cursor/
[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_cursor?category=code
[s7]: https://travis-ci.org/TimonPost/crossterm_cursor.svg?branch=master
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
This crate allows you to move the terminal cursor cross-platform.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
@ -27,7 +26,7 @@ Other sub-crates are:
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Input](https://crates.io/crates/crossterm_input)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.html)
When you want to use other modules as well you might want to use crossterm with [feature flags](http://atcentra.com/crossterm/feature_flags.html).
## Table of contents:
- [Getting started](#getting-started)

View File

@ -1,30 +1,31 @@
[package]
name = "crossterm_input"
version = "0.1.0"
version = "0.2.0"
authors = ["T. Post"]
description = "A cross-platform library for reading userinput."
repository = "https://github.com/TimonPost/crossterm"
documentation = "https://docs.rs/crossterm_input/"
license = "MIT"
keywords = ["input", "keys", "crossterm", "crossplatform", "terminal"]
keywords = ["input", "keys", "crossterm", "events", "terminal"]
exclude = ["target", "Cargo.lock"]
readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.5", features = ["winnt"] }
crossterm_winapi = "0.1.1"
winapi = { version = "0.3.5", features = ["winnt", "winuser"] }
crossterm_winapi = "0.1.2"
[target.'cfg(unix)'.dependencies]
libc = "0.2.43"
[dependencies]
crossterm_utils = "0.1.0"
crossterm_screen = "0.1.0"
[[example]]
name = "input"
path = "examples/input.rs"
[[example]]
name = "async_input"
path = "examples/async_input.rs"
name = "key_events"
path = "examples/key_events.rs"

View File

@ -1,5 +1,5 @@
# Crossterm Input | cross-platform input reading .
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
[s1]: https://img.shields.io/crates/v/crossterm_input.svg
[l1]: https://crates.io/crates/crossterm_input
@ -13,13 +13,12 @@
[s3]: https://docs.rs/crossterm_input/badge.svg
[l3]: https://docs.rs/crossterm_input/
[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_input?category=code
[s7]: https://travis-ci.org/TimonPost/crossterm_input.svg?branch=master
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
This crate allows you to read the user input cross-platform.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
This crate is a sub-crate of [crossterm](https://crates.io/crates/crossterm) to read the user input, and can be use individually.
This crate is a sub-crate of [crossterm](https://crates.io/crates/crossterm) to read the user input and can be used individually.
Other sub-crates are:
- [Crossterm Style](https://crates.io/crates/crossterm_style)
@ -27,7 +26,7 @@ Other sub-crates are:
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.html)
When you want to use other modules as well you might want to use crossterm with [feature flags](http://atcentra.com/crossterm/feature_flags.html).
## Table of contents:
- [Getting started](#getting-started)
@ -42,13 +41,13 @@ When you want to use other modules as well you might want to use crossterm with
## Getting Started
This documentation is only for `crossterm_input` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate.
This documentation is only for `crossterm_input` version `0.2` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_input/examples) folders with detailed examples for all functionalities of this crate.
Add the `crossterm_input` package to your `Cargo.toml` file.
```
[dependencies]
`crossterm_input` = "0.1"
`crossterm_input` = "0.2"
```
And import the `crossterm_input` modules you want to use.
@ -72,24 +71,26 @@ These are the features of this crate:
- Cross-platform
- Everything is multithreaded (Send, Sync)
- Detailed documentation on every item
- Very few dependenties.
- Input
- Read character
- Read line
- Read async
- Read async until
- Wait for key event (terminal pause)
Planned features:
- Read mouse events
- Read special keys events
- Read key input events async / sync (ALT + Key, CTRL + Key, FN, Arrows, ESC, BackSpace, HOME, DELETE. INSERT, PAGEUP/DOWN, and more)
- Read mouse input events (Press, Release, Position, Button)
## Examples
Check out the [examples](/examples/) for more information about how to use this crate.
The examples folder has more complete and verbose examples, please have a look at that as well.
Good documentation could be found in the [docs][l3] as well in the [book](http://atcentra.com/crossterm/input.html).
_available imports_
```rust
use crossterm_input::input;
use crossterm_input::{
input, InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput, AsyncReader, SyncReader, Screen
};
```
_Simple Readings_
```rust
let mut input = input();
match input.read_char() {
@ -101,8 +102,40 @@ let mut input = input();
Ok(s) => println!("string typed: {}", s),
Err(e) => println!("error: {}", e),
}
```
_Read input events synchronously or asynchronously._
```rust
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let mut input = input();
// either read the input synchronously
let stdin = input.read_sync();
// or asynchronously
let stdin = input.read_async();
if let Some(key_event) = stdin.next() {
match key_event {
InputEvent::Keyboard(event: KeyEvent) => matche vent { /* check key event */ }
InputEvent::Mouse(event: MouseEvent) => match event { /* check mouse event */ }
}
}
```
_Enable mouse input events._
```rust
let input = input();
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
```
## Tested terminals
- Windows Powershell

View File

@ -1,131 +0,0 @@
extern crate crossterm_input;
use self::crossterm_input::input;
use std::io::{stdout, Read, Write};
use std::time::Duration;
use std::{thread, time};
/// this will capture the input until the given key.
/// TODO: make sure terminal is in raw mode before this function is called.
/// for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag.
pub fn read_async_until() {
// TODO: make sure terminal is in raw mode.
// for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag.
// init some modules we use for this demo
let input = input();
let mut stdin = input.read_until_async(b'\r').bytes();
for _i in 0..100 {
let a = stdin.next();
println!("pressed key: {:?}", a);
if let Some(Ok(b'\r')) = a {
println!("The enter key is hit and program is not listening to input anymore.");
break;
}
if let Some(Ok(b'x')) = a {
println!("The key: x was pressed and program is terminated.");
break;
}
thread::sleep(time::Duration::from_millis(100));
}
}
/// this will read pressed characters async until `x` is typed.
/// TODO: make sure terminal is in raw mode before this function is called.
/// for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag.
pub fn read_async() {
let input = input();
let mut stdin = input.read_async().bytes();
for _i in 0..100 {
let a = stdin.next();
println!("pressed key: {:?}", a);
if let Some(Ok(b'x')) = a {
println!("The key: `x` was pressed and program is terminated.");
break;
}
thread::sleep(time::Duration::from_millis(50));
}
}
/// TODO: make sure terminal is in raw mode before this function is called.
/// for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag.
pub fn read_async_demo() {
// init some modules we use for this demo
let input = input();
// this will setup the async reading.
let mut stdin = input.read_async().bytes();
// clear terminal and reset the cursor.
terminal.clear(ClearType::All);
cursor.goto(1, 1);
// loop until the enter key (\r) is pressed.
loop {
terminal.clear(ClearType::All);
cursor.goto(1, 1);
// get the next pressed key
let pressed_key = stdin.next();
terminal.write(format!("{:?} <- Character pressed", pressed_key));
// check if pressed key is enter (\r)
if let Some(Ok(b'\r')) = pressed_key {
break;
}
// wait 200 ms and reset cursor write
thread::sleep(Duration::from_millis(200));
}
}
/// TODO: make sure terminal is in raw mode before this function is called.
/// for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag.
pub fn async_reading_on_alternate_screen() {
let screen = Screen::new(false);
// switch to alternate screen
if let Ok(alternate) = screen.enable_alternate_modes(true) {
let crossterm = Crossterm::from_screen(&alternate.screen);
// init some modules we use for this demo
let input = crossterm.input();
let terminal = crossterm.terminal();
let mut cursor = crossterm.cursor();
// this will setup the async reading.
let mut stdin = input.read_async().bytes();
// loop until the enter key (\r) is pressed.
loop {
terminal.clear(ClearType::All);
cursor.goto(1, 1);
// get the next pressed key
let pressed_key = stdin.next();
terminal.write(format!("{:?} <- Character pressed", pressed_key));
// check if pressed key is enter (\r)
if let Some(Ok(b'\r')) = pressed_key {
break;
}
// wait 200 ms and reset cursor write
thread::sleep(Duration::from_millis(200));
}
}
}
fn main() {}

View File

@ -1,6 +1,6 @@
extern crate crossterm_input;
use self::crossterm_input::{input, KeyEvent, Screen, TerminalInput};
use self::crossterm_input::input;
pub fn read_char() {
let input = input();
@ -20,10 +20,10 @@ pub fn read_line() {
}
}
pub fn pause_terminal() {
println!("Press 'x' to quit...");
let terminal_input = TerminalInput::new();
terminal_input.wait_until(KeyEvent::OnKeyPress(b'x'));
}
fn main() {
// un-comment below and run with
// `cargo run --example input`:
fn main() {}
read_char();
read_line();
}

View File

@ -0,0 +1,180 @@
extern crate crossterm_input;
extern crate crossterm_screen;
extern crate crossterm_utils;
use crossterm_input::{InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput};
use crossterm_screen::Screen;
use std::{thread, time::Duration};
fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool {
match key_event {
InputEvent::Keyboard(k) => match k {
KeyEvent::Char(c) => match c {
'q' => {
screen.stdout.write_str("The 'q' key is hit and the program is not listening to input anymore.\n\n").unwrap();
return true;
}
_ => {
screen
.stdout
.write_string(format!("'{}' pressed\n\n", c))
.unwrap();
}
},
KeyEvent::Alt(c) => {
screen
.stdout
.write_string(format!("ALT +'{}' pressed\n\n", c))
.unwrap();
}
KeyEvent::Ctrl(c) => {
screen
.stdout
.write_string(format!("CTRL +'{}' Pressed\n\n", c))
.unwrap();
}
KeyEvent::Esc => {
screen
.stdout
.write_string(format!("ESC pressed\n\n"))
.unwrap();
}
KeyEvent::F(number) => {
screen
.stdout
.write_string(format!("F{} key pressed\n\n", number))
.unwrap();
}
KeyEvent::PageUp => {
screen.stdout.write_string(format!("Page Up\n\n")).unwrap();
}
KeyEvent::PageDown => {
screen
.stdout
.write_string(format!("Page Down\n\n"))
.unwrap();
}
KeyEvent::Delete => {
screen.stdout.write_string(format!("Delete\n\n")).unwrap();
}
_ => {
screen
.stdout
.write_string(format!("OTHER: {:?}\n\n", k))
.unwrap();
()
}
},
InputEvent::Mouse(m) => match m {
MouseEvent::Press(b, x, y) => match b {
MouseButton::Left => {
screen
.stdout
.write_string(format!("left mouse press @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::Right => {
screen
.stdout
.write_string(format!("right mouse press @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::Middle => {
screen
.stdout
.write_string(format!("mid mouse press @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::WheelUp => {
screen
.stdout
.write_string(format!("wheel up @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::WheelDown => {
screen
.stdout
.write_string(format!("wheel down @ {}, {}\n\n", x, y))
.unwrap();
}
},
MouseEvent::Release(x, y) => {
screen
.stdout
.write_string(format!("mouse released @ {}, {}\n\n", x, y))
.unwrap();
}
MouseEvent::Hold(x, y) => {
screen
.stdout
.write_string(format!("dragging @ {}, {}\n\n", x, y))
.unwrap();
}
_ => {
screen.stdout.write_str("Unknown mouse event").unwrap();
}
},
_ => println!("Unknown!"),
}
return false;
}
pub fn read_asynchronously() {
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let input = TerminalInput::from_output(&screen.stdout);
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
let mut async_stdin = input.read_async();
loop {
if let Some(key_event) = async_stdin.next() {
if process_input_event(key_event, &screen) {
break;
}
}
thread::sleep(Duration::from_millis(50));
}
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
} // <=== background reader will be disposed when dropped.
pub fn read_synchronously() {
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let input = TerminalInput::from_output(&screen.stdout);
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
let mut sync_stdin = input.read_sync();
loop {
let event = sync_stdin.next();
if let Some(key_event) = event {
if process_input_event(key_event, &screen) {
break;
}
}
}
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
}
fn main() {
// un-comment below and run with
// `cargo run --example key_events`:
// read_synchronously();
read_asynchronously();
}

View File

@ -2,7 +2,9 @@
//! Like reading a line, reading a character and reading asynchronously.
use super::*;
use std::{thread, time::Duration};
use std::io::{Error, ErrorKind};
use std::iter::Iterator;
use std::str;
use crossterm_utils::TerminalOutput;
@ -126,131 +128,77 @@ impl<'stdout> TerminalInput<'stdout> {
self.terminal_input.read_char(&self.stdout)
}
/// Read the input asynchronously from the user.
/// Read the input asynchronously, which means that input events are gathered on the background and will be queued for you to read.
///
/// # Remarks
/// - This call will not block the current thread.
/// A thread will be fired to read input, on unix systems from TTY and on windows systems with '_getwch' and '_getwche'.
/// - Requires 'raw screen to be enabled'.
/// Not sure what this is, please checkout the 'crossterm_screen' crate.
///
/// ```rust
/// // we need to enable raw mode otherwise the characters will be outputted by default before we are able to read them.
/// let screen = Screen::new(true);
/// let input = crossterm::input::from_screen(&screen);
///
/// let mut stdin = input.read_async().bytes();
///
/// for i in 0..100 {
///
/// // Get the next character typed. This is None if nothing is pressed. And Some(Ok(u8 value of character))
/// let a = stdin.next();
///
/// println!("pressed key: {:?}", a);
///
/// if let Some(Ok(b'x')) = a {
/// println!("The key: `x` was pressed and program is terminated.");
/// break;
/// }
/// // simulate some timeout so that we can see the character on the screen.
/// thread::sleep(time::Duration::from_millis(50));
/// }
///
/// ```
pub fn read_async(&self) -> AsyncReader {
self.terminal_input.read_async(&self.stdout)
}
/// Read the input asynchronously until a certain character is hit.
/// If you want a blocking, or less resource consuming read to happen use `read_sync()`, this will leave a way all the thread and queueing and will be a blocking read.
///
/// This is the same as `read_async()` but stops reading when a certain character is hit.
///
/// # Remarks
/// - This call will not block the current thread.
/// A thread will be fired to read input, on unix systems from TTY and on windows systems with '_getwch' and '_getwche'.
/// - Readings won't be blocking calls.
/// A thread will be fired to read input, on unix systems from TTY and on windows WinApi
/// `ReadConsoleW` will be used.
/// - Input events read from the user will be queued on a MPSC-channel.
/// - The reading thread will be cleaned up when it drops.
/// - Requires 'raw screen to be enabled'.
/// Not sure what this is, please checkout the 'crossterm_screen' crate.
/// - Thread is automatically destroyed when the 'delimiter' is hit.
/// Not sure what this is? Please checkout the 'crossterm_screen' crate.
///
/// ```rust
/// // we need to enable raw mode otherwise the characters will be outputted by default before we are able to read them.
/// let screen = Screen::new(true);
///
/// // create an instance of `Crossterm` which will preform the actions on the raw screen.
/// let crossterm = Crossterm::from_screen(&screen);
/// let input = crossterm.input();
/// let terminal = crossterm.terminal();
/// let mut cursor = crossterm.cursor();
///
/// let mut stdin = input.read_until_async(b'\r').bytes();
///
/// for i in 0..100 {
/// terminal.clear(ClearType::All);
/// cursor.goto(1, 1);
/// let a = stdin.next();
///
/// println!("pressed key: {:?}", a);
///
/// if let Some(Ok(b'\r')) = a {
/// println!("The enter key is hit and program is not listening to input anymore.");
/// break;
/// }
///
/// if let Some(Ok(b'x')) = a {
/// println!("The key: x was pressed and program is terminated.");
/// break;
/// }
///
/// thread::sleep(time::Duration::from_millis(100));
/// }
/// ```
pub fn read_until_async(&self, delimiter: u8) -> AsyncReader {
self.terminal_input
.read_until_async(delimiter, &self.stdout)
/// # Examples
/// Please checkout the example folder in the repository.
pub fn read_async(&self) -> AsyncReader {
self.terminal_input.read_async()
}
/// This will prevent the current thread from continuing until the passed `KeyEvent` has happened.
/// Read the input asynchronously until a certain character is hit, which means that input events are gathered on the background and will be queued for you to read.
///
/// If you want a blocking, or less resource consuming read to happen use `read_sync()`, this will leave a way all the thread and queueing and will be a blocking read.
///
/// This is the same as `read_async()` but stops reading when a certain character is hit.
///
/// # Remarks
/// - Readings won't be blocking calls.
/// A thread will be fired to read input, on unix systems from TTY and on windows WinApi
/// `ReadConsoleW` will be used.
/// - Input events read from the user will be queued on a MPSC-channel.
/// - The reading thread will be cleaned up when it drops.
/// - Requires 'raw screen to be enabled'.
/// Not sure what this is? Please checkout the 'crossterm_screen' crate.
///
/// # Examples
/// Please checkout the example folder in the repository.
pub fn read_until_async(&self, delimiter: u8) -> AsyncReader {
self.terminal_input.read_until_async(delimiter)
}
/// Read the input synchronously from the user, which means that reading call wil be blocking ones.
/// It also uses less resources than the `AsyncReader` because background thread and queues are left away.
///
/// In case you don't want the reading to block your program you could consider `read_async`.
///
/// # Remark
/// - Requires 'raw screen to be enabled'.
/// Not sure what this is, please checkout the 'crossterm_screen' crate.
/// - Readings will be blocking calls.
///
/// ```
/// use crossterm::input::{TerminalInput, KeyEvent};
/// # Examples
/// Please checkout the example folder in the repository.
pub fn read_sync(&self) -> SyncReader {
self.terminal_input.read_sync()
}
/// Enable mouse events to be captured.
///
/// fn main() {
/// println!("Press 'x' to quit...");
/// TerminalInput::wait_until(KeyEvent::OnKeyPress(b'x'));
/// }
/// ```
pub fn wait_until(&self, key_event: KeyEvent) {
let mut stdin = self.read_async().bytes();
loop {
let pressed_key: Option<Result<u8, Error>> = stdin.next();
match pressed_key {
Some(Ok(value)) => match key_event {
KeyEvent::OnKeyPress(ascii_code) => {
if value == ascii_code {
break;
}
}
KeyEvent::OnEnter => {
if value == b'\r' {
break;
}
}
KeyEvent::OnAnyKeyPress => {
break;
}
},
_ => {}
/// When enabling mouse input you will be able to capture, mouse movements, pressed buttons and locations.
///
/// # Remark
/// - Mouse events will be send over the reader created with `read_async`, `read_async_until`, `read_sync`.
pub fn enable_mouse_mode(&self) -> io::Result<()> {
self.terminal_input.enable_mouse_mode(&self.stdout)
}
// some sleeping time so that we don't 'dos' our cpu.
thread::sleep(Duration::from_millis(10));
}
/// Disable mouse events to be captured.
///
/// When disabling mouse input you won't be able to capture, mouse movements, pressed buttons and locations anymore.
pub fn disable_mouse_mode(&self) -> io::Result<()> {
self.terminal_input.disable_mouse_mode(&self.stdout)
}
}
@ -258,3 +206,259 @@ impl<'stdout> TerminalInput<'stdout> {
pub fn input<'stdout>() -> TerminalInput<'stdout> {
TerminalInput::new()
}
/// Parse an Event from `item` and possibly subsequent bytes through `iter`.
pub(crate) fn parse_event<I>(item: u8, iter: &mut I) -> Result<InputEvent>
where
I: Iterator<Item = u8>,
{
let error = Error::new(ErrorKind::Other, "Could not parse an event");
let input_event = match item {
b'\x1B' => {
let a = iter.next();
// This is an escape character, leading a control sequence.
match a {
Some(b'O') => {
match iter.next() {
// F1-F4
Some(val @ b'P'...b'S') => {
InputEvent::Keyboard(KeyEvent::F(1 + val - b'P'))
}
_ => return Err(error),
}
}
Some(b'[') => {
// This is a CSI sequence.
parse_csi(iter)
}
Some(b'\x1B') => InputEvent::Keyboard(KeyEvent::Esc),
Some(c) => {
let ch = parse_utf8_char(c, iter);
InputEvent::Keyboard(KeyEvent::Alt(ch?))
}
None => InputEvent::Keyboard(KeyEvent::Esc),
}
}
b'\n' | b'\r' => InputEvent::Keyboard(KeyEvent::Char('\n')),
b'\t' => InputEvent::Keyboard(KeyEvent::Char('\t')),
b'\x7F' => InputEvent::Keyboard(KeyEvent::Backspace),
c @ b'\x01'...b'\x1A' => {
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char))
}
c @ b'\x1C'...b'\x1F' => {
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char))
}
b'\0' => InputEvent::Keyboard(KeyEvent::Null),
c => {
let ch = parse_utf8_char(c, iter);
InputEvent::Keyboard(KeyEvent::Char(ch?))
}
};
Ok(input_event)
}
/// Parses a CSI sequence, just after reading ^[
/// Returns Event::Unknown if an unrecognized sequence is found.
/// Most of this parsing code is been taken over from 'termion`.
fn parse_csi<I>(iter: &mut I) -> InputEvent
where
I: Iterator<Item = u8>,
{
match iter.next() {
Some(b'[') => match iter.next() {
// NOTE (@imdaveho): cannot find when this occurs;
// having another '[' after ESC[ not a likely scenario
Some(val @ b'A'...b'E') => InputEvent::Keyboard(KeyEvent::F(1 + val - b'A')),
_ => InputEvent::Unknown,
},
Some(b'D') => InputEvent::Keyboard(KeyEvent::Left),
Some(b'C') => InputEvent::Keyboard(KeyEvent::Right),
Some(b'A') => InputEvent::Keyboard(KeyEvent::Up),
Some(b'B') => InputEvent::Keyboard(KeyEvent::Down),
Some(b'H') => InputEvent::Keyboard(KeyEvent::Home),
Some(b'F') => InputEvent::Keyboard(KeyEvent::End),
Some(b'M') => {
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only).
// NOTE (@imdaveho): cannot find documentation on this
let mut next = || iter.next().unwrap();
let cb = next() as i8 - 32;
// (1, 1) are the coords for upper left.
let cx = next().saturating_sub(32) as u16;
let cy = next().saturating_sub(32) as u16;
InputEvent::Mouse(match cb & 0b11 {
0 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelUp, cx, cy)
} else {
MouseEvent::Press(MouseButton::Left, cx, cy)
}
}
1 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelDown, cx, cy)
} else {
MouseEvent::Press(MouseButton::Middle, cx, cy)
}
}
2 => MouseEvent::Press(MouseButton::Right, cx, cy),
3 => MouseEvent::Release(cx, cy),
_ => MouseEvent::Unknown,
})
}
Some(b'<') => {
// xterm mouse handling:
// ESC [ < Cb ; Cx ; Cy (;) (M or m)
let mut buf = Vec::new();
let mut c = iter.next().unwrap();
while match c {
b'm' | b'M' => false,
_ => true,
} {
buf.push(c);
c = iter.next().unwrap();
}
let str_buf = String::from_utf8(buf).unwrap();
let nums = &mut str_buf.split(';');
let cb = nums.next().unwrap().parse::<u16>().unwrap();
let cx = nums.next().unwrap().parse::<u16>().unwrap();
let cy = nums.next().unwrap().parse::<u16>().unwrap();
match cb {
0...2 | 64...65 => {
let button = match cb {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
64 => MouseButton::WheelUp,
65 => MouseButton::WheelDown,
_ => unreachable!(),
};
match c {
b'M' => InputEvent::Mouse(MouseEvent::Press(button, cx, cy)),
b'm' => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
}
}
32 => InputEvent::Mouse(MouseEvent::Hold(cx, cy)),
3 => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
}
}
Some(c @ b'0'...b'9') => {
// Numbered escape code.
let mut buf = Vec::new();
buf.push(c);
let mut character = iter.next().unwrap();
// The final byte of a CSI sequence can be in the range 64-126, so
// let's keep reading anything else.
while character < 64 || character > 126 {
buf.push(character);
character = iter.next().unwrap();
}
match character {
// rxvt mouse encoding:
// ESC [ Cb ; Cx ; Cy ; M
b'M' => {
let str_buf = String::from_utf8(buf).unwrap();
let nums: Vec<u16> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
let cb = nums[0];
let cx = nums[1];
let cy = nums[2];
let event = match cb {
32 => MouseEvent::Press(MouseButton::Left, cx, cy),
33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
34 => MouseEvent::Press(MouseButton::Right, cx, cy),
35 => MouseEvent::Release(cx, cy),
64 => MouseEvent::Hold(cx, cy),
96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
_ => MouseEvent::Unknown,
};
InputEvent::Mouse(event)
}
// Special key code.
b'~' => {
let str_buf = String::from_utf8(buf).unwrap();
// This CSI sequence can be a list of semicolon-separated numbers.
let nums: Vec<u8> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
if nums.is_empty() {
return InputEvent::Unknown;
}
// TODO: handle multiple values for key modifiers (ex: values [3, 2] means Shift+Delete)
if nums.len() > 1 {
return InputEvent::Unknown;
}
match nums[0] {
1 | 7 => InputEvent::Keyboard(KeyEvent::Home),
2 => InputEvent::Keyboard(KeyEvent::Insert),
3 => InputEvent::Keyboard(KeyEvent::Delete),
4 | 8 => InputEvent::Keyboard(KeyEvent::End),
5 => InputEvent::Keyboard(KeyEvent::PageUp),
6 => InputEvent::Keyboard(KeyEvent::PageDown),
v @ 11...15 => InputEvent::Keyboard(KeyEvent::F(v - 10)),
v @ 17...21 => InputEvent::Keyboard(KeyEvent::F(v - 11)),
v @ 23...24 => InputEvent::Keyboard(KeyEvent::F(v - 12)),
_ => InputEvent::Unknown,
}
}
_ => InputEvent::Unknown,
}
}
_ => InputEvent::Unknown,
}
}
/// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char.
fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char>
where
I: Iterator<Item = u8>,
{
let error = Err(Error::new(
ErrorKind::Other,
"Input character is not valid UTF-8",
));
if c.is_ascii() {
Ok(c as char)
} else {
let mut bytes = Vec::new();
bytes.push(c);
while let Some(next) = iter.next() {
bytes.push(next);
if let Ok(st) = str::from_utf8(&bytes) {
return Ok(st.chars().next().unwrap());
}
if bytes.len() >= 4 {
return error;
}
}
return error;
}
}
#[cfg(test)]
#[test]
fn test_parse_utf8() {
let st = "abcéŷ¤£€ù%323";
let ref mut bytes = st.bytes().map(|x| Ok(x));
let chars = st.chars();
for c in chars {
let b = bytes.next().unwrap().unwrap();
assert_eq!(c, parse_utf8_char(b, bytes).unwrap());
}
}

View File

@ -13,12 +13,16 @@ use self::unix_input::UnixInput;
#[cfg(target_os = "windows")]
use self::windows_input::WindowsInput;
use self::input::parse_event;
pub use self::input::{input, TerminalInput};
use std::io::{self, Error, ErrorKind, Read};
use std::io::{self, Result};
use std::sync::{mpsc, Arc};
use crossterm_utils::TerminalOutput;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, Sender};
use std::thread;
/// This trait defines the actions that can be preformed with the terminal input.
/// This trait can be implemented so that a concrete implementation of the ITerminalInput can fulfill
@ -32,52 +36,162 @@ trait ITerminalInput {
/// Read one character from the user input
fn read_char(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<char>;
/// Read the input asynchronously from the user.
fn read_async(&self, stdout: &Option<&Arc<TerminalOutput>>) -> AsyncReader;
fn read_async(&self) -> AsyncReader;
/// Read the input asynchronously until a certain character is hit.
fn read_until_async(&self, delimiter: u8, stdout: &Option<&Arc<TerminalOutput>>)
-> AsyncReader;
fn read_until_async(&self, delimiter: u8) -> AsyncReader;
/// Read the input synchronously from the user.
fn read_sync(&self) -> SyncReader;
fn enable_mouse_mode(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()>;
fn disable_mouse_mode(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()>;
}
/// This is a wrapper for reading from the input asynchronously.
/// This wrapper has a channel receiver that receives the input from the user whenever it typed something.
/// You only need to check whether there are new characters available.
pub struct AsyncReader {
recv: mpsc::Receiver<io::Result<u8>>,
/// Enum to specify which input event has occurred.
#[derive(Debug, PartialOrd, PartialEq)]
pub enum InputEvent {
/// A single key or a combination is pressed.
Keyboard(KeyEvent),
/// A mouse event occurred.
Mouse(MouseEvent),
/// A unsupported event has occurred.
Unsupported(Vec<u8>),
/// An unknown event has occurred.
Unknown,
}
/// This enum represents key events which could be caused by the user.
/// Enum to specify which mouse event has occurred.
#[derive(Debug, PartialOrd, PartialEq)]
pub enum MouseEvent {
/// A mouse press has occurred, this contains the pressed button and the position of the press.
Press(MouseButton, u16, u16),
/// A mouse button was released.
Release(u16, u16),
/// A mouse button was hold.
Hold(u16, u16),
/// An unknown mouse event has occurred.
Unknown,
}
/// Enum to define mouse buttons.
#[derive(Debug, PartialOrd, PartialEq)]
pub enum MouseButton {
/// Left mouse button
Left,
/// Right mouse button
Right,
/// Middle mouse button
Middle,
/// Scroll up
WheelUp,
/// Scroll down
WheelDown,
}
/// Enum with different key or key combinations.
#[derive(Debug, PartialOrd, PartialEq)]
pub enum KeyEvent {
/// Represents a specific key press.
OnKeyPress(u8),
/// Represents a key press from any key.
OnAnyKeyPress,
/// Represents a key press from enter.
OnEnter,
Backspace,
Left,
Right,
Up,
Down,
Home,
End,
PageUp,
PageDown,
Delete,
Insert,
F(u8),
Char(char),
Alt(char),
Ctrl(char),
Null,
Esc,
}
impl Read for AsyncReader {
/// Read from the byte stream.
/// This type allows you to read input synchronously, which means that reading call will be blocking ones.
///
/// This will never block, but try to drain the event queue until empty. If the total number of
/// bytes written is lower than the buffer's length, the event queue is empty or that the event
/// stream halted.
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut total = 0;
/// This type is an iterator, and could be used to iterate over input events.
///
/// If you don't want to block your calls use [AsyncReader](./LINK), which will read input on the background and queue it for you to read.
pub struct SyncReader;
loop {
if total >= buf.len() {
break;
/// This type allows you to read the input asynchronously which means that input events are gathered on the background and will be queued for you to read.
///
/// **[SyncReader](./LINK)**
/// If you want a blocking, or less resource consuming read to happen use `SyncReader`, this will leave a way all the thread and queueing and will be a blocking read.
///
/// This type is an iterator, and could be used to iterate over input events.
///
/// # Remarks
/// - Threads spawned will be disposed of as soon the `AsyncReader` goes out of scope.
/// - MPSC-channels are used to queue input events, this type implements an iterator of the rx side of the queue.
pub struct AsyncReader {
function: Box<Fn(&Sender<u8>, &Arc<AtomicBool>) + Send>,
event_rx: Receiver<u8>,
shutdown: Arc<AtomicBool>,
}
match self.recv.try_iter().next() {
Some(Ok(value)) => {
buf[total] = value;
total += 1;
}
_ => return Err(Error::new(ErrorKind::Other, "No characters pressed.")),
impl AsyncReader {
/// Construct a new instance of the `AsyncReader`.
/// The reading will immediately start when calling this function.
pub fn new(function: Box<Fn(&Sender<u8>, &Arc<AtomicBool>) + Send>) -> AsyncReader {
let shutdown_handle = Arc::new(AtomicBool::new(false));
let (event_tx, event_rx) = mpsc::channel();
let thread_shutdown = shutdown_handle.clone();
let function = function.function;
thread::spawn(move || loop {
function(&event_tx, &thread_shutdown);
});
AsyncReader {
function,
event_rx,
shutdown: shutdown_handle,
}
}
Ok(total)
/// Stop the input event reading.
///
/// You don't necessarily have to call this function because it will automatically be called when this reader goes out of scope.
///
/// # Remarks
/// - Background thread will be closed.
/// - This will consume the handle you won't be able to restart the reading with this handle, create a new `AsyncReader` instead.
pub fn stop_reading(&mut self) {
self.shutdown.store(true, Ordering::SeqCst);
}
}
impl Iterator for AsyncReader {
type Item = InputEvent;
/// Check if there are input events to read.
///
/// It will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read.
///
/// # Remark
/// - This is **not** a blocking call.
fn next(&mut self) -> Option<Self::Item> {
let mut iterator = self.event_rx.try_iter();
match iterator.next() {
Some(char_value) => {
if let Ok(char_value) = parse_event(char_value, &mut iterator) {
Some(char_value)
} else {
None
}
}
None => None,
}
}
}
impl Drop for AsyncReader {
fn drop(&mut self) {
self.stop_reading();
}
}

View File

@ -3,9 +3,9 @@
use super::*;
use crate::sys::unix::{get_tty, read_char, read_char_raw};
use crossterm_utils::TerminalOutput;
use crossterm_utils::{csi, write, TerminalOutput};
use std::char;
use std::thread;
use std::io::Read;
pub struct UnixInput;
@ -29,45 +29,85 @@ impl ITerminalInput for UnixInput {
}
}
fn read_async(&self, __stdout: &Option<&Arc<TerminalOutput>>) -> AsyncReader {
let (send, recv) = mpsc::channel();
thread::spawn(move || {
fn read_async(&self) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| {
for i in get_tty().unwrap().bytes() {
if send.send(i).is_err() {
if event_tx.send(i.unwrap()).is_err() {
return;
}
if cancellation_token.load(Ordering::SeqCst) {
return;
}
}
});
AsyncReader { recv }
}))
}
fn read_until_async(
&self,
delimiter: u8,
__stdout: &Option<&Arc<TerminalOutput>>,
) -> AsyncReader {
let (send, recv) = mpsc::channel();
fn read_sync(&self) -> SyncReader {
SyncReader
}
thread::spawn(move || {
for i in get_tty().unwrap().bytes() {
match i {
Ok(byte) => {
fn read_until_async(&self, delimiter: u8) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| {
for byte in get_tty().unwrap().bytes() {
let byte = byte.unwrap();
let end_of_stream = byte == delimiter;
let send_error = send.send(Ok(byte)).is_err();
let send_error = event_tx.send(byte).is_err();
if end_of_stream || send_error {
if end_of_stream || send_error || cancellation_token.load(Ordering::SeqCst) {
return;
}
}
Err(_) => {
return;
}))
}
}
}
});
AsyncReader { recv }
fn enable_mouse_mode(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()> {
write(
stdout,
format!(
"{}h{}h{}h{}h",
csi!("?1000"),
csi!("?1002"),
csi!("?1015"),
csi!("?1006")
),
)?;
Ok(())
}
fn disable_mouse_mode(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()> {
write(
stdout,
format!(
"{}l{}l{}l{}l",
csi!("?1006"),
csi!("?1015"),
csi!("?1002"),
csi!("?1000")
),
)?;
Ok(())
}
}
impl Iterator for SyncReader {
type Item = InputEvent;
/// Read input from the user.
///
/// If there are no keys pressed this will be a blocking call until there are.
/// This will return `None` in case of a failure and `Some(InputEvent) in case of an occurred input event.`
fn next(&mut self) -> Option<Self::Item> {
let mut iterator = get_tty().unwrap().bytes().flatten();
match iterator.next() {
None => None,
Some(byte) => {
if let Ok(event) = parse_event(byte, &mut iterator) {
Some(event)
} else {
None
}
}
}
}
}

View File

@ -3,9 +3,25 @@
use super::*;
use crossterm_utils::TerminalOutput;
use std::char;
use std::thread;
use crossterm_winapi::{
ButtonState, Console, ConsoleMode, EventFlags, Handle, InputEventType, KeyEventRecord,
MouseEvent,
};
use winapi::um::wincon::{
LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED,
};
use winapi::um::winnt::INT;
use winapi::um::winuser::{
VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F10, VK_F11, VK_F12,
VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_HOME, VK_INSERT, VK_LEFT, VK_MENU,
VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP,
};
use std::thread;
use std::{char, io};
use std::sync::atomic::Ordering;
use std::time::Duration;
pub struct WindowsInput;
@ -15,6 +31,11 @@ impl WindowsInput {
}
}
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
// NOTE (@imdaveho): this global var is terrible -> move it elsewhere...
static mut ORIG_MODE: u32 = 0;
impl ITerminalInput for WindowsInput {
fn read_char(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<char> {
let is_raw_screen = match stdout {
@ -50,76 +71,78 @@ impl ITerminalInput for WindowsInput {
}
}
fn read_async(&self, stdout: &Option<&Arc<TerminalOutput>>) -> AsyncReader {
let (tx, rx) = mpsc::channel();
let is_raw_screen = match stdout {
Some(output) => output.is_in_raw_mode,
None => false,
};
thread::spawn(move || {
loop {
// _getwch is without echo and _getwche is with echo
let pressed_char = unsafe {
if is_raw_screen {
_getwch()
} else {
_getwche()
fn read_async(&self) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
for i in into_virtual_terminal_sequence().unwrap().1 {
if event_tx.send(i).is_err() {
return;
}
}
};
// we could return error but maybe option to keep listening until valid character is inputted.
if pressed_char == 0 || pressed_char == 0xe0 {
if cancellation_token.load(Ordering::SeqCst) {
return;
}
if let Err(_) = tx.send(Ok(pressed_char as u8)) {
println!("Could not send pressed char to receiver.")
}
}
});
AsyncReader { recv: rx }
thread::sleep(Duration::from_millis(1));
}))
}
fn read_until_async(
&self,
delimiter: u8,
stdout: &Option<&Arc<TerminalOutput>>,
) -> AsyncReader {
let (tx, rx) = mpsc::channel();
fn read_sync(&self) -> SyncReader {
SyncReader {}
}
let is_raw_screen = match stdout {
Some(output) => output.is_in_raw_mode,
None => false,
};
thread::spawn(move || {
loop {
// _getwch is without echo and _getwche is with echo
let pressed_char = unsafe {
if is_raw_screen {
_getwch()
fn read_until_async(&self, delimiter: u8) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
for i in into_virtual_terminal_sequence().unwrap().1 {
if i == delimiter || cancellation_token.load(Ordering::SeqCst) {
return;
} else {
_getwche()
}
} as u8;
let end_of_stream = pressed_char == delimiter;
// we could return error but maybe option to keep listening until valid character is inputted.
if pressed_char == 0 || pressed_char == 0xe0 || end_of_stream {
if event_tx.send(i).is_err() {
return;
}
}
if let Err(_) = tx.send(Ok(pressed_char as u8)) {
println!("Could not send pressed char to receiver.")
thread::sleep(Duration::from_millis(1));
}
}))
}
fn enable_mouse_mode(&self, __stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
unsafe {
ORIG_MODE = mode.mode()?;
mode.set_mode(ENABLE_MOUSE_MODE)?;
}
Ok(())
}
fn disable_mouse_mode(&self, __stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
mode.set_mode(unsafe { ORIG_MODE })
}
}
});
AsyncReader { recv: rx }
impl Iterator for SyncReader {
type Item = InputEvent;
/// Read input from the user.
///
/// If there are no keys pressed this will be a blocking call until there are.
/// This will return `None` in case of a failure and `Some(InputEvent) in case of an occurred input event.`
fn next(&mut self) -> Option<Self::Item> {
let mut iterator = into_virtual_terminal_sequence().unwrap().1.into_iter();
match iterator.next() {
None => None,
Some(byte) => {
if let Ok(event) = parse_event(byte, &mut iterator) {
Some(event)
} else {
None
}
}
}
}
}
@ -127,3 +150,273 @@ extern "C" {
fn _getwche() -> INT;
fn _getwch() -> INT;
}
/// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130
fn into_virtual_terminal_sequence() -> Result<(u32, Vec<u8>)> {
let console = Console::from(Handle::current_in_handle()?);
let mut vts: Vec<u8> = Vec::new();
let result = console.read_console_input()?;
for input in result.1 {
unsafe {
match input.event_type {
InputEventType::KeyEvent => {
let key_event = KeyEventRecord::from(*input.event.KeyEvent());
if key_event.key_down {
// NOTE (@imdaveho): only handle key down, this is because unix limits key events to key press
continue;
}
handle_key_event(&key_event, &mut vts);
}
InputEventType::MouseEvent => {
let mouse_event = MouseEvent::from(*input.event.MouseEvent());
// TODO: handle mouse events
handle_mouse_event(&mouse_event, &mut vts);
}
// NOTE (@imdaveho): ignore below
InputEventType::WindowBufferSizeEvent => (),
InputEventType::FocusEvent => (),
InputEventType::MenuEvent => (),
}
}
}
return Ok((result.0, vts));
}
fn handle_key_event(key_event: &KeyEventRecord, seq: &mut Vec<u8>) {
// println!("key code: {:?}, state: {:?}", key_event.virtual_key_code, key_event.control_key_state);
match key_event.virtual_key_code as i32 {
VK_SHIFT | VK_CONTROL | VK_MENU => {
// ignore SHIFT, CTRL, ALT standalone presses
}
VK_BACK => {
seq.push(b'\x7F');
}
VK_ESCAPE => {
seq.push(b'\x1B');
}
VK_RETURN => {
seq.push(b'\n');
}
VK_F1 | VK_F2 | VK_F3 | VK_F4 => {
// F1 - F4 are support by default VT100
seq.push(b'\x1B');
seq.push(b'O');
seq.push([b'P', b'Q', b'R', b'S'][(key_event.virtual_key_code - 0x70) as usize]);
}
VK_F5 | VK_F6 | VK_F7 | VK_F8 => {
// NOTE: F Key Escape Codes:
// http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
// F5 - F8
seq.push(b'\x1B');
seq.push(b'[');
seq.push(b'1');
seq.push([b'5', b'7', b'8', b'9'][(key_event.virtual_key_code - 0x74) as usize]);
seq.push(b'~');
}
VK_F9 | VK_F10 | VK_F11 | VK_F12 => {
seq.push(b'\x1B');
seq.push(b'[');
seq.push(b'2');
seq.push([b'0', b'1', b'3', b'4'][(key_event.virtual_key_code - 0x78) as usize]);
seq.push(b'~');
}
VK_LEFT | VK_UP | VK_RIGHT | VK_DOWN => {
seq.push(b'\x1B');
seq.push(b'[');
seq.push([b'D', b'A', b'C', b'B'][(key_event.virtual_key_code - 0x25) as usize]);
}
VK_PRIOR | VK_NEXT => {
seq.push(b'\x1B');
seq.push(b'[');
seq.push([b'5', b'6'][(key_event.virtual_key_code - 0x21) as usize]);
seq.push(b'~');
}
VK_END | VK_HOME => {
seq.push(b'\x1B');
seq.push(b'[');
seq.push([b'F', b'H'][(key_event.virtual_key_code - 0x23) as usize]);
}
VK_DELETE => {
seq.push(b'\x1B');
seq.push(b'[');
seq.push([b'2', b'3'][(key_event.virtual_key_code - 0x2D) as usize]);
seq.push(b'~');
}
VK_INSERT => {
seq.push(b'\x1B');
seq.push(b'[');
seq.push(b'2');
seq.push(b'~');
}
_ => {
// Modifier Keys (Ctrl, Alt, Shift) Support
// NOTE (@imdaveho): test to check if characters outside of
// alphabet or alphanumerics are supported
let character = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) };
if character < 255 {
let character = character as u8 as char;
let key_state = &key_event.control_key_state;
if key_state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) {
seq.push(b'\x1B');
// If the ALT key is held down, pressing the A key produces ALT+A, which the system does not treat as a character at all, but rather as a system command.
// The pressed command is stored in `virtual_key_code`.
let command = key_event.virtual_key_code as u8 as char;
if (command).is_alphabetic() {
seq.push(command as u8);
}
} else if key_state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) {
seq.push(character as u8);
} else if key_state.has_state(SHIFT_PRESSED) {
// Shift + key press, essentially the same as single key press
// Separating to be explicit about the Shift press.
seq.push(character as u8);
} else {
seq.push(character as u8);
}
}
}
}
}
fn handle_mouse_event(event: &MouseEvent, seq: &mut Vec<u8>) {
// NOTE (@imdaveho): xterm emulation takes the digits of the coords and passes them
// individually as bytes into a buffer; the below cxbs and cybs replicates that and
// mimicks the behavior; additionally, in xterm, mouse move is only handled when a
// mouse button is held down (ie. mouse drag)
let cxbs: Vec<u8> = event
.mouse_position
.x
.to_string()
.chars()
.map(|d| d as u8)
.collect();
let cybs: Vec<u8> = event
.mouse_position
.y
.to_string()
.chars()
.map(|d| d as u8)
.collect();
// TODO (@imdaveho): check if linux only provides coords for visible terminal window vs the total buffer
match event.event_flags {
EventFlags::PressOrRelease => {
// Single click
match event.button_state {
ButtonState::Release => {
seq.append(&mut vec![b'\x1B', b'[', b'<', b'3', b';']);
for x in cxbs {
seq.push(x);
}
seq.push(b';');
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'm');
}
ButtonState::FromLeft1stButtonPressed => {
// left click
seq.append(&mut vec![b'\x1B', b'[', b'<', b'0', b';']);
for x in cxbs {
seq.push(x);
}
seq.push(b';');
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
}
ButtonState::RightmostButtonPressed => {
// right click
seq.append(&mut vec![b'\x1B', b'[', b'<', b'2', b';']);
for x in cxbs {
seq.push(x);
}
seq.push(b';');
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
}
ButtonState::FromLeft2ndButtonPressed => {
// middle click
seq.append(&mut vec![b'\x1B', b'[', b'<', b'1', b';']);
for x in cxbs {
seq.push(x);
}
seq.push(b';');
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
}
_ => (),
}
}
EventFlags::MouseMoved => {
// Click + Move
// NOTE (@imdaveho) only register when mouse is not released
if event.button_state != ButtonState::Release {
seq.append(&mut vec![b'\x1B', b'[', b'<', b'3', b'2', b';']);
for x in cxbs {
seq.push(x);
}
seq.push(b';');
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
} else {
()
}
}
EventFlags::MouseWheeled => {
// Vertical scroll
// NOTE (@imdaveho) from https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
// if `button_state` is negative then the wheel was rotated backward, toward the user.
if event.button_state != ButtonState::Negative {
seq.append(&mut vec![b'\x1B', b'[', b'<', b'6', b'4', b';']);
for x in cxbs {
seq.push(x);
}
seq.push(b';');
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
} else {
seq.append(&mut vec![b'\x1B', b'[', b'<', b'6', b'5', b';']);
for x in cxbs {
seq.push(x);
}
seq.push(b';');
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
}
}
EventFlags::DoubleClick => (), // NOTE (@imdaveho): double click not supported by unix terminals
EventFlags::MouseHwheeled => (), // NOTE (@imdaveho): horizontal scroll not supported by unix terminals
// TODO: Handle Ctrl + Mouse, Alt + Mouse, etc.
};
}

View File

@ -1,8 +1,14 @@
extern crate crossterm_screen;
extern crate crossterm_utils;
#[cfg(unix)]
extern crate libc;
mod input;
mod sys;
pub use self::input::{input, AsyncReader, KeyEvent, TerminalInput};
pub use self::input::{
input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput,
};
pub use self::crossterm_screen::Screen;

View File

@ -16,4 +16,4 @@ crossterm_utils = "0.1.0"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.5", features = ["minwindef", "wincon"] }
crossterm_winapi = "0.1.1"
crossterm_winapi = "0.1.2"

View File

@ -1,5 +1,5 @@
# Crossterm Screen | cross-platform alternate, raw screen.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
[s1]: https://img.shields.io/crates/v/crossterm_screen.svg
[l1]: https://crates.io/crates/crossterm_screen
@ -13,8 +13,7 @@
[s3]: https://docs.rs/crossterm_screen/badge.svg
[l3]: https://docs.rs/crossterm_screen/
[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_screen?category=code
[s7]: https://travis-ci.org/TimonPost/crossterm_screen.svg?branch=master
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
This crate allows you to work with alternate and raw screen cross-platform.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
@ -27,7 +26,7 @@ Other sub-crates are:
- [Crossterm Input](https://crates.io/crates/crossterm_input)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.html)
When you want to use other modules as well you might want to use crossterm with [feature flags](http://atcentra.com/crossterm/feature_flags.html).
In case you are wondering what 'alternate' or 'raw' screen is, you could checkout the [book](http://atcentra.com/crossterm/screen.html) describing this in more detail.
@ -45,7 +44,7 @@ In case you are wondering what 'alternate' or 'raw' screen is, you could checkou
## Getting Started
This documentation is only for `crossterm_screen` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md).
Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate
Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_screen/examples) folders with detailed examples for all functionality of this crate
and the [book](http://atcentra.com/crossterm/screen.html) for more information about how to use the alternate or raw screen options.
Add the `crossterm_screen` package to your `Cargo.toml` file.

View File

@ -13,7 +13,7 @@ edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.5", features = ["wincon"] }
crossterm_winapi = "0.1.1"
crossterm_winapi = "0.1.2"
[dependencies]
crossterm_utils = "0.1.0"

View File

@ -1,5 +1,5 @@
# Crossterm Style | cross-platform styling.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
[s1]: https://img.shields.io/crates/v/crossterm_style.svg
[l1]: https://crates.io/crates/crossterm_style
@ -13,8 +13,7 @@
[s3]: https://docs.rs/crossterm_style/badge.svg
[l3]: https://docs.rs/crossterm_style/
[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_style?category=code
[s7]: https://travis-ci.org/TimonPost/crossterm_style.svg?branch=master
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
This crate allows you to style te terminal cross-platform.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
@ -27,7 +26,7 @@ Other sub-crates are:
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.html)
When you want to use other modules as well you might want to use crossterm with [feature flags](http://atcentra.com/crossterm/feature_flags.html).
## Table of contents:
- [Getting started](#getting-started)
@ -42,13 +41,13 @@ When you want to use other modules as well you might want to use crossterm with
## Getting Started
This documentation is only for `crossterm_style` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate.
This documentation is only for `crossterm_style` version `0.2` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_style/examples) folders with detailed examples for all functionality of this crate.
Add the `crossterm_style` package to your `Cargo.toml` file.
```
[dependencies]
`crossterm_style` = "0.1"
`crossterm_style` = "0.2"
```
And import the `crossterm_style` modules you want to use.

View File

@ -12,7 +12,7 @@ readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
crossterm_winapi = "0.1.1"
crossterm_winapi = "0.1.2"
[target.'cfg(unix)'.dependencies]
libc = "0.2.43"

View File

@ -1,5 +1,5 @@
# Crossterm Terminal | cross-platform terminal actions.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
[s1]: https://img.shields.io/crates/v/crossterm_terminal.svg
[l1]: https://crates.io/crates/crossterm_terminal
@ -13,8 +13,7 @@
[s3]: https://docs.rs/crossterm_terminal/badge.svg
[l3]: https://docs.rs/crossterm_terminal/
[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_terminal?category=code
[s7]: https://travis-ci.org/TimonPost/crossterm_terminal.svg?branch=master
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
This crate allows you to perform terminal related actions cross-platform e.g clearing, resizing etc.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
@ -27,7 +26,7 @@ Other sub-crates are:
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.html)
When you want to use other modules as well you might want to use crossterm with [feature flags](http://atcentra.com/crossterm/feature_flags.html).
## Table of contents:
- [Getting started](#getting-started)
@ -42,7 +41,7 @@ When you want to use other modules as well you might want to use crossterm with
## Getting Started
This documentation is only for `crossterm_terminal` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate.
This documentation is only for `crossterm_terminal` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_terminal/examples) folders with detailed examples for all functionality of this crate.
Add the `crossterm_terminal` package to your `Cargo.toml` file.

View File

@ -5,7 +5,7 @@ authors = ["Timon Post <timonpost@hotmail.nl>"]
edition = "2018"
[target.'cfg(windows)'.dependencies]
crossterm_winapi = "0.1.1"
crossterm_winapi = "0.1.2"
winapi = { version = "0.3.5", features = ["wincon"] }
[target.'cfg(unix)'.dependencies]

View File

@ -1,5 +1,5 @@
# Crossterm Utils | crossterm common used code.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
[s1]: https://img.shields.io/crates/v/crossterm_utils.svg
[l1]: https://crates.io/crates/crossterm_utils
@ -13,8 +13,7 @@
[s3]: https://docs.rs/crossterm_utils/badge.svg
[l3]: https://docs.rs/crossterm_utils/
[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_utils?category=code
[s7]: https://travis-ci.org/TimonPost/crossterm_utils.svg?branch=master
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
This crate is a utilities crate used by the following [crossterm](https://crates.io/crates/crossterm) modules:
- [Crossterm Style](https://crates.io/crates/crossterm_style)

View File

@ -1,6 +1,6 @@
[package]
name = "crossterm_winapi"
version = "0.1.0"
version = "0.1.2"
authors = ["T. Post"]
description = "An WinApi wrapper that provides some basic simple abstractions aground common WinApi calls"
repository = "https://github.com/TimonPost/crossterm_winapi"

View File

@ -1,5 +1,5 @@
# Crossterm Winapi | Common WinApi Abstractions
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
[s1]: https://img.shields.io/crates/v/crossterm_winapi.svg
[l1]: https://crates.io/crates/crossterm_winapi
@ -10,16 +10,16 @@
[s3]: https://docs.rs/crossterm_winapi/badge.svg
[l3]: https://docs.rs/crossterm_winapi/
[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_winapi?category=code
[s7]: https://travis-ci.org/TimonPost/crossterm_winapi.svg?branch=master
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
This crate provides some wrappers aground common used WinApi functions.
The purpose of this library is originally meant for [crossterm](https://github.com/TimonPost/crossterm),
and it is very unstable right because of that some changes could be expected.
# Features
This crate provides some abstractions over:
This crate provides some abstractions over reading input, console screen buffer, and handle.
_The following WinApi calls_
- CONSOLE_SCREEN_BUFFER_INFO (used to extract information like cursor pos, terminal size etc.)
- HANDLE (the handle needed to run functions from WinApi)
- SetConsoleActiveScreenBuffer (activate an other screen buffer)
@ -28,10 +28,12 @@ This crate provides some abstractions over:
- SetConsoleWindowInfo (changing the buffer location e.g. scrolling)
- FillConsoleOutputAttribute, FillConsoleOutputCharacter (used to replace some block of cells with a color or character.)
- SetConsoleInfo
- ReadConsoleW
# Example
Here are some examples do demonstrate how to work whit this crate.
Please see [examples](https://github.com/TimonPost/crossterm_winapi) for more
## Screenbuffer information
```rust
use crossterm_winapi::{ScreenBuffer, Handle};

View File

@ -2,16 +2,20 @@ use super::{is_true, Coord, Handle, HandleType, WindowPositions};
use std::io::{self, Error, Result};
use std::str;
use std::mem::zeroed;
use winapi::ctypes::c_void;
use winapi::shared::minwindef::DWORD;
use winapi::shared::ntdef::NULL;
use winapi::um::consoleapi::ReadConsoleInputW;
use winapi::um::{
consoleapi::WriteConsoleW,
wincon::{
FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetLargestConsoleWindowSize,
SetConsoleTextAttribute, SetConsoleWindowInfo, COORD, SMALL_RECT,
SetConsoleTextAttribute, SetConsoleWindowInfo, COORD, INPUT_RECORD, SMALL_RECT,
},
winnt::HANDLE,
};
use InputRecord;
/// Could be used to do some basic things with the console.
pub struct Console {
@ -159,6 +163,30 @@ impl Console {
}
Ok(utf8.as_bytes().len())
}
pub fn read_console_input(&self) -> Result<(u32, Vec<InputRecord>)> {
let mut buf: [INPUT_RECORD; 0x1000] = unsafe { zeroed() };
let mut size = 0;
if !is_true(unsafe {
ReadConsoleInputW(
*self.handle,
buf.as_mut_ptr(),
buf.len() as DWORD,
&mut size,
)
}) {
return Err(Error::last_os_error());
}
Ok((
size,
buf[..(size as usize)]
.iter()
.map(|x| InputRecord::from(*x))
.collect::<Vec<InputRecord>>(),
))
}
}
impl From<Handle> for Console {

View File

@ -13,7 +13,10 @@ pub use self::{
csbi::ScreenBufferInfo,
handle::{Handle, HandleType},
screen_buffer::ScreenBuffer,
structs::{Coord, Size, WindowPositions},
structs::{
ButtonState, ControlKeyState, Coord, EventFlags, InputEventType, InputRecord,
KeyEventRecord, MouseEvent, Size, WindowPositions,
},
};
/// Parses the given integer to an bool by checking if the value is 0 or 1.

View File

@ -5,7 +5,7 @@
use winapi::um::wincon::COORD;
/// This is type represents the position of something on a certain 'x' and 'y'.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[derive(Copy, Clone, Debug, Default, Eq, PartialOrd, PartialEq)]
pub struct Coord {
/// the position on the x axis
pub x: i16,

View File

@ -0,0 +1,213 @@
//! This module provides a few structs to wrap common input struts to a rusty interface
//!
//! Types like:
//! - `KEY_EVENT_RECORD`
//! - `MOUSE_EVENT_RECORD`
//! - `ControlKeyState`
//! - `ButtonState`
//! - `EventFlags`
//! - `InputEventType`
//! - `INPUT_RECORD`
use winapi::shared::minwindef::{DWORD, WORD};
use winapi::um::wincon::{
INPUT_RECORD_Event, KEY_EVENT_RECORD_uChar, FOCUS_EVENT, INPUT_RECORD, KEY_EVENT,
KEY_EVENT_RECORD, MENU_EVENT, MOUSE_EVENT, MOUSE_EVENT_RECORD, WINDOW_BUFFER_SIZE_EVENT,
};
use Coord;
/// Describes a keyboard input event in a console INPUT_RECORD structure.
/// link: [https://docs.microsoft.com/en-us/windows/console/key-event-record-str]
pub struct KeyEventRecord {
/// If the key is pressed, this member is TRUE. Otherwise, this member is FALSE (the key is released).
pub key_down: bool,
/// The repeat count, which indicates that a key is being held down.
/// For example, when a key is held down, you might get five events with this member equal to 1, one event with this member equal to 5, or multiple events with this member greater than or equal to 1.
pub repeat_count: u16,
/// A virtual-key code that identifies the given key in a device-independent manner.
pub virtual_key_code: WORD,
/// The virtual scan code of the given key that represents the device-dependent value generated by the keyboard hardware.
pub virtual_scan_code: u16,
/// A union of the following members.
///
/// - UnicodeChar
/// Translated Unicode character.
///
/// - AsciiChar
/// Translated ASCII character.
/// TODO, make this a rust type.
pub u_char: KEY_EVENT_RECORD_uChar,
/// The state of the control keys.
pub control_key_state: ControlKeyState,
}
impl KeyEventRecord {}
impl From<KEY_EVENT_RECORD> for KeyEventRecord {
fn from(event: KEY_EVENT_RECORD) -> Self {
KeyEventRecord {
key_down: event.bKeyDown == 1,
repeat_count: event.wRepeatCount,
virtual_key_code: event.wVirtualKeyCode,
virtual_scan_code: event.wVirtualScanCode,
u_char: event.uChar,
control_key_state: ControlKeyState(event.dwControlKeyState),
}
}
}
#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
pub struct MouseEvent {
pub mouse_position: Coord,
pub button_state: ButtonState,
pub control_key_state: ControlKeyState,
pub event_flags: EventFlags,
}
impl From<MOUSE_EVENT_RECORD> for MouseEvent {
fn from(event: MOUSE_EVENT_RECORD) -> Self {
MouseEvent {
mouse_position: Coord::from(event.dwMousePosition),
button_state: ButtonState::from(event.dwButtonState),
control_key_state: ControlKeyState(event.dwControlKeyState),
event_flags: EventFlags::from(event.dwEventFlags),
}
}
}
/// The status of the mouse buttons.
/// The least significant bit corresponds to the leftmost mouse button.
/// The next least significant bit corresponds to the rightmost mouse button.
/// The next bit indicates the next-to-leftmost mouse button.
/// The bits then correspond left to right to the mouse buttons.
/// A bit is 1 if the button was pressed.
///
/// [Ms Docs](https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str#members)
#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
pub enum ButtonState {
Release = 0x0000,
/// The leftmost mouse button.
FromLeft1stButtonPressed = 0x0001,
/// The second button from the left.
FromLeft2ndButtonPressed = 0x0004,
/// The third button from the left.
FromLeft3rdButtonPressed = 0x0008,
/// The fourth button from the left.
FromLeft4thButtonPressed = 0x0010,
/// The rightmost mouse button.
RightmostButtonPressed = 0x0002,
/// This button state is not recognized.
Unknown = 0x0021,
/// The wheel was rotated backward, toward the user; this will only be activated for `MOUSE_WHEELED ` from `dwEventFlags`
Negative = 0x0020,
}
impl From<DWORD> for ButtonState {
fn from(event: DWORD) -> Self {
let e = event as i32;
match e {
0x0000 => ButtonState::Release,
0x0001 => ButtonState::FromLeft1stButtonPressed,
0x0004 => ButtonState::FromLeft2ndButtonPressed,
0x0008 => ButtonState::FromLeft3rdButtonPressed,
0x0010 => ButtonState::FromLeft4thButtonPressed,
0x0002 => ButtonState::RightmostButtonPressed,
_ if e < 0 => ButtonState::Negative,
_ => ButtonState::Unknown,
}
}
}
#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
pub struct ControlKeyState(u32);
impl ControlKeyState {
pub fn has_state(&self, state: u32) -> bool {
(state & self.0) != 0
}
}
/// The type of mouse event.
/// If this value is zero, it indicates a mouse button being pressed or released.
/// Otherwise, this member is one of the following values.
///
/// [Ms Docs](https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str#members)
#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
pub enum EventFlags {
PressOrRelease = 0x0000,
// The second click (button press) of a double-click occurred. The first click is returned as a regular button-press event.
DoubleClick = 0x0002,
// The horizontal mouse wheel was moved.
MouseHwheeled = 0x0008,
// If the high word of the dwButtonState member contains a positive value, the wheel was rotated to the right. Otherwise, the wheel was rotated to the left.
MouseMoved = 0x0001,
// A change in mouse position occurred.
// The vertical mouse wheel was moved, if the high word of the dwButtonState member contains a positive value, the wheel was rotated forward, away from the user.
// Otherwise, the wheel was rotated backward, toward the user.
MouseWheeled = 0x0004,
}
impl From<DWORD> for EventFlags {
fn from(event: DWORD) -> Self {
match event {
0x0000 => EventFlags::PressOrRelease,
0x0002 => EventFlags::DoubleClick,
0x0008 => EventFlags::MouseHwheeled,
0x0001 => EventFlags::MouseMoved,
0x0004 => EventFlags::MouseWheeled,
_ => panic!("Event flag {} does not exist.", event),
}
}
}
/// Describes an input event in the console input buffer.
/// These records can be read from the input buffer by using the `ReadConsoleInput` or `PeekConsoleInput` function, or written to the input buffer by using the `WriteConsoleInput` function.
///
/// [Ms Docs](https://docs.microsoft.com/en-us/windows/console/input-record-str)
pub struct InputRecord {
/// A handle to the type of input event and the event record stored in the Event member.
pub event_type: InputEventType,
/// The event information. The format of this member depends on the event type specified by the EventType member.
/// Todo: wrap with rust type.
pub event: INPUT_RECORD_Event,
}
impl From<INPUT_RECORD> for InputRecord {
fn from(event: INPUT_RECORD) -> Self {
InputRecord {
event_type: InputEventType::from(event.EventType),
event: event.Event,
}
}
}
/// A handle to the type of input event and the event record stored in the Event member.
///
/// [Ms Docs](https://docs.microsoft.com/en-us/windows/console/input-record-str#members)
#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
pub enum InputEventType {
/// The Event member contains a `KEY_EVENT_RECORD` structure with information about a keyboard event.
KeyEvent = KEY_EVENT as isize,
/// The Event member contains a `MOUSE_EVENT_RECORD` structure with information about a mouse movement or button press event.
MouseEvent = MOUSE_EVENT as isize,
/// The Event member contains a `WINDOW_BUFFER_SIZE_RECORD` structure with information about the new size of the console screen buffer.
WindowBufferSizeEvent = WINDOW_BUFFER_SIZE_EVENT as isize,
/// The Event member contains a `FOCUS_EVENT_RECORD` structure. These events are used internally and should be ignored.
FocusEvent = FOCUS_EVENT as isize,
/// The Event member contains a `MENU_EVENT_RECORD` structure. These events are used internally and should be ignored.
MenuEvent = MENU_EVENT as isize,
}
impl From<WORD> for InputEventType {
fn from(event: WORD) -> Self {
match event {
KEY_EVENT => InputEventType::KeyEvent,
MOUSE_EVENT => InputEventType::MouseEvent,
WINDOW_BUFFER_SIZE_EVENT => InputEventType::WindowBufferSizeEvent,
FOCUS_EVENT => InputEventType::FocusEvent,
MENU_EVENT => InputEventType::MenuEvent,
_ => panic!("Input event type {} does not exist.", event),
}
}
}

View File

@ -1,7 +1,12 @@
mod coord;
mod input;
mod size;
mod window_coords;
pub use self::coord::Coord;
pub use self::input::{
ButtonState, ControlKeyState, EventFlags, InputEventType, InputRecord, KeyEventRecord,
MouseEvent,
};
pub use self::size::Size;
pub use self::window_coords::WindowPositions;

View File

@ -1,3 +1,10 @@
# Changes crossterm 0.8.0
- Upgraded to `crossterm_input 0.2.0`; Input key, mouse events support.
- Upgraded crossterm_winapi 0.2
# Changes crossterm 0.7.0
- Upgraded to `crossterm_style 0.2`; easier styling.
# Changes crossterm 0.6.0
- Introduced feature flags; input, cursor, style, terminal, screen.
- All modules are moved to their own crate.

View File

@ -1,7 +1,10 @@
## Upgrade crossterm to 0.8.0
This update will cause problems with `read_async`. `read_async` first returned a type implementing `Read` it returns an `Iterator` of input events now.
See the examples for details on how this works.
## Upgrade crossterm to 0.7.0
Upgrade to `crossterm_style 0.2` caused some API changes.
- Introduced more `Attributes`
- Introduced easier ways to style text [issue 87](https://github.com/TimonPost/crossterm/issues/87).
- Introduced more `Attributes` and renamed some.
- Removed `ColorType` since it was unnecessary.
## Upgrade crossterm to 0.6.0

View File

@ -1,77 +1,121 @@
Crossterm provides a way to work with the terminal input. We will not cover the basic usage but instead asynchronous reading of input.
Crossterm provides a way to work with the terminal input. We will not cover the basic usage but instead asynchronous and synchronous reading of input.
Please check out these [examples](https://github.com/TimonPost/crossterm/blob/master/examples/input/keyboard/input.rs) for reading a line or a character from the user.
So what does 'reading async input' mean?
This means that you can catch user input without your program having to wait for it.
The user input will be read from another thread.
UNIX systems will get input from TTY and Windows will get input with '_getwch' and '_getwche'.
## Differences Synchronous and Asynchronous
Crossterm provides two ways to read user input, synchronous and asynchronous.
This could be useful in a case where you want to perform some logic with a periodic check if the user entered some character.
### Synchronous reading
Read the input synchronously from the user, the reads performed will be blocking calls.
Using synchronous over asynchronous reading has the benefit that it is using fewer resources than the asynchronous because background thread and queues are left away."
### Asynchronous reading
Read the input asynchronously, input events are gathered on the background and will be queued for you to read.
Using asynchronous reading has the benefit that input events are queued until you read them. You can poll for occurred events, and the reads won't block your program.
### Technical details
On UNIX systems crossterm reads from the TTY, on Windows, it uses `ReadConsoleInputW`. For asynchronous reading, a background thread will be fired up to read input events.
Occurred events will be queued on an MPSC-channel for the user to iterate over.
# Example
In the following example we will run some loop until the user has pressed 'x'.
In the following example, we will create a small program that will listen for mouse and keyboard input.
On the press of the 'escape' key, the program will be stopped.
So lets start by setting up the basics.
So let's start by setting up the basics.
```
use std::io::Read;
use crossterm::{input, Screen};
use std::{thread, time::Duration};
use crossterm::{TerminalInput, Screen, InputEvent, KeyEvent};
fn main() {
println!("Press 'x' to quit.");
println!("Press 'ESC' to quit.");
/* next code here */
}
```
Next we need to put the terminal into raw mode. We do this because whe don't want the user input to be printed to the terminal screen.
Once the user pressed 'x' we manually want to process it and stop the loop.
Next, we need to put the terminal into raw mode. We do this because we don't want the user input to be printed to the terminal screen.
```rust
// enable raw modes by passing in 'true'
let screen = Screen::new(true);
// create a input from our screen.
let input = input::from_screen(&screen);
let input = TerminalInput::from_output(&screen.stdout);
/* next code here */
```
Take note that whe need to use our 'Screen' to create an `TerminalInput` instance, check [this](screen.md#important-notice) out for more information why that is.
Take note that we need to use our `Screen` to create a `TerminalInput` instance, check [this](screen.md#important-notice) out for more information about why that is.
Next we call `input.read_async()`. This will spin up a thread which will poll all input from the user.
This thread send all input via an 'mpsc' channel to the `AsyncReader` we got back from this function.
By calling `bytes()` on the `AsyncReader` we get an iterator back over the characters (bytes).
Now that we have constructed a `TerminalInput` instance we can go ahead an start the reading.
Do this by calling `input.read_async()`, which returns an [AsyncReader](https://docs.rs/crossterm/0.7.0/crossterm/struct.AsyncReader.html).
This is an iterator over the input events that you could as any other iterator.
```rust
let mut stdin = input.read_async().bytes();
let mut async_stdin = input.read_async();
/* next code here */
```
Now we got an iterator back we can call `next()` on it to get the next pressed character (byte).
If an character is pressed we will get `Some(Ok(u8))` back which we compare to see if 'x' is pressed.
If the 'x' is pressed we break the loop.
```rust
loop {
let pressed_char: Option<Result<u8>> = stdin.next();
if let Some(key_event) = async_stdin.next() {
/* next code here */
}
thread::sleep(Duration::from_millis(50));
}
```
// check if the pressed char is equal to 'c'
if let Some(Ok(b'x')) = pressed_char {
println!("The key: `x` was pressed and program is terminated.");
The [AsyncReader](https://docs.rs/crossterm/0.7.0/crossterm/struct.AsyncReader.html) iterator will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read.
I use a thread delay to prevent spamming the iterator.
Next up we can start pattern matching to see if there are input events we'd like to catch.
In our case, we want to catch the `Escape Key`.
```rust
match key_event {
InputEvent::Keyboard(k) => match k {
KeyEvent::Esc => {
println!("Program closing ...");
break
}
_ => println!("Key {:?} was pressed!", k)
}
_ => { /* Mouse Event */ }
}
```
As you see, we check if the `KeyEvent::Esc` was pressed, if that's true we stop the program by breaking out of the loop.
_final code_
```rust
fn main() {
println!("Press 'ESC' to quit.");
// enable raw modes by passing in 'true'
let screen = Screen::new(true);
// create a input from our screen.
let input = TerminalInput::from_output(&screen.stdout);
// create async reader
let mut async_stdin = input.read_async();
loop {
// try to get the next input event.
if let Some(key_event) = async_stdin.next() {
match key_event {
InputEvent::Keyboard(k) => match k {
KeyEvent::Esc => {
println!("Program closing ...");
break;
}
thread::sleep(Duration::from_millis(20));
_ => println!("Key {:?} was pressed!", k)
}
_ => { }
}
}
thread::sleep(Duration::from_millis(50));
}
} // <=== background reader will be disposed when dropped.
```
You should now have a fully functional program waiting for the user to press 'x'.
User input will be recorded on the background so that the main logic of your program can continue.
The code from this tutorial could be found [here](https://github.com/TimonPost/crossterm/blob/master/examples/input/keyboard/async_input.rs#L45).
---------------------------------------------------------------------------------------------------------------------------------------------
More examples could be found at this [link](https://github.com/TimonPost/crossterm/tree/master/examples/input/keyboard).
More robust and complete examples on all input aspects like mouse, keys could be found [here](https://github.com/TimonPost/crossterm/tree/master/examples/).

View File

@ -5,7 +5,7 @@ It has 4 modules:
- cursor (this is about all the actions you can perform with the cursor)
- terminal (this is about all the actions you can perform on the terminal)
- input (this is about all input actions you can perform on with terminal)
- async_input (this is about reading async input)
- key_events (this is about reading occurred key events)
- crossterm (this is about the struct `Crossterm`)
- alternate_screen (this is about switching to an alternate screen buffer)
- raw_screen (this is about enabling raw screen)

View File

@ -1,136 +0,0 @@
extern crate crossterm;
use self::crossterm::{input, Screen, TerminalInput, Crossterm, ClearType};
use std::io::{stdout, Read, Write};
use std::time::Duration;
use std::{thread, time};
/// this will capture the input until the given key.
pub fn read_async_until() {
// create raw screen
let screen = Screen::new(true);
let input = TerminalInput::from_output(&screen.stdout);
let mut stdin = input.read_until_async(b'\r').bytes();
for _i in 0..100 {
let a = stdin.next();
println!("pressed key: {:?}", a);
if let Some(Ok(b'\r')) = a {
println!("The enter key is hit and program is not listening to input anymore.");
break;
}
if let Some(Ok(b'x')) = a {
println!("The key: x was pressed and program is terminated.");
break;
}
thread::sleep(time::Duration::from_millis(100));
}
}
/// this will read pressed characters async until `x` is typed.
pub fn read_async() {
// create raw screen
let screen = Screen::new(true);
let input = TerminalInput::from_output(&screen.stdout);
let mut stdin = input.read_async().bytes();
for _i in 0..100 {
let a = stdin.next();
println!("pressed key: {:?}", a);
if let Some(Ok(b'x')) = a {
println!("The key: `x` was pressed and program is terminated.");
break;
}
thread::sleep(time::Duration::from_millis(50));
}
}
pub fn read_async_demo() {
// create raw screen
let screen = Screen::new(true);
let crossterm = Crossterm::from_screen(&screen);
let input = crossterm.input();
let terminal = crossterm.terminal();
let cursor = crossterm.cursor();
// this will setup the async reading.
let mut stdin = input.read_async().bytes();
// clear terminal and reset the cursor.
terminal.clear(ClearType::All);
cursor.goto(1, 1);
// loop until the enter key (\r) is pressed.
loop {
terminal.clear(ClearType::All);
cursor.goto(1, 1);
// get the next pressed key
let pressed_key = stdin.next();
terminal.write(format!("{:?} <- Character pressed", pressed_key));
// check if pressed key is enter (\r)
if let Some(Ok(b'\r')) = pressed_key {
break;
}
// wait 200 ms and reset cursor write
thread::sleep(Duration::from_millis(200));
}
}
pub fn async_reading_on_alternate_screen() {
// create raw screen
let screen = Screen::new(true);
let input = TerminalInput::from_output(&screen.stdout);
// switch to alternate screen
if let Ok(alternate) = screen.enable_alternate_modes(true) {
let crossterm = Crossterm::from_screen(&alternate.screen);
// init some modules we use for this demo
let input = crossterm.input();
let terminal = crossterm.terminal();
let mut cursor = crossterm.cursor();
// this will setup the async reading.
let mut stdin = input.read_async().bytes();
// loop until the enter key (\r) is pressed.
loop {
terminal.clear(ClearType::All);
cursor.goto(1, 1);
// get the next pressed key
let pressed_key = stdin.next();
terminal.write(format!("{:?} <- Character pressed", pressed_key));
// check if pressed key is enter (\r)
if let Some(Ok(b'\r')) = pressed_key {
break;
}
// wait 200 ms and reset cursor write
thread::sleep(Duration::from_millis(200));
}
}
}
fn main() {
async_reading_on_alternate_screen();
}

177
examples/key_events.rs Normal file
View File

@ -0,0 +1,177 @@
extern crate crossterm;
use crossterm::{
InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput, Screen
};
use std::{thread, time::Duration};
fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool {
match key_event {
InputEvent::Keyboard(k) => match k {
KeyEvent::Char(c) => match c {
'q' => {
screen.stdout.write_str("The 'q' key is hit and the program is not listening to input anymore.\n\n").unwrap();
return true;
}
_ => {
screen
.stdout
.write_string(format!("'{}' pressed\n\n", c))
.unwrap();
}
},
KeyEvent::Alt(c) => {
screen
.stdout
.write_string(format!("ALT +'{}' pressed\n\n", c))
.unwrap();
}
KeyEvent::Ctrl(c) => {
screen
.stdout
.write_string(format!("CTRL +'{}' Pressed\n\n", c))
.unwrap();
}
KeyEvent::Esc => {
screen
.stdout
.write_string(format!("ESC pressed\n\n"))
.unwrap();
}
KeyEvent::F(number) => {
screen
.stdout
.write_string(format!("F{} key pressed\n\n", number))
.unwrap();
}
KeyEvent::PageUp => {
screen.stdout.write_string(format!("Page Up\n\n")).unwrap();
}
KeyEvent::PageDown => {
screen
.stdout
.write_string(format!("Page Down\n\n"))
.unwrap();
}
KeyEvent::Delete => {
screen.stdout.write_string(format!("Delete\n\n")).unwrap();
}
_ => {
screen
.stdout
.write_string(format!("OTHER: {:?}\n\n", k))
.unwrap();
()
}
},
InputEvent::Mouse(m) => match m {
MouseEvent::Press(b, x, y) => match b {
MouseButton::Left => {
screen
.stdout
.write_string(format!("left mouse press @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::Right => {
screen
.stdout
.write_string(format!("right mouse press @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::Middle => {
screen
.stdout
.write_string(format!("mid mouse press @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::WheelUp => {
screen
.stdout
.write_string(format!("wheel up @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::WheelDown => {
screen
.stdout
.write_string(format!("wheel down @ {}, {}\n\n", x, y))
.unwrap();
}
},
MouseEvent::Release(x, y) => {
screen
.stdout
.write_string(format!("mouse released @ {}, {}\n\n", x, y))
.unwrap();
}
MouseEvent::Hold(x, y) => {
screen
.stdout
.write_string(format!("dragging @ {}, {}\n\n", x, y))
.unwrap();
}
_ => {
screen.stdout.write_str("Unknown mouse event").unwrap();
}
},
_ => println!("Unknown!"),
}
return false;
}
pub fn read_asynchronously() {
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let input = TerminalInput::from_output(&screen.stdout);
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
let stdin = input.read_async();
loop {
if let Some(key_event) = stdin.next() {
if process_input_event(key_event, &screen) {
break;
}
}
thread::sleep(Duration::from_millis(50));
}
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
} // <=== background reader will be disposed when dropped.
pub fn read_synchronously() {
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let input = TerminalInput::from_output(&screen.stdout);
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
let mut sync_stdin = input.read_sync();
loop {
let event = sync_stdin.next();
if let Some(key_event) = event {
if process_input_event(key_event, &screen) {
break;
}
}
}
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
}
fn main() {
// un-comment below and run with
// `cargo run --example key_events`:
// read_synchronously();
read_asynchronously();
}