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