1 use crate::proto::unsafe_protocol; 2 use crate::{CStr16, Result, ResultExt, Status, StatusExt}; 3 use core::fmt; 4 use uefi_raw::protocol::console::{SimpleTextOutputMode, SimpleTextOutputProtocol}; 5 6 /// Interface for text-based output devices. 7 /// 8 /// It implements the fmt::Write trait, so you can use it to print text with 9 /// standard Rust constructs like the `write!()` and `writeln!()` macros. 10 /// 11 /// # Accessing `Output` protocol 12 /// 13 /// The standard output and standard error output protocols can be accessed 14 /// using [`system::stdout`] and [`system::stderr`], respectively. 15 /// 16 /// An `Output` protocol can also be accessed like any other UEFI protocol. 17 /// See the [`boot`] documentation for more details of how to open a 18 /// protocol. 19 /// 20 /// [`system::stdout`]: crate::system::with_stdout 21 /// [`system::stderr`]: crate::system::with_stderr 22 /// [`boot`]: crate::boot#accessing-protocols 23 #[derive(Debug)] 24 #[repr(transparent)] 25 #[unsafe_protocol(SimpleTextOutputProtocol::GUID)] 26 pub struct Output(SimpleTextOutputProtocol); 27 28 impl Output { 29 /// Resets and clears the text output device hardware. reset(&mut self, extended: bool) -> Result30 pub fn reset(&mut self, extended: bool) -> Result { 31 unsafe { (self.0.reset)(&mut self.0, extended) }.to_result() 32 } 33 34 /// Clears the output screen. 35 /// 36 /// The background is set to the current background color. 37 /// The cursor is moved to (0, 0). clear(&mut self) -> Result38 pub fn clear(&mut self) -> Result { 39 unsafe { (self.0.clear_screen)(&mut self.0) }.to_result() 40 } 41 42 /// Writes a string to the output device. output_string(&mut self, string: &CStr16) -> Result43 pub fn output_string(&mut self, string: &CStr16) -> Result { 44 unsafe { (self.0.output_string)(&mut self.0, string.as_ptr().cast()) }.to_result() 45 } 46 47 /// Writes a string to the output device. If the string contains 48 /// unknown characters that cannot be rendered they will be silently 49 /// skipped. output_string_lossy(&mut self, string: &CStr16) -> Result50 pub fn output_string_lossy(&mut self, string: &CStr16) -> Result { 51 self.output_string(string).handle_warning(|err| { 52 if err.status() == Status::WARN_UNKNOWN_GLYPH { 53 Ok(()) 54 } else { 55 Err(err) 56 } 57 }) 58 } 59 60 /// Checks if a string contains only supported characters. 61 /// 62 /// UEFI applications are encouraged to try to print a string even if it contains 63 /// some unsupported characters. test_string(&mut self, string: &CStr16) -> Result<bool>64 pub fn test_string(&mut self, string: &CStr16) -> Result<bool> { 65 match unsafe { (self.0.test_string)(&mut self.0, string.as_ptr().cast()) } { 66 Status::UNSUPPORTED => Ok(false), 67 other => other.to_result_with_val(|| true), 68 } 69 } 70 71 /// Returns an iterator of all supported text modes. 72 // TODO: Bring back impl Trait once the story around bounds improves modes(&mut self) -> OutputModeIter<'_>73 pub fn modes(&mut self) -> OutputModeIter<'_> { 74 let max = self.data().max_mode as usize; 75 OutputModeIter { 76 output: self, 77 current: 0, 78 max, 79 } 80 } 81 82 /// Returns the width (column count) and height (row count) of a text mode. 83 /// 84 /// Devices are required to support at least an 80x25 text mode and to 85 /// assign index 0 to it. If 80x50 is supported, then it will be mode 1, 86 /// otherwise querying for mode 1 will return the `Unsupported` error. 87 /// Modes 2+ will describe other text modes supported by the device. 88 /// 89 /// If you want to iterate over all text modes supported by the device, 90 /// consider using the iterator produced by `modes()` as a more ergonomic 91 /// alternative to this method. query_mode(&self, index: usize) -> Result<(usize, usize)>92 fn query_mode(&self, index: usize) -> Result<(usize, usize)> { 93 let (mut columns, mut rows) = (0, 0); 94 let this: *const _ = &self.0; 95 unsafe { (self.0.query_mode)(this.cast_mut(), index, &mut columns, &mut rows) } 96 .to_result_with_val(|| (columns, rows)) 97 } 98 99 /// Returns the current text mode. current_mode(&self) -> Result<Option<OutputMode>>100 pub fn current_mode(&self) -> Result<Option<OutputMode>> { 101 match self.data().mode { 102 -1 => Ok(None), 103 n if n >= 0 => { 104 let index = n as usize; 105 self.query_mode(index) 106 .map(|dims| Some(OutputMode { index, dims })) 107 } 108 _ => unreachable!(), 109 } 110 } 111 112 /// Sets a mode as current. set_mode(&mut self, mode: OutputMode) -> Result113 pub fn set_mode(&mut self, mode: OutputMode) -> Result { 114 unsafe { (self.0.set_mode)(&mut self.0, mode.index) }.to_result() 115 } 116 117 /// Returns whether the cursor is currently shown or not. 118 #[must_use] cursor_visible(&self) -> bool119 pub const fn cursor_visible(&self) -> bool { 120 self.data().cursor_visible 121 } 122 123 /// Make the cursor visible or invisible. 124 /// 125 /// The output device may not support this operation, in which case an 126 /// `Unsupported` error will be returned. enable_cursor(&mut self, visible: bool) -> Result127 pub fn enable_cursor(&mut self, visible: bool) -> Result { 128 unsafe { (self.0.enable_cursor)(&mut self.0, visible) }.to_result() 129 } 130 131 /// Returns the column and row of the cursor. 132 #[must_use] cursor_position(&self) -> (usize, usize)133 pub const fn cursor_position(&self) -> (usize, usize) { 134 let column = self.data().cursor_column; 135 let row = self.data().cursor_row; 136 (column as usize, row as usize) 137 } 138 139 /// Sets the cursor's position, relative to the top-left corner, which is (0, 0). 140 /// 141 /// This function will fail if the cursor's new position would exceed the screen's bounds. set_cursor_position(&mut self, column: usize, row: usize) -> Result142 pub fn set_cursor_position(&mut self, column: usize, row: usize) -> Result { 143 unsafe { (self.0.set_cursor_position)(&mut self.0, column, row) }.to_result() 144 } 145 146 /// Sets the text and background colors for the console. 147 /// 148 /// Note that for the foreground color you can choose any color. 149 /// The background must be one of the first 8 colors. set_color(&mut self, foreground: Color, background: Color) -> Result150 pub fn set_color(&mut self, foreground: Color, background: Color) -> Result { 151 let fgc = foreground as usize; 152 let bgc = background as usize; 153 154 assert!(bgc < 8, "An invalid background color was requested"); 155 156 let attr = ((bgc & 0x7) << 4) | (fgc & 0xF); 157 unsafe { (self.0.set_attribute)(&mut self.0, attr) }.to_result() 158 } 159 160 /// Get a reference to `OutputData`. The lifetime of the reference is tied 161 /// to `self`. data(&self) -> &SimpleTextOutputMode162 const fn data(&self) -> &SimpleTextOutputMode { 163 // Can't dereference mut pointers in a const function, so cast to const. 164 let mode = self.0.mode.cast_const(); 165 unsafe { &*mode } 166 } 167 } 168 169 impl fmt::Write for Output { write_str(&mut self, s: &str) -> fmt::Result170 fn write_str(&mut self, s: &str) -> fmt::Result { 171 // Allocate a small buffer on the stack. 172 const BUF_SIZE: usize = 128; 173 // Add 1 extra character for the null terminator. 174 let mut buf = [0u16; BUF_SIZE + 1]; 175 176 let mut i = 0; 177 178 // This closure writes the local buffer to the output and resets the buffer. 179 let mut flush_buffer = |buf: &mut [u16], i: &mut usize| { 180 buf[*i] = 0; 181 let codes = &buf[..=*i]; 182 *i = 0; 183 184 let text = CStr16::from_u16_with_nul(codes).map_err(|_| fmt::Error)?; 185 186 self.output_string(text).map_err(|_| fmt::Error) 187 }; 188 189 // This closure converts a character to UCS-2 and adds it to the buffer, 190 // flushing it as necessary. 191 let mut add_char = |ch| { 192 // UEFI only supports UCS-2 characters, not UTF-16, 193 // so there are no multibyte characters. 194 buf[i] = ch; 195 i += 1; 196 197 if i == BUF_SIZE { 198 flush_buffer(&mut buf, &mut i).map_err(|_| ucs2::Error::BufferOverflow) 199 } else { 200 Ok(()) 201 } 202 }; 203 204 // This one converts Rust line feeds to UEFI line feeds beforehand 205 let add_ch = |ch| { 206 if ch == '\n' as u16 { 207 add_char('\r' as u16)?; 208 } 209 add_char(ch) 210 }; 211 212 // Translate and write the input string, flushing the buffer when needed 213 ucs2::encode_with(s, add_ch).map_err(|_| fmt::Error)?; 214 215 // Flush the remainder of the buffer 216 flush_buffer(&mut buf, &mut i) 217 } 218 } 219 220 /// The text mode (resolution) of the output device. 221 #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] 222 pub struct OutputMode { 223 index: usize, 224 dims: (usize, usize), 225 } 226 227 impl OutputMode { 228 /// Returns the index of this mode. 229 #[inline] 230 #[must_use] index(&self) -> usize231 pub const fn index(&self) -> usize { 232 self.index 233 } 234 235 /// Returns the width in columns. 236 #[inline] 237 #[must_use] columns(&self) -> usize238 pub const fn columns(&self) -> usize { 239 self.dims.0 240 } 241 242 /// Returns the height in rows. 243 #[inline] 244 #[must_use] rows(&self) -> usize245 pub const fn rows(&self) -> usize { 246 self.dims.1 247 } 248 } 249 250 /// An iterator of the text modes (possibly) supported by a device. 251 #[derive(Debug)] 252 pub struct OutputModeIter<'out> { 253 output: &'out mut Output, 254 current: usize, 255 max: usize, 256 } 257 258 impl<'out> Iterator for OutputModeIter<'out> { 259 type Item = OutputMode; 260 next(&mut self) -> Option<Self::Item>261 fn next(&mut self) -> Option<Self::Item> { 262 let index = self.current; 263 if index < self.max { 264 self.current += 1; 265 266 if let Ok(dims) = self.output.query_mode(index) { 267 Some(OutputMode { index, dims }) 268 } else { 269 self.next() 270 } 271 } else { 272 None 273 } 274 } 275 } 276 277 /// Colors for the UEFI console. 278 /// 279 /// All colors can be used as foreground colors. 280 /// The first 8 colors can also be used as background colors. 281 #[allow(missing_docs)] 282 #[derive(Debug, Copy, Clone)] 283 pub enum Color { 284 Black = 0, 285 Blue, 286 Green, 287 Cyan, 288 Red, 289 Magenta, 290 Brown, 291 LightGray, 292 DarkGray, 293 LightBlue, 294 LightGreen, 295 LightCyan, 296 LightRed, 297 LightMagenta, 298 Yellow, 299 White, 300 } 301