1 | use crate::proto::unsafe_protocol ; |
2 | use crate::{CStr16, Char16, Result, ResultExt, Status}; |
3 | use core::fmt; |
4 | use 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" )] |
25 | pub 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 | |
43 | impl 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 | |
180 | impl 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 | |
231 | impl 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)] |
262 | pub struct OutputMode { |
263 | index: usize, |
264 | dims: (usize, usize), |
265 | } |
266 | |
267 | impl 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. |
291 | pub struct OutputModeIter<'out> { |
292 | output: &'out mut Output, |
293 | current: usize, |
294 | max: usize, |
295 | } |
296 | |
297 | impl<'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)] |
319 | struct 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)] |
341 | pub 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 | |