From dea122ad162e4e5f932516ac20926b9d44c5b643 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 20 Feb 2024 15:36:25 -0700 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 c1f0bbd9c6060cca26f9d71c701d5f9be5f8eb7c Mon Sep 17 00:00:00 2001 From: Fenrir Date: Tue, 27 Feb 2024 21:54:46 -0700 Subject: [PATCH 9/9] 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;