1 use std::io; 2 3 use winapi::shared::minwindef::WORD; 4 use 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 }; 8 use winapi_util as winutil; 9 10 const FG_CYAN: WORD = FG_BLUE | FG_GREEN; 11 const FG_MAGENTA: WORD = FG_BLUE | FG_RED; 12 const FG_YELLOW: WORD = FG_GREEN | FG_RED; 13 const 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)] 28 pub struct Console { 29 kind: HandleKind, 30 start_attr: TextAttributes, 31 cur_attr: TextAttributes, 32 } 33 34 #[derive(Clone, Copy, Debug)] 35 enum HandleKind { 36 Stdout, 37 Stderr, 38 } 39 40 impl HandleKind { handle(&self) -> winutil::HandleRef41 fn handle(&self) -> winutil::HandleRef { 42 match *self { 43 HandleKind::Stdout => winutil::HandleRef::stdout(), 44 HandleKind::Stderr => winutil::HandleRef::stderr(), 45 } 46 } 47 } 48 49 impl Console { 50 /// Get a console for a standard I/O stream. create_for_stream(kind: HandleKind) -> io::Result<Console>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. stdout() -> io::Result<Console>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. stderr() -> io::Result<Console>68 pub fn stderr() -> io::Result<Console> { 69 Self::create_for_stream(HandleKind::Stderr) 70 } 71 72 /// Applies the current text attributes. set(&mut self) -> io::Result<()>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. fg(&mut self, intense: Intense, color: Color) -> io::Result<()>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. bg(&mut self, intense: Intense, color: Color) -> io::Result<()>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. reset(&mut self) -> io::Result<()>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. set_virtual_terminal_processing( &mut self, yes: bool, ) -> io::Result<()>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)] 141 struct TextAttributes { 142 fg_color: Color, 143 fg_intense: Intense, 144 bg_color: Color, 145 bg_intense: Intense, 146 } 147 148 impl TextAttributes { to_word(&self) -> WORD149 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 from_word(word: WORD) -> TextAttributes158 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)] 171 pub enum Intense { 172 Yes, 173 No, 174 } 175 176 impl Intense { to_bg(&self) -> WORD177 fn to_bg(&self) -> WORD { 178 self.to_fg() << 4 179 } 180 from_bg(word: WORD) -> Intense181 fn from_bg(word: WORD) -> Intense { 182 Intense::from_fg(word >> 4) 183 } 184 to_fg(&self) -> WORD185 fn to_fg(&self) -> WORD { 186 match *self { 187 Intense::No => 0, 188 Intense::Yes => FG_INTENSITY, 189 } 190 } 191 from_fg(word: WORD) -> Intense192 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)] 204 pub enum Color { 205 Black, 206 Blue, 207 Green, 208 Red, 209 Cyan, 210 Magenta, 211 Yellow, 212 White, 213 } 214 215 impl Color { to_bg(&self) -> WORD216 fn to_bg(&self) -> WORD { 217 self.to_fg() << 4 218 } 219 from_bg(word: WORD) -> Color220 fn from_bg(word: WORD) -> Color { 221 Color::from_fg(word >> 4) 222 } 223 to_fg(&self) -> WORD224 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 from_fg(word: WORD) -> Color237 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 } 250