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