|
|
@ -6,26 +6,25 @@ |
|
|
|
use crate::services::{apt::Apt, gfx::Gfx}; |
|
|
|
use crate::services::{apt::Apt, gfx::Gfx}; |
|
|
|
use ctru_sys::{ |
|
|
|
use ctru_sys::{ |
|
|
|
aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle, |
|
|
|
aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle, |
|
|
|
svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdExtra, |
|
|
|
svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdLearningData, |
|
|
|
SwkbdLearningData, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, |
|
|
|
SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, NS_APPID, |
|
|
|
NS_APPID, |
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
use bitflags::bitflags; |
|
|
|
use bitflags::bitflags; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use std::borrow::Cow; |
|
|
|
use std::ffi::{CStr, CString}; |
|
|
|
use std::ffi::{CStr, CString}; |
|
|
|
use std::fmt::Display; |
|
|
|
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<Cow<'static, str>>); |
|
|
|
|
|
|
|
|
|
|
|
/// 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>>, |
|
|
|
filter_callback: Option<Box<CallbackFunction>>, |
|
|
|
error_message: Option<CString>, |
|
|
|
|
|
|
|
initial_text: Option<CString>, |
|
|
|
initial_text: Option<CString>, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -212,9 +211,9 @@ 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
|
|
|
|
#[derive(Copy, Clone)] |
|
|
|
struct MessageCallbackData { |
|
|
|
struct MessageCallbackData { |
|
|
|
extra: *mut SwkbdExtra, |
|
|
|
filter_callback: *const Box<CallbackFunction>, |
|
|
|
swkbd_shared_mem_ptr: *mut libc::c_void, |
|
|
|
swkbd_shared_mem_ptr: *mut libc::c_void, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -243,8 +242,7 @@ impl SoftwareKeyboard { |
|
|
|
ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1); |
|
|
|
ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1); |
|
|
|
Self { |
|
|
|
Self { |
|
|
|
state, |
|
|
|
state, |
|
|
|
callback: None, |
|
|
|
filter_callback: None, |
|
|
|
error_message: None, |
|
|
|
|
|
|
|
initial_text: None, |
|
|
|
initial_text: None, |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -275,21 +273,12 @@ 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 { |
|
|
|
match self.swkbd_input_text(&mut output) { |
|
|
|
// The filter callback gets reset every time the SoftwareKeyboard is used.
|
|
|
|
ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), |
|
|
|
ctru_sys::swkbdSetFilterCallback( |
|
|
|
ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), |
|
|
|
self.state.as_mut(), |
|
|
|
ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), |
|
|
|
Some(Self::internal_callback), |
|
|
|
ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)), |
|
|
|
(self as *mut Self).cast(), |
|
|
|
_ => 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!(), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -355,17 +344,13 @@ impl SoftwareKeyboard { |
|
|
|
/// # fn main() {
|
|
|
|
/// # fn main() {
|
|
|
|
/// #
|
|
|
|
/// #
|
|
|
|
/// use std::borrow::Cow;
|
|
|
|
/// use std::borrow::Cow;
|
|
|
|
/// use std::ffi::CString;
|
|
|
|
|
|
|
|
/// use ctru::applets::swkbd::{SoftwareKeyboard, CallbackResult};
|
|
|
|
/// use ctru::applets::swkbd::{SoftwareKeyboard, CallbackResult};
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// 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(move |str| {
|
|
|
|
/// if str.to_str().unwrap().contains("boo") {
|
|
|
|
/// if str.contains("boo") {
|
|
|
|
/// return (
|
|
|
|
/// return (CallbackResult::Retry, Some("Ah, you scared me!".into()));
|
|
|
|
/// CallbackResult::Retry,
|
|
|
|
|
|
|
|
/// Some(CString::new("Ah, you scared me!").unwrap()),
|
|
|
|
|
|
|
|
/// );
|
|
|
|
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// (CallbackResult::Ok, None)
|
|
|
|
/// (CallbackResult::Ok, None)
|
|
|
@ -373,45 +358,7 @@ impl SoftwareKeyboard { |
|
|
|
/// #
|
|
|
|
/// #
|
|
|
|
/// # }
|
|
|
|
/// # }
|
|
|
|
pub fn set_filter_callback(&mut self, callback: Option<Box<CallbackFunction>>) { |
|
|
|
pub fn set_filter_callback(&mut self, callback: Option<Box<CallbackFunction>>) { |
|
|
|
self.callback = callback; |
|
|
|
self.filter_callback = callback; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Internal function called by the filter callback.
|
|
|
|
|
|
|
|
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: *mut SoftwareKeyboard = user.cast(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
// Reset any leftover error message.
|
|
|
|
|
|
|
|
(*this).error_message = None; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let text = CStr::from_ptr(text); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let result = { |
|
|
|
|
|
|
|
// Run the callback if still available.
|
|
|
|
|
|
|
|
if let Some(callback) = &mut (*this).callback { |
|
|
|
|
|
|
|
let (res, cstr) = 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; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(newstr) = &(*this).error_message { |
|
|
|
|
|
|
|
*pp_message = newstr.as_ptr(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
@ -642,7 +589,7 @@ impl SoftwareKeyboard { |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let swkbd = self.state.as_mut(); |
|
|
|
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
|
|
|
|
// Calculate shared mem size
|
|
|
|
let mut shared_mem_size = 0; |
|
|
|
let mut shared_mem_size = 0; |
|
|
@ -766,7 +713,7 @@ impl SoftwareKeyboard { |
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if extra.callback.is_some() { |
|
|
|
if self.filter_callback.is_some() { |
|
|
|
swkbd.filter_flags |= SWKBD_FILTER_CALLBACK; |
|
|
|
swkbd.filter_flags |= SWKBD_FILTER_CALLBACK; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
swkbd.filter_flags &= !SWKBD_FILTER_CALLBACK; |
|
|
|
swkbd.filter_flags &= !SWKBD_FILTER_CALLBACK; |
|
|
@ -776,16 +723,19 @@ impl SoftwareKeyboard { |
|
|
|
unsafe { |
|
|
|
unsafe { |
|
|
|
swkbd.__bindgen_anon_1.reserved.fill(0); |
|
|
|
swkbd.__bindgen_anon_1.reserved.fill(0); |
|
|
|
|
|
|
|
|
|
|
|
let mut callback_data = MessageCallbackData { |
|
|
|
// We need to pass a thin pointer to the boxed closure over FFI. Since we know that the message callback will finish before
|
|
|
|
extra: std::ptr::addr_of_mut!(extra), |
|
|
|
// `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::addr_of!(self.filter_callback).cast(), |
|
|
|
swkbd_shared_mem_ptr, |
|
|
|
swkbd_shared_mem_ptr, |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if extra.callback.is_some() { |
|
|
|
if self.filter_callback.is_some() { |
|
|
|
aptSetMessageCallback( |
|
|
|
aptSetMessageCallback( |
|
|
|
Some(Self::swkbd_message_callback), |
|
|
|
Some(Self::swkbd_message_callback), |
|
|
|
std::ptr::addr_of_mut!(callback_data).cast(), |
|
|
|
std::ptr::addr_of_mut!(message_callback_data).cast(), |
|
|
|
); |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
aptLaunchLibraryApplet( |
|
|
|
aptLaunchLibraryApplet( |
|
|
@ -795,7 +745,7 @@ impl SoftwareKeyboard { |
|
|
|
swkbd_shared_mem_handle, |
|
|
|
swkbd_shared_mem_handle, |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if extra.callback.is_some() { |
|
|
|
if self.filter_callback.is_some() { |
|
|
|
aptSetMessageCallback(None, std::ptr::null_mut()); |
|
|
|
aptSetMessageCallback(None, std::ptr::null_mut()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -846,62 +796,46 @@ impl SoftwareKeyboard { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`.
|
|
|
|
// 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( |
|
|
|
unsafe extern "C" fn swkbd_message_callback( |
|
|
|
user: *mut libc::c_void, |
|
|
|
user: *mut libc::c_void, |
|
|
|
sender: NS_APPID, |
|
|
|
sender: NS_APPID, |
|
|
|
msg: *mut libc::c_void, |
|
|
|
msg: *mut libc::c_void, |
|
|
|
msg_size: libc::size_t, |
|
|
|
msg_size: libc::size_t, |
|
|
|
) { |
|
|
|
) { |
|
|
|
let data = unsafe { &mut *user.cast::<MessageCallbackData>() }; |
|
|
|
|
|
|
|
let swkbd = unsafe { &mut *msg.cast::<SwkbdState>() }; |
|
|
|
|
|
|
|
let extra = unsafe { &mut *data.extra }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD |
|
|
|
if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD |
|
|
|
|| msg_size != std::mem::size_of::<SwkbdState>() |
|
|
|
|| msg_size != std::mem::size_of::<SwkbdState>() |
|
|
|
{ |
|
|
|
{ |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let swkbd = unsafe { &mut *msg.cast::<SwkbdState>() }; |
|
|
|
|
|
|
|
let data = unsafe { *user.cast::<MessageCallbackData>() }; |
|
|
|
|
|
|
|
|
|
|
|
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 filter_callback = unsafe { &**data.filter_callback }; |
|
|
|
|
|
|
|
|
|
|
|
if let Some(cb) = extra.callback { |
|
|
|
let (result, retmsg) = filter_callback(&text8); |
|
|
|
swkbd.callback_result = unsafe { |
|
|
|
|
|
|
|
cb( |
|
|
|
|
|
|
|
extra.callback_user, |
|
|
|
|
|
|
|
&mut retmsg, |
|
|
|
|
|
|
|
text8.as_ptr(), |
|
|
|
|
|
|
|
text8.len(), |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} as _ |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
"\0" |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let callback_msg = &mut swkbd.callback_msg; |
|
|
|
swkbd.callback_result = result as _; |
|
|
|
|
|
|
|
|
|
|
|
for (idx, code_unit) in retmsg |
|
|
|
if let Some(msg) = retmsg.as_deref() { |
|
|
|
.encode_utf16() |
|
|
|
for (idx, code_unit) in msg |
|
|
|
.take(callback_msg.len() - 1) |
|
|
|
.encode_utf16() |
|
|
|
.enumerate() |
|
|
|
.take(swkbd.callback_msg.len() - 1) |
|
|
|
{ |
|
|
|
.chain(once(0)) |
|
|
|
callback_msg[idx] = code_unit; |
|
|
|
.enumerate() |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
swkbd.callback_msg[idx] = code_unit; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let _ = unsafe { |
|
|
|
let _ = unsafe { |
|
|
|