ctru/applets/
error.rs

1//! Error applet.
2//!
3//! This applet displays error text as a pop-up message on the lower screen.
4
5use crate::Utf16Writer;
6use crate::services::{apt::Apt, gfx::Gfx};
7
8use ctru_sys::{errorConf, errorDisp, errorInit};
9
10/// Configuration struct to set up the Error applet.
11#[doc(alias = "errorConf")]
12pub struct PopUp {
13    state: Box<errorConf>,
14}
15
16/// Determines whether the Error applet will use word wrapping when displaying a message.
17#[doc(alias = "errorType")]
18#[derive(Copy, Clone, Debug, PartialEq, Eq)]
19#[repr(u16)]
20pub enum WordWrap {
21    /// Error text is centered in the error applet window and does not use word wrapping.
22    Disabled = ctru_sys::ERROR_TEXT,
23    /// Error text starts at the top of the error applet window and uses word wrapping.
24    Enabled = ctru_sys::ERROR_TEXT_WORD_WRAP,
25}
26
27/// Error returned by an unsuccessful [`PopUp::launch()`].
28#[doc(alias = "errorReturnCode")]
29#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30#[repr(i8)]
31pub enum Error {
32    /// Unknown error occurred.
33    Unknown = ctru_sys::ERROR_UNKNOWN,
34    /// Operation not supported.
35    NotSupported = ctru_sys::ERROR_NOT_SUPPORTED,
36    /// Home button pressed while [`PopUp`] was running.
37    HomePressed = ctru_sys::ERROR_HOME_BUTTON,
38    /// Power button pressed while [`PopUp`] was running.
39    PowerPressed = ctru_sys::ERROR_POWER_BUTTON,
40    /// Reset button pressed while [`PopUp`] was running.
41    ResetPressed = ctru_sys::ERROR_SOFTWARE_RESET,
42}
43
44impl PopUp {
45    /// Initializes the error applet with the provided word wrap setting.
46    #[doc(alias = "errorInit")]
47    pub fn new(word_wrap: WordWrap) -> Self {
48        let mut state = Box::<errorConf>::default();
49
50        unsafe { errorInit(state.as_mut(), word_wrap as _, 0) };
51
52        Self { state }
53    }
54
55    /// Returns a [`Utf16Writer`] that writes its output to the [`PopUp`]'s internal text buffer.
56    ///
57    /// # Notes
58    ///
59    /// The input will be converted to UTF-16 for display with the applet, and the message will be
60    /// truncated if it exceeds 1900 UTF-16 code units in length after conversion.
61    ///
62    /// # Example
63    ///
64    /// ```
65    /// # let _runner = test_runner::GdbRunner::default();
66    /// # fn main() {
67    /// #
68    /// use ctru::applets::error::{PopUp, WordWrap};
69    /// use std::fmt::Write;
70    ///
71    /// let mut popup = PopUp::new(WordWrap::Enabled);
72    ///
73    /// let _ = write!(popup.writer(), "Look mom, I'm a custom error message!");
74    /// #
75    /// # }
76    /// ```
77    #[doc(alias = "errorText")]
78    pub fn writer(&mut self) -> Utf16Writer<'_> {
79        Utf16Writer::new(&mut self.state.Text)
80    }
81
82    /// Launches the error applet.
83    #[doc(alias = "errorDisp")]
84    pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(), Error> {
85        unsafe { self.launch_unchecked() }
86    }
87
88    /// Launches the error applet without requiring an [`Apt`] or [`Gfx`] handle.
89    ///
90    /// # Safety
91    ///
92    /// Potentially leads to undefined behavior if the aforementioned services are not actually active when the applet launches.
93    unsafe fn launch_unchecked(&mut self) -> Result<(), Error> {
94        unsafe { ctru_sys::errorDisp(self.state.as_mut()) };
95
96        match self.state.returnCode {
97            ctru_sys::ERROR_NONE | ctru_sys::ERROR_SUCCESS => Ok(()),
98            ctru_sys::ERROR_NOT_SUPPORTED => Err(Error::NotSupported),
99            ctru_sys::ERROR_HOME_BUTTON => Err(Error::HomePressed),
100            ctru_sys::ERROR_POWER_BUTTON => Err(Error::PowerPressed),
101            ctru_sys::ERROR_SOFTWARE_RESET => Err(Error::ResetPressed),
102            _ => Err(Error::Unknown),
103        }
104    }
105}
106
107pub(crate) fn set_panic_hook(call_old_hook: bool) {
108    use crate::services::gfx::GFX_ACTIVE;
109    use std::fmt::Write;
110    use std::sync::{Mutex, TryLockError};
111
112    static ERROR_CONF: Mutex<errorConf> = unsafe { Mutex::new(std::mem::zeroed()) };
113
114    let mut lock = ERROR_CONF.lock().unwrap();
115
116    unsafe { errorInit(&mut *lock, WordWrap::Enabled as _, 0) };
117
118    let old_hook = std::panic::take_hook();
119
120    std::panic::set_hook(Box::new(move |panic_info| {
121        // If we get a `WouldBlock` error, we know that the `Gfx` service has been initialized.
122        // Otherwise fallback to using the old panic hook.
123        if let (Err(TryLockError::WouldBlock), Ok(_apt)) = (GFX_ACTIVE.try_lock(), Apt::new()) {
124            if call_old_hook {
125                old_hook(panic_info);
126            }
127
128            let mut lock = ERROR_CONF.lock().unwrap();
129
130            let error_conf = &mut *lock;
131
132            let mut writer = Utf16Writer::new(&mut error_conf.Text);
133
134            let thread = std::thread::current();
135
136            let name = thread.name().unwrap_or("<unnamed>");
137
138            let _ = write!(writer, "thread '{name}' {panic_info}");
139
140            unsafe {
141                errorDisp(error_conf);
142            }
143        } else {
144            old_hook(panic_info);
145        }
146    }));
147}
148
149impl std::fmt::Display for Error {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        match self {
152            Self::NotSupported => write!(f, "operation not supported"),
153            Self::HomePressed => write!(f, "home button pressed while error applet was running"),
154            Self::PowerPressed => write!(f, "power button pressed while error applet was running"),
155            Self::ResetPressed => write!(f, "reset button pressed while error applet was running"),
156            Self::Unknown => write!(f, "an unknown error occurred"),
157        }
158    }
159}
160
161impl std::error::Error for Error {}