winapi_util/
console.rs

1use std::{io, mem};
2
3use windows_sys::Win32::Foundation::HANDLE;
4use windows_sys::Win32::System::Console::{GetConsoleMode, SetConsoleMode};
5use windows_sys::Win32::System::Console::{
6    GetConsoleScreenBufferInfo, SetConsoleTextAttribute,
7    CONSOLE_SCREEN_BUFFER_INFO, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
8    FOREGROUND_BLUE, FOREGROUND_GREEN, FOREGROUND_INTENSITY, FOREGROUND_RED,
9};
10
11use crate::{AsHandleRef, HandleRef};
12
13use FOREGROUND_BLUE as FG_BLUE;
14use FOREGROUND_GREEN as FG_GREEN;
15use FOREGROUND_INTENSITY as FG_INTENSITY;
16use FOREGROUND_RED as FG_RED;
17
18const FG_CYAN: u16 = FG_BLUE | FG_GREEN;
19const FG_MAGENTA: u16 = FG_BLUE | FG_RED;
20const FG_YELLOW: u16 = FG_GREEN | FG_RED;
21const FG_WHITE: u16 = FG_BLUE | FG_GREEN | FG_RED;
22
23/// Query the given handle for information about the console's screen buffer.
24///
25/// The given handle should represent a console. Otherwise, an error is
26/// returned.
27///
28/// This corresponds to calling [`GetConsoleScreenBufferInfo`].
29///
30/// [`GetConsoleScreenBufferInfo`]: https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
31pub fn screen_buffer_info<H: AsHandleRef>(
32    h: H,
33) -> io::Result<ScreenBufferInfo> {
34    unsafe {
35        let mut info: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
36        let rc = GetConsoleScreenBufferInfo(h.as_raw() as HANDLE, &mut info);
37        if rc == 0 {
38            return Err(io::Error::last_os_error());
39        }
40        Ok(ScreenBufferInfo(info))
41    }
42}
43
44/// Set the text attributes of the console represented by the given handle.
45///
46/// This corresponds to calling [`SetConsoleTextAttribute`].
47///
48/// [`SetConsoleTextAttribute`]: https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute
49pub fn set_text_attributes<H: AsHandleRef>(
50    h: H,
51    attributes: u16,
52) -> io::Result<()> {
53    if unsafe { SetConsoleTextAttribute(h.as_raw() as HANDLE, attributes) }
54        == 0
55    {
56        Err(io::Error::last_os_error())
57    } else {
58        Ok(())
59    }
60}
61
62/// Query the mode of the console represented by the given handle.
63///
64/// This corresponds to calling [`GetConsoleMode`], which describes the return
65/// value.
66///
67/// [`GetConsoleMode`]: https://docs.microsoft.com/en-us/windows/console/getconsolemode
68pub fn mode<H: AsHandleRef>(h: H) -> io::Result<u32> {
69    let mut mode = 0;
70    if unsafe { GetConsoleMode(h.as_raw() as HANDLE, &mut mode) } == 0 {
71        Err(io::Error::last_os_error())
72    } else {
73        Ok(mode)
74    }
75}
76
77/// Set the mode of the console represented by the given handle.
78///
79/// This corresponds to calling [`SetConsoleMode`], which describes the format
80/// of the mode parameter.
81///
82/// [`SetConsoleMode`]: https://docs.microsoft.com/en-us/windows/console/setconsolemode
83pub fn set_mode<H: AsHandleRef>(h: H, mode: u32) -> io::Result<()> {
84    if unsafe { SetConsoleMode(h.as_raw() as HANDLE, mode) } == 0 {
85        Err(io::Error::last_os_error())
86    } else {
87        Ok(())
88    }
89}
90
91/// Represents console screen buffer information such as size, cursor position
92/// and styling attributes.
93///
94/// This wraps a [`CONSOLE_SCREEN_BUFFER_INFO`].
95///
96/// [`CONSOLE_SCREEN_BUFFER_INFO`]: https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str
97#[derive(Clone)]
98pub struct ScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO);
99
100impl ScreenBufferInfo {
101    /// Returns the size of the console screen buffer, in character columns and
102    /// rows.
103    ///
104    /// This corresponds to `dwSize`.
105    pub fn size(&self) -> (i16, i16) {
106        (self.0.dwSize.X, self.0.dwSize.Y)
107    }
108
109    /// Returns the position of the cursor in terms of column and row
110    /// coordinates of the console screen buffer.
111    ///
112    /// This corresponds to `dwCursorPosition`.
113    pub fn cursor_position(&self) -> (i16, i16) {
114        (self.0.dwCursorPosition.X, self.0.dwCursorPosition.Y)
115    }
116
117    /// Returns the character attributes associated with this console.
118    ///
119    /// This corresponds to `wAttributes`.
120    ///
121    /// See [`char info`] for more details.
122    ///
123    /// [`char info`]: https://docs.microsoft.com/en-us/windows/console/char-info-str
124    pub fn attributes(&self) -> u16 {
125        self.0.wAttributes
126    }
127
128    /// Returns the maximum size of the console window, in character columns
129    /// and rows, given the current screen buffer size and font and the screen
130    /// size.
131    pub fn max_window_size(&self) -> (i16, i16) {
132        (self.0.dwMaximumWindowSize.X, self.0.dwMaximumWindowSize.Y)
133    }
134
135    /// Returns the console screen buffer coordinates of the upper-left and
136    /// lower-right corners of the display window.
137    ///
138    /// This corresponds to `srWindow`.
139    pub fn window_rect(&self) -> SmallRect {
140        SmallRect {
141            left: self.0.srWindow.Left,
142            top: self.0.srWindow.Top,
143            right: self.0.srWindow.Right,
144            bottom: self.0.srWindow.Bottom,
145        }
146    }
147}
148
149/// Defines the coordinates of the upper left and lower right corners of a rectangle.
150///
151/// This corresponds to [`SMALL_RECT`].
152///
153/// [`SMALL_RECT`]: https://docs.microsoft.com/en-us/windows/console/small-rect-str
154pub struct SmallRect {
155    pub left: i16,
156    pub top: i16,
157    pub right: i16,
158    pub bottom: i16,
159}
160
161/// A Windows console.
162///
163/// This represents a very limited set of functionality available to a Windows
164/// console. In particular, it can only change text attributes such as color
165/// and intensity. This may grow over time. If you need more routines, please
166/// file an issue and/or PR.
167///
168/// There is no way to "write" to this console. Simply write to
169/// stdout or stderr instead, while interleaving instructions to the console
170/// to change text attributes.
171///
172/// A common pitfall when using a console is to forget to flush writes to
173/// stdout before setting new text attributes.
174///
175/// # Example
176/// ```no_run
177/// # #[cfg(windows)]
178/// # {
179/// use winapi_util::console::{Console, Color, Intense};
180///
181/// let mut con = Console::stdout().unwrap();
182/// con.fg(Intense::Yes, Color::Cyan).unwrap();
183/// println!("This text will be intense cyan.");
184/// con.reset().unwrap();
185/// println!("This text will be normal.");
186/// # }
187/// ```
188#[derive(Debug)]
189pub struct Console {
190    kind: HandleKind,
191    start_attr: TextAttributes,
192    cur_attr: TextAttributes,
193}
194
195#[derive(Clone, Copy, Debug)]
196enum HandleKind {
197    Stdout,
198    Stderr,
199}
200
201impl HandleKind {
202    fn handle(&self) -> HandleRef {
203        match *self {
204            HandleKind::Stdout => HandleRef::stdout(),
205            HandleKind::Stderr => HandleRef::stderr(),
206        }
207    }
208}
209
210impl Console {
211    /// Get a console for a standard I/O stream.
212    fn create_for_stream(kind: HandleKind) -> io::Result<Console> {
213        let h = kind.handle();
214        let info = screen_buffer_info(&h)?;
215        let attr = TextAttributes::from_word(info.attributes());
216        Ok(Console { kind, start_attr: attr, cur_attr: attr })
217    }
218
219    /// Create a new Console to stdout.
220    ///
221    /// If there was a problem creating the console, then an error is returned.
222    pub fn stdout() -> io::Result<Console> {
223        Self::create_for_stream(HandleKind::Stdout)
224    }
225
226    /// Create a new Console to stderr.
227    ///
228    /// If there was a problem creating the console, then an error is returned.
229    pub fn stderr() -> io::Result<Console> {
230        Self::create_for_stream(HandleKind::Stderr)
231    }
232
233    /// Applies the current text attributes.
234    fn set(&mut self) -> io::Result<()> {
235        set_text_attributes(self.kind.handle(), self.cur_attr.to_word())
236    }
237
238    /// Apply the given intensity and color attributes to the console
239    /// foreground.
240    ///
241    /// If there was a problem setting attributes on the console, then an error
242    /// is returned.
243    pub fn fg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
244        self.cur_attr.fg_color = color;
245        self.cur_attr.fg_intense = intense;
246        self.set()
247    }
248
249    /// Apply the given intensity and color attributes to the console
250    /// background.
251    ///
252    /// If there was a problem setting attributes on the console, then an error
253    /// is returned.
254    pub fn bg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
255        self.cur_attr.bg_color = color;
256        self.cur_attr.bg_intense = intense;
257        self.set()
258    }
259
260    /// Reset the console text attributes to their original settings.
261    ///
262    /// The original settings correspond to the text attributes on the console
263    /// when this `Console` value was created.
264    ///
265    /// If there was a problem setting attributes on the console, then an error
266    /// is returned.
267    pub fn reset(&mut self) -> io::Result<()> {
268        self.cur_attr = self.start_attr;
269        self.set()
270    }
271
272    /// Toggle virtual terminal processing.
273    ///
274    /// This method attempts to toggle virtual terminal processing for this
275    /// console. If there was a problem toggling it, then an error returned.
276    /// On success, the caller may assume that toggling it was successful.
277    ///
278    /// When virtual terminal processing is enabled, characters emitted to the
279    /// console are parsed for VT100 and similar control character sequences
280    /// that control color and other similar operations.
281    pub fn set_virtual_terminal_processing(
282        &mut self,
283        yes: bool,
284    ) -> io::Result<()> {
285        let vt = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
286
287        let handle = self.kind.handle();
288        let old_mode = mode(&handle)?;
289        let new_mode = if yes { old_mode | vt } else { old_mode & !vt };
290        if old_mode == new_mode {
291            return Ok(());
292        }
293        set_mode(&handle, new_mode)
294    }
295}
296
297/// A representation of text attributes for the Windows console.
298#[derive(Copy, Clone, Debug, Eq, PartialEq)]
299struct TextAttributes {
300    fg_color: Color,
301    fg_intense: Intense,
302    bg_color: Color,
303    bg_intense: Intense,
304}
305
306impl TextAttributes {
307    fn to_word(&self) -> u16 {
308        let mut w = 0;
309        w |= self.fg_color.to_fg();
310        w |= self.fg_intense.to_fg();
311        w |= self.bg_color.to_bg();
312        w |= self.bg_intense.to_bg();
313        w
314    }
315
316    fn from_word(word: u16) -> TextAttributes {
317        TextAttributes {
318            fg_color: Color::from_fg(word),
319            fg_intense: Intense::from_fg(word),
320            bg_color: Color::from_bg(word),
321            bg_intense: Intense::from_bg(word),
322        }
323    }
324}
325
326/// Whether to use intense colors or not.
327#[allow(missing_docs)]
328#[derive(Clone, Copy, Debug, Eq, PartialEq)]
329pub enum Intense {
330    Yes,
331    No,
332}
333
334impl Intense {
335    fn to_bg(&self) -> u16 {
336        self.to_fg() << 4
337    }
338
339    fn from_bg(word: u16) -> Intense {
340        Intense::from_fg(word >> 4)
341    }
342
343    fn to_fg(&self) -> u16 {
344        match *self {
345            Intense::No => 0,
346            Intense::Yes => FG_INTENSITY,
347        }
348    }
349
350    fn from_fg(word: u16) -> Intense {
351        if word & FG_INTENSITY > 0 {
352            Intense::Yes
353        } else {
354            Intense::No
355        }
356    }
357}
358
359/// The set of available colors for use with a Windows console.
360#[allow(missing_docs)]
361#[derive(Clone, Copy, Debug, Eq, PartialEq)]
362pub enum Color {
363    Black,
364    Blue,
365    Green,
366    Red,
367    Cyan,
368    Magenta,
369    Yellow,
370    White,
371}
372
373impl Color {
374    fn to_bg(&self) -> u16 {
375        self.to_fg() << 4
376    }
377
378    fn from_bg(word: u16) -> Color {
379        Color::from_fg(word >> 4)
380    }
381
382    fn to_fg(&self) -> u16 {
383        match *self {
384            Color::Black => 0,
385            Color::Blue => FG_BLUE,
386            Color::Green => FG_GREEN,
387            Color::Red => FG_RED,
388            Color::Cyan => FG_CYAN,
389            Color::Magenta => FG_MAGENTA,
390            Color::Yellow => FG_YELLOW,
391            Color::White => FG_WHITE,
392        }
393    }
394
395    fn from_fg(word: u16) -> Color {
396        match word & 0b111 {
397            FG_BLUE => Color::Blue,
398            FG_GREEN => Color::Green,
399            FG_RED => Color::Red,
400            FG_CYAN => Color::Cyan,
401            FG_MAGENTA => Color::Magenta,
402            FG_YELLOW => Color::Yellow,
403            FG_WHITE => Color::White,
404            _ => Color::Black,
405        }
406    }
407}