From 9df976ed292f59f5823cf01039dc768d61a008ce Mon Sep 17 00:00:00 2001 From: Timon Date: Sat, 21 Jul 2018 11:11:31 +0000 Subject: [PATCH] added read_char, read_line, read_async, read_async until for unix. Not 100% tested. --- examples/Crossterm 0.3.1/bin.rs | 17 +- .../input/keyboard/async_input.rs | 112 +++++++++++- examples/Crossterm 0.3.1/input/keyboard/input | 0 src/input/input.rs | 1 - src/input/mod.rs | 3 +- src/input/unix_input.rs | 168 +++++++----------- src/kernel/unix_kernel/terminal.rs | 67 ++++++- src/shared/crossterm.rs | 7 + src/shared/screen.rs | 2 + 9 files changed, 257 insertions(+), 120 deletions(-) delete mode 100644 examples/Crossterm 0.3.1/input/keyboard/input diff --git a/examples/Crossterm 0.3.1/bin.rs b/examples/Crossterm 0.3.1/bin.rs index e373dfc..d91f566 100644 --- a/examples/Crossterm 0.3.1/bin.rs +++ b/examples/Crossterm 0.3.1/bin.rs @@ -23,11 +23,24 @@ mod input; use input::keyboard::{async_input, input as stdin}; +use crossterm::raw::IntoRawMode; + fn main() { - async_input::read_async(); -// stdin::read_line(); + let context = Context::new(); + + { +// let screen = ::crossterm::screen::AlternateScreen::from(context.clone()); +// screen.into_raw_mode(context.clone()); + + + async_input::async_reading_on_alternate_screen(); +// async_input::test(); +// stdin::t(); +// stdin::read_line(); +// stdin::read_char(); // stdin::read_char(); + } } diff --git a/examples/Crossterm 0.3.1/input/keyboard/async_input.rs b/examples/Crossterm 0.3.1/input/keyboard/async_input.rs index aa602ed..cbc3e9c 100644 --- a/examples/Crossterm 0.3.1/input/keyboard/async_input.rs +++ b/examples/Crossterm 0.3.1/input/keyboard/async_input.rs @@ -2,12 +2,18 @@ extern crate crossterm; use self::crossterm::input::input; use self::crossterm::Context; +use self::crossterm::Crossterm; +use crossterm::terminal::ClearType; use std::{thread, time}; -use std::io::Read; +use crossterm::raw::IntoRawMode; -// this will capture the input until the given key was pressed. -pub fn capture_input_until_a_certain_char_async() +use std::io::{Read, Write, stdout}; +use std::time::Duration; + + +/// this will capture the input until the given key. +pub fn read_async_until() { let context = Context::new(); let input = input(&context); @@ -18,8 +24,15 @@ pub fn capture_input_until_a_certain_char_async() { 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."); + println!("The key: x was pressed and program is terminated."); break; } @@ -27,7 +40,7 @@ pub fn capture_input_until_a_certain_char_async() } } -// this will capture an character input until the given key was pressed. +/// this will read pressed characters async until `x` is typed . pub fn read_async() { let context = Context::new(); @@ -39,13 +52,98 @@ pub fn read_async() { let a = stdin.next(); - println!("pressed: {:?}", a); + println!("pressed key: {:?}", a); if let Some(Ok(b'x')) = a { - println!("The key: x was pressed."); + println!("The key: `x` was pressed and program is terminated."); break; } thread::sleep(time::Duration::from_millis(50)); } +} + +pub fn read_async_demo() +{ + let crossterm = Crossterm::new(); + + // init some modules we use for this demo + let input = crossterm.input(); + let terminal = crossterm.terminal(); + let mut cursor = crossterm.cursor(); + + // put stdout in raw mode so that characters wil not be outputted. + let mut stdout = stdout().into_raw_mode(crossterm.context()).unwrap(); + + // 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(); + + write!(stdout, "\r{:?} <- Character pressed", pressed_key).unwrap(); + + // 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() +{ + use crossterm::screen::AlternateScreen; + + let crossterm = Crossterm::new(); + + // init some modules we use for this demo + let input = crossterm.input(); + let terminal = crossterm.terminal(); + let mut cursor = crossterm.cursor(); + + // switch to alternate screen + let mut alternate_screen = AlternateScreen::from(crossterm.context()); + // put alternate screen in raw mode so that characters wil not be outputted. + let mut raw_screen = alternate_screen.into_raw_mode(crossterm.context()); + + // 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); + +// panic!(); + + // 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(); + + write!(alternate_screen, "\r{:?} <- Character pressed", pressed_key).unwrap(); + + // 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)); + } } \ No newline at end of file diff --git a/examples/Crossterm 0.3.1/input/keyboard/input b/examples/Crossterm 0.3.1/input/keyboard/input deleted file mode 100644 index e69de29..0000000 diff --git a/src/input/input.rs b/src/input/input.rs index 8f8ebf9..09f389e 100644 --- a/src/input/input.rs +++ b/src/input/input.rs @@ -44,7 +44,6 @@ impl TerminalInput pub fn read_async(&self) -> AsyncReader { self.terminal_input.read_async() - // todo: async reading } pub fn read_until_async(&self, delimiter: u8) -> AsyncReader diff --git a/src/input/mod.rs b/src/input/mod.rs index 6ac4f56..35e8ee8 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -7,8 +7,9 @@ use self::windows_input::WindowsInput; #[cfg(target_os = "windows")] mod windows_input; -#[cfg(target_os = "windows")] +#[cfg(not(target_os = "windows"))] use self::unix_input::UnixInput; +#[cfg(not(target_os = "windows"))] mod unix_input; diff --git a/src/input/unix_input.rs b/src/input/unix_input.rs index 296bec5..cb8f711 100644 --- a/src/input/unix_input.rs +++ b/src/input/unix_input.rs @@ -3,124 +3,76 @@ use std::io::Write; use std::char; use std::sync::mpsc; use std::thread; +use std::io::Read; use super::super::terminal::terminal; -//use super::super::kernel::unix_kernel::terminal::get_tty; +use super::super::kernel::unix_kernel::terminal::{get_tty, read_char}; use super::{ Key, ITerminalInput, AsyncReader }; pub struct UnixInput; + impl UnixInput { pub fn new() -> UnixInput { UnixInput {} } -} -// fn read_line(&self) -> io::Result -// { -// let mut rv = String::new(); -// io::stdin().read_line(&mut rv)?; -// let len = rv.trim_right_matches(&['\r', '\n'][..]).len(); -// rv.truncate(len); -// Ok(rv) -// } -// -// fn read_char(&self) -> io::Result -// { -// let mut buf = [0u8; 20]; -// let mut termios = termios::Termios::from_fd(fd)?; -// let original = termios.clone(); -// termios::cfmakeraw(&mut termios); -// termios::tcsetattr(fd, termios::TCSADRAIN, &termios)?; -// let rv = unsafe { -// let read = libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, 20); -// if read < 0 { -// Err(io::Error::last_os_error()) -// } else if buf[0] == b'\x03' { -// Err(io::Error::new(io::ErrorKind::Interrupted, "read interrupted")) -// } else { -// Ok(key_from_escape_codes(&buf[..read as usize])) -// } -// }; -// termios::tcsetattr(fd, termios::TCSADRAIN, &original)?; -// -// // if the user hit ^C we want to signal SIGINT to outselves. -// if let Err(ref err) = rv { -// if err.kind() == io::ErrorKind::Interrupted { -// unsafe { libc::raise(libc::SIGINT); } -// } -// } -// -// rv -// } -// -// fn read_pressed_key(&self) -> io::Result -// { -// use Context; -// let context = Context::new(); -// -// let buf: [u8; 1024] = unsafe { ::std::mem::zeroed() }; -//// reading::read(&mut buf, &context.screen_manager); -// -// Ok(Key::Unknown) -//// let pressed_char = unsafe { _getwch() }; -//// -//// // if 0 or 0xe0 we need to listen again because the next key will be an special key -//// if pressed_char == 0 || pressed_char == 0xe0 { -//// let special_key: i32 = unsafe { _getwch() }; -//// println!("spkey {}",special_key); -//// return Ok(key_from_key_code(0x26)); -//// } else { -//// match char::from_u32(pressed_char as u32) -//// { -//// Some(c) => return Ok(Key::Char(c)), -//// None => { panic!("Some error needs to be returned") } -//// } -//// } -// } -// -// fn read_async(&self) -> AsyncReader -// { -// let (send, recv) = mpsc::channel(); -// -// thread::spawn(move || for i in get_tty().unwrap().bytes() { -// -// match i { -// Ok(byte) => { -// let end_of_stream = &byte == &delimiter; -// let send_error = send.send(Ok(byte)).is_err(); -// -// if end_of_stream || send_error { return; } -// }, -// Err(_) => { return; } -// } -// }); -// -// AsyncReader { recv: recv } -// } -// -// fn read_until_async(&self, delimiter: u8) -> AsyncReader -// { -// let (tx, rx) = mpsc::channel(); -// -// thread::spawn(move || { -// loop -// { -// let pressed_char: u8 = (unsafe { _getwch() }) 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 { -// return; -// } -// -// tx.send(Ok(pressed_char as u8)); -// } -// }); -// -// AsyncReader { recv: rx } -// } -//} + +} + +impl ITerminalInput for UnixInput +{ + fn read_line(&self) -> io::Result + { + let mut rv = String::new(); + io::stdin().read_line(&mut rv)?; + let len = rv.trim_right_matches(&['\r', '\n'][..]).len(); + rv.truncate(len); + Ok(rv) + } + + fn read_char(&self) -> io::Result + { + read_char() + } + + fn read_pressed_key(&self) -> io::Result + { + Ok(Key::Unknown) + } + + fn read_async(&self) -> AsyncReader + { + let (send, recv) = mpsc::channel(); + + thread::spawn(move || for i in get_tty().unwrap().bytes() { + if send.send(i).is_err() { + return; + } + }); + + AsyncReader { recv: recv } + } + + fn read_until_async(&self, delimiter: u8) -> AsyncReader + { + let (send, recv) = mpsc::channel(); + + thread::spawn(move || for i in get_tty().unwrap().bytes() { + + match i { + Ok(byte) => { + let end_of_stream = &byte == &delimiter; + let send_error = send.send(Ok(byte)).is_err(); + + if end_of_stream || send_error { return; } + }, + Err(_) => { return; } + } + }); + + AsyncReader { recv: recv } + } +} diff --git a/src/kernel/unix_kernel/terminal.rs b/src/kernel/unix_kernel/terminal.rs index 5b1aa8e..f5b2132 100644 --- a/src/kernel/unix_kernel/terminal.rs +++ b/src/kernel/unix_kernel/terminal.rs @@ -3,12 +3,13 @@ pub use self::libc::termios; use self::libc::{c_int, c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ}; use state::commands::{IStateCommand, NoncanonicalModeCommand}; -use termios::Termios; use {libc, CommandManager, Context, StateManager}; +use termios::{ Termios,cfmakeraw,tcsetattr,TCSADRAIN }; use std::io::Error; use std::rc::Rc; use std::{io, mem, fs}; +use std::os::unix::io::AsRawFd; /// A representation of the size of the current terminal. #[repr(C)] @@ -140,6 +141,70 @@ pub fn get_tty() -> io::Result { fs::OpenOptions::new().read(true).write(true).open("/dev/tty") } + +pub fn read_char() -> io::Result +{ + let mut buf = [0u8; 20]; + + // get tty raw handle. + let tty_f; + + let fd = unsafe + { + if libc::isatty(libc::STDIN_FILENO) == 1 + { + libc::STDIN_FILENO + } else { + tty_f = fs::File::open("/dev/tty")?; + tty_f.as_raw_fd() + } + }; + + + let mut termios = Termios::from_fd(fd)?; + let original = termios.clone(); + + make_raw(&mut termios); + tcsetattr(fd, TCSADRAIN, &termios)?; + + // read input and convert it to char + let rv = unsafe { + let read = libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, 20); + + if read < 0 { + Err(io::Error::last_os_error()) + } else if buf[0] == b'\x03' { + Err(io::Error::new(io::ErrorKind::Interrupted, "read interrupted")) + } else { + let mut pressed_char = Ok(' '); + + if let Ok(s) = ::std::str::from_utf8(&buf[..read as usize]) + { + if let Some(c) = s.chars().next() + { + pressed_char = Ok(c); + } + }else { + pressed_char = Err(io::Error::new(io::ErrorKind::Interrupted, "Could not parse char to utf8 char")); + } + + pressed_char + } + }; + + + tcsetattr(fd, TCSADRAIN, &original)?; + + // if the user hit ^C we want to signal SIGINT to outselves. + if let Err(ref err) = rv { + if err.kind() == io::ErrorKind::Interrupted { + unsafe { libc::raise(libc::SIGINT); } + } + } + + rv +} + pub fn exit() { ::std::process::exit(0); } diff --git a/src/shared/crossterm.rs b/src/shared/crossterm.rs index 8a27201..37e4fe9 100644 --- a/src/shared/crossterm.rs +++ b/src/shared/crossterm.rs @@ -27,6 +27,8 @@ use super::super::cursor; use super::super::style; use super::super::terminal::terminal; +use super::super::input::input; + use Context; use std::fmt::Display; @@ -126,6 +128,11 @@ impl Crossterm { return style::TerminalColor::new(self.context.clone()); } + pub fn input(&self) -> input::TerminalInput + { + return input::TerminalInput::new(self.context.clone()); + } + /// Wraps an displayable object so it can be formatted with colors and attributes. /// /// Check `/examples/color` in the library for more specific examples. diff --git a/src/shared/screen.rs b/src/shared/screen.rs index 3494a53..6135fc3 100644 --- a/src/shared/screen.rs +++ b/src/shared/screen.rs @@ -107,6 +107,7 @@ impl AlternateScreen { context: context.clone(), command_id: command_id, }; + screen.to_alternate(); return screen; } @@ -134,6 +135,7 @@ impl AlternateScreen { let mut mx = &state_manager.get(self.command_id); { let mut command = mx.lock().unwrap(); + command.execute(); } }