test\term/
win.rs

1//! Windows console handling
2
3// FIXME (#13400): this is only a tiny fraction of the Windows console api
4
5use std::io;
6use std::io::prelude::*;
7
8use super::{Terminal, color};
9
10/// A Terminal implementation that uses the Win32 Console API.
11pub(crate) struct WinConsole<T> {
12    buf: T,
13    def_foreground: color::Color,
14    def_background: color::Color,
15    foreground: color::Color,
16    background: color::Color,
17}
18
19type SHORT = i16;
20type WORD = u16;
21type DWORD = u32;
22type BOOL = i32;
23type HANDLE = *mut u8;
24// https://docs.microsoft.com/en-us/windows/console/getstdhandle
25const STD_OUTPUT_HANDLE: DWORD = -11 as _;
26
27#[allow(non_snake_case)]
28#[repr(C)]
29struct SMALL_RECT {
30    Left: SHORT,
31    Top: SHORT,
32    Right: SHORT,
33    Bottom: SHORT,
34}
35
36#[allow(non_snake_case)]
37#[repr(C)]
38struct COORD {
39    X: SHORT,
40    Y: SHORT,
41}
42
43#[allow(non_snake_case)]
44#[repr(C)]
45struct CONSOLE_SCREEN_BUFFER_INFO {
46    dwSize: COORD,
47    dwCursorPosition: COORD,
48    wAttributes: WORD,
49    srWindow: SMALL_RECT,
50    dwMaximumWindowSize: COORD,
51}
52
53#[allow(non_snake_case)]
54#[link(name = "kernel32")]
55unsafe extern "system" {
56    fn SetConsoleTextAttribute(handle: HANDLE, attr: WORD) -> BOOL;
57    fn GetStdHandle(which: DWORD) -> HANDLE;
58    fn GetConsoleScreenBufferInfo(handle: HANDLE, info: *mut CONSOLE_SCREEN_BUFFER_INFO) -> BOOL;
59}
60
61fn color_to_bits(color: color::Color) -> u16 {
62    // magic numbers from mingw-w64's wincon.h
63
64    let bits = match color % 8 {
65        color::BLACK => 0,
66        color::BLUE => 0x1,
67        color::GREEN => 0x2,
68        color::RED => 0x4,
69        color::YELLOW => 0x2 | 0x4,
70        color::MAGENTA => 0x1 | 0x4,
71        color::CYAN => 0x1 | 0x2,
72        color::WHITE => 0x1 | 0x2 | 0x4,
73        _ => unreachable!(),
74    };
75
76    if color >= 8 { bits | 0x8 } else { bits }
77}
78
79fn bits_to_color(bits: u16) -> color::Color {
80    let color = match bits & 0x7 {
81        0 => color::BLACK,
82        0x1 => color::BLUE,
83        0x2 => color::GREEN,
84        0x4 => color::RED,
85        0x6 => color::YELLOW,
86        0x5 => color::MAGENTA,
87        0x3 => color::CYAN,
88        0x7 => color::WHITE,
89        _ => unreachable!(),
90    };
91
92    color | (u32::from(bits) & 0x8) // copy the hi-intensity bit
93}
94
95impl<T: Write + Send + 'static> WinConsole<T> {
96    fn apply(&mut self) {
97        let _unused = self.buf.flush();
98        let mut accum: WORD = 0;
99        accum |= color_to_bits(self.foreground);
100        accum |= color_to_bits(self.background) << 4;
101
102        unsafe {
103            // You may be wondering, "but what about stderr?", and the answer
104            // to that is that setting terminal attributes on the stdout
105            // handle also sets them for stderr, since they go to the same
106            // terminal! Admittedly, this is fragile, since stderr could be
107            // redirected to a different console. This is good enough for
108            // rustc though. See #13400.
109            let out = GetStdHandle(STD_OUTPUT_HANDLE);
110            SetConsoleTextAttribute(out, accum);
111        }
112    }
113
114    pub(crate) fn new(out: T) -> WinConsole<T> {
115        use std::mem::MaybeUninit;
116
117        let fg;
118        let bg;
119        unsafe {
120            let mut buffer_info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFO>::uninit();
121            let handle = GetStdHandle(STD_OUTPUT_HANDLE);
122            if GetConsoleScreenBufferInfo(handle, buffer_info.as_mut_ptr()) != 0 {
123                let buffer_info = buffer_info.assume_init();
124                fg = bits_to_color(buffer_info.wAttributes);
125                bg = bits_to_color(buffer_info.wAttributes >> 4);
126            } else {
127                fg = color::WHITE;
128                bg = color::BLACK;
129            }
130        }
131        WinConsole {
132            buf: out,
133            def_foreground: fg,
134            def_background: bg,
135            foreground: fg,
136            background: bg,
137        }
138    }
139}
140
141impl<T: Write> Write for WinConsole<T> {
142    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
143        self.buf.write(buf)
144    }
145
146    fn flush(&mut self) -> io::Result<()> {
147        self.buf.flush()
148    }
149}
150
151impl<T: Write + Send + 'static> Terminal for WinConsole<T> {
152    fn fg(&mut self, color: color::Color) -> io::Result<bool> {
153        self.foreground = color;
154        self.apply();
155
156        Ok(true)
157    }
158
159    fn reset(&mut self) -> io::Result<bool> {
160        self.foreground = self.def_foreground;
161        self.background = self.def_background;
162        self.apply();
163
164        Ok(true)
165    }
166}