diff --git a/examples/examples.rs b/examples/examples.rs index 75c8457..1b0399f 100644 --- a/examples/examples.rs +++ b/examples/examples.rs @@ -16,7 +16,6 @@ mod some_types; mod input; use std::io::Write; -use std::{thread,time}; use crossterm::style::{style, Color, DisplayableObject}; use crossterm::terminal::terminal; @@ -25,16 +24,28 @@ use crossterm::Screen; use crossterm::output::TerminalOutput; use crossterm::cursor::TerminalCursor; +use crossterm::terminal::Terminal; +use std::{thread,time}; + fn main() { - let screen = Screen::default(); - let cursor = TerminalCursor::new(&screen.stdout); + let screen = Screen::new(false); + let terminal = Terminal::new(&screen.stdout); - cursor.goto(5, 5); - let (x, y) = cursor.pos(); + // get terminal size + let (x, y) = terminal.terminal_size(); - assert_eq!(x, 5); - assert_eq!(y, 5); + // set size to 30, 50 + terminal.set_size(30,50); - println!("x: {} y: {}", x,y); + // if we uncomment the line below the code will work perfectly fine and we will get the new dimensions. + // if we comment this line the terminal dimensions gotten from terminal_size() are equal to the old dimensions. + + // thread::sleep(time::Duration::from_millis(20)); + + // get new dimensions + let (x_new, y_new) = terminal.terminal_size(); + + println!("old width: {} old height: {}", x, y); + println!("new width: {} new height: {}", x_new, y_new); } diff --git a/src/common/commands/unix_command.rs b/src/common/commands/unix_command.rs index a60187f..03b5d9a 100644 --- a/src/common/commands/unix_command.rs +++ b/src/common/commands/unix_command.rs @@ -44,6 +44,8 @@ impl NoncanonicalModeCommand { "Could not set console mode when enabling raw mode", )); } + + unsafe { terminal::RAW_MODE_ENABLED_BY_USER = true } Ok(()) } @@ -56,6 +58,7 @@ impl NoncanonicalModeCommand { } } + unsafe { terminal::RAW_MODE_ENABLED_BY_USER = false } Ok(()) } } @@ -72,12 +75,16 @@ impl RawModeCommand { /// Enables raw mode. pub fn enable(&mut self) -> Result<()> { terminal::into_raw_mode(); + + unsafe { terminal::RAW_MODE_ENABLED_BY_USER = true } Ok(()) } /// Disables raw mode. pub fn disable(&mut self) -> Result<()> { terminal::disable_raw_mode(); + + unsafe { terminal::RAW_MODE_ENABLED_BY_USER = false } Ok(()) } } diff --git a/src/common/functions.rs b/src/common/functions.rs index ad48721..aaa9be9 100644 --- a/src/common/functions.rs +++ b/src/common/functions.rs @@ -23,7 +23,8 @@ pub fn get_terminal_size() -> (u16, u16) { /// Get the cursor position based on the current platform. pub fn get_cursor_position(stdout: &Arc) -> (u16, u16) { #[cfg(unix)] - return pos(stdout); + return pos().expect("Valide position"); +// return pos().unwrap_or_else(|x| { return (0,0) }); #[cfg(windows)] return pos(); } diff --git a/src/kernel/unix_kernel/terminal.rs b/src/kernel/unix_kernel/terminal.rs index f8f0b70..35b8fe2 100644 --- a/src/kernel/unix_kernel/terminal.rs +++ b/src/kernel/unix_kernel/terminal.rs @@ -1,18 +1,22 @@ //! This module contains all `unix` specific terminal related logic. -use self::libc::{c_int, c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ}; -use common::commands::unix_command::NoncanonicalModeCommand; +use self::libc::{c_int, c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ, TIOCSWINSZ, signal, SIG_IGN, SIGWINCH}; + +use common::commands::unix_command::RawModeCommand; use {libc, TerminalOutput, Screen}; pub use libc::termios; use std::sync::Arc; -use std::io::{self, Error, ErrorKind, Read}; +use std::io::{self, Error, ErrorKind, Read, Write}; use std::os::unix::io::AsRawFd; use std::time::{Duration, SystemTime}; use std::{fs, mem}; -use termios::{cfmakeraw, tcsetattr, Termios, TCSADRAIN}; +use termios::{cfmakeraw, tcsetattr, Termios, TCSADRAIN, CREAD, ECHO, ICANON, TCSAFLUSH}; + +const FD_STDIN: ::std::os::unix::io::RawFd = 1; use Crossterm; +use input::input; /// A representation of the size of the current terminal. #[repr(C)] @@ -35,58 +39,115 @@ pub fn terminal_size() -> (u16, u16) { ws_xpixel: 0, ws_ypixel: 0, }; + let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &us) }; + if r == 0 { // because crossterm works starts counting at 0 and unix terminal starts at cell 1 you have subtract one to get 0-based results. - (us.cols - 1, us.rows - 1) + (us.cols, us.rows) } else { - (0, 0) + (0,0) } } -/// Get the current cursor position. -pub fn pos(stdout: &Arc) -> (u16, u16) { - let mut crossterm = Crossterm::new(&Screen::default()); - let input = crossterm.input(); +// maybe this coudl be used over ANSI escape code +//pub fn set_terminal_size() -> io::Result<(u16,u16)> +//{ +// let new_size = UnixSize { +// rows: 40, +// cols: 40, +// ws_xpixel: 0, +// ws_ypixel: 0, +// }; +// +// let r = unsafe { ioctl(STDOUT_FILENO, TIOCSWINSZ, &new_size) }; +// +// if r == 0 { +// // because crossterm works starts counting at 0 and unix terminal starts at cell 1 you have subtract one to get 0-based results. +// (us.cols, us.rows) +// } else { +// Err(ErrorKind::Other, "Could not resize try ansi escape codes") +// (0, 0) +// } +//} - let delimiter = b'R'; - let mut stdin = input.read_until_async(delimiter); +pub fn pos() -> io::Result<(u16, u16)> +{ + let screen = Screen::new(false); - // Where is the cursor? - // Use `ESC [ 6 n`. - stdout.write_str("\x1B[6n"); + // if we enable raw modes with screen, this could cause problems if raw mode is already enabled in applicaition. + // I am not completely happy with this approach so feel free to find an other way. - let mut buf: [u8; 1] = [0]; - let mut read_chars = Vec::new(); - - let timeout = Duration::from_millis(2000); - let now = SystemTime::now(); - - // Either consume all data up to R or wait for a timeout. - while buf[0] != delimiter && now.elapsed().unwrap() < timeout { - if let Ok(c) = stdin.read(&mut buf) { - if c >= 0 { - read_chars.push(buf[0]); - } + unsafe { + if !RAW_MODE_ENABLED_BY_USER || !RAW_MODE_ENABLED_BY_SYSTEM { + // set this boolean so that we know that the systems has enabled raw mode. + RAW_MODE_ENABLED_BY_SYSTEM = true; + into_raw_mode(); } } - if read_chars.len() == 0 { - return (0, 0); + // Where is the cursor? + // Use `ESC [ 6 n`. + let mut stdout = io::stdout(); + + // Write command + stdout.write(b"\x1B[6n")?; + stdout.flush()?; + + let mut buf = [0u8; 2]; + + // Expect `ESC[` + io::stdin().read_exact(&mut buf)?; + if buf[0] != 0x1B || buf[1] as char != '[' { + return Err(Error::new(ErrorKind::Other, "test")); } - // The answer will look like `ESC [ Cy ; Cx R`. + // Read rows and cols through a ad-hoc integer parsing function + let read_num: fn() -> Result<(i32, char), Error> = || -> Result<(i32, char), Error> { + let mut num = 0; + let mut c: char; - read_chars.pop(); // remove trailing R. - let read_str = String::from_utf8(read_chars).unwrap(); - let beg = read_str.rfind('[').unwrap(); - let coords: String = read_str.chars().skip(beg + 1).collect(); - let mut nums = coords.split(';'); + loop { + let mut buf = [0u8; 1]; + io::stdin().read_exact(&mut buf)?; + c = buf[0] as char; + if let Some(d) = c.to_digit(10) { + num = if num == 0 { 0 } else { num * 10 }; + num += d as i32; + } else { + break; + } + } - let cy = nums.next().unwrap().parse::().unwrap(); - let cx = nums.next().unwrap().parse::().unwrap(); + Ok((num, c)) + }; - (cx, cy) + // Read rows and expect `;` + let (rows, c) = read_num()?; + if c != ';' { + return Err(Error::new(ErrorKind::Other, "test")); + } + + // Read cols + let (cols, c) = read_num()?; + + // Expect `R` + let res = if c == 'R' { + Ok(((cols -1) as u16, (rows -1) as u16)) + } else { + return Err(Error::new(ErrorKind::Other, "test")); + }; + + // If raw mode is enabled from else where in the application (by the user) we do not want to disable raw modes. + // I am not completely happy with this approach so feel free to find an other way. + unsafe { + if RAW_MODE_ENABLED_BY_SYSTEM && !RAW_MODE_ENABLED_BY_USER { + RAW_MODE_ENABLED_BY_SYSTEM = false; + disable_raw_mode(); + } + } + + return res } /// Set the terminal mode to the given mode. @@ -106,6 +167,8 @@ pub fn make_raw(termios: &mut Termios) { } static mut ORIGINAL_TERMINAL_MODE: Option = None; +static mut RAW_MODE_ENABLED_BY_SYSTEM: bool = false; +pub static mut RAW_MODE_ENABLED_BY_USER: bool = false; pub fn into_raw_mode() -> io::Result<()> { @@ -132,6 +195,8 @@ pub fn into_raw_mode() -> io::Result<()> make_raw(&mut termios); tcsetattr(fd, TCSADRAIN, &termios)?; + + Ok(()) } diff --git a/src/modules/cursor/test.rs b/src/modules/cursor/test.rs index 5254053..74b0d9a 100644 --- a/src/modules/cursor/test.rs +++ b/src/modules/cursor/test.rs @@ -1,4 +1,3 @@ -use modules::cursor::winapi_cursor::WinApiCursor; use modules::cursor::ansi_cursor::AnsiCursor; use modules::cursor::ITerminalCursor; @@ -8,6 +7,7 @@ use Screen; /* ======================== WinApi =========================== */ #[cfg(windows)] mod winapi_tests { + use modules::cursor::winapi_cursor::WinApiCursor; use super::*; #[test] @@ -79,12 +79,14 @@ fn goto_ansi() fn try_enable_ansi() -> bool { - if cfg!(target_os = "windows") { - #[cfg(windows)] - use kernel::windows_kernel::ansi_support::try_enable_ansi_support; + #[cfg(windows)] + { + if cfg!(target_os = "windows") { + use kernel::windows_kernel::ansi_support::try_enable_ansi_support; - if !try_enable_ansi_support() + if !try_enable_ansi_support() { return false; } + } } return true; diff --git a/src/modules/input/mod.rs b/src/modules/input/mod.rs index fe9cec4..5045c30 100644 --- a/src/modules/input/mod.rs +++ b/src/modules/input/mod.rs @@ -46,11 +46,6 @@ pub struct AsyncReader { recv: mpsc::Receiver>, } -impl AsyncReader -{ - -} - impl Read for AsyncReader { /// Read from the byte stream. /// diff --git a/src/modules/output/test.rs b/src/modules/output/test.rs index 6b23d98..e9d9d92 100644 --- a/src/modules/output/test.rs +++ b/src/modules/output/test.rs @@ -1,4 +1,3 @@ -use modules::output::winapi_output::WinApiOutput; use modules::output::ansi_output::AnsiOutput; use modules::output::IStdout; @@ -7,7 +6,7 @@ use Screen; #[cfg(windows)] mod winapi_tests { - + use modules::output::winapi_output::WinApiOutput; use super::*; /* ======================== WinApi =========================== */ #[test] @@ -66,13 +65,15 @@ fn is_valid_write(result: ::std::io::Result, str_length: usize) fn try_enable_ansi() -> bool { - if cfg!(target_os = "windows") { - #[cfg(windows)] - use kernel::windows_kernel::ansi_support::try_enable_ansi_support; + #[cfg(windows)] + { + if cfg!(target_os = "windows") { + use kernel::windows_kernel::ansi_support::try_enable_ansi_support; - if !try_enable_ansi_support() - { return false; } - } + if !try_enable_ansi_support() + { return false; } + } + } return true; -} +} \ No newline at end of file diff --git a/src/modules/terminal/ansi_terminal.rs b/src/modules/terminal/ansi_terminal.rs index 73e5794..32f5241 100644 --- a/src/modules/terminal/ansi_terminal.rs +++ b/src/modules/terminal/ansi_terminal.rs @@ -7,6 +7,7 @@ use super::*; pub struct AnsiTerminal; use cursor::TerminalCursor; + impl AnsiTerminal { pub fn new() -> AnsiTerminal { AnsiTerminal {} diff --git a/src/modules/terminal/test.rs b/src/modules/terminal/test.rs index 0bad62f..cb6751f 100644 --- a/src/modules/terminal/test.rs +++ b/src/modules/terminal/test.rs @@ -1,4 +1,3 @@ -use modules::terminal::winapi_terminal::WinApiTerminal; use modules::terminal::ansi_terminal::AnsiTerminal; use modules::terminal::ITerminal; @@ -8,6 +7,7 @@ use Screen; /* ======================== WinApi =========================== */ #[cfg(windows)] mod winapi_tests { + use modules::terminal::winapi_terminal::WinApiTerminal; use super::*; #[test] @@ -29,28 +29,34 @@ mod winapi_tests { #[test] fn resize_ansi() { + use std::{thread, time}; if try_enable_ansi() { let screen = Screen::default(); - let terminal = WinApiTerminal::new(); + let terminal = AnsiTerminal::new(); - terminal.set_size(10,10, &screen.stdout); + terminal.set_size(50,50, &screen.stdout); + + // see issue: https://github.com/eminence/terminal-size/issues/11 + thread::sleep(time::Duration::from_millis(30)); let (x, y) = terminal.terminal_size(&screen.stdout); - assert_eq!(x, 10); - assert_eq!(y, 10); + assert_eq!(x, 50); + assert_eq!(y, 50); } } fn try_enable_ansi() -> bool { - if cfg!(target_os = "windows") { - #[cfg(windows)] - use kernel::windows_kernel::ansi_support::try_enable_ansi_support; + #[cfg(windows)] + { + if cfg!(target_os = "windows") { + use kernel::windows_kernel::ansi_support::try_enable_ansi_support; - if !try_enable_ansi_support() - { return false; } - } + if !try_enable_ansi_support() + { return false; } + } + } return true; }