diff --git a/Cargo.toml b/Cargo.toml index 9df65b0..efa0e2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ readme = "README.md" winapi = { version = "0.3.5", features = ["winbase","winuser","consoleapi","processenv","wincon", "handleapi","errhandlingapi"] } [target.'cfg(unix)'.dependencies] -libc = "0.2.37" +libc = "0.2.43" termios = "0.3.0" [lib] diff --git a/examples/examples.rs b/examples/examples.rs index f38c13c..da8bbe9 100644 --- a/examples/examples.rs +++ b/examples/examples.rs @@ -19,5 +19,7 @@ use std::io::Write; use std::{thread,time}; fn main() { - + input::keyboard::async_input::read_async_demo(); + terminal::raw_mode::print_wait_screen_on_alternate_window(); + thread::sleep(time::Duration::from_millis(2000)); } diff --git a/examples/program_examples/command_bar.rs b/examples/program_examples/command_bar.rs index b6e4159..6c6d6c7 100644 --- a/examples/program_examples/command_bar.rs +++ b/examples/program_examples/command_bar.rs @@ -1,9 +1,9 @@ extern crate crossterm; -use crossterm::{Screen, Crossterm}; +use crossterm::{Screen, Crossterm, screen}; use crossterm::terminal::{terminal,Terminal, ClearType}; -use crossterm::cursor::TerminalCursor; - +use crossterm::cursor::{TerminalCursor, cursor}; +use crossterm::input::input; use std::sync::{Arc,Mutex}; use std::io::Read; use std::{thread,time}; @@ -13,42 +13,45 @@ fn main() { let screen = Screen::new(true); let crossterm = Crossterm::new(&screen); - - let input = crossterm.input(); - let terminal = crossterm.terminal(); let cursor = crossterm.cursor(); - - let mut stdin = input.read_async().bytes(); cursor.hide(); let mut input_buf = Arc::new(Mutex::new(String::new())); + let threads = log(input_buf.clone(),&screen); + + let mut count = 0; - let threads = log(input_buf.clone()); + thread::spawn(move || { + let input = input(&screen); + let mut stdin = input.read_async().bytes(); - loop - { - let a = stdin.next(); - - match a { - Some(Ok(b'\n')) => + loop { - input_buf.lock().unwrap().clear(); + let a = stdin.next(); - // need to start receiving again because if pressed enter then async reading will stop - stdin = input.read_async().bytes(); - } - Some(Ok(val)) => - { - input_buf.lock().unwrap().push(val as char); - } - _ => {} - } + match a { + Some(Ok(13)) => + { + input_buf.lock().unwrap().clear(); + + // need to start receiving again because if pressed enter then async reading will stop +// stdin = input.read_async().bytes(); + } + Some(Ok(val)) => + { +// println!("{:?}",a); + input_buf.lock().unwrap().push(a.unwrap().unwrap() as char); + } + _ => {} + } + + thread::sleep(time::Duration::from_millis(100)); + count += 1; + } + }).join(); - thread::sleep(time::Duration::from_millis(100)); - count += 1; - } for thread in threads { @@ -58,18 +61,20 @@ fn main() { cursor.show(); } -fn log(input_buf: Arc>) -> Vec> +fn log(input_buf: Arc>, screen: &Screen) -> Vec> { let mut threads = Vec::with_capacity(10); - let (_, term_height) = terminal(&Screen::default()).terminal_size(); - for i in 0..10 + let (_, term_height) = terminal(&screen).terminal_size(); + + for i in 0..1 { let input_buffer = input_buf.clone(); + let clone_stdout = screen.stdout.clone(); + + let crossterm = Crossterm::from(screen.stdout.clone()); let join = thread::spawn( move || { - - let crossterm = Crossterm::new(&Screen::new(true)); let cursor = crossterm.cursor(); let terminal = crossterm.terminal(); diff --git a/examples/program_examples/snake/command_bar_working.rs b/examples/program_examples/snake/command_bar_working.rs new file mode 100644 index 0000000..8c9cf11 --- /dev/null +++ b/examples/program_examples/snake/command_bar_working.rs @@ -0,0 +1,98 @@ +extern crate crossterm; + +use crossterm::{Screen, Crossterm}; +use crossterm::terminal::{terminal,Terminal, ClearType}; +use crossterm::cursor::TerminalCursor; + +use std::sync::{Arc,Mutex}; +use std::io::Read; +use std::{thread,time}; + +fn main() { + use crossterm::color; + + let screen = Screen::new(true); + let crossterm = Arc::new(Crossterm::new(&screen)); + + let cursor = crossterm.cursor(); + cursor.hide(); + + let mut input_buf = Arc::new(Mutex::new(String::new())); + + let mut count = 0; + + let threads = log(input_buf.clone(),crossterm.clone()); + + let crossterm_clone = crossterm.clone(); + + thread::spawn(move || { + let input = crossterm_clone.input(); + let mut stdin = input.read_async().bytes(); + + loop + { + let a = stdin.next(); + + match a { + Some(Ok(13)) => + { + input_buf.lock().unwrap().clear(); + // need to start receiving again because if pressed enter then async reading will stop +// stdin = input.read_async().bytes(); + } + Some(Ok(val)) => + { +// println!("{}",val); + input_buf.lock().unwrap().push(a.unwrap().unwrap() as u8 as char); + } + _ => {} + } + + thread::sleep(time::Duration::from_millis(100)); + count += 1; + } + }).join(); + + + for thread in threads + { + thread.join(); + } + + cursor.show(); +} + +fn log(input_buf: Arc>, crossterm: Arc) -> Vec> +{ + let mut threads = Vec::with_capacity(10); + + let (_, term_height) = crossterm.terminal().terminal_size(); + + for i in 0..1 + { + let input_buffer = input_buf.clone(); + let crossterm_clone = crossterm.clone(); + let join = thread::spawn( move || { + + let cursor = crossterm_clone.cursor(); + let terminal = crossterm_clone.terminal(); + + for j in 0..1000 + { + swap_write(format!("Some output: {} from thread: {}", j, i).as_ref(), &input_buffer.lock().unwrap(), &terminal, &cursor, term_height); + thread::sleep(time::Duration::from_millis(300)); + } + }); + + threads.push(join); + } + + return threads; +} + +pub fn swap_write(msg: &str, input_buf: &String, terminal: &Terminal, cursor: &TerminalCursor, term_height: u16) { + cursor.goto(0, term_height); + terminal.clear(ClearType::CurrentLine); + terminal.write(format!("{}\r\n", msg)); + terminal.write(format!(">{}", input_buf)); +} \ No newline at end of file diff --git a/examples/program_examples/snake/src/main.rs b/examples/program_examples/snake/src/main.rs index 21de7c9..16c2d73 100644 --- a/examples/program_examples/snake/src/main.rs +++ b/examples/program_examples/snake/src/main.rs @@ -78,7 +78,9 @@ fn main() { map.spawn_food(&free_positions, &screen); } } + drop(screen); } + game_over_screen(); } diff --git a/examples/program_examples/snake/src/variables.rs b/examples/program_examples/snake/src/variables.rs index 0311631..e4d097e 100644 --- a/examples/program_examples/snake/src/variables.rs +++ b/examples/program_examples/snake/src/variables.rs @@ -41,7 +41,7 @@ impl Position pub fn remove(&self, screen: &Screen) { cursor(screen).goto(self.x as u16, self.y as u16); - terminal(&screen).write(" "); + terminal(&screen).write(" "); } } diff --git a/examples/terminal/raw_mode.rs b/examples/terminal/raw_mode.rs index 5d84b2d..926ef6f 100644 --- a/examples/terminal/raw_mode.rs +++ b/examples/terminal/raw_mode.rs @@ -42,6 +42,6 @@ pub fn print_wait_screen_on_alternate_window() { { print_wait_screen(&mut alternate.screen); } - + drop(screen); println!("Whe are back at the main screen"); } diff --git a/src/common/commands/unix_command.rs b/src/common/commands/unix_command.rs index 62cdc26..a60187f 100644 --- a/src/common/commands/unix_command.rs +++ b/src/common/commands/unix_command.rs @@ -2,9 +2,11 @@ use super::{ IStateCommand}; use kernel::unix_kernel::terminal; -use termios::{tcsetattr, Termios, CREAD, ECHO, ICANON, TCSAFLUSH}; +use termios::{tcsetattr, Termios, CREAD, ECHO, ICANON, TCSAFLUSH, BRKINT, ICRNL, INPCK, ISTRIP, IXON, OPOST, CS8, IEXTEN, ISIG,VTIME, VMIN}; +use libc::STDIN_FILENO; +use std::sync::{Once, ONCE_INIT}; +static TERMINAL_MODE: Once = ONCE_INIT; -const FD_STDIN: ::std::os::unix::io::RawFd = 1; use std::io::{Error, ErrorKind, Result}; @@ -17,16 +19,25 @@ impl NoncanonicalModeCommand { NoncanonicalModeCommand {} } } +static mut ORIGINAL: Option = None; impl NoncanonicalModeCommand { pub fn enable(&mut self) -> Result<()> { // Set noncanonical mode - if let Ok(orig) = Termios::from_fd(FD_STDIN) { + if let Ok(orig) = Termios::from_fd(STDIN_FILENO) { + TERMINAL_MODE.call_once(|| { + unsafe { ORIGINAL = Some(orig.clone()); } + }); + let mut noncan = orig.clone(); - noncan.c_lflag &= !ICANON; - noncan.c_lflag &= !ECHO; - noncan.c_lflag &= !CREAD; - tcsetattr(FD_STDIN, TCSAFLUSH, &noncan)?; + noncan.c_iflag &= !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + noncan.c_oflag &= !(OPOST); + noncan.c_cflag |= (CS8); + noncan.c_lflag &= !(ECHO | ICANON | IEXTEN | ISIG); + noncan.c_cc[VMIN] = 0; + noncan.c_cc[VTIME] = 1; + + tcsetattr(STDIN_FILENO, TCSAFLUSH, &noncan)?; } else { return Err(Error::new( ErrorKind::Other, @@ -37,20 +48,36 @@ impl NoncanonicalModeCommand { } pub fn disable(&self) -> Result<()> { - // Disable noncanonical mode - if let Ok(orig) = Termios::from_fd(FD_STDIN) { - let mut noncan = orig.clone(); - noncan.c_lflag &= ICANON; - noncan.c_lflag &= ECHO; - noncan.c_lflag &= CREAD; - tcsetattr(FD_STDIN, TCSAFLUSH, &noncan)?; - } else { - return Err(Error::new( - ErrorKind::Other, - "Could not set console mode when enabling raw mode", - )); + unsafe { + if let Some(original) = ORIGINAL + { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &original)?; + } } + + Ok(()) + } +} + +// This command is used for enabling and disabling raw mode for the terminal. +/// This command is used for enabling and disabling raw mode for the terminal. +pub struct RawModeCommand; + +impl RawModeCommand { + pub fn new() -> Self { + return RawModeCommand {} + } + + /// Enables raw mode. + pub fn enable(&mut self) -> Result<()> { + terminal::into_raw_mode(); + Ok(()) + } + + /// Disables raw mode. + pub fn disable(&mut self) -> Result<()> { + terminal::disable_raw_mode(); Ok(()) } } diff --git a/src/common/crossterm.rs b/src/common/crossterm.rs index 240b172..bcc09f9 100644 --- a/src/common/crossterm.rs +++ b/src/common/crossterm.rs @@ -122,4 +122,11 @@ impl<'crossterm> Crossterm { D: Display, { style::ObjectStyle::new().apply_to(val) } +} + +impl From> for Crossterm +{ + fn from(stdout: Arc) -> Self { + Crossterm { stdout: stdout } + } } \ No newline at end of file diff --git a/src/common/screen/raw.rs b/src/common/screen/raw.rs index 4af6c39..9796001 100644 --- a/src/common/screen/raw.rs +++ b/src/common/screen/raw.rs @@ -28,11 +28,12 @@ impl RawScreen { pub fn into_raw_mode() -> io::Result<()> { #[cfg(not(target_os = "windows"))] - let mut command = unix_command::NoncanonicalModeCommand::new(); + let mut command = unix_command::RawModeCommand::new(); #[cfg(target_os = "windows")] let mut command = win_commands::RawModeCommand::new(); - command.enable()?; + let result = command.enable(); + Ok(()) } @@ -40,9 +41,9 @@ impl RawScreen { pub fn disable_raw_modes() -> io::Result<()> { #[cfg(not(target_os = "windows"))] - let command = unix_command::NoncanonicalModeCommand::new(); + let mut command = unix_command::RawModeCommand::new(); #[cfg(target_os = "windows")] - let command = win_commands::RawModeCommand::new(); + let mut command = win_commands::RawModeCommand::new(); command.disable()?; return Ok(()) diff --git a/src/common/screen/screen.rs b/src/common/screen/screen.rs index 0e75e5a..569557a 100644 --- a/src/common/screen/screen.rs +++ b/src/common/screen/screen.rs @@ -57,7 +57,7 @@ impl Screen if raw_mode { let screen = Screen { stdout: Arc::new(TerminalOutput::new(true)), buffer: Vec::new() }; - RawScreen::into_raw_mode(); + RawScreen::into_raw_mode().unwrap(); return screen; } diff --git a/src/kernel/unix_kernel/terminal.rs b/src/kernel/unix_kernel/terminal.rs index dcce331..f8f0b70 100644 --- a/src/kernel/unix_kernel/terminal.rs +++ b/src/kernel/unix_kernel/terminal.rs @@ -107,35 +107,70 @@ pub fn make_raw(termios: &mut Termios) { static mut ORIGINAL_TERMINAL_MODE: Option = None; -/// Get the current terminal mode. -pub fn get_terminal_mode() -> io::Result { - extern "C" { - pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int; - } +pub fn into_raw_mode() -> io::Result<()> +{ + 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(); + unsafe { - if let Some(original_mode) = ORIGINAL_TERMINAL_MODE + if let None = ORIGINAL_TERMINAL_MODE { - return Ok(original_mode.clone()) + ORIGINAL_TERMINAL_MODE = Some(original.clone()) } - else { - let mut termios = mem::zeroed(); - is_true(tcgetattr(0, &mut termios))?; - ORIGINAL_TERMINAL_MODE = Some(termios.clone()); - - return Ok(termios) - } - } + + make_raw(&mut termios); + tcsetattr(fd, TCSADRAIN, &termios)?; + Ok(()) +} + +pub fn disable_raw_mode() -> io::Result<()> +{ + 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() + } + }; + + if let Some(original) = unsafe { ORIGINAL_TERMINAL_MODE } + { + tcsetattr(fd, TCSADRAIN, &original)?; + } + Ok(()) } /// Get the TTY device. /// /// This allows for getting stdio representing _only_ the TTY, and not other streams. pub fn get_tty() -> io::Result { - fs::OpenOptions::new() - .read(true) - .write(true) - .open("/dev/tty") + let mut tty_f: fs::File = unsafe { ::std::mem::zeroed() }; + + 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() + } + }; + + return Ok(tty_f); } pub fn read_char() -> io::Result { diff --git a/src/kernel/windows_kernel/ansi_support.rs b/src/kernel/windows_kernel/ansi_support.rs index 8e6f6f5..cef8c75 100644 --- a/src/kernel/windows_kernel/ansi_support.rs +++ b/src/kernel/windows_kernel/ansi_support.rs @@ -6,7 +6,6 @@ static mut HAS_BEEN_TRIED_TO_ENABLE: bool = false; static mut IS_ANSI_ON_WINDOWS_ENABLED: Option = None; static mut DOES_WINDOWS_SUPPORT_ANSI: Option = None; static ENABLE_ANSI: Once = ONCE_INIT; - use common::commands::win_commands::EnableAnsiCommand; use common::commands::IEnableAnsiCommand; diff --git a/src/modules/terminal/ansi_terminal.rs b/src/modules/terminal/ansi_terminal.rs index a2cdbbe..1f52a65 100644 --- a/src/modules/terminal/ansi_terminal.rs +++ b/src/modules/terminal/ansi_terminal.rs @@ -5,6 +5,7 @@ use super::*; /// This struct is an ansi escape code implementation for terminal related actions. pub struct AnsiTerminal; +use cursor::TerminalCursor; impl AnsiTerminal { pub fn new() -> AnsiTerminal { @@ -17,6 +18,7 @@ impl ITerminal for AnsiTerminal { match clear_type { ClearType::All => { stdout.write_str(csi!("2J")); + TerminalCursor::new(stdout).goto(0,0); } ClearType::FromCursorDown => { stdout.write_str(csi!("J"));