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