use crate::proto::unsafe_protocol;
use crate::{CStr16, Result, ResultExt, Status, StatusExt};
use core::fmt;
use uefi_raw::protocol::console::{SimpleTextOutputMode, SimpleTextOutputProtocol};
#[derive(Debug)]
#[repr(transparent)]
#[unsafe_protocol(SimpleTextOutputProtocol::GUID)]
pub struct Output(SimpleTextOutputProtocol);
impl Output {
pub fn reset(&mut self, extended: bool) -> Result {
unsafe { (self.0.reset)(&mut self.0, extended.into()) }.to_result()
}
pub fn clear(&mut self) -> Result {
unsafe { (self.0.clear_screen)(&mut self.0) }.to_result()
}
pub fn output_string(&mut self, string: &CStr16) -> Result {
unsafe { (self.0.output_string)(&mut self.0, string.as_ptr().cast()) }.to_result()
}
pub fn output_string_lossy(&mut self, string: &CStr16) -> Result {
self.output_string(string).handle_warning(|err| {
if err.status() == Status::WARN_UNKNOWN_GLYPH {
Ok(())
} else {
Err(err)
}
})
}
pub fn test_string(&mut self, string: &CStr16) -> Result<bool> {
match unsafe { (self.0.test_string)(&mut self.0, string.as_ptr().cast()) } {
Status::UNSUPPORTED => Ok(false),
other => other.to_result_with_val(|| true),
}
}
pub const fn modes(&mut self) -> OutputModeIter<'_> {
let max = self.data().max_mode as usize;
OutputModeIter {
output: self,
current: 0,
max,
}
}
fn query_mode(&self, index: usize) -> Result<(usize, usize)> {
let (mut columns, mut rows) = (0, 0);
let this: *const _ = &self.0;
unsafe { (self.0.query_mode)(this.cast_mut(), index, &mut columns, &mut rows) }
.to_result_with_val(|| (columns, rows))
}
pub fn current_mode(&self) -> Result<Option<OutputMode>> {
match self.data().mode {
-1 => Ok(None),
n if n >= 0 => {
let index = n as usize;
self.query_mode(index)
.map(|dims| Some(OutputMode { index, dims }))
}
_ => unreachable!(),
}
}
pub fn set_mode(&mut self, mode: OutputMode) -> Result {
unsafe { (self.0.set_mode)(&mut self.0, mode.index) }.to_result()
}
#[must_use]
pub fn cursor_visible(&self) -> bool {
self.data().cursor_visible.into()
}
pub fn enable_cursor(&mut self, visible: bool) -> Result {
unsafe { (self.0.enable_cursor)(&mut self.0, visible.into()) }.to_result()
}
#[must_use]
pub const fn cursor_position(&self) -> (usize, usize) {
let column = self.data().cursor_column;
let row = self.data().cursor_row;
(column as usize, row as usize)
}
pub fn set_cursor_position(&mut self, column: usize, row: usize) -> Result {
unsafe { (self.0.set_cursor_position)(&mut self.0, column, row) }.to_result()
}
pub fn set_color(&mut self, foreground: Color, background: Color) -> Result {
let fgc = foreground as usize;
let bgc = background as usize;
assert!(bgc < 8, "An invalid background color was requested");
let attr = ((bgc & 0x7) << 4) | (fgc & 0xF);
unsafe { (self.0.set_attribute)(&mut self.0, attr) }.to_result()
}
const fn data(&self) -> &SimpleTextOutputMode {
let mode = self.0.mode.cast_const();
unsafe { &*mode }
}
}
impl fmt::Write for Output {
fn write_str(&mut self, s: &str) -> fmt::Result {
const BUF_SIZE: usize = 128;
let mut buf = [0u16; BUF_SIZE + 1];
let mut i = 0;
let mut flush_buffer = |buf: &mut [u16], i: &mut usize| {
buf[*i] = 0;
let codes = &buf[..=*i];
*i = 0;
let text = CStr16::from_u16_with_nul(codes).map_err(|_| fmt::Error)?;
self.output_string(text).map_err(|_| fmt::Error)
};
let mut add_char = |ch| {
buf[i] = ch;
i += 1;
if i == BUF_SIZE {
flush_buffer(&mut buf, &mut i).map_err(|_| ucs2::Error::BufferOverflow)
} else {
Ok(())
}
};
let add_ch = |ch| {
if ch == '\n' as u16 {
add_char('\r' as u16)?;
}
add_char(ch)
};
ucs2::encode_with(s, add_ch).map_err(|_| fmt::Error)?;
flush_buffer(&mut buf, &mut i)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct OutputMode {
index: usize,
dims: (usize, usize),
}
impl OutputMode {
#[inline]
#[must_use]
pub const fn index(&self) -> usize {
self.index
}
#[inline]
#[must_use]
pub const fn columns(&self) -> usize {
self.dims.0
}
#[inline]
#[must_use]
pub const fn rows(&self) -> usize {
self.dims.1
}
}
#[derive(Debug)]
pub struct OutputModeIter<'out> {
output: &'out mut Output,
current: usize,
max: usize,
}
impl Iterator for OutputModeIter<'_> {
type Item = OutputMode;
fn next(&mut self) -> Option<Self::Item> {
let index = self.current;
if index < self.max {
self.current += 1;
if let Ok(dims) = self.output.query_mode(index) {
Some(OutputMode { index, dims })
} else {
self.next()
}
} else {
None
}
}
}
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone)]
pub enum Color {
Black = 0,
Blue,
Green,
Cyan,
Red,
Magenta,
Brown,
LightGray,
DarkGray,
LightBlue,
LightGreen,
LightCyan,
LightRed,
LightMagenta,
Yellow,
White,
}