Skip to main content

uefi/proto/console/text/
output.rs

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