From 22773f644a9f39a4656f1ca2f21675ab4c7a90c8 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 14 Feb 2024 14:11:58 -0700 Subject: [PATCH 01/74] Store initial_text data in SoftwareKeyboard --- ctru-rs/src/applets/swkbd.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 41d40cd..b1c4a28 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -22,6 +22,7 @@ pub struct SoftwareKeyboard { state: Box, callback: Option>, error_message: Option, + initial_text: Option, } /// Configuration structure to setup the Parental Lock applet. @@ -233,6 +234,7 @@ impl SoftwareKeyboard { state, callback: None, error_message: None, + initial_text: None, } } } @@ -503,8 +505,12 @@ impl SoftwareKeyboard { #[doc(alias = "swkbdSetInitialText")] pub fn set_initial_text(&mut self, text: &str) { unsafe { - let nul_terminated: String = text.chars().chain(once('\0')).collect(); - ctru_sys::swkbdSetInitialText(self.state.as_mut(), nul_terminated.as_ptr()); + self.initial_text = Some(CString::new(text).unwrap()); + + ctru_sys::swkbdSetInitialText( + self.state.as_mut(), + self.initial_text.as_ref().unwrap().as_ptr(), + ); } } From 684bfb3251801757d245c80c15c95676239f0ca6 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 14 Feb 2024 16:35:52 -0700 Subject: [PATCH 02/74] Use addr_of_mut! when referring to EMPTY_CONSOLE --- ctru-rs/src/console.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index cb98840..9fedd2a 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -143,7 +143,7 @@ impl<'screen> Console<'screen> { /// ``` pub fn exists() -> bool { unsafe { - let current_console = ctru_sys::consoleSelect(&mut EMPTY_CONSOLE); + let current_console = ctru_sys::consoleSelect(std::ptr::addr_of_mut!(EMPTY_CONSOLE)); let res = (*current_console).consoleInitialised; @@ -364,7 +364,7 @@ impl Drop for Console<'_> { // the screen, but it won't crash either. // Get the current console by replacing it with an empty one. - let current_console = ctru_sys::consoleSelect(&mut EMPTY_CONSOLE); + let current_console = ctru_sys::consoleSelect(std::ptr::addr_of_mut!(EMPTY_CONSOLE)); if std::ptr::eq(current_console, &*self.context) { // Console dropped while selected. We just replaced it with the From a0aa6d5d9e87ef851ce2a07c2f6c6b9a2093c5a5 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 14 Feb 2024 16:53:16 -0700 Subject: [PATCH 03/74] Wrap PrintConsole in UnsafeCell --- ctru-rs/src/console.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 9fedd2a..bee9dc5 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -5,7 +5,7 @@ //! //! Have a look at [`Soc::redirect_to_3dslink()`](crate::services::soc::Soc::redirect_to_3dslink) for a better alternative when debugging applications. -use std::cell::RefMut; +use std::cell::{RefMut, UnsafeCell}; use std::default::Default; use ctru_sys::{consoleClear, consoleInit, consoleSelect, consoleSetWindow, PrintConsole}; @@ -63,7 +63,7 @@ impl ConsoleScreen for S {} /// More info in the [`cargo-3ds` docs](https://github.com/rust3ds/cargo-3ds#running-executables). #[doc(alias = "PrintConsole")] pub struct Console<'screen> { - context: Box, + context: Box>, screen: RefMut<'screen, dyn ConsoleScreen>, } @@ -107,9 +107,9 @@ impl<'screen> Console<'screen> { /// ``` #[doc(alias = "consoleInit")] pub fn new(screen: RefMut<'screen, S>) -> Self { - let mut context = Box::::default(); + let context = Box::>::default(); - unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; + unsafe { consoleInit(screen.as_raw(), context.get()) }; Console { context, screen } } @@ -190,7 +190,7 @@ impl<'screen> Console<'screen> { #[doc(alias = "consoleSelect")] pub fn select(&self) { unsafe { - consoleSelect(self.context.as_ref() as *const _ as *mut _); + consoleSelect(self.context.get()); } } @@ -248,7 +248,7 @@ impl<'screen> Console<'screen> { unsafe { consoleSetWindow( - self.context.as_mut(), + self.context.get(), x.into(), y.into(), width.into(), @@ -338,7 +338,10 @@ impl Swap for Console<'_> { /// This should be called once per frame at most. fn swap_buffers(&mut self) { self.screen.swap_buffers(); - self.context.frameBuffer = self.screen.raw_framebuffer().ptr as *mut u16; + + unsafe { + (*self.context.get()).frameBuffer = self.screen.raw_framebuffer().ptr as *mut u16 + }; } fn set_double_buffering(&mut self, enabled: bool) { @@ -366,7 +369,7 @@ impl Drop for Console<'_> { // Get the current console by replacing it with an empty one. let current_console = ctru_sys::consoleSelect(std::ptr::addr_of_mut!(EMPTY_CONSOLE)); - if std::ptr::eq(current_console, &*self.context) { + if std::ptr::eq(current_console, self.context.get()) { // Console dropped while selected. We just replaced it with the // empty console so nothing more to do. } else { From 9eea3f0d1f92fed4e473540347aff627fa4389e3 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 15 Feb 2024 13:17:27 -0700 Subject: [PATCH 04/74] Make initial text argument optional This allows the user to clear the initial_text field by supplying a `None` --- ctru-rs/src/applets/swkbd.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index b1c4a28..9dd75d1 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -499,18 +499,23 @@ impl SoftwareKeyboard { /// use ctru::applets::swkbd::SoftwareKeyboard; /// let mut keyboard = SoftwareKeyboard::default(); /// - /// keyboard.set_initial_text("Write here what you like!"); + /// keyboard.set_initial_text(Some("Write here what you like!")); /// # /// # } #[doc(alias = "swkbdSetInitialText")] - pub fn set_initial_text(&mut self, text: &str) { - unsafe { - self.initial_text = Some(CString::new(text).unwrap()); + pub fn set_initial_text(&mut self, text: Option<&str>) { + if let Some(text) = text { + let initial_text = CString::new(text).unwrap(); - ctru_sys::swkbdSetInitialText( - self.state.as_mut(), - self.initial_text.as_ref().unwrap().as_ptr(), - ); + unsafe { + ctru_sys::swkbdSetInitialText(self.state.as_mut(), initial_text.as_ptr()); + } + + self.initial_text = Some(initial_text); + } else { + unsafe { ctru_sys::swkbdSetInitialText(self.state.as_mut(), std::ptr::null()) }; + + self.initial_text = None; } } From 742ec8d73815a29cb461e2e617a56b655533888b Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 11 Feb 2024 13:09:33 -0700 Subject: [PATCH 05/74] Get swkbd input without max byte size parameter Is this much unsafe code worth such a small quality of life change? Maybe! --- ctru-rs/Cargo.toml | 1 + ctru-rs/examples/file-explorer.rs | 2 +- ctru-rs/examples/software-keyboard.rs | 2 +- ctru-rs/src/applets/swkbd.rs | 373 +++++++++++++++++++++----- 4 files changed, 311 insertions(+), 67 deletions(-) diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index 4d6b363..14084fc 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -24,6 +24,7 @@ shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" } libc = "0.2.121" bitflags = "2.3.3" +widestring = "1.0.2" [build-dependencies] toml = "0.5" diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 5c158ac..9647e41 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -165,7 +165,7 @@ impl<'a> FileExplorer<'a> { fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) { let mut keyboard = SoftwareKeyboard::default(); - match keyboard.get_string(2048, self.apt, self.gfx) { + match keyboard.get_string(self.apt, self.gfx) { Ok((path, Button::Right)) => { // Clicked "OK". action(self, path); diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index 1d24a7d..581874a 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -46,7 +46,7 @@ fn main() { if hid.keys_down().contains(KeyPad::A) { // Raise the software keyboard. You can perform different actions depending on which // software button the user pressed. - match keyboard.get_string(2048, &apt, &gfx) { + match keyboard.get_string(&apt, &gfx) { Ok((text, Button::Right)) => println!("You entered: {text}"), Ok((_, Button::Left)) => println!("Cancelled"), Ok((_, Button::Middle)) => println!("How did you even press this?"), diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 9dd75d1..cf763b2 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -4,7 +4,12 @@ #![doc(alias = "keyboard")] use crate::services::{apt::Apt, gfx::Gfx}; -use ctru_sys::{self, SwkbdState}; +use ctru_sys::{ + self, aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcBreak, svcCloseHandle, + svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdExtra, + SwkbdLearningData, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, + NS_APPID, SWKBD_CALLBACK_OK, USERBREAK_PANIC, +}; use bitflags::bitflags; use libc; @@ -16,6 +21,10 @@ use std::str; type CallbackFunction = dyn Fn(&CStr) -> (CallbackResult, Option); +// I hate that we have to use this, but sometimes you gotta smuggle pointers into C callbacks +// and that's just how things are +static mut SWKBD_SHARED_MEM: *mut libc::c_void = std::ptr::null_mut(); + /// Configuration structure to setup the Software Keyboard applet. #[doc(alias = "SwkbdState")] pub struct SoftwareKeyboard { @@ -241,11 +250,6 @@ impl SoftwareKeyboard { /// Launches the applet based on the given configuration and returns a string containing the text input. /// - /// # Notes - /// - /// The text received from the keyboard will be truncated if it is longer than `max_bytes`. - /// Use [`SoftwareKeyboard::set_max_text_len()`] to make sure the buffer can contain the input text. - /// /// # Example /// /// ``` @@ -260,67 +264,15 @@ impl SoftwareKeyboard { /// use ctru::applets::swkbd::SoftwareKeyboard; /// let mut keyboard = SoftwareKeyboard::default(); /// - /// let (text, button) = keyboard.get_string(2048, &apt, &gfx)?; + /// let (text, button) = keyboard.get_string(&apt, &gfx)?; /// # /// # Ok(()) /// # } /// ``` #[doc(alias = "swkbdInputText")] - pub fn get_string( - &mut self, - max_bytes: usize, - apt: &Apt, - gfx: &Gfx, - ) -> Result<(String, Button), Error> { - // Unfortunately the libctru API doesn't really provide a way to get the exact length - // of the string that it receieves from the software keyboard. Instead it expects you - // to pass in a buffer and hope that it's big enough to fit the entire string, so - // you have to set some upper limit on the potential size of the user's input. - let mut tmp = vec![0u8; max_bytes]; - let button = self.write_exact(&mut tmp, apt, gfx)?; - - // libctru does, however, seem to ensure that the buffer will always contain a properly - // terminated UTF-8 sequence even if the input has to be truncated, so these operations - // should be safe. - let len = unsafe { libc::strlen(tmp.as_ptr()) }; - tmp.truncate(len); - - let res = unsafe { String::from_utf8_unchecked(tmp) }; - - Ok((res, button)) - } + pub fn get_string(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> { + let mut output = String::new(); - /// Fills the provided buffer with a UTF-8 encoded, NUL-terminated sequence of bytes from - /// this software keyboard. - /// - /// # Notes - /// - /// If the buffer is too small to contain the entire sequence received from the keyboard, - /// the output will be truncated. - /// - /// # Example - /// - /// ``` - /// # let _runner = test_runner::GdbRunner::default(); - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// # use ctru::services::{apt::Apt, gfx::Gfx}; - /// # - /// # let gfx = Gfx::new().unwrap(); - /// # let apt = Apt::new().unwrap(); - /// # - /// use ctru::applets::swkbd::SoftwareKeyboard; - /// let mut keyboard = SoftwareKeyboard::default(); - /// - /// let mut buffer = vec![0; 100]; - /// - /// let button = keyboard.write_exact(&mut buffer, &apt, &gfx)?; - /// # - /// # Ok(()) - /// # } - /// ``` - #[doc(alias = "swkbdInputText")] - pub fn write_exact(&mut self, buf: &mut [u8], _apt: &Apt, _gfx: &Gfx) -> Result { unsafe { // The filter callback gets reset every time the SoftwareKeyboard is used. ctru_sys::swkbdSetFilterCallback( @@ -329,11 +281,11 @@ impl SoftwareKeyboard { (self as *mut Self).cast(), ); - match ctru_sys::swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) { + match Self::swkbd_input_text(self.state.as_mut(), output.as_mut_vec()) { ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), - ctru_sys::SWKBD_BUTTON_LEFT => Ok(Button::Left), - ctru_sys::SWKBD_BUTTON_MIDDLE => Ok(Button::Middle), - ctru_sys::SWKBD_BUTTON_RIGHT => Ok(Button::Right), + ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), + ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), + ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)), _ => unreachable!(), } } @@ -675,6 +627,297 @@ impl SoftwareKeyboard { // Activate the specific validation rule for maximum length. self.state.valid_input = ValidInput::FixedLen.into(); } + + // A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to + // get text from the software keyboard and put it directly into a `String` without requiring + // an intermediate fixed-size buffer + unsafe fn swkbd_input_text(swkbd: *mut SwkbdState, buf: &mut Vec) -> SwkbdButton { + use ctru_sys::{ + MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, + SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0, + SWKBD_D1_CLICK1, SWKBD_D2_CLICK0, SWKBD_D2_CLICK1, SWKBD_D2_CLICK2, + SWKBD_FILTER_CALLBACK, SWKBD_OUTOFMEM, + }; + + let mut extra = unsafe { (*swkbd).__bindgen_anon_1.extra }; + + // Calculate shared mem size + let mut shared_mem_size = 0; + + shared_mem_size += + (std::mem::size_of::() * ((*swkbd).max_text_len as usize + 1) + 3) & !3; + + let dict_off = shared_mem_size; + + shared_mem_size += + (std::mem::size_of::() * (*swkbd).dict_word_count as usize + 3) & !3; + + let status_off = shared_mem_size; + + shared_mem_size += if (*swkbd).initial_learning_offset >= 0 { + std::mem::size_of::() + } else { + 0 + }; + + let learning_off = shared_mem_size; + + shared_mem_size += if (*swkbd).initial_learning_offset >= 0 { + std::mem::size_of::() + } else { + 0 + }; + + if (*swkbd).save_state_flags & (1 << 0) != 0 { + (*swkbd).status_offset = shared_mem_size as _; + shared_mem_size += std::mem::size_of::(); + } + + if (*swkbd).save_state_flags & (1 << 1) != 0 { + (*swkbd).learning_offset = shared_mem_size as _; + shared_mem_size += std::mem::size_of::(); + } + + shared_mem_size = (shared_mem_size + 0xFFF) & !0xFFF; + + (*swkbd).shared_memory_size = shared_mem_size; + + // Allocate shared mem + unsafe { SWKBD_SHARED_MEM = libc::memalign(0x1000, shared_mem_size).cast() }; + + let mut swkbd_shared_mem_handle = 0; + + if unsafe { SWKBD_SHARED_MEM == std::ptr::null_mut() } { + (*swkbd).result = SWKBD_OUTOFMEM; + return SWKBD_BUTTON_NONE; + } + + let res = unsafe { + svcCreateMemoryBlock( + &mut swkbd_shared_mem_handle, + SWKBD_SHARED_MEM as _, + shared_mem_size as _, + MEMPERM_READ | MEMPERM_WRITE, + MEMPERM_READ | MEMPERM_WRITE, + ) + }; + + if R_FAILED(res) { + unsafe { + libc::free(SWKBD_SHARED_MEM); + (*swkbd).result = SWKBD_OUTOFMEM; + return SWKBD_BUTTON_NONE; + } + } + + // Copy stuff to shared mem + if extra.initial_text != std::ptr::null() { + (*swkbd).initial_text_offset = 0; + + let utf16_iter = CStr::from_ptr(extra.initial_text) + .to_str() + .unwrap() + .encode_utf16() + .chain(once(0)); + + let mut initial_text_cursor = SWKBD_SHARED_MEM.cast::(); + + for ch in utf16_iter { + *initial_text_cursor = ch; + unsafe { initial_text_cursor = initial_text_cursor.add(1) }; + } + } + + if extra.dict != std::ptr::null() { + (*swkbd).dict_offset = dict_off as _; + unsafe { + libc::memcpy( + SWKBD_SHARED_MEM.add(dict_off), + extra.dict.cast(), + std::mem::size_of::() * (*swkbd).dict_word_count as usize, + ) + }; + } + + if (*swkbd).initial_status_offset >= 0 { + (*swkbd).initial_status_offset = status_off as _; + unsafe { + libc::memcpy( + SWKBD_SHARED_MEM.add(status_off), + extra.status_data.cast(), + std::mem::size_of::(), + ) + }; + } + + if (*swkbd).initial_learning_offset >= 0 { + (*swkbd).initial_learning_offset = learning_off as _; + unsafe { + libc::memcpy( + SWKBD_SHARED_MEM.add(learning_off), + extra.learning_data.cast(), + std::mem::size_of::(), + ) + }; + } + + if extra.callback.is_some() { + (*swkbd).filter_flags |= SWKBD_FILTER_CALLBACK; + } else { + (*swkbd).filter_flags &= !SWKBD_FILTER_CALLBACK; + } + + // Launch swkbd + unsafe { + libc::memset( + std::ptr::addr_of_mut!((*swkbd).__bindgen_anon_1.reserved).cast(), + 0, + std::mem::size_of_val(&(*swkbd).__bindgen_anon_1.reserved), + ); + + if extra.callback.is_some() { + aptSetMessageCallback( + Some(Self::swkbd_message_callback), + std::ptr::addr_of_mut!(extra).cast(), + ); + } + + aptLaunchLibraryApplet( + APPID_SOFTWARE_KEYBOARD, + swkbd.cast(), + std::mem::size_of::(), + swkbd_shared_mem_handle, + ); + + if extra.callback.is_some() { + aptSetMessageCallback(None, std::ptr::null_mut()); + } + + let _ = svcCloseHandle(swkbd_shared_mem_handle); + } + + let button = match (*swkbd).result { + SWKBD_D1_CLICK0 | SWKBD_D2_CLICK0 => SWKBD_BUTTON_LEFT, + SWKBD_D2_CLICK1 => SWKBD_BUTTON_MIDDLE, + SWKBD_D0_CLICK | SWKBD_D1_CLICK1 | SWKBD_D2_CLICK2 => SWKBD_BUTTON_RIGHT, + _ => SWKBD_BUTTON_NONE, + }; + + let text16 = if (*swkbd).text_length > 0 { + unsafe { + widestring::Utf16Str::from_slice(std::slice::from_raw_parts( + SWKBD_SHARED_MEM + .add((*swkbd).text_offset as _) + .cast::(), + (*swkbd).text_length as usize + 1, + )) + .unwrap() + } + } else { + widestring::utf16str!("\0") + }; + + buf.extend(text16.encode_utf8()); + + if (*swkbd).save_state_flags & (1 << 0) != 0 { + unsafe { + libc::memcpy( + extra.status_data.cast(), + SWKBD_SHARED_MEM.add((*swkbd).status_offset as _), + std::mem::size_of::(), + ) + }; + } + + if (*swkbd).save_state_flags & (1 << 1) != 0 { + unsafe { + libc::memcpy( + extra.learning_data.cast(), + SWKBD_SHARED_MEM.add((*swkbd).learning_offset as _), + std::mem::size_of::(), + ) + }; + } + + unsafe { libc::free(SWKBD_SHARED_MEM) }; + + button + } + + // A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`. + // This is only needed because the original function is private to libctru, so we can't + // simply reuse their version + #[allow(non_snake_case)] + unsafe extern "C" fn swkbd_message_callback( + user: *mut libc::c_void, + sender: NS_APPID, + msg: *mut libc::c_void, + msg_size: libc::size_t, + ) { + let extra = user.cast::(); + let swkbd = msg.cast::(); + + if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD + || msg_size != std::mem::size_of::() + { + return; + } + + let text16 = unsafe { + widestring::Utf16Str::from_slice(std::slice::from_raw_parts( + SWKBD_SHARED_MEM + .add((*swkbd).text_offset as _) + .cast::(), + (*swkbd).text_length as usize + 1, + )) + .unwrap() + }; + + let text8 = match String::from_utf8(text16.encode_utf8().collect()) { + Ok(s) => s, + Err(_) => { + svcBreak(USERBREAK_PANIC); + unreachable!(); + } + }; + + let mut retmsg = std::ptr::null(); + + (*swkbd).callback_result = (*extra).callback.unwrap()( + (*extra).callback_user, + &mut retmsg, + text8.as_ptr(), + text8.len(), + ) as _; + + let retmsg = if retmsg != std::ptr::null() { + unsafe { + let len = libc::strlen(retmsg); + std::str::from_utf8_unchecked(std::slice::from_raw_parts(retmsg, len + 1)) + } + } else { + "\0" + }; + + let callback_msg = &mut (*swkbd).callback_msg; + + if (*swkbd).callback_result > SWKBD_CALLBACK_OK as _ { + for (idx, ch) in retmsg.encode_utf16().take(callback_msg.len()).enumerate() { + callback_msg[idx] = ch; + } + } else { + callback_msg[0] = 0; + } + + let _ = APT_SendParameter( + envGetAptAppId(), + sender, + APTCMD_MESSAGE, + swkbd.cast(), + std::mem::size_of::() as _, + 0, + ); + } } impl ParentalLock { From 2864c06207ea3cd76e8a1d8980fb0a6a410a9804 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 11 Feb 2024 18:39:08 -0700 Subject: [PATCH 06/74] Don't include nul terminator when passing string data back to Rust --- ctru-rs/src/applets/swkbd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index cf763b2..c6ad7d8 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -809,12 +809,12 @@ impl SoftwareKeyboard { SWKBD_SHARED_MEM .add((*swkbd).text_offset as _) .cast::(), - (*swkbd).text_length as usize + 1, + (*swkbd).text_length as usize, )) .unwrap() } } else { - widestring::utf16str!("\0") + widestring::utf16str!("") }; buf.extend(text16.encode_utf8()); From 5413e77ec398ab5d7da1b400b7122ad93a6c8b25 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 11 Feb 2024 18:43:12 -0700 Subject: [PATCH 07/74] Use .is_null() instead of comparing to std::ptr::null() --- ctru-rs/src/applets/swkbd.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index c6ad7d8..04b8329 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -687,7 +687,7 @@ impl SoftwareKeyboard { let mut swkbd_shared_mem_handle = 0; - if unsafe { SWKBD_SHARED_MEM == std::ptr::null_mut() } { + if unsafe { SWKBD_SHARED_MEM.is_null() } { (*swkbd).result = SWKBD_OUTOFMEM; return SWKBD_BUTTON_NONE; } @@ -711,7 +711,7 @@ impl SoftwareKeyboard { } // Copy stuff to shared mem - if extra.initial_text != std::ptr::null() { + if !extra.initial_text.is_null() { (*swkbd).initial_text_offset = 0; let utf16_iter = CStr::from_ptr(extra.initial_text) @@ -728,7 +728,7 @@ impl SoftwareKeyboard { } } - if extra.dict != std::ptr::null() { + if !extra.dict.is_null() { (*swkbd).dict_offset = dict_off as _; unsafe { libc::memcpy( @@ -890,7 +890,7 @@ impl SoftwareKeyboard { text8.len(), ) as _; - let retmsg = if retmsg != std::ptr::null() { + let retmsg = if !retmsg.is_null() { unsafe { let len = libc::strlen(retmsg); std::str::from_utf8_unchecked(std::slice::from_raw_parts(retmsg, len + 1)) From 3a2000f6f59411c4212ea3f339cb3a9a3e9cee7c Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 12 Feb 2024 02:47:21 -0700 Subject: [PATCH 08/74] Use references and make unsafe blocks more fine-grained --- ctru-rs/src/applets/swkbd.rs | 108 +++++++++++++++++------------------ 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 04b8329..4e763d3 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -631,7 +631,7 @@ impl SoftwareKeyboard { // A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to // get text from the software keyboard and put it directly into a `String` without requiring // an intermediate fixed-size buffer - unsafe fn swkbd_input_text(swkbd: *mut SwkbdState, buf: &mut Vec) -> SwkbdButton { + fn swkbd_input_text(swkbd: &mut SwkbdState, buf: &mut Vec) -> SwkbdButton { use ctru_sys::{ MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0, @@ -639,22 +639,22 @@ impl SoftwareKeyboard { SWKBD_FILTER_CALLBACK, SWKBD_OUTOFMEM, }; - let mut extra = unsafe { (*swkbd).__bindgen_anon_1.extra }; + let mut extra = unsafe { swkbd.__bindgen_anon_1.extra }; // Calculate shared mem size let mut shared_mem_size = 0; shared_mem_size += - (std::mem::size_of::() * ((*swkbd).max_text_len as usize + 1) + 3) & !3; + (std::mem::size_of::() * (swkbd.max_text_len as usize + 1) + 3) & !3; let dict_off = shared_mem_size; shared_mem_size += - (std::mem::size_of::() * (*swkbd).dict_word_count as usize + 3) & !3; + (std::mem::size_of::() * swkbd.dict_word_count as usize + 3) & !3; let status_off = shared_mem_size; - shared_mem_size += if (*swkbd).initial_learning_offset >= 0 { + shared_mem_size += if swkbd.initial_learning_offset >= 0 { std::mem::size_of::() } else { 0 @@ -662,25 +662,25 @@ impl SoftwareKeyboard { let learning_off = shared_mem_size; - shared_mem_size += if (*swkbd).initial_learning_offset >= 0 { + shared_mem_size += if swkbd.initial_learning_offset >= 0 { std::mem::size_of::() } else { 0 }; - if (*swkbd).save_state_flags & (1 << 0) != 0 { - (*swkbd).status_offset = shared_mem_size as _; + if swkbd.save_state_flags & (1 << 0) != 0 { + swkbd.status_offset = shared_mem_size as _; shared_mem_size += std::mem::size_of::(); } - if (*swkbd).save_state_flags & (1 << 1) != 0 { - (*swkbd).learning_offset = shared_mem_size as _; + if swkbd.save_state_flags & (1 << 1) != 0 { + swkbd.learning_offset = shared_mem_size as _; shared_mem_size += std::mem::size_of::(); } shared_mem_size = (shared_mem_size + 0xFFF) & !0xFFF; - (*swkbd).shared_memory_size = shared_mem_size; + swkbd.shared_memory_size = shared_mem_size; // Allocate shared mem unsafe { SWKBD_SHARED_MEM = libc::memalign(0x1000, shared_mem_size).cast() }; @@ -688,7 +688,7 @@ impl SoftwareKeyboard { let mut swkbd_shared_mem_handle = 0; if unsafe { SWKBD_SHARED_MEM.is_null() } { - (*swkbd).result = SWKBD_OUTOFMEM; + swkbd.result = SWKBD_OUTOFMEM; return SWKBD_BUTTON_NONE; } @@ -705,42 +705,44 @@ impl SoftwareKeyboard { if R_FAILED(res) { unsafe { libc::free(SWKBD_SHARED_MEM); - (*swkbd).result = SWKBD_OUTOFMEM; + swkbd.result = SWKBD_OUTOFMEM; return SWKBD_BUTTON_NONE; } } // Copy stuff to shared mem if !extra.initial_text.is_null() { - (*swkbd).initial_text_offset = 0; + swkbd.initial_text_offset = 0; - let utf16_iter = CStr::from_ptr(extra.initial_text) - .to_str() - .unwrap() - .encode_utf16() - .chain(once(0)); + unsafe { + let utf16_iter = CStr::from_ptr(extra.initial_text) + .to_str() + .unwrap() + .encode_utf16() + .chain(once(0)); - let mut initial_text_cursor = SWKBD_SHARED_MEM.cast::(); + let mut initial_text_cursor = SWKBD_SHARED_MEM.cast::(); - for ch in utf16_iter { - *initial_text_cursor = ch; - unsafe { initial_text_cursor = initial_text_cursor.add(1) }; + for ch in utf16_iter { + *initial_text_cursor = ch; + initial_text_cursor = initial_text_cursor.add(1); + } } } if !extra.dict.is_null() { - (*swkbd).dict_offset = dict_off as _; + swkbd.dict_offset = dict_off as _; unsafe { libc::memcpy( SWKBD_SHARED_MEM.add(dict_off), extra.dict.cast(), - std::mem::size_of::() * (*swkbd).dict_word_count as usize, + std::mem::size_of::() * swkbd.dict_word_count as usize, ) }; } - if (*swkbd).initial_status_offset >= 0 { - (*swkbd).initial_status_offset = status_off as _; + if swkbd.initial_status_offset >= 0 { + swkbd.initial_status_offset = status_off as _; unsafe { libc::memcpy( SWKBD_SHARED_MEM.add(status_off), @@ -750,8 +752,8 @@ impl SoftwareKeyboard { }; } - if (*swkbd).initial_learning_offset >= 0 { - (*swkbd).initial_learning_offset = learning_off as _; + if swkbd.initial_learning_offset >= 0 { + swkbd.initial_learning_offset = learning_off as _; unsafe { libc::memcpy( SWKBD_SHARED_MEM.add(learning_off), @@ -762,17 +764,17 @@ impl SoftwareKeyboard { } if extra.callback.is_some() { - (*swkbd).filter_flags |= SWKBD_FILTER_CALLBACK; + swkbd.filter_flags |= SWKBD_FILTER_CALLBACK; } else { - (*swkbd).filter_flags &= !SWKBD_FILTER_CALLBACK; + swkbd.filter_flags &= !SWKBD_FILTER_CALLBACK; } // Launch swkbd unsafe { libc::memset( - std::ptr::addr_of_mut!((*swkbd).__bindgen_anon_1.reserved).cast(), + std::ptr::addr_of_mut!(swkbd.__bindgen_anon_1.reserved).cast(), 0, - std::mem::size_of_val(&(*swkbd).__bindgen_anon_1.reserved), + swkbd.__bindgen_anon_1.reserved.len(), ); if extra.callback.is_some() { @@ -784,7 +786,7 @@ impl SoftwareKeyboard { aptLaunchLibraryApplet( APPID_SOFTWARE_KEYBOARD, - swkbd.cast(), + swkbd as *mut _ as *mut _, std::mem::size_of::(), swkbd_shared_mem_handle, ); @@ -796,20 +798,18 @@ impl SoftwareKeyboard { let _ = svcCloseHandle(swkbd_shared_mem_handle); } - let button = match (*swkbd).result { + let button = match swkbd.result { SWKBD_D1_CLICK0 | SWKBD_D2_CLICK0 => SWKBD_BUTTON_LEFT, SWKBD_D2_CLICK1 => SWKBD_BUTTON_MIDDLE, SWKBD_D0_CLICK | SWKBD_D1_CLICK1 | SWKBD_D2_CLICK2 => SWKBD_BUTTON_RIGHT, _ => SWKBD_BUTTON_NONE, }; - let text16 = if (*swkbd).text_length > 0 { + let text16 = if swkbd.text_length > 0 { unsafe { widestring::Utf16Str::from_slice(std::slice::from_raw_parts( - SWKBD_SHARED_MEM - .add((*swkbd).text_offset as _) - .cast::(), - (*swkbd).text_length as usize, + SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast::(), + swkbd.text_length as usize, )) .unwrap() } @@ -819,21 +819,21 @@ impl SoftwareKeyboard { buf.extend(text16.encode_utf8()); - if (*swkbd).save_state_flags & (1 << 0) != 0 { + if swkbd.save_state_flags & (1 << 0) != 0 { unsafe { libc::memcpy( extra.status_data.cast(), - SWKBD_SHARED_MEM.add((*swkbd).status_offset as _), + SWKBD_SHARED_MEM.add(swkbd.status_offset as _), std::mem::size_of::(), ) }; } - if (*swkbd).save_state_flags & (1 << 1) != 0 { + if swkbd.save_state_flags & (1 << 1) != 0 { unsafe { libc::memcpy( extra.learning_data.cast(), - SWKBD_SHARED_MEM.add((*swkbd).learning_offset as _), + SWKBD_SHARED_MEM.add(swkbd.learning_offset as _), std::mem::size_of::(), ) }; @@ -854,8 +854,8 @@ impl SoftwareKeyboard { msg: *mut libc::c_void, msg_size: libc::size_t, ) { - let extra = user.cast::(); - let swkbd = msg.cast::(); + let extra = &mut *user.cast::(); + let swkbd = &mut *msg.cast::(); if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD || msg_size != std::mem::size_of::() @@ -865,10 +865,8 @@ impl SoftwareKeyboard { let text16 = unsafe { widestring::Utf16Str::from_slice(std::slice::from_raw_parts( - SWKBD_SHARED_MEM - .add((*swkbd).text_offset as _) - .cast::(), - (*swkbd).text_length as usize + 1, + SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast::(), + swkbd.text_length as usize + 1, )) .unwrap() }; @@ -883,8 +881,8 @@ impl SoftwareKeyboard { let mut retmsg = std::ptr::null(); - (*swkbd).callback_result = (*extra).callback.unwrap()( - (*extra).callback_user, + swkbd.callback_result = extra.callback.unwrap()( + extra.callback_user, &mut retmsg, text8.as_ptr(), text8.len(), @@ -899,9 +897,9 @@ impl SoftwareKeyboard { "\0" }; - let callback_msg = &mut (*swkbd).callback_msg; + let callback_msg = &mut swkbd.callback_msg; - if (*swkbd).callback_result > SWKBD_CALLBACK_OK as _ { + if swkbd.callback_result > SWKBD_CALLBACK_OK as _ { for (idx, ch) in retmsg.encode_utf16().take(callback_msg.len()).enumerate() { callback_msg[idx] = ch; } @@ -913,7 +911,7 @@ impl SoftwareKeyboard { envGetAptAppId(), sender, APTCMD_MESSAGE, - swkbd.cast(), + swkbd as *mut _ as *mut _, std::mem::size_of::() as _, 0, ); From bd19f97050e8f183080ba90cf1bb2f867e112781 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 13 Feb 2024 22:37:41 -0700 Subject: [PATCH 09/74] Use rust functions instead of memcpy and memset --- ctru-rs/src/applets/swkbd.rs | 46 ++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 4e763d3..4e41915 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -733,10 +733,10 @@ impl SoftwareKeyboard { if !extra.dict.is_null() { swkbd.dict_offset = dict_off as _; unsafe { - libc::memcpy( - SWKBD_SHARED_MEM.add(dict_off), - extra.dict.cast(), - std::mem::size_of::() * swkbd.dict_word_count as usize, + std::ptr::copy_nonoverlapping( + extra.dict, + SWKBD_SHARED_MEM.add(dict_off).cast(), + swkbd.dict_word_count as _, ) }; } @@ -744,10 +744,10 @@ impl SoftwareKeyboard { if swkbd.initial_status_offset >= 0 { swkbd.initial_status_offset = status_off as _; unsafe { - libc::memcpy( - SWKBD_SHARED_MEM.add(status_off), - extra.status_data.cast(), - std::mem::size_of::(), + std::ptr::copy_nonoverlapping( + extra.status_data, + SWKBD_SHARED_MEM.add(status_off).cast(), + 1, ) }; } @@ -755,10 +755,10 @@ impl SoftwareKeyboard { if swkbd.initial_learning_offset >= 0 { swkbd.initial_learning_offset = learning_off as _; unsafe { - libc::memcpy( - SWKBD_SHARED_MEM.add(learning_off), - extra.learning_data.cast(), - std::mem::size_of::(), + std::ptr::copy_nonoverlapping( + extra.learning_data, + SWKBD_SHARED_MEM.add(learning_off).cast(), + 1, ) }; } @@ -771,11 +771,7 @@ impl SoftwareKeyboard { // Launch swkbd unsafe { - libc::memset( - std::ptr::addr_of_mut!(swkbd.__bindgen_anon_1.reserved).cast(), - 0, - swkbd.__bindgen_anon_1.reserved.len(), - ); + swkbd.__bindgen_anon_1.reserved.fill(0); if extra.callback.is_some() { aptSetMessageCallback( @@ -821,20 +817,20 @@ impl SoftwareKeyboard { if swkbd.save_state_flags & (1 << 0) != 0 { unsafe { - libc::memcpy( - extra.status_data.cast(), - SWKBD_SHARED_MEM.add(swkbd.status_offset as _), - std::mem::size_of::(), + std::ptr::copy_nonoverlapping( + SWKBD_SHARED_MEM.add(swkbd.status_offset as _).cast(), + extra.status_data, + 1, ) }; } if swkbd.save_state_flags & (1 << 1) != 0 { unsafe { - libc::memcpy( - extra.learning_data.cast(), - SWKBD_SHARED_MEM.add(swkbd.learning_offset as _), - std::mem::size_of::(), + std::ptr::copy_nonoverlapping( + SWKBD_SHARED_MEM.add(swkbd.learning_offset as _).cast(), + extra.learning_data, + 1, ) }; } From fa6f98d62c520570444e98d85124216371a2c38f Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 13 Feb 2024 22:56:44 -0700 Subject: [PATCH 10/74] Simplify string conversion code --- ctru-rs/src/applets/swkbd.rs | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 4e41915..98291e0 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -5,10 +5,10 @@ use crate::services::{apt::Apt, gfx::Gfx}; use ctru_sys::{ - self, aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcBreak, svcCloseHandle, + self, aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle, svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdExtra, SwkbdLearningData, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, - NS_APPID, SWKBD_CALLBACK_OK, USERBREAK_PANIC, + NS_APPID, SWKBD_CALLBACK_OK, }; use bitflags::bitflags; @@ -281,7 +281,7 @@ impl SoftwareKeyboard { (self as *mut Self).cast(), ); - match Self::swkbd_input_text(self.state.as_mut(), output.as_mut_vec()) { + match Self::swkbd_input_text(self.state.as_mut(), &mut output) { ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), @@ -631,7 +631,7 @@ impl SoftwareKeyboard { // A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to // get text from the software keyboard and put it directly into a `String` without requiring // an intermediate fixed-size buffer - fn swkbd_input_text(swkbd: &mut SwkbdState, buf: &mut Vec) -> SwkbdButton { + fn swkbd_input_text(swkbd: &mut SwkbdState, output: &mut String) -> SwkbdButton { use ctru_sys::{ MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0, @@ -715,11 +715,11 @@ impl SoftwareKeyboard { swkbd.initial_text_offset = 0; unsafe { - let utf16_iter = CStr::from_ptr(extra.initial_text) - .to_str() - .unwrap() - .encode_utf16() - .chain(once(0)); + let utf16_iter = + str::from_utf8_unchecked(CStr::from_ptr(extra.initial_text).to_bytes()) + .encode_utf16() + .take(swkbd.max_text_len as _) + .chain(once(0)); let mut initial_text_cursor = SWKBD_SHARED_MEM.cast::(); @@ -803,17 +803,16 @@ impl SoftwareKeyboard { let text16 = if swkbd.text_length > 0 { unsafe { - widestring::Utf16Str::from_slice(std::slice::from_raw_parts( + widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast::(), swkbd.text_length as usize, )) - .unwrap() } } else { widestring::utf16str!("") }; - buf.extend(text16.encode_utf8()); + *output = text16.to_string(); if swkbd.save_state_flags & (1 << 0) != 0 { unsafe { @@ -860,20 +859,13 @@ impl SoftwareKeyboard { } let text16 = unsafe { - widestring::Utf16Str::from_slice(std::slice::from_raw_parts( + widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast::(), swkbd.text_length as usize + 1, )) - .unwrap() }; - let text8 = match String::from_utf8(text16.encode_utf8().collect()) { - Ok(s) => s, - Err(_) => { - svcBreak(USERBREAK_PANIC); - unreachable!(); - } - }; + let text8 = text16.to_string(); let mut retmsg = std::ptr::null(); From f867d965f83f18c96974e95b45fe56cddb5a18ac Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 13 Feb 2024 23:53:14 -0700 Subject: [PATCH 11/74] Use if let instead of unwrap for nullable callback --- ctru-rs/src/applets/swkbd.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 98291e0..fb4735b 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -869,12 +869,14 @@ impl SoftwareKeyboard { let mut retmsg = std::ptr::null(); - swkbd.callback_result = extra.callback.unwrap()( - extra.callback_user, - &mut retmsg, - text8.as_ptr(), - text8.len(), - ) as _; + if let Some(cb) = extra.callback { + swkbd.callback_result = cb( + extra.callback_user, + &mut retmsg, + text8.as_ptr(), + text8.len(), + ) as _ + }; let retmsg = if !retmsg.is_null() { unsafe { From 61f91312d3c2de2a052ab074fe09e366224c06d2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 14 Feb 2024 12:21:52 -0700 Subject: [PATCH 12/74] Remove mention of deleted function --- ctru-rs/src/applets/swkbd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index fb4735b..32e293d 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -606,7 +606,7 @@ impl SoftwareKeyboard { /// /// Keyboard input is converted from UTF-16 to UTF-8 before being handed to Rust, /// so this code point limit does not necessarily equal the max number of UTF-8 code points - /// receivable by [`SoftwareKeyboard::get_string()`] and [`SoftwareKeyboard::write_exact()`]. + /// receivable by [`SoftwareKeyboard::get_string()`]. /// /// # Example /// From 4de76522d327631b05fc75b6faa71f5380857992 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 14 Feb 2024 12:25:53 -0700 Subject: [PATCH 13/74] swkbd_input_text is still an unsafe fn --- ctru-rs/src/applets/swkbd.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 32e293d..3b61fdf 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -281,7 +281,7 @@ impl SoftwareKeyboard { (self as *mut Self).cast(), ); - match Self::swkbd_input_text(self.state.as_mut(), &mut output) { + match Self::swkbd_input_text(&mut self.state, &mut output) { ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), @@ -631,7 +631,9 @@ impl SoftwareKeyboard { // A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to // get text from the software keyboard and put it directly into a `String` without requiring // an intermediate fixed-size buffer - fn swkbd_input_text(swkbd: &mut SwkbdState, output: &mut String) -> SwkbdButton { + // + // SAFETY: `swkbd` must be initialized by `swkbdInit` before calling this function. + unsafe fn swkbd_input_text(swkbd: &mut SwkbdState, output: &mut String) -> SwkbdButton { use ctru_sys::{ MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0, From 2fcdd110d2e74d899669f722b5bd63a312b26b2b Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 14 Feb 2024 12:33:48 -0700 Subject: [PATCH 14/74] Simplify string output logic --- ctru-rs/src/applets/swkbd.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 3b61fdf..c1a6740 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -803,18 +803,16 @@ impl SoftwareKeyboard { _ => SWKBD_BUTTON_NONE, }; - let text16 = if swkbd.text_length > 0 { - unsafe { + if swkbd.text_length > 0 { + let text16 = unsafe { widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( - SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast::(), - swkbd.text_length as usize, + SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast(), + swkbd.text_length as _, )) - } - } else { - widestring::utf16str!("") - }; + }; - *output = text16.to_string(); + *output = text16.to_string(); + } if swkbd.save_state_flags & (1 << 0) != 0 { unsafe { From f5276234050cfc4d546bfe4158185d265e21039a Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 14 Feb 2024 13:47:11 -0700 Subject: [PATCH 15/74] Fix off-by-one error in swkbd_message_callback --- ctru-rs/src/applets/swkbd.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index c1a6740..fa0f45d 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -890,7 +890,11 @@ impl SoftwareKeyboard { let callback_msg = &mut swkbd.callback_msg; if swkbd.callback_result > SWKBD_CALLBACK_OK as _ { - for (idx, ch) in retmsg.encode_utf16().take(callback_msg.len()).enumerate() { + for (idx, ch) in retmsg + .encode_utf16() + .take(callback_msg.len() - 1) + .enumerate() + { callback_msg[idx] = ch; } } else { From b6fc9a2596e779100398433affcca89235b41e54 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 14 Feb 2024 16:28:43 -0700 Subject: [PATCH 16/74] Simplify some code + rename vars --- ctru-rs/src/applets/swkbd.rs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index fa0f45d..feafdbd 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -8,7 +8,7 @@ use ctru_sys::{ self, aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle, svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdExtra, SwkbdLearningData, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, - NS_APPID, SWKBD_CALLBACK_OK, + NS_APPID, }; use bitflags::bitflags; @@ -723,10 +723,10 @@ impl SoftwareKeyboard { .take(swkbd.max_text_len as _) .chain(once(0)); - let mut initial_text_cursor = SWKBD_SHARED_MEM.cast::(); + let mut initial_text_cursor = SWKBD_SHARED_MEM.cast(); - for ch in utf16_iter { - *initial_text_cursor = ch; + for code_point in utf16_iter { + *initial_text_cursor = code_point; initial_text_cursor = initial_text_cursor.add(1); } } @@ -860,7 +860,7 @@ impl SoftwareKeyboard { let text16 = unsafe { widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( - SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast::(), + SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast(), swkbd.text_length as usize + 1, )) }; @@ -880,8 +880,8 @@ impl SoftwareKeyboard { let retmsg = if !retmsg.is_null() { unsafe { - let len = libc::strlen(retmsg); - std::str::from_utf8_unchecked(std::slice::from_raw_parts(retmsg, len + 1)) + let len = libc::strlen(retmsg) + 1; + std::str::from_utf8_unchecked(std::slice::from_raw_parts(retmsg, len)) } } else { "\0" @@ -889,16 +889,12 @@ impl SoftwareKeyboard { let callback_msg = &mut swkbd.callback_msg; - if swkbd.callback_result > SWKBD_CALLBACK_OK as _ { - for (idx, ch) in retmsg - .encode_utf16() - .take(callback_msg.len() - 1) - .enumerate() - { - callback_msg[idx] = ch; - } - } else { - callback_msg[0] = 0; + for (idx, code_point) in retmsg + .encode_utf16() + .take(callback_msg.len() - 1) + .enumerate() + { + callback_msg[idx] = code_point; } let _ = APT_SendParameter( From bf3ca2a87d4be663bbfcb358981cdcb5d33dd4f4 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 14 Feb 2024 20:28:27 -0700 Subject: [PATCH 17/74] Force consistent use of unsafe blocks Isolating unsafe code is still important even in unsafe functions --- ctru-rs/src/applets/swkbd.rs | 39 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index feafdbd..0f794ac 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -633,6 +633,7 @@ impl SoftwareKeyboard { // an intermediate fixed-size buffer // // SAFETY: `swkbd` must be initialized by `swkbdInit` before calling this function. + #[deny(unsafe_op_in_unsafe_fn)] unsafe fn swkbd_input_text(swkbd: &mut SwkbdState, output: &mut String) -> SwkbdButton { use ctru_sys::{ MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, @@ -842,15 +843,15 @@ impl SoftwareKeyboard { // A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`. // This is only needed because the original function is private to libctru, so we can't // simply reuse their version - #[allow(non_snake_case)] + #[deny(unsafe_op_in_unsafe_fn)] unsafe extern "C" fn swkbd_message_callback( user: *mut libc::c_void, sender: NS_APPID, msg: *mut libc::c_void, msg_size: libc::size_t, ) { - let extra = &mut *user.cast::(); - let swkbd = &mut *msg.cast::(); + let extra = unsafe { &mut *user.cast::() }; + let swkbd = unsafe { &mut *msg.cast::() }; if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD || msg_size != std::mem::size_of::() @@ -870,12 +871,14 @@ impl SoftwareKeyboard { let mut retmsg = std::ptr::null(); if let Some(cb) = extra.callback { - swkbd.callback_result = cb( - extra.callback_user, - &mut retmsg, - text8.as_ptr(), - text8.len(), - ) as _ + swkbd.callback_result = unsafe { + cb( + extra.callback_user, + &mut retmsg, + text8.as_ptr(), + text8.len(), + ) + } as _ }; let retmsg = if !retmsg.is_null() { @@ -897,14 +900,16 @@ impl SoftwareKeyboard { callback_msg[idx] = code_point; } - let _ = APT_SendParameter( - envGetAptAppId(), - sender, - APTCMD_MESSAGE, - swkbd as *mut _ as *mut _, - std::mem::size_of::() as _, - 0, - ); + let _ = unsafe { + APT_SendParameter( + envGetAptAppId(), + sender, + APTCMD_MESSAGE, + swkbd as *mut _ as *mut _, + std::mem::size_of::() as _, + 0, + ) + }; } } From 271d1b598ab9a78b116a1a5c464f5a35119a800c Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 15 Feb 2024 11:49:33 -0700 Subject: [PATCH 18/74] Fix yet another off-by-one error --- ctru-rs/src/applets/swkbd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 0f794ac..a47a7bc 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -862,7 +862,7 @@ impl SoftwareKeyboard { let text16 = unsafe { widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast(), - swkbd.text_length as usize + 1, + swkbd.text_length as _, )) }; From 757afe9fe6f195ffa8b9032a1ce1ee368839046a Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 16 Feb 2024 11:27:04 -0700 Subject: [PATCH 19/74] Make swkbd_input_text a method for SoftwareKeyboard This lets us mark the function as safe since it can only be called with an initialized `SwkbdState` --- ctru-rs/src/applets/swkbd.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index a47a7bc..c7a2be1 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -281,7 +281,7 @@ impl SoftwareKeyboard { (self as *mut Self).cast(), ); - match Self::swkbd_input_text(&mut self.state, &mut output) { + match self.swkbd_input_text(&mut output) { ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), @@ -631,10 +631,7 @@ impl SoftwareKeyboard { // A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to // get text from the software keyboard and put it directly into a `String` without requiring // an intermediate fixed-size buffer - // - // SAFETY: `swkbd` must be initialized by `swkbdInit` before calling this function. - #[deny(unsafe_op_in_unsafe_fn)] - unsafe fn swkbd_input_text(swkbd: &mut SwkbdState, output: &mut String) -> SwkbdButton { + fn swkbd_input_text(&mut self, output: &mut String) -> SwkbdButton { use ctru_sys::{ MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0, @@ -642,6 +639,7 @@ impl SoftwareKeyboard { SWKBD_FILTER_CALLBACK, SWKBD_OUTOFMEM, }; + let swkbd = self.state.as_mut(); let mut extra = unsafe { swkbd.__bindgen_anon_1.extra }; // Calculate shared mem size @@ -862,7 +860,7 @@ impl SoftwareKeyboard { let text16 = unsafe { widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast(), - swkbd.text_length as _, + swkbd.text_length as usize + 1, )) }; From cfd276c175032291af6f48e76b84e26246eb0179 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 16 Feb 2024 13:51:04 -0700 Subject: [PATCH 20/74] Get rid of static mut by using our own callback data --- ctru-rs/src/applets/swkbd.rs | 55 +++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index c7a2be1..935f15a 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -21,15 +21,12 @@ use std::str; type CallbackFunction = dyn Fn(&CStr) -> (CallbackResult, Option); -// I hate that we have to use this, but sometimes you gotta smuggle pointers into C callbacks -// and that's just how things are -static mut SWKBD_SHARED_MEM: *mut libc::c_void = std::ptr::null_mut(); - /// Configuration structure to setup the Software Keyboard applet. #[doc(alias = "SwkbdState")] pub struct SoftwareKeyboard { state: Box, callback: Option>, + callback_data: MessageCallbackData, error_message: Option, initial_text: Option, } @@ -216,6 +213,21 @@ bitflags! { } } +// Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard +struct MessageCallbackData { + extra: *mut SwkbdExtra, + swkbd_shared_mem_ptr: *mut libc::c_void, +} + +impl MessageCallbackData { + fn new() -> Self { + Self { + extra: std::ptr::null_mut(), + swkbd_shared_mem_ptr: std::ptr::null_mut(), + } + } +} + impl SoftwareKeyboard { /// Initialize a new configuration for the Software Keyboard applet depending on how many "exit" buttons are available to the user (1, 2 or 3). /// @@ -244,6 +256,7 @@ impl SoftwareKeyboard { callback: None, error_message: None, initial_text: None, + callback_data: MessageCallbackData::new(), } } } @@ -684,11 +697,11 @@ impl SoftwareKeyboard { swkbd.shared_memory_size = shared_mem_size; // Allocate shared mem - unsafe { SWKBD_SHARED_MEM = libc::memalign(0x1000, shared_mem_size).cast() }; + let swkbd_shared_mem_ptr = unsafe { libc::memalign(0x1000, shared_mem_size) }; let mut swkbd_shared_mem_handle = 0; - if unsafe { SWKBD_SHARED_MEM.is_null() } { + if swkbd_shared_mem_ptr.is_null() { swkbd.result = SWKBD_OUTOFMEM; return SWKBD_BUTTON_NONE; } @@ -696,7 +709,7 @@ impl SoftwareKeyboard { let res = unsafe { svcCreateMemoryBlock( &mut swkbd_shared_mem_handle, - SWKBD_SHARED_MEM as _, + swkbd_shared_mem_ptr as _, shared_mem_size as _, MEMPERM_READ | MEMPERM_WRITE, MEMPERM_READ | MEMPERM_WRITE, @@ -705,7 +718,7 @@ impl SoftwareKeyboard { if R_FAILED(res) { unsafe { - libc::free(SWKBD_SHARED_MEM); + libc::free(swkbd_shared_mem_ptr); swkbd.result = SWKBD_OUTOFMEM; return SWKBD_BUTTON_NONE; } @@ -722,7 +735,7 @@ impl SoftwareKeyboard { .take(swkbd.max_text_len as _) .chain(once(0)); - let mut initial_text_cursor = SWKBD_SHARED_MEM.cast(); + let mut initial_text_cursor = swkbd_shared_mem_ptr.cast(); for code_point in utf16_iter { *initial_text_cursor = code_point; @@ -736,7 +749,7 @@ impl SoftwareKeyboard { unsafe { std::ptr::copy_nonoverlapping( extra.dict, - SWKBD_SHARED_MEM.add(dict_off).cast(), + swkbd_shared_mem_ptr.add(dict_off).cast(), swkbd.dict_word_count as _, ) }; @@ -747,7 +760,7 @@ impl SoftwareKeyboard { unsafe { std::ptr::copy_nonoverlapping( extra.status_data, - SWKBD_SHARED_MEM.add(status_off).cast(), + swkbd_shared_mem_ptr.add(status_off).cast(), 1, ) }; @@ -758,7 +771,7 @@ impl SoftwareKeyboard { unsafe { std::ptr::copy_nonoverlapping( extra.learning_data, - SWKBD_SHARED_MEM.add(learning_off).cast(), + swkbd_shared_mem_ptr.add(learning_off).cast(), 1, ) }; @@ -775,9 +788,12 @@ impl SoftwareKeyboard { swkbd.__bindgen_anon_1.reserved.fill(0); if extra.callback.is_some() { + self.callback_data.extra = std::ptr::addr_of_mut!(extra); + self.callback_data.swkbd_shared_mem_ptr = swkbd_shared_mem_ptr; + aptSetMessageCallback( Some(Self::swkbd_message_callback), - std::ptr::addr_of_mut!(extra).cast(), + std::ptr::addr_of_mut!(self.callback_data).cast(), ); } @@ -805,7 +821,7 @@ impl SoftwareKeyboard { if swkbd.text_length > 0 { let text16 = unsafe { widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( - SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast(), + swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(), swkbd.text_length as _, )) }; @@ -816,7 +832,7 @@ impl SoftwareKeyboard { if swkbd.save_state_flags & (1 << 0) != 0 { unsafe { std::ptr::copy_nonoverlapping( - SWKBD_SHARED_MEM.add(swkbd.status_offset as _).cast(), + swkbd_shared_mem_ptr.add(swkbd.status_offset as _).cast(), extra.status_data, 1, ) @@ -826,14 +842,14 @@ impl SoftwareKeyboard { if swkbd.save_state_flags & (1 << 1) != 0 { unsafe { std::ptr::copy_nonoverlapping( - SWKBD_SHARED_MEM.add(swkbd.learning_offset as _).cast(), + swkbd_shared_mem_ptr.add(swkbd.learning_offset as _).cast(), extra.learning_data, 1, ) }; } - unsafe { libc::free(SWKBD_SHARED_MEM) }; + unsafe { libc::free(swkbd_shared_mem_ptr) }; button } @@ -848,8 +864,9 @@ impl SoftwareKeyboard { msg: *mut libc::c_void, msg_size: libc::size_t, ) { - let extra = unsafe { &mut *user.cast::() }; + let data = unsafe { &mut *user.cast::() }; let swkbd = unsafe { &mut *msg.cast::() }; + let extra = unsafe { &mut *data.extra }; if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD || msg_size != std::mem::size_of::() @@ -859,7 +876,7 @@ impl SoftwareKeyboard { let text16 = unsafe { widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( - SWKBD_SHARED_MEM.add(swkbd.text_offset as _).cast(), + data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(), swkbd.text_length as usize + 1, )) }; From c95cd26d5c19e061c2fb548b3f18394be7d8a189 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 17 Feb 2024 01:55:04 +0100 Subject: [PATCH 21/74] add low hanging sleep/homemenu apt functions --- ctru-rs/src/services/apt.rs | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index 89ad255..ef09527 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -82,6 +82,54 @@ impl Apt { Ok(()) } } + + /// Set if the console is allowed to enter sleep mode. + /// + /// You can check whether the console is allowed to sleep with [Apt::is_sleep_allowed]. + #[doc(alias = "aptSetSleepAllowed")] + pub fn set_sleep_allowed(&mut self, allowed: bool) { + unsafe { + ctru_sys::aptSetSleepAllowed(allowed); + } + } + + /// Check if the console is allowed to enter sleep mode. + /// + /// You can set whether the console is allowed to sleep with [Apt::set_sleep_allowed]. + #[doc(alias = "aptIsSleepAllowed")] + pub fn is_sleep_allowed(&mut self) -> bool { + unsafe { + ctru_sys::aptIsSleepAllowed() + } + } + + /// Set if the console is allowed to enter the home menu. + /// + /// You can check whether the console is allowed to enter the home menu with [Apt::is_home_allowed]. + #[doc(alias = "aptSetHomeAllowed")] + pub fn set_home_allowed(&mut self, allowed: bool) { + unsafe { + ctru_sys::aptSetHomeAllowed(allowed); + } + } + + /// Check if the console is allowed to enter the home menu. + /// + /// You can set whether the console is allowed to enter the home menu with [Apt::set_home_allowed]. + #[doc(alias = "aptIsHomeAllowed")] + pub fn is_home_allowed(&mut self) -> bool { + unsafe { + ctru_sys::aptIsHomeAllowed() + } + } + + /// Immediately jumps to the home menu. + #[doc(alias = "aptIsHomeAllowed")] + pub fn jump_to_home_menu(&mut self) { + unsafe { + ctru_sys::aptJumpToHomeMenu() + } + } } impl Drop for Apt { From 4f3562b5235a39de258f1b54aadbbb36f76e3c57 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 17 Feb 2024 02:31:37 +0100 Subject: [PATCH 22/74] cargo fmt --- ctru-rs/src/services/apt.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index ef09527..09892df 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -98,9 +98,7 @@ impl Apt { /// You can set whether the console is allowed to sleep with [Apt::set_sleep_allowed]. #[doc(alias = "aptIsSleepAllowed")] pub fn is_sleep_allowed(&mut self) -> bool { - unsafe { - ctru_sys::aptIsSleepAllowed() - } + unsafe { ctru_sys::aptIsSleepAllowed() } } /// Set if the console is allowed to enter the home menu. @@ -114,21 +112,17 @@ impl Apt { } /// Check if the console is allowed to enter the home menu. - /// + /// /// You can set whether the console is allowed to enter the home menu with [Apt::set_home_allowed]. #[doc(alias = "aptIsHomeAllowed")] pub fn is_home_allowed(&mut self) -> bool { - unsafe { - ctru_sys::aptIsHomeAllowed() - } + unsafe { ctru_sys::aptIsHomeAllowed() } } /// Immediately jumps to the home menu. #[doc(alias = "aptIsHomeAllowed")] pub fn jump_to_home_menu(&mut self) { - unsafe { - ctru_sys::aptJumpToHomeMenu() - } + unsafe { ctru_sys::aptJumpToHomeMenu() } } } From 03b417dc9efa7f73554aee45b12926db0cbafe5e Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 16 Feb 2024 19:20:14 -0700 Subject: [PATCH 23/74] code_point -> code_unit --- ctru-rs/src/applets/swkbd.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 935f15a..c888743 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -737,8 +737,8 @@ impl SoftwareKeyboard { let mut initial_text_cursor = swkbd_shared_mem_ptr.cast(); - for code_point in utf16_iter { - *initial_text_cursor = code_point; + for code_unit in utf16_iter { + *initial_text_cursor = code_unit; initial_text_cursor = initial_text_cursor.add(1); } } @@ -907,12 +907,12 @@ impl SoftwareKeyboard { let callback_msg = &mut swkbd.callback_msg; - for (idx, code_point) in retmsg + for (idx, code_unit) in retmsg .encode_utf16() .take(callback_msg.len() - 1) .enumerate() { - callback_msg[idx] = code_point; + callback_msg[idx] = code_unit; } let _ = unsafe { From d6f22dc7977a3ee413f59b2dd21e0cb22dfc8948 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 16 Feb 2024 19:19:19 -0700 Subject: [PATCH 24/74] Use .cast() in more places --- ctru-rs/src/applets/swkbd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index c888743..5098c89 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -799,7 +799,7 @@ impl SoftwareKeyboard { aptLaunchLibraryApplet( APPID_SOFTWARE_KEYBOARD, - swkbd as *mut _ as *mut _, + (swkbd as *mut SwkbdState).cast(), std::mem::size_of::(), swkbd_shared_mem_handle, ); @@ -920,7 +920,7 @@ impl SoftwareKeyboard { envGetAptAppId(), sender, APTCMD_MESSAGE, - swkbd as *mut _ as *mut _, + (swkbd as *mut SwkbdState).cast(), std::mem::size_of::() as _, 0, ) From f715fc6eb97ef76b2c86a72d427a568e023103a6 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 16 Feb 2024 19:30:54 -0700 Subject: [PATCH 25/74] Add some comments --- ctru-rs/src/applets/swkbd.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 5098c89..6290e07 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -213,7 +213,8 @@ bitflags! { } } -// Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard +// Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard. +// We only need this because libctru doesn't keep a pointer to the shared memory block in `SwkbdExtra` for whatever reason struct MessageCallbackData { extra: *mut SwkbdExtra, swkbd_shared_mem_ptr: *mut libc::c_void, @@ -855,8 +856,7 @@ impl SoftwareKeyboard { } // A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`. - // This is only needed because the original function is private to libctru, so we can't - // simply reuse their version + // This function sets up and then calls the callback set by `swkbdSetFilterCallback` #[deny(unsafe_op_in_unsafe_fn)] unsafe extern "C" fn swkbd_message_callback( user: *mut libc::c_void, From cc2e535e9655c81cb451837c8f3d029c6223ec8a Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 17 Feb 2024 16:44:18 +0100 Subject: [PATCH 26/74] fix apt mutability requirements --- ctru-rs/src/services/apt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index 09892df..2a3b7fb 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -97,7 +97,7 @@ impl Apt { /// /// You can set whether the console is allowed to sleep with [Apt::set_sleep_allowed]. #[doc(alias = "aptIsSleepAllowed")] - pub fn is_sleep_allowed(&mut self) -> bool { + pub fn is_sleep_allowed(&self) -> bool { unsafe { ctru_sys::aptIsSleepAllowed() } } @@ -115,7 +115,7 @@ impl Apt { /// /// You can set whether the console is allowed to enter the home menu with [Apt::set_home_allowed]. #[doc(alias = "aptIsHomeAllowed")] - pub fn is_home_allowed(&mut self) -> bool { + pub fn is_home_allowed(&self) -> bool { unsafe { ctru_sys::aptIsHomeAllowed() } } From e2364ff5e182cc264befc3628df2d15e771553e6 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 17 Feb 2024 16:42:31 +0100 Subject: [PATCH 27/74] implement chainloader --- ctru-rs/src/services/am.rs | 5 +++++ ctru-rs/src/services/apt.rs | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index f4b6e6a..2bcd47a 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -51,6 +51,11 @@ impl<'a> Title<'a> { pub fn version(&self) -> u16 { self.version } + + /// Returns this title's media type + pub fn media_type(&self) -> MediaType { + self.mediatype + } } /// Handle to the Application Manager service. diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index 2a3b7fb..5df178b 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -132,3 +132,44 @@ impl Drop for Apt { unsafe { ctru_sys::aptExit() }; } } + +pub struct Chainloader<'a> { + _apt: &'a Apt, +} + +impl<'a> Chainloader<'a> { + /// Gets a handle to the chainloader + pub fn new(apt: &'a Apt) -> Self { + Self { _apt: apt } + } + + /// Checks if the chainloader is set + #[doc(alias = "aptIsChainload")] + pub fn is_set(&mut self) { + //unsafe { ctru_sys::aptIsChainload() } + } + + /// Clears the chainloader state. + #[doc(alias = "aptClearChainloader")] + pub fn clear(&mut self) { + unsafe { ctru_sys::aptClearChainloader() } + } + + /// Configures the chainloader to launch a specific application. + #[doc(alias = "aptSetChainloader")] + pub fn set_chainloader(&mut self, title: &super::am::Title<'_>) { + unsafe { ctru_sys::aptSetChainloader(title.id(), title.media_type() as u8) } + } + + /// Configures the chainloader to launch the previous application. + #[doc(alias = "aptSetChainloaderToCaller")] + pub fn set_chainloader_to_caller(&mut self) { + unsafe { ctru_sys::aptSetChainloaderToCaller() } + } + + /// Configures the chainloader to relaunch the current application (i.e. soft-reset) + #[doc(alias = "aptSetChainloaderToSelf")] + pub fn set_chainloader_to_self(&mut self) { + unsafe { ctru_sys::aptSetChainloaderToSelf() } + } +} From 66e5a54952d48dd5de85129191cf9c90db703fc8 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 17 Feb 2024 16:50:20 +0100 Subject: [PATCH 28/74] add simple doc for chainloader --- ctru-rs/src/services/apt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index 5df178b..fa26470 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -133,6 +133,7 @@ impl Drop for Apt { } } +/// Can launch other applications when the current one exits. pub struct Chainloader<'a> { _apt: &'a Apt, } From 144658345cbc612081cf954de88087e23f18b295 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sat, 17 Feb 2024 11:30:09 -0700 Subject: [PATCH 29/74] MessageCallbackData doesn't need to be a struct member --- ctru-rs/src/applets/swkbd.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 6290e07..a1c5f7e 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -26,7 +26,6 @@ type CallbackFunction = dyn Fn(&CStr) -> (CallbackResult, Option); pub struct SoftwareKeyboard { state: Box, callback: Option>, - callback_data: MessageCallbackData, error_message: Option, initial_text: Option, } @@ -220,15 +219,6 @@ struct MessageCallbackData { swkbd_shared_mem_ptr: *mut libc::c_void, } -impl MessageCallbackData { - fn new() -> Self { - Self { - extra: std::ptr::null_mut(), - swkbd_shared_mem_ptr: std::ptr::null_mut(), - } - } -} - impl SoftwareKeyboard { /// Initialize a new configuration for the Software Keyboard applet depending on how many "exit" buttons are available to the user (1, 2 or 3). /// @@ -257,7 +247,6 @@ impl SoftwareKeyboard { callback: None, error_message: None, initial_text: None, - callback_data: MessageCallbackData::new(), } } } @@ -788,13 +777,15 @@ impl SoftwareKeyboard { unsafe { swkbd.__bindgen_anon_1.reserved.fill(0); - if extra.callback.is_some() { - self.callback_data.extra = std::ptr::addr_of_mut!(extra); - self.callback_data.swkbd_shared_mem_ptr = swkbd_shared_mem_ptr; + let mut callback_data = MessageCallbackData { + extra: std::ptr::addr_of_mut!(extra), + swkbd_shared_mem_ptr, + }; + if extra.callback.is_some() { aptSetMessageCallback( Some(Self::swkbd_message_callback), - std::ptr::addr_of_mut!(self.callback_data).cast(), + std::ptr::addr_of_mut!(callback_data).cast(), ); } From 0c55e1ef9b531584c2ea38f3e926bf1920bba8c8 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 17 Feb 2024 20:26:09 +0100 Subject: [PATCH 30/74] fix chainloader --- ctru-rs/src/services/apt.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index fa26470..6bbe075 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -146,8 +146,9 @@ impl<'a> Chainloader<'a> { /// Checks if the chainloader is set #[doc(alias = "aptIsChainload")] - pub fn is_set(&mut self) { - //unsafe { ctru_sys::aptIsChainload() } + pub fn is_set(&self) -> bool { + // static funtion not exported + unsafe { (ctru_sys::envGetSystemRunFlags() & ctru_sys::RUNFLAG_APTCHAINLOAD) != 0 } } /// Clears the chainloader state. From dc2959396c191696ee3d79f9793799d20e9fc1c1 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 19 Feb 2024 14:41:01 -0700 Subject: [PATCH 31/74] 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 f780bcedbb5593849002929a641ca9c8e70790c6 Mon Sep 17 00:00:00 2001 From: Lena Date: Mon, 19 Feb 2024 23:37:27 +0100 Subject: [PATCH 32/74] fix review issues --- ctru-rs/src/services/apt.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index 6bbe075..a391b47 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -120,7 +120,7 @@ impl Apt { } /// Immediately jumps to the home menu. - #[doc(alias = "aptIsHomeAllowed")] + #[doc(alias = "aptJumpToHomeMenu")] pub fn jump_to_home_menu(&mut self) { unsafe { ctru_sys::aptJumpToHomeMenu() } } @@ -159,19 +159,19 @@ impl<'a> Chainloader<'a> { /// Configures the chainloader to launch a specific application. #[doc(alias = "aptSetChainloader")] - pub fn set_chainloader(&mut self, title: &super::am::Title<'_>) { + pub fn set(&mut self, title: &super::am::Title<'_>) { unsafe { ctru_sys::aptSetChainloader(title.id(), title.media_type() as u8) } } /// Configures the chainloader to launch the previous application. #[doc(alias = "aptSetChainloaderToCaller")] - pub fn set_chainloader_to_caller(&mut self) { + pub fn set_to_caller(&mut self) { unsafe { ctru_sys::aptSetChainloaderToCaller() } } /// Configures the chainloader to relaunch the current application (i.e. soft-reset) #[doc(alias = "aptSetChainloaderToSelf")] - pub fn set_chainloader_to_self(&mut self) { + pub fn set_to_self(&mut self) { unsafe { ctru_sys::aptSetChainloaderToSelf() } } } From 0576e34c3fffbbf9bae342e9679100fa32d9168a Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 19 Feb 2024 16:11:36 -0700 Subject: [PATCH 33/74] Remove redudant imports --- ctru-rs/src/applets/swkbd.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index a1c5f7e..193e20d 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -5,14 +5,13 @@ use crate::services::{apt::Apt, gfx::Gfx}; use ctru_sys::{ - self, aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle, + aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle, svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdExtra, SwkbdLearningData, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, NS_APPID, }; use bitflags::bitflags; -use libc; use std::ffi::{CStr, CString}; use std::fmt::Display; From cb67d92512492f3615ca58f8835d175edcd65250 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 19 Feb 2024 16:16:01 -0700 Subject: [PATCH 34/74] SoftwareKeyboard::get_string -> SoftwareKeyboard::launch --- ctru-rs/examples/file-explorer.rs | 2 +- ctru-rs/examples/software-keyboard.rs | 4 ++-- ctru-rs/src/applets/swkbd.rs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 9647e41..14f3474 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -165,7 +165,7 @@ impl<'a> FileExplorer<'a> { fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) { let mut keyboard = SoftwareKeyboard::default(); - match keyboard.get_string(self.apt, self.gfx) { + match keyboard.launch(self.apt, self.gfx) { Ok((path, Button::Right)) => { // Clicked "OK". action(self, path); diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index 581874a..1b58da5 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -44,9 +44,9 @@ fn main() { // Check if the user request to write some input. if hid.keys_down().contains(KeyPad::A) { - // Raise the software keyboard. You can perform different actions depending on which + // Launch the software keyboard. You can perform different actions depending on which // software button the user pressed. - match keyboard.get_string(&apt, &gfx) { + match keyboard.launch(&apt, &gfx) { Ok((text, Button::Right)) => println!("You entered: {text}"), Ok((_, Button::Left)) => println!("Cancelled"), Ok((_, Button::Middle)) => println!("How did you even press this?"), diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 193e20d..a9e4a51 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -114,7 +114,7 @@ pub enum ButtonConfig { LeftMiddleRight = 3, } -/// Error returned by an unsuccessful [`SoftwareKeyboard::get_string()`]. +/// Error returned by an unsuccessful [`SoftwareKeyboard::launch()`]. #[doc(alias = "SwkbdResult")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(i32)] @@ -266,13 +266,13 @@ impl SoftwareKeyboard { /// use ctru::applets::swkbd::SoftwareKeyboard; /// let mut keyboard = SoftwareKeyboard::default(); /// - /// let (text, button) = keyboard.get_string(&apt, &gfx)?; + /// let (text, button) = keyboard.launch(&apt, &gfx)?; /// # /// # Ok(()) /// # } /// ``` #[doc(alias = "swkbdInputText")] - pub fn get_string(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> { + pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> { let mut output = String::new(); unsafe { @@ -608,7 +608,7 @@ impl SoftwareKeyboard { /// /// Keyboard input is converted from UTF-16 to UTF-8 before being handed to Rust, /// so this code point limit does not necessarily equal the max number of UTF-8 code points - /// receivable by [`SoftwareKeyboard::get_string()`]. + /// receivable by [`SoftwareKeyboard::launch()`]. /// /// # Example /// From a75ac1ffd23e42870938f1e6dbc8475e83ab79f2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Mon, 19 Feb 2024 21:08:27 -0700 Subject: [PATCH 35/74] 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 9c33e491f236e2664edbe92fb4f1c0130a349763 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 10:38:59 -0700 Subject: [PATCH 36/74] Use next_multiple_of instead of bitshifts --- ctru-rs/src/applets/swkbd.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index a9e4a51..93f69ec 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -647,13 +647,13 @@ impl SoftwareKeyboard { // Calculate shared mem size let mut shared_mem_size = 0; - shared_mem_size += - (std::mem::size_of::() * (swkbd.max_text_len as usize + 1) + 3) & !3; + shared_mem_size += (std::mem::size_of::() * (swkbd.max_text_len as usize + 1)) + .next_multiple_of(std::mem::size_of::()); let dict_off = shared_mem_size; - shared_mem_size += - (std::mem::size_of::() * swkbd.dict_word_count as usize + 3) & !3; + shared_mem_size += (std::mem::size_of::() * swkbd.dict_word_count as usize) + .next_multiple_of(std::mem::size_of::()); let status_off = shared_mem_size; @@ -681,7 +681,7 @@ impl SoftwareKeyboard { shared_mem_size += std::mem::size_of::(); } - shared_mem_size = (shared_mem_size + 0xFFF) & !0xFFF; + shared_mem_size = shared_mem_size.next_multiple_of(0x1000); swkbd.shared_memory_size = shared_mem_size; From 6d6f9c0c18b3ad5d46474939f5ea58950a014f34 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 10:57:23 -0700 Subject: [PATCH 37/74] Update CI nightly version --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 358f2f7..f9c2388 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: matrix: toolchain: # Run against a "known good" nightly. Rustc version is 1 day behind the toolchain date - - nightly-2023-06-01 + - nightly-2024-02-18 # Check for breakage on latest nightly - nightly @@ -53,7 +53,7 @@ jobs: strategy: matrix: toolchain: - - nightly-2023-06-01 + - nightly-2024-02-18 - nightly continue-on-error: ${{ matrix.toolchain == 'nightly' }} runs-on: ubuntu-latest From 636c8d70280296937a9ec06ce9820136880847ca Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 11:03:40 -0700 Subject: [PATCH 38/74] Update MSRV to 1.73 --- ctru-rs/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index 14084fc..ddd15db 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -10,7 +10,7 @@ categories = ["os", "api-bindings", "hardware-support"] exclude = ["examples"] license = "Zlib" edition = "2021" -rust-version = "1.70" +rust-version = "1.73" [lib] crate-type = ["rlib"] From a18dfc49f7811d6ff5b136c4039e8cd2dcf27fc8 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 21 Feb 2024 20:46:24 -0700 Subject: [PATCH 39/74] Require unsafe blocks in unsafe fns --- ctru-rs/src/applets/swkbd.rs | 1 - ctru-rs/src/console.rs | 1 - ctru-rs/src/lib.rs | 1 + ctru-rs/src/linear.rs | 4 +++- ctru-rs/src/services/ir_user.rs | 8 +++++--- ctru-rs/src/services/ndsp/mod.rs | 1 - ctru-rs/src/services/svc.rs | 23 ++++++++++++++++------- 7 files changed, 25 insertions(+), 14 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 93f69ec..cf9aa5d 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -847,7 +847,6 @@ impl SoftwareKeyboard { // A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`. // This function sets up and then calls the callback set by `swkbdSetFilterCallback` - #[deny(unsafe_op_in_unsafe_fn)] unsafe extern "C" fn swkbd_message_callback( user: *mut libc::c_void, sender: NS_APPID, diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index bee9dc5..02702cc 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -6,7 +6,6 @@ //! Have a look at [`Soc::redirect_to_3dslink()`](crate::services::soc::Soc::redirect_to_3dslink) for a better alternative when debugging applications. use std::cell::{RefMut, UnsafeCell}; -use std::default::Default; use ctru_sys::{consoleClear, consoleInit, consoleSelect, consoleSetWindow, PrintConsole}; diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 2874839..eafd530 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -18,6 +18,7 @@ #![crate_type = "rlib"] #![crate_name = "ctru"] #![warn(missing_docs)] +#![deny(unsafe_op_in_unsafe_fn)] #![feature(custom_test_frameworks)] #![feature(try_trait_v2)] #![feature(allocator_api)] diff --git a/ctru-rs/src/linear.rs b/ctru-rs/src/linear.rs index 927d556..548f0ad 100644 --- a/ctru-rs/src/linear.rs +++ b/ctru-rs/src/linear.rs @@ -42,6 +42,8 @@ unsafe impl Allocator for LinearAllocator { #[doc(alias = "linearFree")] unsafe fn deallocate(&self, ptr: NonNull, _layout: Layout) { - ctru_sys::linearFree(ptr.as_ptr().cast()); + unsafe { + ctru_sys::linearFree(ptr.as_ptr().cast()); + } } } diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 672fe20..0a03f4d 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -347,9 +347,11 @@ impl IrUser { let mut shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem = shared_mem_guard.as_mut().unwrap(); - shared_mem - .service_handle - .send_service_request(request, expected_response_len) + unsafe { + shared_mem + .service_handle + .send_service_request(request, expected_response_len) + } } } diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs index 3e96184..beb3772 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -21,7 +21,6 @@ use crate::error::ResultCode; use crate::services::ServiceReference; use std::cell::{RefCell, RefMut}; -use std::default::Default; use std::error; use std::fmt; use std::sync::Mutex; diff --git a/ctru-rs/src/services/svc.rs b/ctru-rs/src/services/svc.rs index 11530f0..9b036a3 100644 --- a/ctru-rs/src/services/svc.rs +++ b/ctru-rs/src/services/svc.rs @@ -50,19 +50,28 @@ impl HandleExt for Handle { ) -> crate::Result> { // Copy over the request let cmd_buffer_ptr = unsafe { ctru_sys::getThreadCommandBuffer() }; - std::ptr::copy_nonoverlapping(request.as_ptr(), cmd_buffer_ptr, request.len()); - // Send the request - ResultCode(ctru_sys::svcSendSyncRequest(self))?; + unsafe { + std::ptr::copy_nonoverlapping(request.as_ptr(), cmd_buffer_ptr, request.len()); + + // Send the request + ResultCode(ctru_sys::svcSendSyncRequest(self))?; - // Handle the result returned by the service - let result = unsafe { std::ptr::read(cmd_buffer_ptr.add(1)) }; - ResultCode(result as ctru_sys::Result)?; + // Handle the result returned by the service + let result = std::ptr::read(cmd_buffer_ptr.add(1)); + ResultCode(result as ctru_sys::Result)?; + } // Copy back the response request.clear(); request.resize(expected_response_len, 0); - std::ptr::copy_nonoverlapping(cmd_buffer_ptr, request.as_mut_ptr(), expected_response_len); + unsafe { + std::ptr::copy_nonoverlapping( + cmd_buffer_ptr, + request.as_mut_ptr(), + expected_response_len, + ); + } Ok(request) } From e6dddff3c369451d1d6b234034ff6e7c6f04c50d Mon Sep 17 00:00:00 2001 From: Lena Date: Fri, 23 Feb 2024 01:34:58 +0100 Subject: [PATCH 40/74] add "see also" --- ctru-rs/src/services/apt.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index a391b47..e97f55f 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -158,6 +158,8 @@ impl<'a> Chainloader<'a> { } /// Configures the chainloader to launch a specific application. + /// + /// See also [super::am::Title] #[doc(alias = "aptSetChainloader")] pub fn set(&mut self, title: &super::am::Title<'_>) { unsafe { ctru_sys::aptSetChainloader(title.id(), title.media_type() as u8) } From dea122ad162e4e5f932516ac20926b9d44c5b643 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 15:36:25 -0700 Subject: [PATCH 41/74] Improve filter callback API --- ctru-rs/examples/software-keyboard.rs | 7 +- ctru-rs/src/applets/swkbd.rs | 114 +++++++++++++------------- 2 files changed, 60 insertions(+), 61 deletions(-) diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index 1b58da5..b0b79f8 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -5,8 +5,6 @@ use ctru::applets::swkbd::{Button, CallbackResult, SoftwareKeyboard}; use ctru::prelude::*; -use std::ffi::CString; - fn main() { let apt = Apt::new().unwrap(); let mut hid = Hid::new().unwrap(); @@ -22,11 +20,10 @@ fn main() { // Using this callback it's possible to integrate the applet // with custom error messages when the input is incorrect. keyboard.set_filter_callback(Some(Box::new(|str| { - // The string is guaranteed to contain valid Unicode text, so we can safely unwrap and use it as a normal `&str`. - if str.to_str().unwrap().contains("boo") { + if str.contains("boo") { return ( CallbackResult::Retry, - Some(CString::new("Ah, you scared me!").unwrap()), + Some(String::from("Ah, you scared me!")), ); } diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index cf9aa5d..d3c1154 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -18,14 +18,14 @@ use std::fmt::Display; use std::iter::once; use std::str; -type CallbackFunction = dyn Fn(&CStr) -> (CallbackResult, Option); +type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option); /// Configuration structure to setup the Software Keyboard applet. #[doc(alias = "SwkbdState")] pub struct SoftwareKeyboard { state: Box, callback: Option>, - error_message: Option, + callback_message: Option, initial_text: Option, } @@ -244,7 +244,7 @@ impl SoftwareKeyboard { Self { state, callback: None, - error_message: None, + callback_message: None, initial_text: None, } } @@ -275,21 +275,23 @@ impl SoftwareKeyboard { pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> { let mut output = String::new(); - unsafe { - // The filter callback gets reset every time the SoftwareKeyboard is used. - ctru_sys::swkbdSetFilterCallback( - self.state.as_mut(), - Some(Self::internal_callback), - (self as *mut Self).cast(), - ); - - match self.swkbd_input_text(&mut output) { - ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), - ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), - ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), - ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)), - _ => unreachable!(), + // Set the filter callback if the user has supplied a closure to run. + if self.callback.is_some() { + unsafe { + ctru_sys::swkbdSetFilterCallback( + self.state.as_mut(), + Some(Self::internal_callback), + (self as *mut Self).cast(), + ) } + }; + + match self.swkbd_input_text(&mut output) { + ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), + ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), + ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), + ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)), + _ => unreachable!(), } } @@ -361,10 +363,10 @@ impl SoftwareKeyboard { /// let mut keyboard = SoftwareKeyboard::default(); /// /// keyboard.set_filter_callback(Some(Box::new(|str| { - /// if str.to_str().unwrap().contains("boo") { + /// if str.contains("boo") { /// return ( /// CallbackResult::Retry, - /// Some(CString::new("Ah, you scared me!").unwrap()), + /// Some(String::from("Ah, you scared me!")), /// ); /// } /// @@ -377,41 +379,45 @@ impl SoftwareKeyboard { } /// Internal function called by the filter callback. - extern "C" fn internal_callback( + #[deny(unsafe_op_in_unsafe_fn)] + unsafe extern "C" fn internal_callback( user: *mut libc::c_void, pp_message: *mut *const libc::c_char, text: *const libc::c_char, - _text_size: libc::size_t, + text_size: libc::size_t, ) -> ctru_sys::SwkbdCallbackResult { - let this: *mut SoftwareKeyboard = user.cast(); + let this = unsafe { &mut *user.cast::() }; - unsafe { - // Reset any leftover error message. - (*this).error_message = None; + // Reset any leftover callback message. + this.callback_message = None; - let text = CStr::from_ptr(text); + let text = + unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(text, text_size)) }; - let result = { - // Run the callback if still available. - if let Some(callback) = &mut (*this).callback { - let (res, cstr) = callback(text); + let result = { + // Run the callback if still available. + if let Some(callback) = this.callback.as_deref() { + let (res, msg) = callback(text); - // Due to how `libctru` operates, the user is expected to keep the error message alive until - // the end of the Software Keyboard prompt. We ensure that happens by saving it within the configuration. - (*this).error_message = cstr; + // Due to how `libctru` operates, the user is expected to keep the callback message alive until + // the end of the Software Keyboard prompt. We ensure that happens by saving it within the configuration. + this.callback_message = msg; - if let Some(newstr) = &(*this).error_message { - *pp_message = newstr.as_ptr(); + if let Some(msg) = this.callback_message.as_ref() { + unsafe { + // Since we control the other side of the callback and we've stashed the callback message in a stable location, + // we can do some sneaky pointer shenanigans to pass the message struct directly instead of dealing with c-style strings + *pp_message = std::ptr::addr_of!(*msg).cast(); } - - res - } else { - CallbackResult::Ok } - }; - result.into() - } + res + } else { + CallbackResult::Ok + } + }; + + result.into() } /// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled. @@ -866,19 +872,19 @@ impl SoftwareKeyboard { let text16 = unsafe { widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(), - swkbd.text_length as usize + 1, + swkbd.text_length as _, )) }; let text8 = text16.to_string(); - let mut retmsg = std::ptr::null(); + let mut retmsg: *const String = std::ptr::null(); - if let Some(cb) = extra.callback { + if let Some(callback) = extra.callback { swkbd.callback_result = unsafe { - cb( + callback( extra.callback_user, - &mut retmsg, + std::ptr::addr_of_mut!(retmsg).cast(), text8.as_ptr(), text8.len(), ) @@ -886,22 +892,18 @@ impl SoftwareKeyboard { }; let retmsg = if !retmsg.is_null() { - unsafe { - let len = libc::strlen(retmsg) + 1; - std::str::from_utf8_unchecked(std::slice::from_raw_parts(retmsg, len)) - } + unsafe { &**retmsg } } else { - "\0" + "" }; - let callback_msg = &mut swkbd.callback_msg; - for (idx, code_unit) in retmsg .encode_utf16() - .take(callback_msg.len() - 1) + .chain(once(0)) + .take(swkbd.callback_msg.len() - 1) .enumerate() { - callback_msg[idx] = code_unit; + swkbd.callback_msg[idx] = code_unit; } let _ = unsafe { From 032acb1524ddb55d377323237dbca183fb346bc0 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 19:36:21 -0700 Subject: [PATCH 42/74] Call the filter_callback directly in swkbd_message_callback --- ctru-rs/src/applets/swkbd.rs | 115 ++++++++--------------------------- 1 file changed, 24 insertions(+), 91 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index d3c1154..a674b24 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -6,9 +6,8 @@ use crate::services::{apt::Apt, gfx::Gfx}; use ctru_sys::{ aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle, - svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdExtra, - SwkbdLearningData, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, - NS_APPID, + svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdLearningData, + SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, NS_APPID, }; use bitflags::bitflags; @@ -25,7 +24,6 @@ type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option); pub struct SoftwareKeyboard { state: Box, callback: Option>, - callback_message: Option, initial_text: Option, } @@ -212,9 +210,8 @@ bitflags! { } // Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard. -// We only need this because libctru doesn't keep a pointer to the shared memory block in `SwkbdExtra` for whatever reason struct MessageCallbackData { - extra: *mut SwkbdExtra, + callback: *const Box, swkbd_shared_mem_ptr: *mut libc::c_void, } @@ -244,7 +241,6 @@ impl SoftwareKeyboard { Self { state, callback: None, - callback_message: None, initial_text: None, } } @@ -275,17 +271,6 @@ impl SoftwareKeyboard { pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> { let mut output = String::new(); - // Set the filter callback if the user has supplied a closure to run. - if self.callback.is_some() { - unsafe { - ctru_sys::swkbdSetFilterCallback( - self.state.as_mut(), - Some(Self::internal_callback), - (self as *mut Self).cast(), - ) - } - }; - match self.swkbd_input_text(&mut output) { ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), @@ -378,48 +363,6 @@ impl SoftwareKeyboard { self.callback = callback; } - /// Internal function called by the filter callback. - #[deny(unsafe_op_in_unsafe_fn)] - unsafe extern "C" fn internal_callback( - user: *mut libc::c_void, - pp_message: *mut *const libc::c_char, - text: *const libc::c_char, - text_size: libc::size_t, - ) -> ctru_sys::SwkbdCallbackResult { - let this = unsafe { &mut *user.cast::() }; - - // Reset any leftover callback message. - this.callback_message = None; - - let text = - unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(text, text_size)) }; - - let result = { - // Run the callback if still available. - if let Some(callback) = this.callback.as_deref() { - let (res, msg) = callback(text); - - // Due to how `libctru` operates, the user is expected to keep the callback message alive until - // the end of the Software Keyboard prompt. We ensure that happens by saving it within the configuration. - this.callback_message = msg; - - if let Some(msg) = this.callback_message.as_ref() { - unsafe { - // Since we control the other side of the callback and we've stashed the callback message in a stable location, - // we can do some sneaky pointer shenanigans to pass the message struct directly instead of dealing with c-style strings - *pp_message = std::ptr::addr_of!(*msg).cast(); - } - } - - res - } else { - CallbackResult::Ok - } - }; - - result.into() - } - /// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled. /// /// # Example @@ -648,7 +591,7 @@ impl SoftwareKeyboard { }; let swkbd = self.state.as_mut(); - let mut extra = unsafe { swkbd.__bindgen_anon_1.extra }; + let extra = unsafe { swkbd.__bindgen_anon_1.extra }; // Calculate shared mem size let mut shared_mem_size = 0; @@ -772,7 +715,7 @@ impl SoftwareKeyboard { }; } - if extra.callback.is_some() { + if self.callback.is_some() { swkbd.filter_flags |= SWKBD_FILTER_CALLBACK; } else { swkbd.filter_flags &= !SWKBD_FILTER_CALLBACK; @@ -783,15 +726,17 @@ impl SoftwareKeyboard { swkbd.__bindgen_anon_1.reserved.fill(0); let mut callback_data = MessageCallbackData { - extra: std::ptr::addr_of_mut!(extra), + callback: std::ptr::null(), swkbd_shared_mem_ptr, }; - if extra.callback.is_some() { + if let Some(callback) = self.callback.as_ref() { + callback_data.callback = std::ptr::addr_of!(*callback); + aptSetMessageCallback( Some(Self::swkbd_message_callback), std::ptr::addr_of_mut!(callback_data).cast(), - ); + ) } aptLaunchLibraryApplet( @@ -801,7 +746,7 @@ impl SoftwareKeyboard { swkbd_shared_mem_handle, ); - if extra.callback.is_some() { + if self.callback.is_some() { aptSetMessageCallback(None, std::ptr::null_mut()); } @@ -852,7 +797,7 @@ impl SoftwareKeyboard { } // A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`. - // This function sets up and then calls the callback set by `swkbdSetFilterCallback` + // This function sets up and then calls the filter callback unsafe extern "C" fn swkbd_message_callback( user: *mut libc::c_void, sender: NS_APPID, @@ -861,7 +806,6 @@ impl SoftwareKeyboard { ) { let data = unsafe { &mut *user.cast::() }; let swkbd = unsafe { &mut *msg.cast::() }; - let extra = unsafe { &mut *data.extra }; if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD || msg_size != std::mem::size_of::() @@ -878,32 +822,21 @@ impl SoftwareKeyboard { let text8 = text16.to_string(); - let mut retmsg: *const String = std::ptr::null(); + let filter_callback = unsafe { &**data.callback }; - if let Some(callback) = extra.callback { - swkbd.callback_result = unsafe { - callback( - extra.callback_user, - std::ptr::addr_of_mut!(retmsg).cast(), - text8.as_ptr(), - text8.len(), - ) - } as _ - }; + let (result, retmsg) = filter_callback(&text8); - let retmsg = if !retmsg.is_null() { - unsafe { &**retmsg } - } else { - "" - }; + swkbd.callback_result = result as _; - for (idx, code_unit) in retmsg - .encode_utf16() - .chain(once(0)) - .take(swkbd.callback_msg.len() - 1) - .enumerate() - { - swkbd.callback_msg[idx] = code_unit; + if let Some(msg) = retmsg.as_deref() { + for (idx, code_unit) in msg + .encode_utf16() + .chain(once(0)) + .take(swkbd.callback_msg.len() - 1) + .enumerate() + { + swkbd.callback_msg[idx] = code_unit; + } } let _ = unsafe { From 3d59bdd2b71b8ec52cdbd670734783b3b01b7a0e Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 21:49:10 -0700 Subject: [PATCH 43/74] Return Cow from filter callback --- ctru-rs/examples/software-keyboard.rs | 5 +---- ctru-rs/src/applets/swkbd.rs | 9 +++------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index b0b79f8..144b587 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -21,10 +21,7 @@ fn main() { // with custom error messages when the input is incorrect. keyboard.set_filter_callback(Some(Box::new(|str| { if str.contains("boo") { - return ( - CallbackResult::Retry, - Some(String::from("Ah, you scared me!")), - ); + return (CallbackResult::Retry, Some("Ah, you scared me!".into())); } (CallbackResult::Ok, None) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index a674b24..f5d7c68 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -12,12 +12,13 @@ use ctru_sys::{ use bitflags::bitflags; +use std::borrow::Cow; use std::ffi::{CStr, CString}; use std::fmt::Display; use std::iter::once; use std::str; -type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option); +type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option>); /// Configuration structure to setup the Software Keyboard applet. #[doc(alias = "SwkbdState")] @@ -342,17 +343,13 @@ impl SoftwareKeyboard { /// # fn main() { /// # /// use std::borrow::Cow; - /// use std::ffi::CString; /// use ctru::applets::swkbd::{SoftwareKeyboard, CallbackResult}; /// /// let mut keyboard = SoftwareKeyboard::default(); /// /// keyboard.set_filter_callback(Some(Box::new(|str| { /// if str.contains("boo") { - /// return ( - /// CallbackResult::Retry, - /// Some(String::from("Ah, you scared me!")), - /// ); + /// return (CallbackResult::Retry, Some("Ah, you scared me!".into())); /// } /// /// (CallbackResult::Ok, None) From 2f7a04b08d07e8ba4185916d4f3df286b626a27f Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 22:07:34 -0700 Subject: [PATCH 44/74] Add comment clarifying callback pointer safety --- ctru-rs/src/applets/swkbd.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index f5d7c68..db2f34e 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -727,8 +727,10 @@ impl SoftwareKeyboard { swkbd_shared_mem_ptr, }; - if let Some(callback) = self.callback.as_ref() { - callback_data.callback = std::ptr::addr_of!(*callback); + // We need to pass a thin pointer to the boxed closure over FFI. Since we know that the callback will finish before + // `self` is allowed to be moved again, we can safely use a pointer to the local value contained in `self.callback` + if let Some(ref_to_boxed_closure) = self.callback.as_ref() { + callback_data.callback = ref_to_boxed_closure as *const _; aptSetMessageCallback( Some(Self::swkbd_message_callback), From becdc05abd09270a08081233fc771ce787b72e48 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 22:36:12 -0700 Subject: [PATCH 45/74] Rename variables for clarity --- ctru-rs/src/applets/swkbd.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index db2f34e..922c050 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -24,7 +24,7 @@ type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option>); #[doc(alias = "SwkbdState")] pub struct SoftwareKeyboard { state: Box, - callback: Option>, + filter_callback: Option>, initial_text: Option, } @@ -212,7 +212,7 @@ bitflags! { // Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard. struct MessageCallbackData { - callback: *const Box, + filter_callback: *const Box, swkbd_shared_mem_ptr: *mut libc::c_void, } @@ -241,7 +241,7 @@ impl SoftwareKeyboard { ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1); Self { state, - callback: None, + filter_callback: None, initial_text: None, } } @@ -357,7 +357,7 @@ impl SoftwareKeyboard { /// # /// # } pub fn set_filter_callback(&mut self, callback: Option>) { - self.callback = callback; + self.filter_callback = callback; } /// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled. @@ -712,7 +712,7 @@ impl SoftwareKeyboard { }; } - if self.callback.is_some() { + if self.filter_callback.is_some() { swkbd.filter_flags |= SWKBD_FILTER_CALLBACK; } else { swkbd.filter_flags &= !SWKBD_FILTER_CALLBACK; @@ -722,19 +722,19 @@ impl SoftwareKeyboard { unsafe { swkbd.__bindgen_anon_1.reserved.fill(0); - let mut callback_data = MessageCallbackData { - callback: std::ptr::null(), + let mut message_callback_data = MessageCallbackData { + filter_callback: std::ptr::null(), swkbd_shared_mem_ptr, }; - // We need to pass a thin pointer to the boxed closure over FFI. Since we know that the callback will finish before - // `self` is allowed to be moved again, we can safely use a pointer to the local value contained in `self.callback` - if let Some(ref_to_boxed_closure) = self.callback.as_ref() { - callback_data.callback = ref_to_boxed_closure as *const _; + // We need to pass a thin pointer to the boxed closure over FFI. Since we know that the message callback will finish before + // `self` is allowed to be moved again, we can safely use a pointer to the local value contained in `self.filter_callback` + if let Some(ref_to_boxed_closure) = self.filter_callback.as_ref() { + message_callback_data.filter_callback = ref_to_boxed_closure as *const _; aptSetMessageCallback( Some(Self::swkbd_message_callback), - std::ptr::addr_of_mut!(callback_data).cast(), + std::ptr::addr_of_mut!(message_callback_data).cast(), ) } @@ -745,7 +745,7 @@ impl SoftwareKeyboard { swkbd_shared_mem_handle, ); - if self.callback.is_some() { + if self.filter_callback.is_some() { aptSetMessageCallback(None, std::ptr::null_mut()); } @@ -821,7 +821,7 @@ impl SoftwareKeyboard { let text8 = text16.to_string(); - let filter_callback = unsafe { &**data.callback }; + let filter_callback = unsafe { &**data.filter_callback }; let (result, retmsg) = filter_callback(&text8); From 1ef2164822f54cc8d43751df2a846b7c848f8fff Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 21 Feb 2024 13:24:33 -0700 Subject: [PATCH 46/74] Minor refactor for additonal clarity --- ctru-rs/src/applets/swkbd.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 922c050..667a459 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -18,7 +18,7 @@ use std::fmt::Display; use std::iter::once; use std::str; -type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option>); +type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option>); /// Configuration structure to setup the Software Keyboard applet. #[doc(alias = "SwkbdState")] @@ -722,16 +722,15 @@ impl SoftwareKeyboard { unsafe { swkbd.__bindgen_anon_1.reserved.fill(0); + // We need to pass a thin pointer to the boxed closure over FFI. Since we know that the message callback will finish before + // `self` is allowed to be moved again, we can safely use a pointer to the local value contained in `self.filter_callback` + // The cast here is also sound since the pointer will only be read from if `self.filter_callback.is_some()` returns true. let mut message_callback_data = MessageCallbackData { - filter_callback: std::ptr::null(), + filter_callback: std::ptr::addr_of!(self.filter_callback).cast(), swkbd_shared_mem_ptr, }; - // We need to pass a thin pointer to the boxed closure over FFI. Since we know that the message callback will finish before - // `self` is allowed to be moved again, we can safely use a pointer to the local value contained in `self.filter_callback` - if let Some(ref_to_boxed_closure) = self.filter_callback.as_ref() { - message_callback_data.filter_callback = ref_to_boxed_closure as *const _; - + if self.filter_callback.is_some() { aptSetMessageCallback( Some(Self::swkbd_message_callback), std::ptr::addr_of_mut!(message_callback_data).cast(), From d4ad0ea8b0fcfe6007940ebd2977cefb471cb6c7 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Wed, 21 Feb 2024 14:30:43 -0700 Subject: [PATCH 47/74] Move swkbd_message_callback sanity checks to the top of the fn It's kinda pointless to have those checks *after* attempting to deref the data rather than before. --- ctru-rs/src/applets/swkbd.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 667a459..69b6f98 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -211,6 +211,7 @@ bitflags! { } // Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard. +#[derive(Copy, Clone)] struct MessageCallbackData { filter_callback: *const Box, swkbd_shared_mem_ptr: *mut libc::c_void, @@ -802,15 +803,15 @@ impl SoftwareKeyboard { msg: *mut libc::c_void, msg_size: libc::size_t, ) { - let data = unsafe { &mut *user.cast::() }; - let swkbd = unsafe { &mut *msg.cast::() }; - if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD || msg_size != std::mem::size_of::() { return; } + let swkbd = unsafe { &mut *msg.cast::() }; + let data = unsafe { *user.cast::() }; + let text16 = unsafe { widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(), From 7a84ce3d70a2dade0f29ce8b691722152c2ffa23 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 22 Feb 2024 19:17:14 -0700 Subject: [PATCH 48/74] Use `move` closures in filter callback examples Trying to capture values by ref won't work for anything that's not `'static` --- ctru-rs/examples/software-keyboard.rs | 2 +- ctru-rs/src/applets/swkbd.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index 144b587..40635de 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -19,7 +19,7 @@ fn main() { // Custom filter callback to handle the given input. // Using this callback it's possible to integrate the applet // with custom error messages when the input is incorrect. - keyboard.set_filter_callback(Some(Box::new(|str| { + keyboard.set_filter_callback(Some(Box::new(move |str| { if str.contains("boo") { return (CallbackResult::Retry, Some("Ah, you scared me!".into())); } diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 69b6f98..c5f386c 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -348,7 +348,7 @@ impl SoftwareKeyboard { /// /// let mut keyboard = SoftwareKeyboard::default(); /// - /// keyboard.set_filter_callback(Some(Box::new(|str| { + /// keyboard.set_filter_callback(Some(Box::new(move |str| { /// if str.contains("boo") { /// return (CallbackResult::Retry, Some("Ah, you scared me!".into())); /// } From 80853b2238265d70dbf23a9fd4b37608089300fe Mon Sep 17 00:00:00 2001 From: Lena Date: Fri, 23 Feb 2024 20:53:55 +0100 Subject: [PATCH 49/74] Apply apt review suggestion Co-authored-by: Meziu <55318903+Meziu@users.noreply.github.com> --- ctru-rs/src/services/apt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index e97f55f..e1f1f57 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -159,7 +159,7 @@ impl<'a> Chainloader<'a> { /// Configures the chainloader to launch a specific application. /// - /// See also [super::am::Title] + /// See also [`Title`](crate::services::am::Title] #[doc(alias = "aptSetChainloader")] pub fn set(&mut self, title: &super::am::Title<'_>) { unsafe { ctru_sys::aptSetChainloader(title.id(), title.media_type() as u8) } From cd309813483d4fffdc2061487b37f9e3f50b0163 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Fri, 23 Feb 2024 15:41:52 -0700 Subject: [PATCH 50/74] 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 51/74] 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 52/74] 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 53/74] 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 54/74] 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 55/74] 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 56/74] 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 432fca3c162720bc50486be361d2e07d96c89fb8 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 25 Feb 2024 19:51:07 -0500 Subject: [PATCH 57/74] Enable layout tests for generated bindings --- .github/workflows/ci.yml | 5 +++++ ctru-sys/build.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9c2388..2db5b64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,11 +43,16 @@ jobs: - name: Check formatting run: cargo fmt --all --verbose -- --check + # We always run the next two steps, so that even if formatting fails we + # still get compilation errors for the same run (mainly for matchers). + - name: Cargo check ctru-sys (without tests) run: cargo 3ds clippy --package ctru-sys --color=always --verbose + if: success() || failure() - name: Cargo check ctru-rs (including tests) run: cargo 3ds clippy --package ctru-rs --color=always --verbose --all-targets + if: success() || failure() test: strategy: diff --git a/ctru-sys/build.rs b/ctru-sys/build.rs index 5e71ae0..461149f 100644 --- a/ctru-sys/build.rs +++ b/ctru-sys/build.rs @@ -73,7 +73,7 @@ fn main() { .use_core() .trust_clang_mangling(false) .must_use_type("Result") - .layout_tests(false) + .layout_tests(true) .ctypes_prefix("::libc") .prepend_enum_name(false) .blocklist_type("u(8|16|32|64)") From d630c25926b194563807f458afb3344b859771f2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 25 Feb 2024 17:53:12 -0700 Subject: [PATCH 58/74] 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 59/74] 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 60/74] 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 61/74] 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 62/74] 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 63/74] 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 64/74] 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 65/74] 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; From c1f0bbd9c6060cca26f9d71c701d5f9be5f8eb7c Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 27 Feb 2024 21:54:46 -0700 Subject: [PATCH 66/74] Properly handle nul termination in swkbd_message_callback --- ctru-rs/src/applets/swkbd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index c5f386c..ce2ab69 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -830,8 +830,8 @@ impl SoftwareKeyboard { if let Some(msg) = retmsg.as_deref() { for (idx, code_unit) in msg .encode_utf16() - .chain(once(0)) .take(swkbd.callback_msg.len() - 1) + .chain(once(0)) .enumerate() { swkbd.callback_msg[idx] = code_unit; From 83ebecbb1f2a6cb4c930bc4d8a96eba474d79daa Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 16:43:07 -0700 Subject: [PATCH 67/74] Use Rust strings in set_initial_text --- ctru-rs/src/applets/swkbd.rs | 45 ++++++++++++++---------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index ce2ab69..fd00b31 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -13,7 +13,6 @@ use ctru_sys::{ use bitflags::bitflags; use std::borrow::Cow; -use std::ffi::{CStr, CString}; use std::fmt::Display; use std::iter::once; use std::str; @@ -25,7 +24,7 @@ type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option, filter_callback: Option>, - initial_text: Option, + initial_text: Option>, } /// Configuration structure to setup the Parental Lock applet. @@ -391,33 +390,25 @@ impl SoftwareKeyboard { /// /// The initial text is the text already written when you open the software keyboard. /// + /// # Notes + /// + /// Passing [`None`] will clear the initial text. + /// /// # Example /// /// ``` - /// # let _runner = test_runner::GdbRunner::default(); + /// # let _runner = test_runner::GdbRunner::default()>; /// # fn main() { /// # /// use ctru::applets::swkbd::SoftwareKeyboard; /// let mut keyboard = SoftwareKeyboard::default(); /// - /// keyboard.set_initial_text(Some("Write here what you like!")); + /// keyboard.set_initial_text(Some(String::from("Write here what you like!"))); /// # /// # } #[doc(alias = "swkbdSetInitialText")] - pub fn set_initial_text(&mut self, text: Option<&str>) { - if let Some(text) = text { - let initial_text = CString::new(text).unwrap(); - - unsafe { - ctru_sys::swkbdSetInitialText(self.state.as_mut(), initial_text.as_ptr()); - } - - self.initial_text = Some(initial_text); - } else { - unsafe { ctru_sys::swkbdSetInitialText(self.state.as_mut(), std::ptr::null()) }; - - self.initial_text = None; - } + pub fn set_initial_text(&mut self, text: Option>) { + self.initial_text = text; } /// Set the hint text for this software keyboard. @@ -661,19 +652,17 @@ impl SoftwareKeyboard { } // Copy stuff to shared mem - if !extra.initial_text.is_null() { + if let Some(initial_text) = self.initial_text.as_deref() { swkbd.initial_text_offset = 0; - unsafe { - let utf16_iter = - str::from_utf8_unchecked(CStr::from_ptr(extra.initial_text).to_bytes()) - .encode_utf16() - .take(swkbd.max_text_len as _) - .chain(once(0)); - - let mut initial_text_cursor = swkbd_shared_mem_ptr.cast(); + let mut initial_text_cursor = swkbd_shared_mem_ptr.cast(); - for code_unit in utf16_iter { + for code_unit in initial_text + .encode_utf16() + .take(swkbd.max_text_len as _) + .chain(once(0)) + { + unsafe { *initial_text_cursor = code_unit; initial_text_cursor = initial_text_cursor.add(1); } From 01731cac0eea8c7c7821d9294e56dc9056eca981 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 7 Mar 2024 01:45:50 -0700 Subject: [PATCH 68/74] Set hint text directly instead of using libctru fn --- ctru-rs/src/applets/swkbd.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index fd00b31..5ebeb93 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -397,13 +397,13 @@ impl SoftwareKeyboard { /// # Example /// /// ``` - /// # let _runner = test_runner::GdbRunner::default()>; + /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # /// use ctru::applets::swkbd::SoftwareKeyboard; /// let mut keyboard = SoftwareKeyboard::default(); /// - /// keyboard.set_initial_text(Some(String::from("Write here what you like!"))); + /// keyboard.set_initial_text(Some("Write here what you like!".into())); /// # /// # } #[doc(alias = "swkbdSetInitialText")] @@ -415,6 +415,13 @@ impl SoftwareKeyboard { /// /// The hint text is the text shown in gray before any text gets written in the input box. /// + /// # Notes + /// + /// Passing [`None`] will clear the hint text. + /// + /// The hint text will be converted to UTF-16 when passed to the software keyboard, and the text will be truncated + /// if the length exceeds 64 code units after conversion. + /// /// # Example /// /// ``` @@ -424,14 +431,22 @@ impl SoftwareKeyboard { /// use ctru::applets::swkbd::SoftwareKeyboard; /// let mut keyboard = SoftwareKeyboard::default(); /// - /// keyboard.set_hint_text("Write here what you like!"); + /// keyboard.set_hint_text(Some("Write here what you like!")); /// # /// # } #[doc(alias = "swkbdSetHintText")] - pub fn set_hint_text(&mut self, text: &str) { - unsafe { - let nul_terminated: String = text.chars().chain(once('\0')).collect(); - ctru_sys::swkbdSetHintText(self.state.as_mut(), nul_terminated.as_ptr()); + pub fn set_hint_text(&mut self, text: Option<&str>) { + if let Some(text) = text { + for (idx, code_point) in text + .encode_utf16() + .take(self.state.hint_text.len() - 1) + .chain(once(0)) + .enumerate() + { + self.state.hint_text[idx] = code_point; + } + } else { + self.state.hint_text[0] = 0; } } From e5581b46e75829ed055b93da4b5aa4b3b02716c4 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 7 Mar 2024 02:09:28 -0700 Subject: [PATCH 69/74] swkbd_input_text should still be an unsafe fn --- ctru-rs/src/applets/swkbd.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 5ebeb93..80e9f78 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -272,12 +272,14 @@ impl SoftwareKeyboard { pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> { let mut output = String::new(); - match self.swkbd_input_text(&mut output) { - ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), - ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), - ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), - ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)), - _ => unreachable!(), + unsafe { + match self.swkbd_input_text(&mut output) { + ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), + ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), + ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), + ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)), + _ => unreachable!(), + } } } @@ -583,10 +585,13 @@ impl SoftwareKeyboard { self.state.valid_input = ValidInput::FixedLen.into(); } - // A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to - // get text from the software keyboard and put it directly into a `String` without requiring - // an intermediate fixed-size buffer - fn swkbd_input_text(&mut self, output: &mut String) -> SwkbdButton { + /// A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to fix various + /// API nits and get rid of awkward type conversions when interacting with the Software Keyboard. + /// + /// # Safety + /// + /// The [`Apt`] and [`Gfx`] services must be active when this function is called. + unsafe fn swkbd_input_text(&mut self, output: &mut String) -> SwkbdButton { use ctru_sys::{ MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0, From 33a4b1ca0c30479dcb17a1de687835765bcdc25d Mon Sep 17 00:00:00 2001 From: Jhynjhiruu Date: Thu, 14 Mar 2024 17:25:24 +0000 Subject: [PATCH 70/74] Update uds.rs --- ctru-rs/src/services/uds.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/services/uds.rs b/ctru-rs/src/services/uds.rs index 1ec7407..5eb81b8 100644 --- a/ctru-rs/src/services/uds.rs +++ b/ctru-rs/src/services/uds.rs @@ -128,7 +128,7 @@ impl TryFrom for ConnectionType { /// ID for a node on the network. #[doc(alias = "NetworkNodeID")] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum NodeID { /// No node ID set (not connected to a network). None, From efaccdd2e5d9717993e8cf8c2d493b34440f4262 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sun, 10 Mar 2024 18:28:00 -0600 Subject: [PATCH 71/74] swkbd_input_text is no longer an unsafe fn --- ctru-rs/src/applets/swkbd.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 80e9f78..09f37bd 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -269,17 +269,15 @@ impl SoftwareKeyboard { /// # } /// ``` #[doc(alias = "swkbdInputText")] - pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> { + pub fn launch(&mut self, apt: &Apt, gfx: &Gfx) -> Result<(String, Button), Error> { let mut output = String::new(); - unsafe { - match self.swkbd_input_text(&mut output) { - ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), - ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), - ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), - ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)), - _ => unreachable!(), - } + match self.swkbd_input_text(&mut output, apt, gfx) { + ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), + ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), + ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), + ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)), + _ => unreachable!(), } } @@ -585,13 +583,9 @@ impl SoftwareKeyboard { self.state.valid_input = ValidInput::FixedLen.into(); } - /// A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to fix various - /// API nits and get rid of awkward type conversions when interacting with the Software Keyboard. - /// - /// # Safety - /// - /// The [`Apt`] and [`Gfx`] services must be active when this function is called. - unsafe fn swkbd_input_text(&mut self, output: &mut String) -> SwkbdButton { + // A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to fix various + // API nits and get rid of awkward type conversions when interacting with the Software Keyboard. + fn swkbd_input_text(&mut self, output: &mut String, _apt: &Apt, _gfx: &Gfx) -> SwkbdButton { use ctru_sys::{ MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0, From d8cffcff611b28c17d4df81fae04d7e5dc07ca1a Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 21 Mar 2024 02:15:04 -0600 Subject: [PATCH 72/74] Remove allocation in Swkbd::configure_button --- ctru-rs/src/applets/swkbd.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 09f37bd..df3b5e4 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -541,15 +541,18 @@ impl SoftwareKeyboard { /// # } #[doc(alias = "swkbdSetButton")] pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) { - unsafe { - let nul_terminated: String = text.chars().chain(once('\0')).collect(); - ctru_sys::swkbdSetButton( - self.state.as_mut(), - button.into(), - nul_terminated.as_ptr(), - submit, - ); + let button_text = &mut self.state.button_text[button as usize]; + + for (idx, code_unit) in text + .encode_utf16() + .take(ctru_sys::SWKBD_MAX_BUTTON_TEXT_LEN as _) + .chain(once(0)) + .enumerate() + { + button_text[idx] = code_unit; } + + self.state.button_submits_text[button as usize] = submit; } /// Configure the maximum number of UTF-16 code units that can be entered into the software From 476a8120eac7ce950b84accd963d77e6f855a77e Mon Sep 17 00:00:00 2001 From: Fenrir Date: Thu, 21 Mar 2024 02:18:53 -0600 Subject: [PATCH 73/74] Variable name and style fixes --- ctru-rs/src/applets/swkbd.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index df3b5e4..6a42ebd 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -437,13 +437,13 @@ impl SoftwareKeyboard { #[doc(alias = "swkbdSetHintText")] pub fn set_hint_text(&mut self, text: Option<&str>) { if let Some(text) = text { - for (idx, code_point) in text + for (idx, code_unit) in text .encode_utf16() .take(self.state.hint_text.len() - 1) .chain(once(0)) .enumerate() { - self.state.hint_text[idx] = code_point; + self.state.hint_text[idx] = code_unit; } } else { self.state.hint_text[0] = 0; @@ -732,7 +732,7 @@ impl SoftwareKeyboard { // We need to pass a thin pointer to the boxed closure over FFI. Since we know that the message callback will finish before // `self` is allowed to be moved again, we can safely use a pointer to the local value contained in `self.filter_callback` // The cast here is also sound since the pointer will only be read from if `self.filter_callback.is_some()` returns true. - let mut message_callback_data = MessageCallbackData { + let mut data = MessageCallbackData { filter_callback: std::ptr::addr_of!(self.filter_callback).cast(), swkbd_shared_mem_ptr, }; @@ -740,7 +740,7 @@ impl SoftwareKeyboard { if self.filter_callback.is_some() { aptSetMessageCallback( Some(Self::swkbd_message_callback), - std::ptr::addr_of_mut!(message_callback_data).cast(), + std::ptr::addr_of_mut!(data).cast(), ) } From ad8b328387458843682ba766eff7b171ef467658 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 26 Mar 2024 18:12:58 -0600 Subject: [PATCH 74/74] Use array len instead of libctru constant --- ctru-rs/src/applets/swkbd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 6a42ebd..c3b49fc 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -545,7 +545,7 @@ impl SoftwareKeyboard { for (idx, code_unit) in text .encode_utf16() - .take(ctru_sys::SWKBD_MAX_BUTTON_TEXT_LEN as _) + .take(button_text.len() - 1) .chain(once(0)) .enumerate() {