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}