diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e292221a..94a5dae7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,17 +6,17 @@ on: branches: [master] jobs: build-msrv: - name: Build on MSRV (1.40) + name: Build on MSRV (1.48) strategy: fail-fast: false matrix: include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu - rust: 1.40.0 + rust: 1.48.0 - os: windows-latest target: i686-pc-windows-msvc - rust: 1.40.0 + rust: 1.48.0 runs-on: ${{ matrix.os }} steps: - name: Install rust @@ -41,7 +41,7 @@ jobs: include: - os: macos-latest target: x86_64-apple-darwin - rust: 1.51.0 + rust: stable - os: ubuntu-latest target: x86_64-unknown-linux-gnu rust: 1.51.0 @@ -92,7 +92,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.51.0 + toolchain: stable target: ${{ matrix.target }} override: true - name: Checkout diff --git a/Cargo.toml b/Cargo.toml index 449cbf0f..ae5d8543 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,21 +13,27 @@ readme = "README.md" [features] default = ["unicode-width", "ansi-parsing"] -windows-console-colors = ["ansi-parsing", "regex", "winapi-util"] +windows-console-colors = ["ansi-parsing", "regex"] ansi-parsing = [] [dependencies] libc = "0.2.30" -terminal_size = "0.1.14" regex = { version = "1.4.2", optional = true, default-features = false, features = ["std"] } unicode-width = { version = "0.1", optional = true } lazy_static = "1.4.0" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winbase", "winuser", "consoleapi", "processenv", "wincon"] } -winapi-util = { version = "0.1.3", optional = true } encode_unicode = "0.3" +[target.'cfg(windows)'.dependencies.windows-sys] +version = "0.42.0" +features = [ + "Win32_Foundation", + "Win32_System_Console", + "Win32_Storage_FileSystem", + "Win32_UI_Input_KeyboardAndMouse", +] + [dev-dependencies] proptest = "1.0.0" regex = "1.4.2" diff --git a/README.md b/README.md index df706a7c..3605f975 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://github.com/console-rs/console/workflows/CI/badge.svg?branch=master)](https://github.com/console-rs/console/actions?query=workflow%3ACI) [![Crates.io](https://img.shields.io/crates/d/console.svg)](https://crates.io/crates/console) [![License](https://img.shields.io/github/license/console-rs/console)](https://github.com/console-rs/console/blob/master/LICENSE) -[![rustc 1.40.0](https://img.shields.io/badge/rust-1.40%2B-orange.svg)](https://img.shields.io/badge/rust-1.40%2B-orange.svg) +[![rustc 1.48.0](https://img.shields.io/badge/rust-1.48%2B-orange.svg)](https://img.shields.io/badge/rust-1.48%2B-orange.svg) [![Documentation](https://docs.rs/console/badge.svg)](https://docs.rs/console) **console** is a library for Rust that provides access to various terminal diff --git a/src/term.rs b/src/term.rs index 350a7894..1715a2ff 100644 --- a/src/term.rs +++ b/src/term.rs @@ -507,8 +507,6 @@ impl Term { if self.is_msys_tty || !self.is_tty { self.write_through_common(bytes) } else { - use winapi_util::console::Console; - match self.inner.target { TermTarget::Stdout => console_colors(self, Console::stdout()?, bytes), TermTarget::Stderr => console_colors(self, Console::stderr()?, bytes), @@ -578,8 +576,9 @@ impl AsRawFd for Term { #[cfg(windows)] impl AsRawHandle for Term { fn as_raw_handle(&self) -> RawHandle { - use winapi::um::processenv::GetStdHandle; - use winapi::um::winbase::{STD_ERROR_HANDLE, STD_OUTPUT_HANDLE}; + use windows_sys::Win32::System::Console::{ + GetStdHandle, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE, + }; unsafe { GetStdHandle(match self.inner.target { diff --git a/src/unix_term.rs b/src/unix_term.rs index f4ed2897..fa00ba03 100644 --- a/src/unix_term.rs +++ b/src/unix_term.rs @@ -42,9 +42,24 @@ pub fn c_result libc::c_int>(f: F) -> io::Result<()> { } } -#[inline] pub fn terminal_size(out: &Term) -> Option<(u16, u16)> { - terminal_size::terminal_size_using_fd(out.as_raw_fd()).map(|x| ((x.1).0, (x.0).0)) + unsafe { + if libc::isatty(libc::STDOUT_FILENO) != 1 { + return None; + } + + let mut winsize: libc::winsize = std::mem::zeroed(); + + // FIXME: ".into()" used as a temporary fix for a libc bug + // https://github.com/rust-lang/libc/pull/704 + #[allow(clippy::useless_conversion)] + libc::ioctl(out.as_raw_fd(), libc::TIOCGWINSZ.into(), &mut winsize); + if winsize.ws_row > 0 && winsize.ws_col > 0 { + Some((winsize.ws_row as u16, winsize.ws_col as u16)) + } else { + None + } + } } pub fn read_secure() -> io::Result { diff --git a/src/windows_term/colors.rs b/src/windows_term/colors.rs new file mode 100644 index 00000000..5efd4f15 --- /dev/null +++ b/src/windows_term/colors.rs @@ -0,0 +1,346 @@ +use std::io; +use std::mem; +use std::os::windows::io::AsRawHandle; + +use regex::Regex; +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; + +lazy_static::lazy_static! { + static ref INTENSE_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)8;5;(8|9|1[0-5])m").unwrap(); + static ref NORMAL_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)([0-7])m").unwrap(); + static ref ATTR_RE: Regex = Regex::new(r"\x1b\[([1-8])m").unwrap(); +} + +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 { + 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 { + 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 { + 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 { + 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(cap) = INTENSE_COLOR_RE.captures(part) { + let color = get_color_from_ansi(cap.get(2).unwrap().as_str()); + + match cap.get(1).unwrap().as_str() { + "3" => con.fg(Intense::Yes, color)?, + "4" => con.bg(Intense::Yes, color)?, + _ => unreachable!(), + }; + } else if let Some(cap) = NORMAL_COLOR_RE.captures(part) { + let color = get_color_from_ansi(cap.get(2).unwrap().as_str()); + + match cap.get(1).unwrap().as_str() { + "3" => con.fg(Intense::No, color)?, + "4" => con.bg(Intense::No, color)?, + _ => unreachable!(), + }; + } else if !ATTR_RE.is_match(part) { + out.write_through_common(part.as_bytes())?; + } + } + } + + Ok(()) +} + +fn get_color_from_ansi(ansi: &str) -> Color { + match ansi { + "0" | "8" => Color::Black, + "1" | "9" => Color::Red, + "2" | "10" => Color::Green, + "3" | "11" => Color::Yellow, + "4" | "12" => Color::Blue, + "5" | "13" => Color::Magenta, + "6" | "14" => Color::Cyan, + "7" | "15" => Color::White, + _ => unreachable!(), + } +} diff --git a/src/windows_term.rs b/src/windows_term/mod.rs similarity index 74% rename from src/windows_term.rs rename to src/windows_term/mod.rs index 71b209f8..ec2613b1 100644 --- a/src/windows_term.rs +++ b/src/windows_term/mod.rs @@ -5,6 +5,7 @@ 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; @@ -12,47 +13,34 @@ use std::{char, mem::MaybeUninit}; use encode_unicode::error::InvalidUtf16Tuple; use encode_unicode::CharExt; -use winapi::ctypes::c_void; -use winapi::shared::minwindef::DWORD; -use winapi::shared::minwindef::MAX_PATH; -use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode}; -use winapi::um::consoleapi::{GetNumberOfConsoleInputEvents, ReadConsoleInputW}; -use winapi::um::fileapi::FILE_NAME_INFO; -use winapi::um::handleapi::INVALID_HANDLE_VALUE; -use winapi::um::minwinbase::FileNameInfo; -use winapi::um::processenv::GetStdHandle; -use winapi::um::winbase::GetFileInformationByHandleEx; -use winapi::um::winbase::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; -use winapi::um::wincon::{ - FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetConsoleCursorInfo, - GetConsoleScreenBufferInfo, SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleTitleW, +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, + KEY_EVENT_RECORD, STD_ERROR_HANDLE, STD_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, }; -use winapi::um::winnt::{CHAR, HANDLE, INT, WCHAR}; -#[cfg(feature = "windows-console-colors")] -use winapi_util::console::{Color, Console, Intense}; +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")] -use regex::Regex; +mod colors; #[cfg(feature = "windows-console-colors")] -lazy_static::lazy_static! { - static ref INTENSE_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)8;5;(8|9|1[0-5])m").unwrap(); - static ref NORMAL_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)([0-7])m").unwrap(); - static ref ATTR_RE: Regex = Regex::new(r"\x1b\[([1-8])m").unwrap(); -} +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 winapi::um::winnt::HANDLE and std::os::windows::raw::HANDLE - // which are both c_void. would be nice to find a better way to do this + // convert between windows_sys::Win32::Foundation::HANDLE and std::os::windows::raw::HANDLE term.as_raw_handle() as HANDLE } @@ -110,7 +98,7 @@ fn enable_ansi_on(out: &Term) -> bool { } } -unsafe fn console_on_any(fds: &[DWORD]) -> bool { +unsafe fn console_on_any(fds: &[STD_HANDLE]) -> bool { for &fd in fds { let mut out = 0; let handle = GetStdHandle(fd); @@ -121,9 +109,37 @@ unsafe fn console_on_any(fds: &[DWORD]) -> bool { false } -#[inline] pub fn terminal_size(out: &Term) -> Option<(u16, u16)> { - terminal_size::terminal_size_using_handle(out.as_raw_handle()).map(|x| ((x.1).0, (x.0).0)) + 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 w = (csbi.srWindow.Right - csbi.srWindow.Left + 1) as u16; + let h = (csbi.srWindow.Bottom - csbi.srWindow.Top + 1) as u16; + Some((w, h)) } pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> { @@ -208,8 +224,8 @@ pub fn clear_line(out: &Term) -> io::Result<()> { Y: csbi.dwCursorPosition.Y, }; let mut written = 0; - FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written); - FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written); + FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as u32, pos, &mut written); + FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, pos, &mut written); SetConsoleCursorPosition(hand, pos); } } @@ -228,8 +244,8 @@ pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> { Y: csbi.dwCursorPosition.Y, }; let mut written = 0; - FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written); - FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written); + FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as u32, pos, &mut written); + FillConsoleOutputAttribute(hand, csbi.wAttributes, width as u32, pos, &mut written); SetConsoleCursorPosition(hand, pos); } } @@ -242,10 +258,10 @@ pub fn clear_screen(out: &Term) -> io::Result<()> { } if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { unsafe { - let cells = csbi.dwSize.X as DWORD * csbi.dwSize.Y as DWORD; // as DWORD, or else this causes stack overflows. + 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 DWORD no longer needed. + 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); } @@ -259,15 +275,14 @@ pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> { } if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) { unsafe { - let bottom = csbi.srWindow.Right as DWORD * csbi.srWindow.Bottom as DWORD; - let cells = - bottom - (csbi.dwCursorPosition.X as DWORD * csbi.dwCursorPosition.Y as DWORD); // as DWORD, or else this causes stack overflows. + 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 DWORD no longer needed. + 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); } @@ -317,21 +332,23 @@ fn get_console_cursor_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_CURSOR_INFO) } } -pub fn key_from_key_code(code: INT) -> Key { +pub fn key_from_key_code(code: VIRTUAL_KEY) -> Key { + use windows_sys::Win32::UI::Input::KeyboardAndMouse; + match code { - winapi::um::winuser::VK_LEFT => Key::ArrowLeft, - winapi::um::winuser::VK_RIGHT => Key::ArrowRight, - winapi::um::winuser::VK_UP => Key::ArrowUp, - winapi::um::winuser::VK_DOWN => Key::ArrowDown, - winapi::um::winuser::VK_RETURN => Key::Enter, - winapi::um::winuser::VK_ESCAPE => Key::Escape, - winapi::um::winuser::VK_BACK => Key::Backspace, - winapi::um::winuser::VK_TAB => Key::Tab, - winapi::um::winuser::VK_HOME => Key::Home, - winapi::um::winuser::VK_END => Key::End, - winapi::um::winuser::VK_DELETE => Key::Del, - winapi::um::winuser::VK_SHIFT => Key::Shift, - winapi::um::winuser::VK_MENU => Key::Alt, + 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, } } @@ -361,9 +378,9 @@ pub fn read_secure() -> io::Result { pub fn read_single_key() -> io::Result { let key_event = read_key_event()?; - let unicode_char = unsafe { *key_event.uChar.UnicodeChar() }; + let unicode_char = unsafe { key_event.uChar.UnicodeChar }; if unicode_char == 0 { - Ok(key_from_key_code(key_event.wVirtualKeyCode as INT)) + 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)) { @@ -394,7 +411,7 @@ pub fn read_single_key() -> io::Result { // Read the next character. let next_event = read_key_event()?; - let next_surrogate = unsafe { *next_event.uChar.UnicodeChar() }; + let next_surrogate = unsafe { next_event.uChar.UnicodeChar }; // Attempt to decode it. match char::from_utf16_tuple((unicode_char, Some(next_surrogate))) { @@ -437,9 +454,9 @@ fn get_stdin_handle() -> io::Result { /// 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 { +fn get_key_event_count() -> io::Result { let handle = get_stdin_handle()?; - let mut event_count: DWORD = unsafe { mem::zeroed() }; + let mut event_count: u32 = unsafe { mem::zeroed() }; let success = unsafe { GetNumberOfConsoleInputEvents(handle, &mut event_count) }; if success == 0 { @@ -453,7 +470,7 @@ fn read_key_event() -> io::Result { let handle = get_stdin_handle()?; let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() }; - let mut events_read: DWORD = unsafe { mem::zeroed() }; + let mut events_read: u32 = unsafe { mem::zeroed() }; let mut key_event: KEY_EVENT_RECORD; loop { @@ -468,7 +485,7 @@ fn read_key_event() -> io::Result { )); } - if events_read == 1 && buffer.EventType != KEY_EVENT { + if events_read == 1 && buffer.EventType != KEY_EVENT as u16 { // This isn't a key event; ignore it. continue; } @@ -498,7 +515,7 @@ pub fn msys_tty_on(term: &Term) -> bool { // Check whether the Windows 10 native pty is enabled { let mut out = MaybeUninit::uninit(); - let res = GetConsoleMode(handle as *mut _, out.as_mut_ptr()); + 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 @@ -508,9 +525,9 @@ pub fn msys_tty_on(term: &Term) -> bool { } let size = mem::size_of::(); - let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::()]; + let mut name_info_bytes = vec![0u8; size + MAX_PATH as usize * mem::size_of::()]; let res = GetFileInformationByHandleEx( - handle as *mut _, + handle as HANDLE, FileNameInfo, &mut *name_info_bytes as *mut _ as *mut c_void, name_info_bytes.len() as u32, @@ -543,57 +560,3 @@ pub fn set_title(title: T) { SetConsoleTitleW(buffer.as_ptr()); } } - -#[cfg(feature = "windows-console-colors")] -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(cap) = INTENSE_COLOR_RE.captures(part) { - let color = get_color_from_ansi(cap.get(2).unwrap().as_str()); - - match cap.get(1).unwrap().as_str() { - "3" => con.fg(Intense::Yes, color)?, - "4" => con.bg(Intense::Yes, color)?, - _ => unreachable!(), - }; - } else if let Some(cap) = NORMAL_COLOR_RE.captures(part) { - let color = get_color_from_ansi(cap.get(2).unwrap().as_str()); - - match cap.get(1).unwrap().as_str() { - "3" => con.fg(Intense::No, color)?, - "4" => con.bg(Intense::No, color)?, - _ => unreachable!(), - }; - } else if !ATTR_RE.is_match(part) { - out.write_through_common(part.as_bytes())?; - } - } - } - - Ok(()) -} - -#[cfg(feature = "windows-console-colors")] -fn get_color_from_ansi(ansi: &str) -> Color { - match ansi { - "0" | "8" => Color::Black, - "1" | "9" => Color::Red, - "2" | "10" => Color::Green, - "3" | "11" => Color::Yellow, - "4" | "12" => Color::Blue, - "5" | "13" => Color::Magenta, - "6" | "14" => Color::Cyan, - "7" | "15" => Color::White, - _ => unreachable!(), - } -}