Browse Source

Improve filter callback API

pull/163/head
Fenrir 10 months ago
parent
commit
dea122ad16
  1. 7
      ctru-rs/examples/software-keyboard.rs
  2. 114
      ctru-rs/src/applets/swkbd.rs

7
ctru-rs/examples/software-keyboard.rs

@ -5,8 +5,6 @@
use ctru::applets::swkbd::{Button, CallbackResult, SoftwareKeyboard}; use ctru::applets::swkbd::{Button, CallbackResult, SoftwareKeyboard};
use ctru::prelude::*; use ctru::prelude::*;
use std::ffi::CString;
fn main() { fn main() {
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
@ -22,11 +20,10 @@ fn main() {
// Using this callback it's possible to integrate the applet // Using this callback it's possible to integrate the applet
// with custom error messages when the input is incorrect. // with custom error messages when the input is incorrect.
keyboard.set_filter_callback(Some(Box::new(|str| { 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.contains("boo") {
if str.to_str().unwrap().contains("boo") {
return ( return (
CallbackResult::Retry, CallbackResult::Retry,
Some(CString::new("Ah, you scared me!").unwrap()), Some(String::from("Ah, you scared me!")),
); );
} }

114
ctru-rs/src/applets/swkbd.rs

@ -18,14 +18,14 @@ use std::fmt::Display;
use std::iter::once; use std::iter::once;
use std::str; use std::str;
type CallbackFunction = dyn Fn(&CStr) -> (CallbackResult, Option<CString>); type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option<String>);
/// Configuration structure to setup the Software Keyboard applet. /// Configuration structure to setup the Software Keyboard applet.
#[doc(alias = "SwkbdState")] #[doc(alias = "SwkbdState")]
pub struct SoftwareKeyboard { pub struct SoftwareKeyboard {
state: Box<SwkbdState>, state: Box<SwkbdState>,
callback: Option<Box<CallbackFunction>>, callback: Option<Box<CallbackFunction>>,
error_message: Option<CString>, callback_message: Option<String>,
initial_text: Option<CString>, initial_text: Option<CString>,
} }
@ -244,7 +244,7 @@ impl SoftwareKeyboard {
Self { Self {
state, state,
callback: None, callback: None,
error_message: None, callback_message: None,
initial_text: None, initial_text: None,
} }
} }
@ -275,21 +275,23 @@ impl SoftwareKeyboard {
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(); let mut output = String::new();
unsafe { // Set the filter callback if the user has supplied a closure to run.
// The filter callback gets reset every time the SoftwareKeyboard is used. if self.callback.is_some() {
ctru_sys::swkbdSetFilterCallback( unsafe {
self.state.as_mut(), ctru_sys::swkbdSetFilterCallback(
Some(Self::internal_callback), self.state.as_mut(),
(self as *mut Self).cast(), 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!(),
} }
};
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(); /// let mut keyboard = SoftwareKeyboard::default();
/// ///
/// keyboard.set_filter_callback(Some(Box::new(|str| { /// keyboard.set_filter_callback(Some(Box::new(|str| {
/// if str.to_str().unwrap().contains("boo") { /// if str.contains("boo") {
/// return ( /// return (
/// CallbackResult::Retry, /// 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. /// 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, user: *mut libc::c_void,
pp_message: *mut *const libc::c_char, pp_message: *mut *const libc::c_char,
text: *const libc::c_char, text: *const libc::c_char,
_text_size: libc::size_t, text_size: libc::size_t,
) -> ctru_sys::SwkbdCallbackResult { ) -> ctru_sys::SwkbdCallbackResult {
let this: *mut SoftwareKeyboard = user.cast(); let this = unsafe { &mut *user.cast::<SoftwareKeyboard>() };
unsafe { // Reset any leftover callback message.
// Reset any leftover error message. this.callback_message = None;
(*this).error_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 = { let result = {
// Run the callback if still available. // Run the callback if still available.
if let Some(callback) = &mut (*this).callback { if let Some(callback) = this.callback.as_deref() {
let (res, cstr) = callback(text); let (res, msg) = callback(text);
// Due to how `libctru` operates, the user is expected to keep the error message alive until // 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. // the end of the Software Keyboard prompt. We ensure that happens by saving it within the configuration.
(*this).error_message = cstr; this.callback_message = msg;
if let Some(newstr) = &(*this).error_message { if let Some(msg) = this.callback_message.as_ref() {
*pp_message = newstr.as_ptr(); 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. /// 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 { let text16 = unsafe {
widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts(
data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(), 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 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 { swkbd.callback_result = unsafe {
cb( callback(
extra.callback_user, extra.callback_user,
&mut retmsg, std::ptr::addr_of_mut!(retmsg).cast(),
text8.as_ptr(), text8.as_ptr(),
text8.len(), text8.len(),
) )
@ -886,22 +892,18 @@ impl SoftwareKeyboard {
}; };
let retmsg = if !retmsg.is_null() { let retmsg = if !retmsg.is_null() {
unsafe { unsafe { &**retmsg }
let len = libc::strlen(retmsg) + 1;
std::str::from_utf8_unchecked(std::slice::from_raw_parts(retmsg, len))
}
} else { } else {
"\0" ""
}; };
let callback_msg = &mut swkbd.callback_msg;
for (idx, code_unit) in retmsg for (idx, code_unit) in retmsg
.encode_utf16() .encode_utf16()
.take(callback_msg.len() - 1) .chain(once(0))
.take(swkbd.callback_msg.len() - 1)
.enumerate() .enumerate()
{ {
callback_msg[idx] = code_unit; swkbd.callback_msg[idx] = code_unit;
} }
let _ = unsafe { let _ = unsafe {

Loading…
Cancel
Save