Skip to content

Commit 4b1c2e0

Browse files
authored
Merge pull request #162 from FenrirWolf/impl_error_applet
Add error applet support + error applet panic hook
2 parents eed5fc9 + 9396939 commit 4b1c2e0

File tree

5 files changed

+175
-1
lines changed

5 files changed

+175
-1
lines changed

ctru-rs/src/applets/error.rs

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

ctru-rs/src/applets/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
//!
99
//! Applets block execution of the thread that launches them as long as the user doesn't close the applet.
1010
11+
pub mod error;
1112
pub mod mii_selector;
1213
pub mod swkbd;

ctru-rs/src/services/gfx.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ pub struct Gfx {
239239
_service_handler: ServiceReference,
240240
}
241241

242-
static GFX_ACTIVE: Mutex<()> = Mutex::new(());
242+
pub(crate) static GFX_ACTIVE: Mutex<()> = Mutex::new(());
243243

244244
impl Gfx {
245245
/// Initialize a new default service handle.

ctru-sys/build.rs

+2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ fn main() {
7979
.blocklist_type("u(8|16|32|64)")
8080
.blocklist_type("__builtin_va_list")
8181
.blocklist_type("__va_list")
82+
.blocklist_type("errorReturnCode")
83+
.blocklist_type("errorScreenFlag")
8284
.opaque_type("MiiData")
8385
.derive_default(true)
8486
.wrap_static_fns(true)

ctru-sys/src/lib.rs

+18
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,24 @@
1616
pub mod result;
1717
pub use result::*;
1818

19+
// Fun fact: C compilers are allowed to represent enums as the smallest integer type that can hold all of its variants,
20+
// meaning that enums are allowed to be the size of a `c_short` or a `c_char` rather than the size of a `c_int`.
21+
// Libctru's `errorConf` struct contains two enums that depend on this narrowing property for size and alignment purposes,
22+
// and since `bindgen` generates all enums with `c_int` sizing, we have to blocklist those types and manually define them
23+
// here with the proper size.
24+
pub type errorReturnCode = libc::c_schar;
25+
pub const ERROR_UNKNOWN: errorReturnCode = -1;
26+
pub const ERROR_NONE: errorReturnCode = 0;
27+
pub const ERROR_SUCCESS: errorReturnCode = 1;
28+
pub const ERROR_NOT_SUPPORTED: errorReturnCode = 2;
29+
pub const ERROR_HOME_BUTTON: errorReturnCode = 10;
30+
pub const ERROR_SOFTWARE_RESET: errorReturnCode = 11;
31+
pub const ERROR_POWER_BUTTON: errorReturnCode = 12;
32+
33+
pub type errorScreenFlag = libc::c_char;
34+
pub const ERROR_NORMAL: errorScreenFlag = 0;
35+
pub const ERROR_STEREO: errorScreenFlag = 1;
36+
1937
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
2038

2139
/// In lieu of a proper errno function exposed by libc

0 commit comments

Comments
 (0)