Asynchronous key events
This commit is contained in:
parent
73ae06124a
commit
b65328c304
|
@ -24,7 +24,8 @@ Features
|
||||||
- Redox support.
|
- Redox support.
|
||||||
- 256-color mode.
|
- 256-color mode.
|
||||||
- Panic-free error handling.
|
- Panic-free error handling.
|
||||||
- Special keys events.
|
- Special keys events (modifiers, special keys, etc.).
|
||||||
|
- Asynchronous key events.
|
||||||
|
|
||||||
and much more.
|
and much more.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
extern crate libterm;
|
||||||
|
|
||||||
|
use libterm::{TermWrite, IntoRawMode, async_stdin};
|
||||||
|
use std::io::{Read, Write, stdout, stdin};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let stdout = stdout();
|
||||||
|
let mut stdout = stdout.lock().into_raw_mode().unwrap();
|
||||||
|
let mut stdin = async_stdin().bytes();
|
||||||
|
|
||||||
|
stdout.clear().unwrap();
|
||||||
|
stdout.goto(0, 0).unwrap();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
stdout.clear_line().unwrap();
|
||||||
|
|
||||||
|
let b = stdin.next();
|
||||||
|
write!(stdout, "\r{:?} <- This demonstrates the async read input char. Between each update a 100 ms. is waited, simply to demonstrate the async fashion. \n\r", b).unwrap();
|
||||||
|
if let Some(Ok(b'q')) = b {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.flush().unwrap();
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_millis(50));
|
||||||
|
stdout.write(b"# ").unwrap();
|
||||||
|
stdout.flush().unwrap();
|
||||||
|
thread::sleep(Duration::from_millis(50));
|
||||||
|
stdout.write(b"\r #").unwrap();
|
||||||
|
stdout.goto(0, 0).unwrap();
|
||||||
|
stdout.flush().unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ fn main() {
|
||||||
let stdout = stdout();
|
let stdout = stdout();
|
||||||
let mut stdout = stdout.lock().into_raw_mode().unwrap();
|
let mut stdout = stdout.lock().into_raw_mode().unwrap();
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
|
let stdin = stdin.lock();
|
||||||
|
|
||||||
stdout.goto(5, 5).unwrap();
|
stdout.goto(5, 5).unwrap();
|
||||||
stdout.clear().unwrap();
|
stdout.clear().unwrap();
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
use std::io::{self, Read};
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
/// Construct an asynchronous handle to the standard input.
|
||||||
|
///
|
||||||
|
/// This allows you to read from standard input _without blocking_ the current thread.
|
||||||
|
/// Specifically, it works by firing up another thread to handle the event stream, which will then
|
||||||
|
/// be buffered in a mpsc queue, which will eventually be read by the current thread.
|
||||||
|
///
|
||||||
|
/// Note that this will acquire the Mutex lock on the standard input, making all future stdin
|
||||||
|
/// construction hang the program.
|
||||||
|
pub fn async_stdin() -> AsyncReader {
|
||||||
|
let (send, recv) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let stdin = io::stdin();
|
||||||
|
for i in stdin.lock().bytes() {
|
||||||
|
if send.send(i).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AsyncReader {
|
||||||
|
recv: recv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An asynchronous reader.
|
||||||
|
pub struct AsyncReader {
|
||||||
|
/// The underlying mpsc receiver.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub recv: mpsc::Receiver<io::Result<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for AsyncReader {
|
||||||
|
/// Read from the byte stream.
|
||||||
|
///
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.recv.try_recv() {
|
||||||
|
Ok(Ok(b)) => {
|
||||||
|
buf[total] = b;
|
||||||
|
total += 1;
|
||||||
|
|
||||||
|
if total == buf.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(Err(e)) => return Err(e),
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_async_stdin() {
|
||||||
|
let stdin = async_stdin();
|
||||||
|
stdin.bytes().next();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use std::io::{Write, Result as IoResult};
|
use std::io::{self, Write};
|
||||||
use {Color, Style};
|
use {Color, Style};
|
||||||
|
|
||||||
/// Extension to the `Write` trait.
|
/// Extension to the `Write` trait.
|
||||||
|
@ -8,39 +8,39 @@ use {Color, Style};
|
||||||
pub trait TermWrite {
|
pub trait TermWrite {
|
||||||
|
|
||||||
/// Print the CSI (control sequence introducer) followed by a byte string.
|
/// Print the CSI (control sequence introducer) followed by a byte string.
|
||||||
fn csi(&mut self, b: &[u8]) -> IoResult<usize>;
|
fn csi(&mut self, b: &[u8]) -> io::Result<usize>;
|
||||||
/// Print OSC (operating system command) followed by a byte string.
|
/// Print OSC (operating system command) followed by a byte string.
|
||||||
fn osc(&mut self, b: &[u8]) -> IoResult<usize>;
|
fn osc(&mut self, b: &[u8]) -> io::Result<usize>;
|
||||||
/// Print OSC (device control string) followed by a byte string.
|
/// Print OSC (device control string) followed by a byte string.
|
||||||
fn dsc(&mut self, b: &[u8]) -> IoResult<usize>;
|
fn dsc(&mut self, b: &[u8]) -> io::Result<usize>;
|
||||||
|
|
||||||
|
|
||||||
/// Clear the entire screen.
|
/// Clear the entire screen.
|
||||||
fn clear(&mut self) -> IoResult<usize> {
|
fn clear(&mut self) -> io::Result<usize> {
|
||||||
self.csi(b"2J")
|
self.csi(b"2J")
|
||||||
}
|
}
|
||||||
/// Clear everything _after_ the cursor.
|
/// Clear everything _after_ the cursor.
|
||||||
fn clear_after(&mut self) -> IoResult<usize> {
|
fn clear_after(&mut self) -> io::Result<usize> {
|
||||||
self.csi(b"J")
|
self.csi(b"J")
|
||||||
}
|
}
|
||||||
/// Clear everything _before_ the cursor.
|
/// Clear everything _before_ the cursor.
|
||||||
fn clear_before(&mut self) -> IoResult<usize> {
|
fn clear_before(&mut self) -> io::Result<usize> {
|
||||||
self.csi(b"1J")
|
self.csi(b"1J")
|
||||||
}
|
}
|
||||||
/// Clear the current line.
|
/// Clear the current line.
|
||||||
fn clear_line(&mut self) -> IoResult<usize> {
|
fn clear_line(&mut self) -> io::Result<usize> {
|
||||||
self.csi(b"2K")
|
self.csi(b"2K")
|
||||||
}
|
}
|
||||||
/// Clear from the cursor until newline.
|
/// Clear from the cursor until newline.
|
||||||
fn clear_until_newline(&mut self) -> IoResult<usize> {
|
fn clear_until_newline(&mut self) -> io::Result<usize> {
|
||||||
self.csi(b"K")
|
self.csi(b"K")
|
||||||
}
|
}
|
||||||
/// Show the cursor.
|
/// Show the cursor.
|
||||||
fn show_cursor(&mut self) -> IoResult<usize> {
|
fn show_cursor(&mut self) -> io::Result<usize> {
|
||||||
self.csi(b"?25h")
|
self.csi(b"?25h")
|
||||||
}
|
}
|
||||||
/// Hide the cursor.
|
/// Hide the cursor.
|
||||||
fn hide_cursor(&mut self) -> IoResult<usize> {
|
fn hide_cursor(&mut self) -> io::Result<usize> {
|
||||||
self.csi(b"?25l")
|
self.csi(b"?25l")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,21 +51,21 @@ pub trait TermWrite {
|
||||||
/// Reset the rendition mode.
|
/// Reset the rendition mode.
|
||||||
///
|
///
|
||||||
/// This will reset both the current style and color.
|
/// This will reset both the current style and color.
|
||||||
fn reset(&mut self) -> IoResult<usize> {
|
fn reset(&mut self) -> io::Result<usize> {
|
||||||
self.csi(b"m")
|
self.csi(b"m")
|
||||||
}
|
}
|
||||||
/// Restore the defaults.
|
/// Restore the defaults.
|
||||||
///
|
///
|
||||||
/// This will reset color, position, cursor state, and so on. It is recommended that you use
|
/// This will reset color, position, cursor state, and so on. It is recommended that you use
|
||||||
/// this before you exit your program, to avoid messing up the user's terminal.
|
/// this before you exit your program, to avoid messing up the user's terminal.
|
||||||
fn restore(&mut self) -> IoResult<usize> {
|
fn restore(&mut self) -> io::Result<usize> {
|
||||||
Ok(try!(self.reset()) + try!(self.clear()) + try!(self.goto(0, 0)) + try!(self.show_cursor()))
|
Ok(try!(self.reset()) + try!(self.clear()) + try!(self.goto(0, 0)) + try!(self.show_cursor()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Go to a given position.
|
/// Go to a given position.
|
||||||
///
|
///
|
||||||
/// The position is 0-based.
|
/// The position is 0-based.
|
||||||
fn goto(&mut self, mut x: u16, mut y: u16) -> IoResult<usize> {
|
fn goto(&mut self, mut x: u16, mut y: u16) -> io::Result<usize> {
|
||||||
x += 1;
|
x += 1;
|
||||||
y += 1;
|
y += 1;
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ pub trait TermWrite {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
/// Set graphic rendition.
|
/// Set graphic rendition.
|
||||||
fn rendition(&mut self, r: u8) -> IoResult<usize> {
|
fn rendition(&mut self, r: u8) -> io::Result<usize> {
|
||||||
self.csi(&[
|
self.csi(&[
|
||||||
b'0' + r / 100,
|
b'0' + r / 100,
|
||||||
b'0' + r / 10 % 10,
|
b'0' + r / 10 % 10,
|
||||||
|
@ -94,7 +94,7 @@ pub trait TermWrite {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
/// Set foreground color
|
/// Set foreground color
|
||||||
fn color(&mut self, color: Color) -> IoResult<usize> {
|
fn color(&mut self, color: Color) -> io::Result<usize> {
|
||||||
let ansi = color.to_ansi_val();
|
let ansi = color.to_ansi_val();
|
||||||
self.csi(&[
|
self.csi(&[
|
||||||
b'3',
|
b'3',
|
||||||
|
@ -109,7 +109,7 @@ pub trait TermWrite {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
/// Set background color
|
/// Set background color
|
||||||
fn bg_color(&mut self, color: Color) -> IoResult<usize> {
|
fn bg_color(&mut self, color: Color) -> io::Result<usize> {
|
||||||
let ansi = color.to_ansi_val();
|
let ansi = color.to_ansi_val();
|
||||||
self.csi(&[
|
self.csi(&[
|
||||||
b'4',
|
b'4',
|
||||||
|
@ -124,19 +124,19 @@ pub trait TermWrite {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
/// Set rendition mode (SGR).
|
/// Set rendition mode (SGR).
|
||||||
fn style(&mut self, mode: Style) -> IoResult<usize> {
|
fn style(&mut self, mode: Style) -> io::Result<usize> {
|
||||||
self.rendition(mode as u8)
|
self.rendition(mode as u8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> TermWrite for W {
|
impl<W: Write> TermWrite for W {
|
||||||
fn csi(&mut self, b: &[u8]) -> IoResult<usize> {
|
fn csi(&mut self, b: &[u8]) -> io::Result<usize> {
|
||||||
Ok(try!(self.write(b"\x1B[")) + try!(self.write(b)))
|
Ok(try!(self.write(b"\x1B[")) + try!(self.write(b)))
|
||||||
}
|
}
|
||||||
fn osc(&mut self, b: &[u8]) -> IoResult<usize> {
|
fn osc(&mut self, b: &[u8]) -> io::Result<usize> {
|
||||||
Ok(try!(self.write(b"\x1B]")) + try!(self.write(b)))
|
Ok(try!(self.write(b"\x1B]")) + try!(self.write(b)))
|
||||||
}
|
}
|
||||||
fn dsc(&mut self, b: &[u8]) -> IoResult<usize> {
|
fn dsc(&mut self, b: &[u8]) -> io::Result<usize> {
|
||||||
Ok(try!(self.write(b"\x1BP")) + try!(self.write(b)))
|
Ok(try!(self.write(b"\x1BP")) + try!(self.write(b)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
47
src/input.rs
47
src/input.rs
|
@ -1,11 +1,14 @@
|
||||||
use std::io::{Read, Write, Error, ErrorKind, Result as IoResult};
|
use std::io::{self, Read, Write};
|
||||||
use IntoRawMode;
|
use std::thread;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
use {IntoRawMode, AsyncReader};
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
#[cfg(feature = "nightly")]
|
||||||
use std::io::{Chars, CharsError};
|
use std::io::{Chars, CharsError};
|
||||||
|
|
||||||
/// A key.
|
/// A key.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Key {
|
pub enum Key {
|
||||||
/// Backspace.
|
/// Backspace.
|
||||||
Backspace,
|
Backspace,
|
||||||
|
@ -27,9 +30,8 @@ pub enum Key {
|
||||||
Ctrl(char),
|
Ctrl(char),
|
||||||
/// Invalid character code.
|
/// Invalid character code.
|
||||||
Invalid,
|
Invalid,
|
||||||
// TODO handle errors better?
|
|
||||||
/// IO error.
|
/// IO error.
|
||||||
Error,
|
Error(io::Error),
|
||||||
/// Null byte.
|
/// Null byte.
|
||||||
Null,
|
Null,
|
||||||
|
|
||||||
|
@ -69,7 +71,7 @@ impl<I: Iterator<Item = Result<char, CharsError>>> Iterator for Keys<I> {
|
||||||
None => None,
|
None => None,
|
||||||
Some('\0') => Some(Key::Null),
|
Some('\0') => Some(Key::Null),
|
||||||
Some(Ok(c)) => Some(Key::Char(c)),
|
Some(Ok(c)) => Some(Key::Char(c)),
|
||||||
Some(Err(_)) => Some(Key::Error),
|
Some(Err(e)) => Some(Key::Error(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +86,12 @@ pub trait TermRead {
|
||||||
///
|
///
|
||||||
/// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
|
/// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
|
||||||
/// complete the password input.
|
/// complete the password input.
|
||||||
fn read_passwd<W: Write>(&mut self, writer: &mut W) -> IoResult<Option<String>>;
|
fn read_passwd<W: Write>(&mut self, writer: &mut W) -> io::Result<Option<String>>;
|
||||||
|
|
||||||
|
/// Turn the reader into a asynchronous reader.
|
||||||
|
///
|
||||||
|
/// This will spawn up another thread listening for event, buffering them in a mpsc queue.
|
||||||
|
fn into_async(self) -> AsyncReader where Self: Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> TermRead for R {
|
impl<R: Read> TermRead for R {
|
||||||
|
@ -95,7 +102,7 @@ impl<R: Read> TermRead for R {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_passwd<W: Write>(&mut self, writer: &mut W) -> IoResult<Option<String>> {
|
fn read_passwd<W: Write>(&mut self, writer: &mut W) -> io::Result<Option<String>> {
|
||||||
let _raw = try!(writer.into_raw_mode());
|
let _raw = try!(writer.into_raw_mode());
|
||||||
let mut passbuf = Vec::with_capacity(30);
|
let mut passbuf = Vec::with_capacity(30);
|
||||||
|
|
||||||
|
@ -108,10 +115,32 @@ impl<R: Read> TermRead for R {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let passwd = try!(String::from_utf8(passbuf).map_err(|e| Error::new(ErrorKind::InvalidData, e)));
|
let passwd = try!(String::from_utf8(passbuf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)));
|
||||||
|
|
||||||
Ok(Some(passwd))
|
Ok(Some(passwd))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_async(self) -> AsyncReader where R: Send + 'static {
|
||||||
|
let (send, recv) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut reader = self;
|
||||||
|
loop {
|
||||||
|
let mut buf = [0];
|
||||||
|
if send.send(if let Err(k) = reader.read(&mut buf) {
|
||||||
|
Err(k)
|
||||||
|
} else {
|
||||||
|
Ok(buf[0])
|
||||||
|
}).is_err() {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AsyncReader {
|
||||||
|
recv: recv,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -19,6 +19,9 @@ mod termios;
|
||||||
mod control;
|
mod control;
|
||||||
pub use control::TermWrite;
|
pub use control::TermWrite;
|
||||||
|
|
||||||
|
mod async;
|
||||||
|
pub use async::{AsyncReader, async_stdin};
|
||||||
|
|
||||||
mod input;
|
mod input;
|
||||||
pub use input::{TermRead, Key};
|
pub use input::{TermRead, Key};
|
||||||
#[cfg(feature = "nightly")]
|
#[cfg(feature = "nightly")]
|
||||||
|
|
20
src/size.rs
20
src/size.rs
|
@ -1,4 +1,4 @@
|
||||||
use std::io::{Error, ErrorKind, Result as IoResult};
|
use std::io;
|
||||||
|
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(not(target_os = "redox"))]
|
||||||
use libc::c_ushort;
|
use libc::c_ushort;
|
||||||
|
@ -27,7 +27,7 @@ fn tiocgwinsz() -> u32 {
|
||||||
|
|
||||||
/// Get the size of the terminal.
|
/// Get the size of the terminal.
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(not(target_os = "redox"))]
|
||||||
pub fn terminal_size() -> IoResult<(usize, usize)> {
|
pub fn terminal_size() -> io::Result<(usize, usize)> {
|
||||||
use libc::ioctl;
|
use libc::ioctl;
|
||||||
use libc::STDOUT_FILENO;
|
use libc::STDOUT_FILENO;
|
||||||
|
|
||||||
|
@ -38,22 +38,22 @@ pub fn terminal_size() -> IoResult<(usize, usize)> {
|
||||||
if ioctl(STDOUT_FILENO, tiocgwinsz(), &mut size as *mut _) == 0 {
|
if ioctl(STDOUT_FILENO, tiocgwinsz(), &mut size as *mut _) == 0 {
|
||||||
Ok((size.col as usize, size.row as usize))
|
Ok((size.col as usize, size.row as usize))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new(ErrorKind::Other, "Unable to get the terminal size."))
|
Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal size."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the size of the terminal.
|
/// Get the size of the terminal.
|
||||||
#[cfg(target_os = "redox")]
|
#[cfg(target_os = "redox")]
|
||||||
pub fn terminal_size() -> IoResult<(usize, usize), TerminalError> {
|
pub fn terminal_size() -> io::Result<(usize, usize)> {
|
||||||
fn get_int(s: &'static str) -> IoResult<usize> {
|
fn get_int(s: &'static str) -> io::Result<usize> {
|
||||||
use std::env::{VarError, var};
|
use std::env;
|
||||||
|
|
||||||
var(s).map_err(|e| match e {
|
env::var(s).map_err(|e| match e {
|
||||||
VarError::NotPresent => Error::new(ErrorKind::NotFound, e),
|
env::VarError::NotPresent => io::Error::new(io::ErrorKind::NotFound, e),
|
||||||
VarError::NotUnicode(u) => Error::new(ErrorKind::InvalidData, u),
|
env::VarError::NotUnicode(u) => io::Error::new(io::ErrorKind::InvalidData, u),
|
||||||
}).and_then(|x| {
|
}).and_then(|x| {
|
||||||
x.parse().map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
x.parse().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue