@ -4,24 +4,27 @@
@@ -4,24 +4,27 @@
#![ doc(alias = " keyboard " ) ]
use crate ::services ::{ apt ::Apt , gfx ::Gfx } ;
use ctru_sys ::{ self , SwkbdState } ;
use ctru_sys ::{
aptLaunchLibraryApplet , aptSetMessageCallback , envGetAptAppId , svcCloseHandle ,
svcCreateMemoryBlock , APT_SendParameter , SwkbdButton , SwkbdDictWord , SwkbdLearningData ,
SwkbdState , SwkbdStatusData , APPID_SOFTWARE_KEYBOARD , APTCMD_MESSAGE , NS_APPID ,
} ;
use bitflags ::bitflags ;
use libc ;
use std ::ffi ::{ CStr , CString } ;
use std ::borrow ::Cow ;
use std ::fmt ::Display ;
use std ::iter ::once ;
use std ::str ;
type CallbackFunction = dyn Fn ( & CS tr) -> ( CallbackResult , Option < CString > ) ;
type CallbackFunction = dyn Fn ( & s tr) -> ( CallbackResult , Option < Cow < ' static , str > > ) ;
/// Configuration structure to setup the Software Keyboard applet.
#[ doc(alias = " SwkbdState " ) ]
pub struct SoftwareKeyboard {
state : Box < SwkbdState > ,
callback : Option < Box < CallbackFunction > > ,
error_message : Option < CString > ,
filter_ callback : Option < Box < CallbackFunction > > ,
initial_text : Option < Cow < ' static , str > > ,
}
/// Configuration structure to setup the Parental Lock applet.
@ -109,7 +112,7 @@ pub enum ButtonConfig {
@@ -109,7 +112,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) ]
@ -206,6 +209,13 @@ bitflags! {
@@ -206,6 +209,13 @@ 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 < CallbackFunction > ,
swkbd_shared_mem_ptr : * mut libc ::c_void ,
}
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).
///
@ -231,71 +241,14 @@ impl SoftwareKeyboard {
@@ -231,71 +241,14 @@ impl SoftwareKeyboard {
ctru_sys ::swkbdInit ( state . as_mut ( ) , keyboard_type . into ( ) , buttons . into ( ) , - 1 ) ;
Self {
state ,
callback : None ,
error_message : None ,
filter_ callback : None ,
initial_text : None ,
}
}
}
/// 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
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// # 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 (text, button) = keyboard.get_string(2048, &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! [ 0 u8 ; 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 ) )
}
/// 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
///
/// ```
@ -310,30 +263,21 @@ impl SoftwareKeyboard {
@@ -310,30 +263,21 @@ impl SoftwareKeyboard {
/// 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)?;
/// let (text, button) = keyboard.launch(&apt, &gfx)?;
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " swkbdInputText " ) ]
pub fn write_exact ( & mut self , buf : & mut [ u8 ] , _apt : & Apt , _gfx : & Gfx ) -> Result < Button , Error > {
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 ctru_sys ::swkbdInputText ( self . state . as_mut ( ) , buf . as_mut_ptr ( ) , buf . len ( ) ) {
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 ) ,
_ = > unreachable! ( ) ,
}
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 , 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! ( ) ,
}
}
@ -399,17 +343,13 @@ impl SoftwareKeyboard {
@@ -399,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.to_str().unwrap().contains("boo") {
/// return (
/// CallbackResult::Retry,
/// Some(CString::new("Ah, you scared me!").unwrap()),
/// );
/// keyboard.set_filter_callback(Some(Box::new(move |str| {
/// if str.contains("boo") {
/// return (CallbackResult::Retry, Some("Ah, you scared me!".into()));
/// }
///
/// (CallbackResult::Ok, None)
@ -417,45 +357,7 @@ impl SoftwareKeyboard {
@@ -417,45 +357,7 @@ impl SoftwareKeyboard {
/// #
/// # }
pub fn set_filter_callback ( & mut self , callback : Option < Box < CallbackFunction > > ) {
self . 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 ( )
}
self . filter_callback = callback ;
}
/// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled.
@ -488,6 +390,10 @@ impl SoftwareKeyboard {
@@ -488,6 +390,10 @@ 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
///
/// ```
@ -497,21 +403,25 @@ impl SoftwareKeyboard {
@@ -497,21 +403,25 @@ 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!".into()) );
/// #
/// # }
#[ 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 ( ) ) ;
}
pub fn set_initial_text ( & mut self , text : Option < Cow < ' static , str > > ) {
self . initial_text = text ;
}
/// Set the hint text for this software keyboard.
///
/// 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
///
/// ```
@ -521,14 +431,22 @@ impl SoftwareKeyboard {
@@ -521,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_unit ) in text
. encode_utf16 ( )
. take ( self . state . hint_text . len ( ) - 1 )
. chain ( once ( 0 ) )
. enumerate ( )
{
self . state . hint_text [ idx ] = code_unit ;
}
} else {
self . state . hint_text [ 0 ] = 0 ;
}
}
@ -623,15 +541,18 @@ impl SoftwareKeyboard {
@@ -623,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 ( button_text . len ( ) - 1 )
. 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
@ -643,7 +564,7 @@ impl SoftwareKeyboard {
@@ -643,7 +564,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::launch ()`].
///
/// # Example
///
@ -664,6 +585,276 @@ impl SoftwareKeyboard {
@@ -664,6 +585,276 @@ 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 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 ,
SWKBD_D1_CLICK1 , SWKBD_D2_CLICK0 , SWKBD_D2_CLICK1 , SWKBD_D2_CLICK2 ,
SWKBD_FILTER_CALLBACK , SWKBD_OUTOFMEM ,
} ;
let swkbd = self . state . as_mut ( ) ;
let extra = unsafe { swkbd . __bindgen_anon_1 . extra } ;
// Calculate shared mem size
let mut shared_mem_size = 0 ;
shared_mem_size + = ( std ::mem ::size_of ::< u16 > ( ) * ( swkbd . max_text_len as usize + 1 ) )
. next_multiple_of ( std ::mem ::size_of ::< usize > ( ) ) ;
let dict_off = shared_mem_size ;
shared_mem_size + = ( std ::mem ::size_of ::< SwkbdDictWord > ( ) * swkbd . dict_word_count as usize )
. next_multiple_of ( std ::mem ::size_of ::< usize > ( ) ) ;
let status_off = shared_mem_size ;
shared_mem_size + = if swkbd . initial_learning_offset > = 0 {
std ::mem ::size_of ::< SwkbdStatusData > ( )
} else {
0
} ;
let learning_off = shared_mem_size ;
shared_mem_size + = if swkbd . initial_learning_offset > = 0 {
std ::mem ::size_of ::< SwkbdLearningData > ( )
} else {
0
} ;
if swkbd . save_state_flags & ( 1 < < 0 ) ! = 0 {
swkbd . status_offset = shared_mem_size as _ ;
shared_mem_size + = std ::mem ::size_of ::< SwkbdStatusData > ( ) ;
}
if swkbd . save_state_flags & ( 1 < < 1 ) ! = 0 {
swkbd . learning_offset = shared_mem_size as _ ;
shared_mem_size + = std ::mem ::size_of ::< SwkbdLearningData > ( ) ;
}
shared_mem_size = shared_mem_size . next_multiple_of ( 0x1000 ) ;
swkbd . shared_memory_size = shared_mem_size ;
// Allocate shared mem
let swkbd_shared_mem_ptr = unsafe { libc ::memalign ( 0x1000 , shared_mem_size ) } ;
let mut swkbd_shared_mem_handle = 0 ;
if swkbd_shared_mem_ptr . is_null ( ) {
swkbd . result = SWKBD_OUTOFMEM ;
return SWKBD_BUTTON_NONE ;
}
let res = unsafe {
svcCreateMemoryBlock (
& mut swkbd_shared_mem_handle ,
swkbd_shared_mem_ptr as _ ,
shared_mem_size as _ ,
MEMPERM_READ | MEMPERM_WRITE ,
MEMPERM_READ | MEMPERM_WRITE ,
)
} ;
if R_FAILED ( res ) {
unsafe {
libc ::free ( swkbd_shared_mem_ptr ) ;
swkbd . result = SWKBD_OUTOFMEM ;
return SWKBD_BUTTON_NONE ;
}
}
// Copy stuff to shared mem
if let Some ( initial_text ) = self . initial_text . as_deref ( ) {
swkbd . initial_text_offset = 0 ;
let mut initial_text_cursor = swkbd_shared_mem_ptr . cast ( ) ;
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 ) ;
}
}
}
if ! extra . dict . is_null ( ) {
swkbd . dict_offset = dict_off as _ ;
unsafe {
std ::ptr ::copy_nonoverlapping (
extra . dict ,
swkbd_shared_mem_ptr . add ( dict_off ) . cast ( ) ,
swkbd . dict_word_count as _ ,
)
} ;
}
if swkbd . initial_status_offset > = 0 {
swkbd . initial_status_offset = status_off as _ ;
unsafe {
std ::ptr ::copy_nonoverlapping (
extra . status_data ,
swkbd_shared_mem_ptr . add ( status_off ) . cast ( ) ,
1 ,
)
} ;
}
if swkbd . initial_learning_offset > = 0 {
swkbd . initial_learning_offset = learning_off as _ ;
unsafe {
std ::ptr ::copy_nonoverlapping (
extra . learning_data ,
swkbd_shared_mem_ptr . add ( learning_off ) . cast ( ) ,
1 ,
)
} ;
}
if self . filter_callback . is_some ( ) {
swkbd . filter_flags | = SWKBD_FILTER_CALLBACK ;
} else {
swkbd . filter_flags & = ! SWKBD_FILTER_CALLBACK ;
}
// Launch swkbd
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 data = MessageCallbackData {
filter_callback : std ::ptr ::addr_of ! ( self . filter_callback ) . cast ( ) ,
swkbd_shared_mem_ptr ,
} ;
if self . filter_callback . is_some ( ) {
aptSetMessageCallback (
Some ( Self ::swkbd_message_callback ) ,
std ::ptr ::addr_of_mut ! ( data ) . cast ( ) ,
)
}
aptLaunchLibraryApplet (
APPID_SOFTWARE_KEYBOARD ,
( swkbd as * mut SwkbdState ) . cast ( ) ,
std ::mem ::size_of ::< SwkbdState > ( ) ,
swkbd_shared_mem_handle ,
) ;
if self . filter_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 ,
} ;
if swkbd . text_length > 0 {
let text16 = unsafe {
widestring ::Utf16Str ::from_slice_unchecked ( std ::slice ::from_raw_parts (
swkbd_shared_mem_ptr . add ( swkbd . text_offset as _ ) . cast ( ) ,
swkbd . text_length as _ ,
) )
} ;
* output = text16 . to_string ( ) ;
}
if swkbd . save_state_flags & ( 1 < < 0 ) ! = 0 {
unsafe {
std ::ptr ::copy_nonoverlapping (
swkbd_shared_mem_ptr . add ( swkbd . status_offset as _ ) . cast ( ) ,
extra . status_data ,
1 ,
)
} ;
}
if swkbd . save_state_flags & ( 1 < < 1 ) ! = 0 {
unsafe {
std ::ptr ::copy_nonoverlapping (
swkbd_shared_mem_ptr . add ( swkbd . learning_offset as _ ) . cast ( ) ,
extra . learning_data ,
1 ,
)
} ;
}
unsafe { libc ::free ( swkbd_shared_mem_ptr ) } ;
button
}
// A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`.
// 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 ,
msg : * mut libc ::c_void ,
msg_size : libc ::size_t ,
) {
if sender ! = ctru_sys ::APPID_SOFTWARE_KEYBOARD
| | msg_size ! = std ::mem ::size_of ::< SwkbdState > ( )
{
return ;
}
let swkbd = unsafe { & mut * msg . cast ::< SwkbdState > ( ) } ;
let data = unsafe { * user . cast ::< MessageCallbackData > ( ) } ;
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 _ ,
) )
} ;
let text8 = text16 . to_string ( ) ;
let filter_callback = unsafe { & * * data . filter_callback } ;
let ( result , retmsg ) = filter_callback ( & text8 ) ;
swkbd . callback_result = result as _ ;
if let Some ( msg ) = retmsg . as_deref ( ) {
for ( idx , code_unit ) in msg
. encode_utf16 ( )
. take ( swkbd . callback_msg . len ( ) - 1 )
. chain ( once ( 0 ) )
. enumerate ( )
{
swkbd . callback_msg [ idx ] = code_unit ;
}
}
let _ = unsafe {
APT_SendParameter (
envGetAptAppId ( ) ,
sender ,
APTCMD_MESSAGE ,
( swkbd as * mut SwkbdState ) . cast ( ) ,
std ::mem ::size_of ::< SwkbdState > ( ) as _ ,
0 ,
)
} ;
}
}
impl ParentalLock {