From dc2959396c191696ee3d79f9793799d20e9fc1c1 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 19 Feb 2024 14:41:01 -0700 Subject: [PATCH 01/17] Add basic error applet support --- ctru-rs/src/applets/error.rs | 56 ++++++++++++++++++++++++++++++++++++ ctru-rs/src/applets/mod.rs | 1 + ctru-sys/build.rs | 2 ++ ctru-sys/src/lib.rs | 16 +++++++++++ 4 files changed, 75 insertions(+) create mode 100644 ctru-rs/src/applets/error.rs diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs new file mode 100644 index 0000000..ef7cea7 --- /dev/null +++ b/ctru-rs/src/applets/error.rs @@ -0,0 +1,56 @@ +//! Error applet +//! +//! This applet displays error text as a pop-up message on the lower screen. +#![doc(alias = "Error")] + +use crate::services::{apt::Apt, gfx::Gfx}; + +use ctru_sys::errorConf; + +/// Configuration struct to set up the Error applet. +#[doc(alias = "errorConf")] +pub struct ErrorApplet { + state: Box, +} + +/// The kind of error applet to display. +#[doc(alias = "errorType")] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum Kind { + /// Error text is centered in the error applet window. + Center = ctru_sys::ERROR_TEXT, + /// Error text starts at the top of the error applet window. + Top = ctru_sys::ERROR_TEXT_WORD_WRAP, +} + +impl ErrorApplet { + /// Initialize the error applet with the provided text kind. + #[doc(alias = "errorInit")] + pub fn new(kind: Kind) -> Self { + let mut state = Box::::default(); + + unsafe { ctru_sys::errorInit(state.as_mut(), kind as _, 0) }; + + Self { state } + } + + /// Sets the error text to display. + #[doc(alias = "errorText")] + pub fn set_text(&mut self, text: &str) { + for (idx, code_unit) in text + .encode_utf16() + .chain(std::iter::once(0)) + .take(self.state.Text.len() - 1) + .enumerate() + { + self.state.Text[idx] = code_unit; + } + } + + /// Launches the error applet. + #[doc(alias = "errorDisp")] + pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) { + unsafe { ctru_sys::errorDisp(self.state.as_mut()) } + } +} diff --git a/ctru-rs/src/applets/mod.rs b/ctru-rs/src/applets/mod.rs index 244fb74..838ef6b 100644 --- a/ctru-rs/src/applets/mod.rs +++ b/ctru-rs/src/applets/mod.rs @@ -8,5 +8,6 @@ //! //! Applets block execution of the thread that launches them as long as the user doesn't close the applet. +pub mod error; pub mod mii_selector; pub mod swkbd; diff --git a/ctru-sys/build.rs b/ctru-sys/build.rs index 5e71ae0..ff140bb 100644 --- a/ctru-sys/build.rs +++ b/ctru-sys/build.rs @@ -79,6 +79,8 @@ fn main() { .blocklist_type("u(8|16|32|64)") .blocklist_type("__builtin_va_list") .blocklist_type("__va_list") + .blocklist_type("errorReturnCode") + .blocklist_type("errorScreenFlag") .opaque_type("MiiData") .derive_default(true) .wrap_static_fns(true) diff --git a/ctru-sys/src/lib.rs b/ctru-sys/src/lib.rs index b9f71de..38f0d37 100644 --- a/ctru-sys/src/lib.rs +++ b/ctru-sys/src/lib.rs @@ -16,6 +16,22 @@ pub mod result; pub use result::*; +// Fun fact: bindgen can and will generate enum values of the wrong size, and if a generated struct contains fields +// using those values, then that struct will have the wrong size and field offsets too. To fix that problem, +// you have to blocklist the enum type in bindgen and manually define it with the proper data type. +pub const ERROR_UNKNOWN: errorReturnCode = -1; +pub const ERROR_NONE: errorReturnCode = 0; +pub const ERROR_SUCCESS: errorReturnCode = 1; +pub const ERROR_NOT_SUPPORTED: errorReturnCode = 2; +pub const ERROR_HOME_BUTTON: errorReturnCode = 10; +pub const ERROR_SOFTWARE_RESET: errorReturnCode = 11; +pub const ERROR_POWER_BUTTON: errorReturnCode = 12; +pub type errorReturnCode = libc::c_schar; + +pub const ERROR_NORMAL: errorScreenFlag = 0; +pub const ERROR_STEREO: errorScreenFlag = 1; +pub type errorScreenFlag = libc::c_char; + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); /// In lieu of a proper errno function exposed by libc From a75ac1ffd23e42870938f1e6dbc8475e83ab79f2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 19 Feb 2024 21:08:27 -0700 Subject: [PATCH 02/17] Clarify comment on manually implemented enum types --- ctru-sys/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ctru-sys/src/lib.rs b/ctru-sys/src/lib.rs index 38f0d37..39484a3 100644 --- a/ctru-sys/src/lib.rs +++ b/ctru-sys/src/lib.rs @@ -16,9 +16,11 @@ pub mod result; pub use result::*; -// Fun fact: bindgen can and will generate enum values of the wrong size, and if a generated struct contains fields -// using those values, then that struct will have the wrong size and field offsets too. To fix that problem, -// you have to blocklist the enum type in bindgen and manually define it with the proper data type. +// Fun fact: C compilers are allowed to represent enums as the smallest integer type that can hold all of its variants, +// 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`. +// Libctru's `errorConf` struct contains two enums that depend on this narrowing property for size and alignment purposes, +// and since `bindgen` generates all enums with `c_int` sizing, we have to blocklist those types and manually define them +// here with the proper size. pub const ERROR_UNKNOWN: errorReturnCode = -1; pub const ERROR_NONE: errorReturnCode = 0; pub const ERROR_SUCCESS: errorReturnCode = 1; From cd309813483d4fffdc2061487b37f9e3f50b0163 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 23 Feb 2024 15:41:52 -0700 Subject: [PATCH 03/17] Rename error applet config struct --- ctru-rs/src/applets/error.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index ef7cea7..b5af871 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -1,15 +1,13 @@ //! Error applet //! //! This applet displays error text as a pop-up message on the lower screen. -#![doc(alias = "Error")] - use crate::services::{apt::Apt, gfx::Gfx}; use ctru_sys::errorConf; /// Configuration struct to set up the Error applet. #[doc(alias = "errorConf")] -pub struct ErrorApplet { +pub struct PopUp { state: Box, } From a9ded199fa93ed37cb3cd59f491338a756a41120 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 23 Feb 2024 16:09:41 -0700 Subject: [PATCH 04/17] Handle error codes returned by the Error applet --- ctru-rs/src/applets/error.rs | 44 ++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index b5af871..c60c4a8 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -22,7 +22,24 @@ pub enum Kind { Top = ctru_sys::ERROR_TEXT_WORD_WRAP, } -impl ErrorApplet { +/// Error returned by an unsuccessful [`PopUp::launch()`]. +#[doc(alias = "errorReturnCode")] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(i8)] +pub enum Error { + /// Unknown error occurred. + Unknown = ctru_sys::ERROR_UNKNOWN, + /// Operation not supported. + NotSupported = ctru_sys::ERROR_NOT_SUPPORTED, + /// Home button pressed while [`PopUp`] was running. + HomePressed = ctru_sys::ERROR_HOME_BUTTON, + /// Power button pressed while [`PopUp`] was running. + PowerPressed = ctru_sys::ERROR_POWER_BUTTON, + /// Reset button pressed while [`PopUp`] was running. + ResetPressed = ctru_sys::ERROR_SOFTWARE_RESET, +} + +impl PopUp { /// Initialize the error applet with the provided text kind. #[doc(alias = "errorInit")] pub fn new(kind: Kind) -> Self { @@ -48,7 +65,30 @@ impl ErrorApplet { /// Launches the error applet. #[doc(alias = "errorDisp")] - pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) { + pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(), Error> { unsafe { ctru_sys::errorDisp(self.state.as_mut()) } + + match self.state.returnCode { + ctru_sys::ERROR_NONE | ctru_sys::ERROR_SUCCESS => Ok(()), + ctru_sys::ERROR_NOT_SUPPORTED => Err(Error::NotSupported), + ctru_sys::ERROR_HOME_BUTTON => Err(Error::HomePressed), + ctru_sys::ERROR_POWER_BUTTON => Err(Error::PowerPressed), + ctru_sys::ERROR_SOFTWARE_RESET => Err(Error::ResetPressed), + _ => Err(Error::Unknown), + } } } + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::NotSupported => write!(f, "operation not supported"), + Self::HomePressed => write!(f, "home button pressed while error applet was running"), + Self::PowerPressed => write!(f, "power button pressed while error applet was running"), + Self::ResetPressed => write!(f, "reset button pressed while error applet was running"), + Self::Unknown => write!(f, "an unknown error occurred"), + } + } +} + +impl std::error::Error for Error {} From c6d5cdc3679ba4806be5b2cef80cdd417ee177cb Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 23 Feb 2024 18:25:43 -0700 Subject: [PATCH 05/17] Add error applet panic hook --- ctru-rs/src/applets/error.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index c60c4a8..584a2b4 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -79,6 +79,34 @@ impl PopUp { } } +/// Sets a custom panic hook that uses the error applet to display panic messages. You can also choose to have +/// messages printed over stderr along with the pop-up display. +/// +/// SAFETY: The error applet requires that both the [`Apt`] and [`Gfx`] services are active whenever it launches. +/// By calling this function, you promise that you will keep those services alive until either the program ends or +/// you unregister this hook with [`std::panic::take_hook`](https://doc.rust-lang.org/std/panic/fn.take_hook.html). +pub unsafe fn set_panic_hook(use_stderr: bool) { + std::panic::set_hook(Box::new(move |panic_info| { + let mut popup = PopUp::new(Kind::Top); + + let thread = std::thread::current(); + + let name = thread.name().unwrap_or(""); + + let payload = format!("thread '{name}' {panic_info}"); + + popup.set_text(&payload); + + if use_stderr { + eprintln!("{payload}"); + } + + unsafe { + ctru_sys::errorDisp(popup.state.as_mut()); + } + })) +} + impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { From d32cd1525f2a2a5d34febdf91f6586e7f872cbce Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 23 Feb 2024 18:48:35 -0700 Subject: [PATCH 06/17] Properly document safety concerns --- ctru-rs/src/applets/error.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index 584a2b4..06e5ffe 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -82,7 +82,9 @@ impl PopUp { /// Sets a custom panic hook that uses the error applet to display panic messages. You can also choose to have /// messages printed over stderr along with the pop-up display. /// -/// SAFETY: The error applet requires that both the [`Apt`] and [`Gfx`] services are active whenever it launches. +/// # Safety +/// +/// The error applet requires that both the [`Apt`] and [`Gfx`] services are active whenever it launches. /// By calling this function, you promise that you will keep those services alive until either the program ends or /// you unregister this hook with [`std::panic::take_hook`](https://doc.rust-lang.org/std/panic/fn.take_hook.html). pub unsafe fn set_panic_hook(use_stderr: bool) { From 17dc4c4c76e2581e4fa6da21daaba82e8a47245a Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 23 Feb 2024 19:06:59 -0700 Subject: [PATCH 07/17] Add private PopUp::launch_unchecked method --- ctru-rs/src/applets/error.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index 06e5ffe..ac9a3db 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -77,6 +77,15 @@ impl PopUp { _ => Err(Error::Unknown), } } + + /// Launches the error applet without requiring an [`Apt`] or [`Gfx`] handle. + /// + /// # Safety + /// + /// Causes undefined behavior if the aforementioned services are not actually active when the applet launches. + unsafe fn launch_unchecked(&mut self) { + unsafe { ctru_sys::errorDisp(self.state.as_mut()) }; + } } /// Sets a custom panic hook that uses the error applet to display panic messages. You can also choose to have @@ -104,7 +113,7 @@ pub unsafe fn set_panic_hook(use_stderr: bool) { } unsafe { - ctru_sys::errorDisp(popup.state.as_mut()); + popup.launch_unchecked(); } })) } From a67c8c0bf64378968c6420dcb18cb8d09b67f862 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 25 Feb 2024 01:46:50 -0700 Subject: [PATCH 08/17] Make `set_panic_hook` into a safe fn --- ctru-rs/src/applets/error.rs | 49 +++++++++++++++++++++++++----------- ctru-rs/src/services/gfx.rs | 2 +- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index ac9a3db..6fa7efc 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -91,31 +91,50 @@ impl PopUp { /// Sets a custom panic hook that uses the error applet to display panic messages. You can also choose to have /// messages printed over stderr along with the pop-up display. /// -/// # Safety -/// -/// The error applet requires that both the [`Apt`] and [`Gfx`] services are active whenever it launches. -/// By calling this function, you promise that you will keep those services alive until either the program ends or -/// you unregister this hook with [`std::panic::take_hook`](https://doc.rust-lang.org/std/panic/fn.take_hook.html). -pub unsafe fn set_panic_hook(use_stderr: bool) { +/// If the `Gfx` service is not initialized during a panic, the error applet will not be displayed and the panic +/// message will be printed over stderr. +pub fn set_panic_hook(use_stderr: bool) { + use crate::services::gfx::GFX_ACTIVE; + + use std::io::Write; + use std::sync::TryLockError; + std::panic::set_hook(Box::new(move |panic_info| { - let mut popup = PopUp::new(Kind::Top); + let _apt = Apt::new(); let thread = std::thread::current(); let name = thread.name().unwrap_or(""); - let payload = format!("thread '{name}' {panic_info}"); + // If we get a `WouldBlock` error, we know that the `Gfx` service has been initialized. + // Otherwise fallback to printing over stderr. + match GFX_ACTIVE.try_lock() { + Err(TryLockError::WouldBlock) => { + if use_stderr { + print_to_stderr(name, panic_info); + } - popup.set_text(&payload); + let payload = format!("thread '{name}' {panic_info}"); - if use_stderr { - eprintln!("{payload}"); - } + let mut popup = PopUp::new(Kind::Top); - unsafe { - popup.launch_unchecked(); + popup.set_text(&payload); + + unsafe { + popup.launch_unchecked(); + } + } + _ => { + print_to_stderr(name, panic_info); + } } - })) + })); + + fn print_to_stderr(name: &str, panic_info: &std::panic::PanicInfo) { + let mut stderr = std::io::stderr().lock(); + + let _ = writeln!(stderr, "thread '{name}' {panic_info}"); + } } impl std::fmt::Display for Error { diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 7a6ec6e..9799665 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -239,7 +239,7 @@ pub struct Gfx { _service_handler: ServiceReference, } -static GFX_ACTIVE: Mutex<()> = Mutex::new(()); +pub(crate) static GFX_ACTIVE: Mutex<()> = Mutex::new(()); impl Gfx { /// Initialize a new default service handle. From a65b9ed57738ae50c4df67fbd657713fa24d1201 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 25 Feb 2024 02:06:24 -0700 Subject: [PATCH 09/17] use `if let` in panic hook instead of `match` --- ctru-rs/src/applets/error.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index 6fa7efc..b723aa8 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -108,25 +108,22 @@ pub fn set_panic_hook(use_stderr: bool) { // If we get a `WouldBlock` error, we know that the `Gfx` service has been initialized. // Otherwise fallback to printing over stderr. - match GFX_ACTIVE.try_lock() { - Err(TryLockError::WouldBlock) => { - if use_stderr { - print_to_stderr(name, panic_info); - } + if let Err(TryLockError::WouldBlock) = GFX_ACTIVE.try_lock() { + if use_stderr { + print_to_stderr(name, panic_info); + } - let payload = format!("thread '{name}' {panic_info}"); + let payload = format!("thread '{name}' {panic_info}"); - let mut popup = PopUp::new(Kind::Top); + let mut popup = PopUp::new(Kind::Top); - popup.set_text(&payload); + popup.set_text(&payload); - unsafe { - popup.launch_unchecked(); - } - } - _ => { - print_to_stderr(name, panic_info); + unsafe { + popup.launch_unchecked(); } + } else { + print_to_stderr(name, panic_info); } })); From d630c25926b194563807f458afb3344b859771f2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 25 Feb 2024 17:53:12 -0700 Subject: [PATCH 10/17] Check for APT init errors in panic hook --- ctru-rs/src/applets/error.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index b723aa8..92dc7e3 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -100,15 +100,13 @@ pub fn set_panic_hook(use_stderr: bool) { use std::sync::TryLockError; std::panic::set_hook(Box::new(move |panic_info| { - let _apt = Apt::new(); - let thread = std::thread::current(); let name = thread.name().unwrap_or(""); // If we get a `WouldBlock` error, we know that the `Gfx` service has been initialized. // Otherwise fallback to printing over stderr. - if let Err(TryLockError::WouldBlock) = GFX_ACTIVE.try_lock() { + if let (Err(TryLockError::WouldBlock), Ok(_apt)) = (GFX_ACTIVE.try_lock(), Apt::new()) { if use_stderr { print_to_stderr(name, panic_info); } From 5cec437e1d3f0fd62339d29db6fa05888b4237f9 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 25 Feb 2024 18:06:48 -0700 Subject: [PATCH 11/17] Reuse default hook instead of custom print method --- ctru-rs/src/applets/error.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index 92dc7e3..67665ab 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -88,17 +88,21 @@ impl PopUp { } } -/// Sets a custom panic hook that uses the error applet to display panic messages. You can also choose to have -/// messages printed over stderr along with the pop-up display. +/// Sets a custom panic hook that uses the error applet to display panic messages. You can also choose to have the +/// default panic hook called to print the message over stderr. /// -/// If the `Gfx` service is not initialized during a panic, the error applet will not be displayed and the panic -/// message will be printed over stderr. -pub fn set_panic_hook(use_stderr: bool) { +/// If the `Gfx` service is not initialized during a panic, the error applet will not be displayed and the default +/// hook will be called. +pub fn set_panic_hook(call_default_hook: bool) { use crate::services::gfx::GFX_ACTIVE; - - use std::io::Write; use std::sync::TryLockError; + // Ensure we get the default hook instead of a previously registered user hook. + let default_hook = { + let _ = std::panic::take_hook(); + std::panic::take_hook() + }; + std::panic::set_hook(Box::new(move |panic_info| { let thread = std::thread::current(); @@ -107,8 +111,8 @@ pub fn set_panic_hook(use_stderr: bool) { // If we get a `WouldBlock` error, we know that the `Gfx` service has been initialized. // Otherwise fallback to printing over stderr. if let (Err(TryLockError::WouldBlock), Ok(_apt)) = (GFX_ACTIVE.try_lock(), Apt::new()) { - if use_stderr { - print_to_stderr(name, panic_info); + if call_default_hook { + default_hook(panic_info); } let payload = format!("thread '{name}' {panic_info}"); @@ -121,15 +125,9 @@ pub fn set_panic_hook(use_stderr: bool) { popup.launch_unchecked(); } } else { - print_to_stderr(name, panic_info); + default_hook(panic_info); } })); - - fn print_to_stderr(name: &str, panic_info: &std::panic::PanicInfo) { - let mut stderr = std::io::stderr().lock(); - - let _ = writeln!(stderr, "thread '{name}' {panic_info}"); - } } impl std::fmt::Display for Error { From 6da884e7b68c7a03ca898a97d4bb70bf11b56362 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 25 Feb 2024 18:45:56 -0700 Subject: [PATCH 12/17] Use the previous panic hook instead of just the default --- ctru-rs/src/applets/error.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index 67665ab..d57af55 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -89,19 +89,16 @@ impl PopUp { } /// Sets a custom panic hook that uses the error applet to display panic messages. You can also choose to have the -/// default panic hook called to print the message over stderr. +/// previously registered panic hook called along with the error applet message, which can be useful if you want +/// to use input redirection to display panic messages over `3dslink` or `GDB`. /// -/// If the `Gfx` service is not initialized during a panic, the error applet will not be displayed and the default -/// hook will be called. -pub fn set_panic_hook(call_default_hook: bool) { +/// 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. +pub fn set_panic_hook(call_old_hook: bool) { use crate::services::gfx::GFX_ACTIVE; use std::sync::TryLockError; - // Ensure we get the default hook instead of a previously registered user hook. - let default_hook = { - let _ = std::panic::take_hook(); - std::panic::take_hook() - }; + let old_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic_info| { let thread = std::thread::current(); @@ -111,8 +108,8 @@ pub fn set_panic_hook(call_default_hook: bool) { // If we get a `WouldBlock` error, we know that the `Gfx` service has been initialized. // Otherwise fallback to printing over stderr. if let (Err(TryLockError::WouldBlock), Ok(_apt)) = (GFX_ACTIVE.try_lock(), Apt::new()) { - if call_default_hook { - default_hook(panic_info); + if call_old_hook { + old_hook(panic_info); } let payload = format!("thread '{name}' {panic_info}"); @@ -125,7 +122,7 @@ pub fn set_panic_hook(call_default_hook: bool) { popup.launch_unchecked(); } } else { - default_hook(panic_info); + old_hook(panic_info); } })); } From ffbe6604a68cc496f289553f04106185211fa2bc Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 25 Feb 2024 19:09:28 -0700 Subject: [PATCH 13/17] Improve docs for set_panic_hook --- ctru-rs/src/applets/error.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index d57af55..9bc66da 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -88,12 +88,16 @@ impl PopUp { } } -/// Sets a custom panic hook that uses the error applet to display panic messages. You can also choose to have the -/// previously registered panic hook called along with the error applet message, which can be useful if you want -/// to use input redirection to display panic messages over `3dslink` or `GDB`. +/// 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. /// -/// If the `Gfx` service is not initialized during a panic, the error applet will not be displayed and the old +/// You can also choose to have the previously registered panic hook called along with the error applet message, which can be useful +/// if you want to use output redirection to display panic messages over `3dslink` or `GDB`. +/// +/// 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. +/// +/// You can use [`std::panic::take_hook`](https://doc.rust-lang.org/std/panic/fn.take_hook.html) to unregister the panic hook +/// set by this function. pub fn set_panic_hook(call_old_hook: bool) { use crate::services::gfx::GFX_ACTIVE; use std::sync::TryLockError; From efbf91338082513cdbf1fb15eddb162ebf214433 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 26 Feb 2024 02:45:51 -0700 Subject: [PATCH 14/17] Move error parsing code into launch_unchecked --- ctru-rs/src/applets/error.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index 9bc66da..6450a41 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -1,6 +1,7 @@ //! Error applet //! //! This applet displays error text as a pop-up message on the lower screen. + use crate::services::{apt::Apt, gfx::Gfx}; use ctru_sys::errorConf; @@ -66,7 +67,16 @@ impl PopUp { /// Launches the error applet. #[doc(alias = "errorDisp")] pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(), Error> { - unsafe { ctru_sys::errorDisp(self.state.as_mut()) } + unsafe { self.launch_unchecked() } + } + + /// Launches the error applet without requiring an [`Apt`] or [`Gfx`] handle. + /// + /// # Safety + /// + /// Causes undefined behavior if the aforementioned services are not actually active when the applet launches. + unsafe fn launch_unchecked(&mut self) -> Result<(), Error> { + unsafe { ctru_sys::errorDisp(self.state.as_mut()) }; match self.state.returnCode { ctru_sys::ERROR_NONE | ctru_sys::ERROR_SUCCESS => Ok(()), @@ -77,15 +87,6 @@ impl PopUp { _ => Err(Error::Unknown), } } - - /// Launches the error applet without requiring an [`Apt`] or [`Gfx`] handle. - /// - /// # Safety - /// - /// Causes undefined behavior if the aforementioned services are not actually active when the applet launches. - unsafe fn launch_unchecked(&mut self) { - unsafe { ctru_sys::errorDisp(self.state.as_mut()) }; - } } /// 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. @@ -123,7 +124,7 @@ pub fn set_panic_hook(call_old_hook: bool) { popup.set_text(&payload); unsafe { - popup.launch_unchecked(); + let _ = popup.launch_unchecked(); } } else { old_hook(panic_info); From 559c757d35cbd5ad741b0bb434bcf2af09dd4882 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 26 Feb 2024 03:22:22 -0700 Subject: [PATCH 15/17] Refactor some code and improve documentation --- ctru-rs/src/applets/error.rs | 55 +++++++++++++++++++++--------------- ctru-sys/src/lib.rs | 4 +-- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index 6450a41..44fff08 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -1,4 +1,4 @@ -//! Error applet +//! Error applet. //! //! This applet displays error text as a pop-up message on the lower screen. @@ -12,15 +12,15 @@ pub struct PopUp { state: Box, } -/// The kind of error applet to display. +/// Determines whether the Error applet will use word wrapping when displaying a message. #[doc(alias = "errorType")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] -pub enum Kind { - /// Error text is centered in the error applet window. - Center = ctru_sys::ERROR_TEXT, - /// Error text starts at the top of the error applet window. - Top = ctru_sys::ERROR_TEXT_WORD_WRAP, +pub enum WordWrap { + /// Error text is centered in the error applet window and does not use word wrapping. + Disabled = ctru_sys::ERROR_TEXT, + /// Error text starts at the top of the error applet window and uses word wrapping. + Enabled = ctru_sys::ERROR_TEXT_WORD_WRAP, } /// Error returned by an unsuccessful [`PopUp::launch()`]. @@ -41,17 +41,22 @@ pub enum Error { } impl PopUp { - /// Initialize the error applet with the provided text kind. + /// Initializes the error applet with the provided word wrap setting. #[doc(alias = "errorInit")] - pub fn new(kind: Kind) -> Self { + pub fn new(word_wrap: WordWrap) -> Self { let mut state = Box::::default(); - unsafe { ctru_sys::errorInit(state.as_mut(), kind as _, 0) }; + unsafe { ctru_sys::errorInit(state.as_mut(), word_wrap as _, 0) }; Self { state } } /// Sets the error text to display. + /// + /// # Notes + /// + /// Messages that are too large will be truncated. The exact number of characters displayed can vary depending on factors such as + /// the length of individual words in the message and the chosen word wrap setting. #[doc(alias = "errorText")] pub fn set_text(&mut self, text: &str) { for (idx, code_unit) in text @@ -74,7 +79,7 @@ impl PopUp { /// /// # Safety /// - /// Causes undefined behavior if the aforementioned services are not actually active when the applet launches. + /// Potentially leads to undefined behavior if the aforementioned services are not actually active when the applet launches. unsafe fn launch_unchecked(&mut self) -> Result<(), Error> { unsafe { ctru_sys::errorDisp(self.state.as_mut()) }; @@ -91,14 +96,18 @@ impl PopUp { /// 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. /// -/// You can also choose to have the previously registered panic hook called along with the error applet message, which can be useful +/// You can also choose to have the previously registered panic hook called along with the error applet popup, which can be useful /// if you want to use output redirection to display panic messages over `3dslink` or `GDB`. /// -/// 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. -/// /// You can use [`std::panic::take_hook`](https://doc.rust-lang.org/std/panic/fn.take_hook.html) to unregister the panic hook /// set by this function. +/// +/// # Notes +/// +/// * 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. +/// +/// * As mentioned in [`PopUp::set_text`], the error applet can only display a finite number of characters and so panic messages that are too long +/// can potentially end up being truncated. Consider using this hook along with the default hook so that you can capture full panic messages via stderr. pub fn set_panic_hook(call_old_hook: bool) { use crate::services::gfx::GFX_ACTIVE; use std::sync::TryLockError; @@ -106,22 +115,22 @@ pub fn set_panic_hook(call_old_hook: bool) { let old_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic_info| { - let thread = std::thread::current(); - - let name = thread.name().unwrap_or(""); - // If we get a `WouldBlock` error, we know that the `Gfx` service has been initialized. - // Otherwise fallback to printing over stderr. + // Otherwise fallback to using the old panic hook. if let (Err(TryLockError::WouldBlock), Ok(_apt)) = (GFX_ACTIVE.try_lock(), Apt::new()) { if call_old_hook { old_hook(panic_info); } - let payload = format!("thread '{name}' {panic_info}"); + let thread = std::thread::current(); + + let name = thread.name().unwrap_or(""); + + let message = format!("thread '{name}' {panic_info}"); - let mut popup = PopUp::new(Kind::Top); + let mut popup = PopUp::new(WordWrap::Enabled); - popup.set_text(&payload); + popup.set_text(&message); unsafe { let _ = popup.launch_unchecked(); diff --git a/ctru-sys/src/lib.rs b/ctru-sys/src/lib.rs index 39484a3..5a60de2 100644 --- a/ctru-sys/src/lib.rs +++ b/ctru-sys/src/lib.rs @@ -21,6 +21,7 @@ pub use result::*; // Libctru's `errorConf` struct contains two enums that depend on this narrowing property for size and alignment purposes, // and since `bindgen` generates all enums with `c_int` sizing, we have to blocklist those types and manually define them // here with the proper size. +pub type errorReturnCode = libc::c_schar; pub const ERROR_UNKNOWN: errorReturnCode = -1; pub const ERROR_NONE: errorReturnCode = 0; pub const ERROR_SUCCESS: errorReturnCode = 1; @@ -28,11 +29,10 @@ pub const ERROR_NOT_SUPPORTED: errorReturnCode = 2; pub const ERROR_HOME_BUTTON: errorReturnCode = 10; pub const ERROR_SOFTWARE_RESET: errorReturnCode = 11; pub const ERROR_POWER_BUTTON: errorReturnCode = 12; -pub type errorReturnCode = libc::c_schar; +pub type errorScreenFlag = libc::c_char; pub const ERROR_NORMAL: errorScreenFlag = 0; pub const ERROR_STEREO: errorScreenFlag = 1; -pub type errorScreenFlag = libc::c_char; include!(concat!(env!("OUT_DIR"), "/bindings.rs")); From b967440eb15f37c2257963a425239257aecfe03f Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 26 Feb 2024 19:36:33 -0700 Subject: [PATCH 16/17] properly handle nul termination in set_text --- ctru-rs/src/applets/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index 44fff08..8e323be 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -61,8 +61,8 @@ impl PopUp { pub fn set_text(&mut self, text: &str) { for (idx, code_unit) in text .encode_utf16() - .chain(std::iter::once(0)) .take(self.state.Text.len() - 1) + .chain(std::iter::once(0)) .enumerate() { self.state.Text[idx] = code_unit; From 9396939d648ffb903deba94a94d49c707d0281b3 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 27 Feb 2024 00:03:08 -0700 Subject: [PATCH 17/17] Error messages don't actually get truncated, false alarm everybody --- ctru-rs/src/applets/error.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ctru-rs/src/applets/error.rs b/ctru-rs/src/applets/error.rs index 8e323be..84dc3e7 100644 --- a/ctru-rs/src/applets/error.rs +++ b/ctru-rs/src/applets/error.rs @@ -55,8 +55,8 @@ impl PopUp { /// /// # Notes /// - /// Messages that are too large will be truncated. The exact number of characters displayed can vary depending on factors such as - /// the length of individual words in the message and the chosen word wrap setting. + /// The text will be converted to UTF-16 for display with the applet, and the message will be truncated if it exceeds + /// 1900 UTF-16 code units in length after conversion. #[doc(alias = "errorText")] pub fn set_text(&mut self, text: &str) { for (idx, code_unit) in text @@ -105,9 +105,6 @@ impl PopUp { /// # Notes /// /// * 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. -/// -/// * As mentioned in [`PopUp::set_text`], the error applet can only display a finite number of characters and so panic messages that are too long -/// can potentially end up being truncated. Consider using this hook along with the default hook so that you can capture full panic messages via stderr. pub fn set_panic_hook(call_old_hook: bool) { use crate::services::gfx::GFX_ACTIVE; use std::sync::TryLockError;