summaryrefslogtreecommitdiffstats
path: root/vendor/console/src/windows_term/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/console/src/windows_term/mod.rs')
-rw-r--r--vendor/console/src/windows_term/mod.rs563
1 files changed, 563 insertions, 0 deletions
diff --git a/vendor/console/src/windows_term/mod.rs b/vendor/console/src/windows_term/mod.rs
new file mode 100644
index 000000000..c4ec193cf
--- /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());
+ }
+}