diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/console/src/windows_term | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/console/src/windows_term')
-rw-r--r-- | vendor/console/src/windows_term/colors.rs | 451 | ||||
-rw-r--r-- | vendor/console/src/windows_term/mod.rs | 563 |
2 files changed, 1014 insertions, 0 deletions
diff --git a/vendor/console/src/windows_term/colors.rs b/vendor/console/src/windows_term/colors.rs new file mode 100644 index 0000000..dc8209d --- /dev/null +++ b/vendor/console/src/windows_term/colors.rs @@ -0,0 +1,451 @@ +use std::io; +use std::mem; +use std::os::windows::io::AsRawHandle; +use std::str::Bytes; + +use windows_sys::Win32::Foundation::HANDLE; +use windows_sys::Win32::System::Console::{ + GetConsoleScreenBufferInfo, SetConsoleTextAttribute, CONSOLE_SCREEN_BUFFER_INFO, + FOREGROUND_BLUE as FG_BLUE, FOREGROUND_GREEN as FG_GREEN, FOREGROUND_INTENSITY as FG_INTENSITY, + FOREGROUND_RED as FG_RED, +}; + +use crate::Term; + +type WORD = u16; + +const FG_CYAN: WORD = FG_BLUE | FG_GREEN; +const FG_MAGENTA: WORD = FG_BLUE | FG_RED; +const FG_YELLOW: WORD = FG_GREEN | FG_RED; +const FG_WHITE: WORD = FG_BLUE | FG_GREEN | FG_RED; + +/// Query the given handle for information about the console's screen buffer. +/// +/// The given handle should represent a console. Otherwise, an error is +/// returned. +/// +/// This corresponds to calling [`GetConsoleScreenBufferInfo`]. +/// +/// [`GetConsoleScreenBufferInfo`]: https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo +pub fn screen_buffer_info(h: HANDLE) -> io::Result<ScreenBufferInfo> { + unsafe { + let mut info: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed(); + let rc = GetConsoleScreenBufferInfo(h, &mut info); + if rc == 0 { + return Err(io::Error::last_os_error()); + } + Ok(ScreenBufferInfo(info)) + } +} + +/// Set the text attributes of the console represented by the given handle. +/// +/// This corresponds to calling [`SetConsoleTextAttribute`]. +/// +/// [`SetConsoleTextAttribute`]: https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute +pub fn set_text_attributes(h: HANDLE, attributes: u16) -> io::Result<()> { + if unsafe { SetConsoleTextAttribute(h, attributes) } == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +/// Represents console screen buffer information such as size, cursor position +/// and styling attributes. +/// +/// This wraps a [`CONSOLE_SCREEN_BUFFER_INFO`]. +/// +/// [`CONSOLE_SCREEN_BUFFER_INFO`]: https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str +#[derive(Clone)] +pub struct ScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO); + +impl ScreenBufferInfo { + /// Returns the character attributes associated with this console. + /// + /// This corresponds to `wAttributes`. + /// + /// See [`char info`] for more details. + /// + /// [`char info`]: https://docs.microsoft.com/en-us/windows/console/char-info-str + pub fn attributes(&self) -> u16 { + self.0.wAttributes + } +} + +/// A Windows console. +/// +/// This represents a very limited set of functionality available to a Windows +/// console. In particular, it can only change text attributes such as color +/// and intensity. This may grow over time. If you need more routines, please +/// file an issue and/or PR. +/// +/// There is no way to "write" to this console. Simply write to +/// stdout or stderr instead, while interleaving instructions to the console +/// to change text attributes. +/// +/// A common pitfall when using a console is to forget to flush writes to +/// stdout before setting new text attributes. +#[derive(Debug)] +pub struct Console { + kind: HandleKind, + start_attr: TextAttributes, + cur_attr: TextAttributes, +} + +#[derive(Clone, Copy, Debug)] +enum HandleKind { + Stdout, + Stderr, +} + +impl HandleKind { + fn handle(&self) -> HANDLE { + match *self { + HandleKind::Stdout => io::stdout().as_raw_handle() as HANDLE, + HandleKind::Stderr => io::stderr().as_raw_handle() as HANDLE, + } + } +} + +impl Console { + /// Get a console for a standard I/O stream. + fn create_for_stream(kind: HandleKind) -> io::Result<Console> { + let h = kind.handle(); + let info = screen_buffer_info(h)?; + let attr = TextAttributes::from_word(info.attributes()); + Ok(Console { + kind: kind, + start_attr: attr, + cur_attr: attr, + }) + } + + /// Create a new Console to stdout. + /// + /// If there was a problem creating the console, then an error is returned. + pub fn stdout() -> io::Result<Console> { + Self::create_for_stream(HandleKind::Stdout) + } + + /// Create a new Console to stderr. + /// + /// If there was a problem creating the console, then an error is returned. + pub fn stderr() -> io::Result<Console> { + Self::create_for_stream(HandleKind::Stderr) + } + + /// Applies the current text attributes. + fn set(&mut self) -> io::Result<()> { + set_text_attributes(self.kind.handle(), self.cur_attr.to_word()) + } + + /// Apply the given intensity and color attributes to the console + /// foreground. + /// + /// If there was a problem setting attributes on the console, then an error + /// is returned. + pub fn fg(&mut self, intense: Intense, color: Color) -> io::Result<()> { + self.cur_attr.fg_color = color; + self.cur_attr.fg_intense = intense; + self.set() + } + + /// Apply the given intensity and color attributes to the console + /// background. + /// + /// If there was a problem setting attributes on the console, then an error + /// is returned. + pub fn bg(&mut self, intense: Intense, color: Color) -> io::Result<()> { + self.cur_attr.bg_color = color; + self.cur_attr.bg_intense = intense; + self.set() + } + + /// Reset the console text attributes to their original settings. + /// + /// The original settings correspond to the text attributes on the console + /// when this `Console` value was created. + /// + /// If there was a problem setting attributes on the console, then an error + /// is returned. + pub fn reset(&mut self) -> io::Result<()> { + self.cur_attr = self.start_attr; + self.set() + } +} + +/// A representation of text attributes for the Windows console. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +struct TextAttributes { + fg_color: Color, + fg_intense: Intense, + bg_color: Color, + bg_intense: Intense, +} + +impl TextAttributes { + fn to_word(&self) -> WORD { + let mut w = 0; + w |= self.fg_color.to_fg(); + w |= self.fg_intense.to_fg(); + w |= self.bg_color.to_bg(); + w |= self.bg_intense.to_bg(); + w + } + + fn from_word(word: WORD) -> TextAttributes { + TextAttributes { + fg_color: Color::from_fg(word), + fg_intense: Intense::from_fg(word), + bg_color: Color::from_bg(word), + bg_intense: Intense::from_bg(word), + } + } +} + +/// Whether to use intense colors or not. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Intense { + Yes, + No, +} + +impl Intense { + fn to_bg(&self) -> WORD { + self.to_fg() << 4 + } + + fn from_bg(word: WORD) -> Intense { + Intense::from_fg(word >> 4) + } + + fn to_fg(&self) -> WORD { + match *self { + Intense::No => 0, + Intense::Yes => FG_INTENSITY, + } + } + + fn from_fg(word: WORD) -> Intense { + if word & FG_INTENSITY > 0 { + Intense::Yes + } else { + Intense::No + } + } +} + +/// The set of available colors for use with a Windows console. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Color { + Black, + Blue, + Green, + Red, + Cyan, + Magenta, + Yellow, + White, +} + +impl Color { + fn to_bg(&self) -> WORD { + self.to_fg() << 4 + } + + fn from_bg(word: WORD) -> Color { + Color::from_fg(word >> 4) + } + + fn to_fg(&self) -> WORD { + match *self { + Color::Black => 0, + Color::Blue => FG_BLUE, + Color::Green => FG_GREEN, + Color::Red => FG_RED, + Color::Cyan => FG_CYAN, + Color::Magenta => FG_MAGENTA, + Color::Yellow => FG_YELLOW, + Color::White => FG_WHITE, + } + } + + fn from_fg(word: WORD) -> Color { + match word & 0b111 { + FG_BLUE => Color::Blue, + FG_GREEN => Color::Green, + FG_RED => Color::Red, + FG_CYAN => Color::Cyan, + FG_MAGENTA => Color::Magenta, + FG_YELLOW => Color::Yellow, + FG_WHITE => Color::White, + _ => Color::Black, + } + } +} + +pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<()> { + use crate::ansi::AnsiCodeIterator; + use std::str::from_utf8; + + let s = from_utf8(bytes).expect("data to be printed is not an ansi string"); + let mut iter = AnsiCodeIterator::new(s); + + while !iter.rest_slice().is_empty() { + if let Some((part, is_esc)) = iter.next() { + if !is_esc { + out.write_through_common(part.as_bytes())?; + } else if part == "\x1b[0m" { + con.reset()?; + } else if let Some((intense, color, fg_bg)) = driver(parse_color, part) { + match fg_bg { + FgBg::Foreground => con.fg(intense, color), + FgBg::Background => con.bg(intense, color), + }?; + } else if driver(parse_attr, part).is_none() { + out.write_through_common(part.as_bytes())?; + } + } + } + + Ok(()) +} + +#[derive(Debug, PartialEq, Eq)] +enum FgBg { + Foreground, + Background, +} + +impl FgBg { + fn new(byte: u8) -> Option<Self> { + match byte { + b'3' => Some(Self::Foreground), + b'4' => Some(Self::Background), + _ => None, + } + } +} + +fn driver<Out>(parse: fn(Bytes<'_>) -> Option<Out>, part: &str) -> Option<Out> { + let mut bytes = part.bytes(); + + loop { + while bytes.next()? != b'\x1b' {} + + if let ret @ Some(_) = (parse)(bytes.clone()) { + return ret; + } + } +} + +// `driver(parse_color, s)` parses the equivalent of the regex +// \x1b\[(3|4)8;5;(8|9|1[0-5])m +// for intense or +// \x1b\[(3|4)([0-7])m +// for normal +fn parse_color(mut bytes: Bytes<'_>) -> Option<(Intense, Color, FgBg)> { + parse_prefix(&mut bytes)?; + + let fg_bg = FgBg::new(bytes.next()?)?; + let (intense, color) = match bytes.next()? { + b @ b'0'..=b'7' => (Intense::No, normal_color_ansi_from_byte(b)?), + b'8' => { + if &[bytes.next()?, bytes.next()?, bytes.next()?] != b";5;" { + return None; + } + (Intense::Yes, parse_intense_color_ansi(&mut bytes)?) + } + _ => return None, + }; + + parse_suffix(&mut bytes)?; + Some((intense, color, fg_bg)) +} + +// `driver(parse_attr, s)` parses the equivalent of the regex +// \x1b\[([1-8])m +fn parse_attr(mut bytes: Bytes<'_>) -> Option<u8> { + parse_prefix(&mut bytes)?; + let attr = match bytes.next()? { + attr @ b'1'..=b'8' => attr, + _ => return None, + }; + parse_suffix(&mut bytes)?; + Some(attr) +} + +fn parse_prefix(bytes: &mut Bytes<'_>) -> Option<()> { + if bytes.next()? == b'[' { + Some(()) + } else { + None + } +} + +fn parse_intense_color_ansi(bytes: &mut Bytes<'_>) -> Option<Color> { + let color = match bytes.next()? { + b'8' => Color::Black, + b'9' => Color::Red, + b'1' => match bytes.next()? { + b'0' => Color::Green, + b'1' => Color::Yellow, + b'2' => Color::Blue, + b'3' => Color::Magenta, + b'4' => Color::Cyan, + b'5' => Color::White, + _ => return None, + }, + _ => return None, + }; + Some(color) +} + +fn normal_color_ansi_from_byte(b: u8) -> Option<Color> { + let color = match b { + b'0' => Color::Black, + b'1' => Color::Red, + b'2' => Color::Green, + b'3' => Color::Yellow, + b'4' => Color::Blue, + b'5' => Color::Magenta, + b'6' => Color::Cyan, + b'7' => Color::White, + _ => return None, + }; + Some(color) +} + +fn parse_suffix(bytes: &mut Bytes<'_>) -> Option<()> { + if bytes.next()? == b'm' { + Some(()) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn color_parsing() { + let intense_color = "leading bytes \x1b[38;5;10m trailing bytes"; + let parsed = driver(parse_color, intense_color).unwrap(); + assert_eq!(parsed, (Intense::Yes, Color::Green, FgBg::Foreground)); + + let normal_color = "leading bytes \x1b[40m trailing bytes"; + let parsed = driver(parse_color, normal_color).unwrap(); + assert_eq!(parsed, (Intense::No, Color::Black, FgBg::Background)); + } + + #[test] + fn attr_parsing() { + let attr = "leading bytes \x1b[1m trailing bytes"; + let parsed = driver(parse_attr, attr).unwrap(); + assert_eq!(parsed, b'1'); + } +} diff --git a/vendor/console/src/windows_term/mod.rs b/vendor/console/src/windows_term/mod.rs new file mode 100644 index 0000000..c4ec193 --- /dev/null +++ b/vendor/console/src/windows_term/mod.rs @@ -0,0 +1,563 @@ +use std::cmp; +use std::env; +use std::ffi::OsStr; +use std::fmt::Display; +use std::io; +use std::iter::once; +use std::mem; +use std::os::raw::c_void; +use std::os::windows::ffi::OsStrExt; +use std::os::windows::io::AsRawHandle; +use std::slice; +use std::{char, mem::MaybeUninit}; + +use encode_unicode::error::InvalidUtf16Tuple; +use encode_unicode::CharExt; +use windows_sys::Win32::Foundation::{CHAR, HANDLE, INVALID_HANDLE_VALUE, MAX_PATH}; +use windows_sys::Win32::Storage::FileSystem::{ + FileNameInfo, GetFileInformationByHandleEx, FILE_NAME_INFO, +}; +use windows_sys::Win32::System::Console::{ + FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetConsoleCursorInfo, GetConsoleMode, + GetConsoleScreenBufferInfo, GetNumberOfConsoleInputEvents, GetStdHandle, ReadConsoleInputW, + SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleMode, SetConsoleTitleW, + CONSOLE_CURSOR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, KEY_EVENT, + KEY_EVENT_RECORD, STD_ERROR_HANDLE, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, +}; +use windows_sys::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY; + +use crate::common_term; +use crate::kb::Key; +use crate::term::{Term, TermTarget}; + +#[cfg(feature = "windows-console-colors")] +mod colors; + +#[cfg(feature = "windows-console-colors")] +pub use self::colors::*; + +const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x4; +pub const DEFAULT_WIDTH: u16 = 79; + +pub fn as_handle(term: &Term) -> HANDLE { + // convert between windows_sys::Win32::Foundation::HANDLE and std::os::windows::raw::HANDLE + term.as_raw_handle() as HANDLE +} + +pub fn is_a_terminal(out: &Term) -> bool { + let (fd, others) = match out.target() { + TermTarget::Stdout => (STD_OUTPUT_HANDLE, [STD_INPUT_HANDLE, STD_ERROR_HANDLE]), + TermTarget::Stderr => (STD_ERROR_HANDLE, [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE]), + }; + + if unsafe { console_on_any(&[fd]) } { + // False positives aren't possible. If we got a console then + // we definitely have a tty on stdin. + return true; + } + + // At this point, we *could* have a false negative. We can determine that + // this is true negative if we can detect the presence of a console on + // any of the other streams. If another stream has a console, then we know + // we're in a Windows console and can therefore trust the negative. + if unsafe { console_on_any(&others) } { + return false; + } + + msys_tty_on(out) +} + +pub fn is_a_color_terminal(out: &Term) -> bool { + if !is_a_terminal(out) { + return false; + } + if msys_tty_on(out) { + return match env::var("TERM") { + Ok(term) => term != "dumb", + Err(_) => true, + }; + } + enable_ansi_on(out) +} + +fn enable_ansi_on(out: &Term) -> bool { + unsafe { + let handle = as_handle(out); + + let mut dw_mode = 0; + if GetConsoleMode(handle, &mut dw_mode) == 0 { + return false; + } + + dw_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if SetConsoleMode(handle, dw_mode) == 0 { + return false; + } + + true + } +} + +unsafe fn console_on_any(fds: &[STD_HANDLE]) -> bool { + for &fd in fds { + let mut out = 0; + let handle = GetStdHandle(fd); + if GetConsoleMode(handle, &mut out) != 0 { + return true; + } + } + false +} + +pub fn terminal_size(out: &Term) -> Option<(u16, u16)> { + use windows_sys::Win32::System::Console::SMALL_RECT; + + // convert between windows_sys::Win32::Foundation::HANDLE and std::os::windows::raw::HANDLE + let handle = out.as_raw_handle(); + let hand = handle as windows_sys::Win32::Foundation::HANDLE; + + if hand == INVALID_HANDLE_VALUE { + return None; + } + + let zc = COORD { X: 0, Y: 0 }; + let mut csbi = CONSOLE_SCREEN_BUFFER_INFO { + dwSize: zc, + dwCursorPosition: zc, + wAttributes: 0, + srWindow: SMALL_RECT { + Left: 0, + Top: 0, + Right: 0, + Bottom: 0, + }, + dwMaximumWindowSize: zc, + }; + if unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } == 0 { + return None; + } + + let rows = (csbi.srWindow.Bottom - csbi.srWindow.Top + 1) as u16; + let columns = (csbi.srWindow.Right - csbi.srWindow.Left + 1) as u16; + + Some((rows, columns)) +} + +pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_to(out, x, y); + } + if let Some((hand, _)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + SetConsoleCursorPosition( + hand, + COORD { + X: x as i16, + Y: y as i16, + }, + ); + } + } + Ok(()) +} + +pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_up(out, n); + } + + if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize - n)?; + } + Ok(()) +} + +pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_down(out, n); + } + + if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize + n)?; + } + Ok(()) +} + +pub fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_left(out, n); + } + + if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + move_cursor_to( + out, + csbi.dwCursorPosition.X as usize - n, + csbi.dwCursorPosition.Y as usize, + )?; + } + Ok(()) +} + +pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::move_cursor_right(out, n); + } + + if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + move_cursor_to( + out, + csbi.dwCursorPosition.X as usize + n, + csbi.dwCursorPosition.Y as usize, + )?; + } + Ok(()) +} + +pub fn clear_line(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::clear_line(out); + } + if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + let width = csbi.srWindow.Right - csbi.srWindow.Left; + let pos = COORD { + X: 0, + Y: csbi.dwCursorPosition.Y, + }; + let mut written = 0; + FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as u32, pos, &mut written); + FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, pos, &mut written); + SetConsoleCursorPosition(hand, pos); + } + } + Ok(()) +} + +pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> { + if out.is_msys_tty { + return common_term::clear_chars(out, n); + } + if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + let width = cmp::min(csbi.dwCursorPosition.X, n as i16); + let pos = COORD { + X: csbi.dwCursorPosition.X - width, + Y: csbi.dwCursorPosition.Y, + }; + let mut written = 0; + FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as u32, pos, &mut written); + FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, pos, &mut written); + SetConsoleCursorPosition(hand, pos); + } + } + Ok(()) +} + +pub fn clear_screen(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::clear_screen(out); + } + if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + let cells = csbi.dwSize.X as u32 * csbi.dwSize.Y as u32; // as u32, or else this causes stack overflows. + let pos = COORD { X: 0, Y: 0 }; + let mut written = 0; + FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as u32 no longer needed. + FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written); + SetConsoleCursorPosition(hand, pos); + } + } + Ok(()) +} + +pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::clear_to_end_of_screen(out); + } + if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { + unsafe { + let bottom = csbi.srWindow.Right as u32 * csbi.srWindow.Bottom as u32; + let cells = bottom - (csbi.dwCursorPosition.X as u32 * csbi.dwCursorPosition.Y as u32); // as u32, or else this causes stack overflows. + let pos = COORD { + X: 0, + Y: csbi.dwCursorPosition.Y, + }; + let mut written = 0; + FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as u32 no longer needed. + FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written); + SetConsoleCursorPosition(hand, pos); + } + } + Ok(()) +} + +pub fn show_cursor(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::show_cursor(out); + } + if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) { + unsafe { + cci.bVisible = 1; + SetConsoleCursorInfo(hand, &cci); + } + } + Ok(()) +} + +pub fn hide_cursor(out: &Term) -> io::Result<()> { + if out.is_msys_tty { + return common_term::hide_cursor(out); + } + if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) { + unsafe { + cci.bVisible = 0; + SetConsoleCursorInfo(hand, &cci); + } + } + Ok(()) +} + +fn get_console_screen_buffer_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_SCREEN_BUFFER_INFO)> { + let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() }; + match unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } { + 0 => None, + _ => Some((hand, csbi)), + } +} + +fn get_console_cursor_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_CURSOR_INFO)> { + let mut cci: CONSOLE_CURSOR_INFO = unsafe { mem::zeroed() }; + match unsafe { GetConsoleCursorInfo(hand, &mut cci) } { + 0 => None, + _ => Some((hand, cci)), + } +} + +pub fn key_from_key_code(code: VIRTUAL_KEY) -> Key { + use windows_sys::Win32::UI::Input::KeyboardAndMouse; + + match code { + KeyboardAndMouse::VK_LEFT => Key::ArrowLeft, + KeyboardAndMouse::VK_RIGHT => Key::ArrowRight, + KeyboardAndMouse::VK_UP => Key::ArrowUp, + KeyboardAndMouse::VK_DOWN => Key::ArrowDown, + KeyboardAndMouse::VK_RETURN => Key::Enter, + KeyboardAndMouse::VK_ESCAPE => Key::Escape, + KeyboardAndMouse::VK_BACK => Key::Backspace, + KeyboardAndMouse::VK_TAB => Key::Tab, + KeyboardAndMouse::VK_HOME => Key::Home, + KeyboardAndMouse::VK_END => Key::End, + KeyboardAndMouse::VK_DELETE => Key::Del, + KeyboardAndMouse::VK_SHIFT => Key::Shift, + KeyboardAndMouse::VK_MENU => Key::Alt, + _ => Key::Unknown, + } +} + +pub fn read_secure() -> io::Result<String> { + let mut rv = String::new(); + loop { + match read_single_key()? { + Key::Enter => { + break; + } + Key::Char('\x08') => { + if !rv.is_empty() { + let new_len = rv.len() - 1; + rv.truncate(new_len); + } + } + Key::Char(c) => { + rv.push(c); + } + _ => {} + } + } + Ok(rv) +} + +pub fn read_single_key() -> io::Result<Key> { + let key_event = read_key_event()?; + + let unicode_char = unsafe { key_event.uChar.UnicodeChar }; + if unicode_char == 0 { + Ok(key_from_key_code(key_event.wVirtualKeyCode)) + } else { + // This is a unicode character, in utf-16. Try to decode it by itself. + match char::from_utf16_tuple((unicode_char, None)) { + Ok(c) => { + // Maintain backward compatibility. The previous implementation (_getwch()) would return + // a special keycode for `Enter`, while ReadConsoleInputW() prefers to use '\r'. + if c == '\r' { + Ok(Key::Enter) + } else if c == '\x08' { + Ok(Key::Backspace) + } else if c == '\x1B' { + Ok(Key::Escape) + } else { + Ok(Key::Char(c)) + } + } + // This is part of a surrogate pair. Try to read the second half. + Err(InvalidUtf16Tuple::MissingSecond) => { + // Confirm that there is a next character to read. + if get_key_event_count()? == 0 { + let message = format!( + "Read invlid utf16 {}: {}", + unicode_char, + InvalidUtf16Tuple::MissingSecond + ); + return Err(io::Error::new(io::ErrorKind::InvalidData, message)); + } + + // Read the next character. + let next_event = read_key_event()?; + let next_surrogate = unsafe { next_event.uChar.UnicodeChar }; + + // Attempt to decode it. + match char::from_utf16_tuple((unicode_char, Some(next_surrogate))) { + Ok(c) => Ok(Key::Char(c)), + + // Return an InvalidData error. This is the recommended value for UTF-related I/O errors. + // (This error is given when reading a non-UTF8 file into a String, for example.) + Err(e) => { + let message = format!( + "Read invalid surrogate pair ({}, {}): {}", + unicode_char, next_surrogate, e + ); + Err(io::Error::new(io::ErrorKind::InvalidData, message)) + } + } + } + + // Return an InvalidData error. This is the recommended value for UTF-related I/O errors. + // (This error is given when reading a non-UTF8 file into a String, for example.) + Err(e) => { + let message = format!("Read invalid utf16 {}: {}", unicode_char, e); + Err(io::Error::new(io::ErrorKind::InvalidData, message)) + } + } + } +} + +fn get_stdin_handle() -> io::Result<HANDLE> { + let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) }; + if handle == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +/// Get the number of pending events in the ReadConsoleInput queue. Note that while +/// these aren't necessarily key events, the only way that multiple events can be +/// put into the queue simultaneously is if a unicode character spanning multiple u16's +/// is read. +/// +/// Therefore, this is accurate as long as at least one KEY_EVENT has already been read. +fn get_key_event_count() -> io::Result<u32> { + let handle = get_stdin_handle()?; + let mut event_count: u32 = unsafe { mem::zeroed() }; + + let success = unsafe { GetNumberOfConsoleInputEvents(handle, &mut event_count) }; + if success == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(event_count) + } +} + +fn read_key_event() -> io::Result<KEY_EVENT_RECORD> { + let handle = get_stdin_handle()?; + let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() }; + + let mut events_read: u32 = unsafe { mem::zeroed() }; + + let mut key_event: KEY_EVENT_RECORD; + loop { + let success = unsafe { ReadConsoleInputW(handle, &mut buffer, 1, &mut events_read) }; + if success == 0 { + return Err(io::Error::last_os_error()); + } + if events_read == 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "ReadConsoleInput returned no events, instead of waiting for an event", + )); + } + + if events_read == 1 && buffer.EventType != KEY_EVENT as u16 { + // This isn't a key event; ignore it. + continue; + } + + key_event = unsafe { mem::transmute(buffer.Event) }; + + if key_event.bKeyDown == 0 { + // This is a key being released; ignore it. + continue; + } + + return Ok(key_event); + } +} + +pub fn wants_emoji() -> bool { + // If WT_SESSION is set, we can assume we're running in the nne + // Windows Terminal. The correct way to detect this is not available + // yet. See https://github.com/microsoft/terminal/issues/1040 + env::var("WT_SESSION").is_ok() +} + +/// Returns true if there is an MSYS tty on the given handle. +pub fn msys_tty_on(term: &Term) -> bool { + let handle = term.as_raw_handle(); + unsafe { + // Check whether the Windows 10 native pty is enabled + { + let mut out = MaybeUninit::uninit(); + let res = GetConsoleMode(handle as HANDLE, out.as_mut_ptr()); + if res != 0 // If res is true then out was initialized. + && (out.assume_init() & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + == ENABLE_VIRTUAL_TERMINAL_PROCESSING + { + return true; + } + } + + let size = mem::size_of::<FILE_NAME_INFO>(); + let mut name_info_bytes = vec![0u8; size + MAX_PATH as usize * mem::size_of::<u16>()]; + let res = GetFileInformationByHandleEx( + handle as HANDLE, + FileNameInfo, + &mut *name_info_bytes as *mut _ as *mut c_void, + name_info_bytes.len() as u32, + ); + if res == 0 { + return false; + } + let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO); + let s = slice::from_raw_parts( + name_info.FileName.as_ptr(), + name_info.FileNameLength as usize / 2, + ); + let name = String::from_utf16_lossy(s); + // This checks whether 'pty' exists in the file name, which indicates that + // a pseudo-terminal is attached. To mitigate against false positives + // (e.g., an actual file name that contains 'pty'), we also require that + // either the strings 'msys-' or 'cygwin-' are in the file name as well.) + let is_msys = name.contains("msys-") || name.contains("cygwin-"); + let is_pty = name.contains("-pty"); + is_msys && is_pty + } +} + +pub fn set_title<T: Display>(title: T) { + let buffer: Vec<u16> = OsStr::new(&format!("{}", title)) + .encode_wide() + .chain(once(0)) + .collect(); + unsafe { + SetConsoleTitleW(buffer.as_ptr()); + } +} |