blob: 48ece2db270cb86ec7bcf1797e57faa4df24ebab [file] [log] [blame] [edit]
// Copyright 2013-2019 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Windows console handling
// FIXME (#13400): this is only a tiny fraction of the Windows console api
use crate::color;
use crate::Attr;
use crate::Error;
use crate::Result;
use crate::Terminal;
use std::io;
use std::io::prelude::*;
use std::ops::Deref;
use std::ptr;
use winapi::shared::minwindef::{DWORD, WORD};
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
use winapi::um::fileapi::{CreateFileA, OPEN_EXISTING};
use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
use winapi::um::wincon::FillConsoleOutputAttribute;
use winapi::um::wincon::{
FillConsoleOutputCharacterW, GetConsoleScreenBufferInfo, CONSOLE_SCREEN_BUFFER_INFO, COORD,
};
use winapi::um::wincon::{SetConsoleCursorPosition, SetConsoleTextAttribute};
use winapi::um::wincon::{BACKGROUND_INTENSITY, ENABLE_VIRTUAL_TERMINAL_PROCESSING};
use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE, HANDLE};
/// Console info which can be used by a Terminal implementation
/// which uses the Win32 Console API.
pub struct WinConsoleInfo {
def_foreground: color::Color,
def_background: color::Color,
foreground: color::Color,
background: color::Color,
reverse: bool,
secure: bool,
standout: bool,
}
/// A Terminal implementation which uses the Win32 Console API.
pub struct WinConsole<T> {
buf: T,
info: WinConsoleInfo,
}
fn color_to_bits(color: color::Color) -> u16 {
// magic numbers from mingw-w64's wincon.h
let bits = match color % 8 {
color::BLACK => 0,
color::BLUE => 0x1,
color::GREEN => 0x2,
color::RED => 0x4,
color::YELLOW => 0x2 | 0x4,
color::MAGENTA => 0x1 | 0x4,
color::CYAN => 0x1 | 0x2,
color::WHITE => 0x1 | 0x2 | 0x4,
_ => unreachable!(),
};
if color >= 8 {
bits | 0x8
} else {
bits
}
}
fn bits_to_color(bits: u16) -> color::Color {
let color = match bits & 0x7 {
0 => color::BLACK,
0x1 => color::BLUE,
0x2 => color::GREEN,
0x4 => color::RED,
0x6 => color::YELLOW,
0x5 => color::MAGENTA,
0x3 => color::CYAN,
0x7 => color::WHITE,
_ => unreachable!(),
};
color | (bits as u32 & 0x8) // copy the hi-intensity bit
}
struct HandleWrapper {
inner: HANDLE,
}
impl HandleWrapper {
fn new(h: HANDLE) -> HandleWrapper {
HandleWrapper { inner: h }
}
}
impl Drop for HandleWrapper {
fn drop(&mut self) {
if self.inner != INVALID_HANDLE_VALUE {
unsafe {
CloseHandle(self.inner);
}
}
}
}
impl Deref for HandleWrapper {
type Target = HANDLE;
fn deref(&self) -> &HANDLE {
&self.inner
}
}
/// Just get a handle to the current console buffer whatever it is
fn conout() -> io::Result<HandleWrapper> {
let name = b"CONOUT$\0";
let handle = unsafe {
CreateFileA(
name.as_ptr() as *const i8,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
0,
ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
Err(io::Error::last_os_error())
} else {
Ok(HandleWrapper::new(handle))
}
}
unsafe fn set_flag(handle: HANDLE, flag: DWORD) -> io::Result<()> {
let mut curr_mode: DWORD = 0;
if GetConsoleMode(handle, &mut curr_mode) == 0 {
return Err(io::Error::last_os_error());
}
if SetConsoleMode(handle, curr_mode | flag) == 0 {
return Err(io::Error::last_os_error());
}
return Ok(());
}
/// Check if console supports ansi codes (should succeed on Windows 10)
pub fn supports_ansi() -> bool {
conout()
.and_then(|handle| unsafe { set_flag(*handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING) })
.is_ok()
}
// This test will only pass if it is running in an actual console, probably
#[test]
fn test_conout() {
assert!(conout().is_ok())
}
#[rustversion::before(1.36)]
unsafe fn get_console_screen_buffer_info(handle: HANDLE) -> io::Result<CONSOLE_SCREEN_BUFFER_INFO> {
let mut buffer_info = ::std::mem::uninitialized();
if GetConsoleScreenBufferInfo(handle, &mut buffer_info) == 0 {
Err(io::Error::last_os_error())
} else {
Ok(buffer_info)
}
}
#[rustversion::since(1.36)]
unsafe fn get_console_screen_buffer_info(handle: HANDLE) -> io::Result<CONSOLE_SCREEN_BUFFER_INFO> {
let mut buffer_info = ::std::mem::MaybeUninit::uninit();
if GetConsoleScreenBufferInfo(handle, buffer_info.as_mut_ptr()) == 0 {
Err(io::Error::last_os_error())
} else {
Ok(buffer_info.assume_init())
}
}
// This test will only pass if it is running in an actual console, probably
#[test]
fn test_get_console_screen_buffer_info() {
let handle = conout().unwrap();
unsafe {
let buffer_info = get_console_screen_buffer_info(*handle);
assert!(buffer_info.is_ok());
}
}
impl WinConsoleInfo {
/// Returns `Err` whenever console info cannot be retrieved for some
/// reason.
pub fn from_env() -> io::Result<WinConsoleInfo> {
let fg;
let bg;
let handle = conout()?;
unsafe {
let buffer_info = get_console_screen_buffer_info(*handle)?;
fg = bits_to_color(buffer_info.wAttributes);
bg = bits_to_color(buffer_info.wAttributes >> 4);
}
Ok(WinConsoleInfo {
def_foreground: fg,
def_background: bg,
foreground: fg,
background: bg,
reverse: false,
secure: false,
standout: false,
})
}
}
impl<T: Write + Send> WinConsole<T> {
fn apply(&mut self) -> io::Result<()> {
let out = conout()?;
let _unused = self.buf.flush();
let (mut fg, bg) = if self.info.reverse {
(self.info.background, self.info.foreground)
} else {
(self.info.foreground, self.info.background)
};
if self.info.secure {
fg = bg;
}
let mut accum: WORD = 0;
accum |= color_to_bits(fg);
accum |= color_to_bits(bg) << 4;
if self.info.standout {
accum |= BACKGROUND_INTENSITY;
} else {
accum &= BACKGROUND_INTENSITY ^ 0xFF;
}
unsafe {
SetConsoleTextAttribute(*out, accum);
}
Ok(())
}
/// Create a new WinConsole with the given WinConsoleInfo and out
pub fn new_with_consoleinfo(out: T, info: WinConsoleInfo) -> WinConsole<T> {
WinConsole { buf: out, info }
}
/// Returns `Err` whenever the terminal cannot be created for some
/// reason.
pub fn new(out: T) -> io::Result<WinConsole<T>> {
let info = WinConsoleInfo::from_env()?;
Ok(Self::new_with_consoleinfo(out, info))
}
}
impl<T: Write> Write for WinConsole<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.buf.flush()
}
}
impl<T: Write + Send> Terminal for WinConsole<T> {
type Output = T;
fn fg(&mut self, color: color::Color) -> Result<()> {
self.info.foreground = color;
self.apply()?;
Ok(())
}
fn bg(&mut self, color: color::Color) -> Result<()> {
self.info.background = color;
self.apply()?;
Ok(())
}
fn attr(&mut self, attr: Attr) -> Result<()> {
match attr {
Attr::ForegroundColor(f) => {
self.info.foreground = f;
self.apply()?;
Ok(())
}
Attr::BackgroundColor(b) => {
self.info.background = b;
self.apply()?;
Ok(())
}
Attr::Reverse => {
self.info.reverse = true;
self.apply()?;
Ok(())
}
Attr::Secure => {
self.info.secure = true;
self.apply()?;
Ok(())
}
Attr::Standout(v) => {
self.info.standout = v;
self.apply()?;
Ok(())
}
_ => Err(Error::NotSupported),
}
}
fn supports_attr(&self, attr: Attr) -> bool {
match attr {
Attr::ForegroundColor(_)
| Attr::BackgroundColor(_)
| Attr::Standout(_)
| Attr::Reverse
| Attr::Secure => true,
_ => false,
}
}
fn reset(&mut self) -> Result<()> {
self.info.foreground = self.info.def_foreground;
self.info.background = self.info.def_background;
self.info.reverse = false;
self.info.secure = false;
self.info.standout = false;
self.apply()?;
Ok(())
}
fn supports_reset(&self) -> bool {
true
}
fn supports_color(&self) -> bool {
true
}
fn cursor_up(&mut self) -> Result<()> {
let _unused = self.buf.flush();
let handle = conout()?;
unsafe {
let buffer_info = get_console_screen_buffer_info(*handle)?;
let (x, y) = (
buffer_info.dwCursorPosition.X,
buffer_info.dwCursorPosition.Y,
);
if y == 0 {
// Even though this might want to be a CursorPositionInvalid, on Unix there
// is no checking to see if the cursor is already on the first line.
// I'm not sure what the ideal behavior is, but I think it'd be silly to have
// cursor_up fail in this case.
Ok(())
} else {
let pos = COORD { X: x, Y: y - 1 };
if SetConsoleCursorPosition(*handle, pos) != 0 {
Ok(())
} else {
Err(io::Error::last_os_error().into())
}
}
}
}
fn delete_line(&mut self) -> Result<()> {
let _unused = self.buf.flush();
let handle = conout()?;
unsafe {
let buffer_info = get_console_screen_buffer_info(*handle)?;
let pos = buffer_info.dwCursorPosition;
let size = buffer_info.dwSize;
let num = (size.X - pos.X) as DWORD;
let mut written = 0;
// 0x0020u16 is ' ' (space) in UTF-16 (same as ascii)
if FillConsoleOutputCharacterW(*handle, 0x0020, num, pos, &mut written) == 0 {
return Err(io::Error::last_os_error().into());
}
if FillConsoleOutputAttribute(*handle, 0, num, pos, &mut written) == 0 {
return Err(io::Error::last_os_error().into());
}
// Similar reasoning for not failing as in cursor_up -- it doesn't even make
// sense to
// me that these APIs could have written 0, unless the terminal is width zero.
Ok(())
}
}
fn carriage_return(&mut self) -> Result<()> {
let _unused = self.buf.flush();
let handle = conout()?;
unsafe {
let buffer_info = get_console_screen_buffer_info(*handle)?;
let COORD { X: x, Y: y } = buffer_info.dwCursorPosition;
if x == 0 {
Err(Error::CursorDestinationInvalid)
} else {
let pos = COORD { X: 0, Y: y };
if SetConsoleCursorPosition(*handle, pos) != 0 {
Ok(())
} else {
Err(io::Error::last_os_error().into())
}
}
}
}
fn get_ref<'a>(&'a self) -> &'a T {
&self.buf
}
fn get_mut<'a>(&'a mut self) -> &'a mut T {
&mut self.buf
}
fn into_inner(self) -> T
where
Self: Sized,
{
self.buf
}
}