Skip to main content

wincolor/
win.rs

1use std::io;
2
3use winapi::shared::minwindef::WORD;
4use winapi::um::wincon::{
5    self, FOREGROUND_BLUE as FG_BLUE, FOREGROUND_GREEN as FG_GREEN,
6    FOREGROUND_INTENSITY as FG_INTENSITY, FOREGROUND_RED as FG_RED,
7};
8use winapi_util as winutil;
9
10const FG_CYAN: WORD = FG_BLUE | FG_GREEN;
11const FG_MAGENTA: WORD = FG_BLUE | FG_RED;
12const FG_YELLOW: WORD = FG_GREEN | FG_RED;
13const FG_WHITE: WORD = FG_BLUE | FG_GREEN | FG_RED;
14
15/// A Windows console.
16///
17/// This represents a very limited set of functionality available to a Windows
18/// console. In particular, it can only change text attributes such as color
19/// and intensity.
20///
21/// There is no way to "write" to this console. Simply write to
22/// stdout or stderr instead, while interleaving instructions to the console
23/// to change text attributes.
24///
25/// A common pitfall when using a console is to forget to flush writes to
26/// stdout before setting new text attributes.
27#[derive(Debug)]
28pub struct Console {
29    kind: HandleKind,
30    start_attr: TextAttributes,
31    cur_attr: TextAttributes,
32}
33
34#[derive(Clone, Copy, Debug)]
35enum HandleKind {
36    Stdout,
37    Stderr,
38}
39
40impl HandleKind {
41    fn handle(&self) -> winutil::HandleRef {
42        match *self {
43            HandleKind::Stdout => winutil::HandleRef::stdout(),
44            HandleKind::Stderr => winutil::HandleRef::stderr(),
45        }
46    }
47}
48
49impl Console {
50    /// Get a console for a standard I/O stream.
51    fn create_for_stream(kind: HandleKind) -> io::Result<Console> {
52        let h = kind.handle();
53        let info = winutil::console::screen_buffer_info(&h)?;
54        let attr = TextAttributes::from_word(info.attributes());
55        Ok(Console { kind: kind, start_attr: attr, cur_attr: attr })
56    }
57
58    /// Create a new Console to stdout.
59    ///
60    /// If there was a problem creating the console, then an error is returned.
61    pub fn stdout() -> io::Result<Console> {
62        Self::create_for_stream(HandleKind::Stdout)
63    }
64
65    /// Create a new Console to stderr.
66    ///
67    /// If there was a problem creating the console, then an error is returned.
68    pub fn stderr() -> io::Result<Console> {
69        Self::create_for_stream(HandleKind::Stderr)
70    }
71
72    /// Applies the current text attributes.
73    fn set(&mut self) -> io::Result<()> {
74        winutil::console::set_text_attributes(
75            self.kind.handle(),
76            self.cur_attr.to_word(),
77        )
78    }
79
80    /// Apply the given intensity and color attributes to the console
81    /// foreground.
82    ///
83    /// If there was a problem setting attributes on the console, then an error
84    /// is returned.
85    pub fn fg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
86        self.cur_attr.fg_color = color;
87        self.cur_attr.fg_intense = intense;
88        self.set()
89    }
90
91    /// Apply the given intensity and color attributes to the console
92    /// background.
93    ///
94    /// If there was a problem setting attributes on the console, then an error
95    /// is returned.
96    pub fn bg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
97        self.cur_attr.bg_color = color;
98        self.cur_attr.bg_intense = intense;
99        self.set()
100    }
101
102    /// Reset the console text attributes to their original settings.
103    ///
104    /// The original settings correspond to the text attributes on the console
105    /// when this `Console` value was created.
106    ///
107    /// If there was a problem setting attributes on the console, then an error
108    /// is returned.
109    pub fn reset(&mut self) -> io::Result<()> {
110        self.cur_attr = self.start_attr;
111        self.set()
112    }
113
114    /// Toggle virtual terminal processing.
115    ///
116    /// This method attempts to toggle virtual terminal processing for this
117    /// console. If there was a problem toggling it, then an error returned.
118    /// On success, the caller may assume that toggling it was successful.
119    ///
120    /// When virtual terminal processing is enabled, characters emitted to the
121    /// console are parsed for VT100 and similar control character sequences
122    /// that control color and other similar operations.
123    pub fn set_virtual_terminal_processing(
124        &mut self,
125        yes: bool,
126    ) -> io::Result<()> {
127        let vt = wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
128
129        let handle = self.kind.handle();
130        let old_mode = winutil::console::mode(&handle)?;
131        let new_mode = if yes { old_mode | vt } else { old_mode & !vt };
132        if old_mode == new_mode {
133            return Ok(());
134        }
135        winutil::console::set_mode(&handle, new_mode)
136    }
137}
138
139/// A representation of text attributes for the Windows console.
140#[derive(Copy, Clone, Debug, Eq, PartialEq)]
141struct TextAttributes {
142    fg_color: Color,
143    fg_intense: Intense,
144    bg_color: Color,
145    bg_intense: Intense,
146}
147
148impl TextAttributes {
149    fn to_word(&self) -> WORD {
150        let mut w = 0;
151        w |= self.fg_color.to_fg();
152        w |= self.fg_intense.to_fg();
153        w |= self.bg_color.to_bg();
154        w |= self.bg_intense.to_bg();
155        w
156    }
157
158    fn from_word(word: WORD) -> TextAttributes {
159        TextAttributes {
160            fg_color: Color::from_fg(word),
161            fg_intense: Intense::from_fg(word),
162            bg_color: Color::from_bg(word),
163            bg_intense: Intense::from_bg(word),
164        }
165    }
166}
167
168/// Whether to use intense colors or not.
169#[allow(missing_docs)]
170#[derive(Clone, Copy, Debug, Eq, PartialEq)]
171pub enum Intense {
172    Yes,
173    No,
174}
175
176impl Intense {
177    fn to_bg(&self) -> WORD {
178        self.to_fg() << 4
179    }
180
181    fn from_bg(word: WORD) -> Intense {
182        Intense::from_fg(word >> 4)
183    }
184
185    fn to_fg(&self) -> WORD {
186        match *self {
187            Intense::No => 0,
188            Intense::Yes => FG_INTENSITY,
189        }
190    }
191
192    fn from_fg(word: WORD) -> Intense {
193        if word & FG_INTENSITY > 0 {
194            Intense::Yes
195        } else {
196            Intense::No
197        }
198    }
199}
200
201/// The set of available colors for use with a Windows console.
202#[allow(missing_docs)]
203#[derive(Clone, Copy, Debug, Eq, PartialEq)]
204pub enum Color {
205    Black,
206    Blue,
207    Green,
208    Red,
209    Cyan,
210    Magenta,
211    Yellow,
212    White,
213}
214
215impl Color {
216    fn to_bg(&self) -> WORD {
217        self.to_fg() << 4
218    }
219
220    fn from_bg(word: WORD) -> Color {
221        Color::from_fg(word >> 4)
222    }
223
224    fn to_fg(&self) -> WORD {
225        match *self {
226            Color::Black => 0,
227            Color::Blue => FG_BLUE,
228            Color::Green => FG_GREEN,
229            Color::Red => FG_RED,
230            Color::Cyan => FG_CYAN,
231            Color::Magenta => FG_MAGENTA,
232            Color::Yellow => FG_YELLOW,
233            Color::White => FG_WHITE,
234        }
235    }
236
237    fn from_fg(word: WORD) -> Color {
238        match word & 0b111 {
239            FG_BLUE => Color::Blue,
240            FG_GREEN => Color::Green,
241            FG_RED => Color::Red,
242            FG_CYAN => Color::Cyan,
243            FG_MAGENTA => Color::Magenta,
244            FG_YELLOW => Color::Yellow,
245            FG_WHITE => Color::White,
246            _ => Color::Black,
247        }
248    }
249}