diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 3ad37c9..5c158ac 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) { + match keyboard.get_string(2048, self.apt, self.gfx) { Ok((path, Button::Right)) => { // Clicked "OK". action(self, path); diff --git a/ctru-rs/examples/mii-selector.rs b/ctru-rs/examples/mii-selector.rs index 3b2b0ef..55b6b15 100644 --- a/ctru-rs/examples/mii-selector.rs +++ b/ctru-rs/examples/mii-selector.rs @@ -21,7 +21,7 @@ fn main() { mii_selector.set_title("Great Mii Selector!"); // Launch the Mii Selector and use its result to print the selected Mii's information. - match mii_selector.launch() { + match mii_selector.launch(&apt, &gfx) { Ok(result) => { println!("Mii type: {:?}", result.mii_type); println!("Name: {:?}", result.mii_data.name); diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index 61c32db..1d24a7d 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -2,15 +2,37 @@ //! //! This example showcases the use of the Software Keyboard applet to receive text input from the user. -use ctru::applets::swkbd::{Button, SoftwareKeyboard}; +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(); let gfx = Gfx::new().unwrap(); let _console = Console::new(gfx.top_screen.borrow_mut()); + // Prepares a software keyboard with two buttons: one to cancel input and one + // to accept it. You can also use `SoftwareKeyboard::new()` to launch the keyboard + // with different configurations. + let mut keyboard = SoftwareKeyboard::default(); + + // 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| { + // 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") { + return ( + CallbackResult::Retry, + Some(CString::new("Ah, you scared me!").unwrap()), + ); + } + + (CallbackResult::Ok, None) + }))); + println!("Press A to enter some text or press Start to exit."); while apt.main_loop() { @@ -22,14 +44,9 @@ fn main() { // Check if the user request to write some input. if hid.keys_down().contains(KeyPad::A) { - // Prepares a software keyboard with two buttons: One to cancel input and one - // to accept it. You can also use `SoftwareKeyboard::new()` to launch the keyboard in different - // configurations. - let mut keyboard = SoftwareKeyboard::default(); - // Raise the software keyboard. You can perform different actions depending on which // software button the user pressed. - match keyboard.get_string(2048) { + match keyboard.get_string(2048, &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/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs index 2353d7d..43cdbb3 100644 --- a/ctru-rs/src/applets/mii_selector.rs +++ b/ctru-rs/src/applets/mii_selector.rs @@ -4,6 +4,8 @@ //! The selected Mii is readable as a [`Mii`]. use crate::mii::Mii; +use crate::services::{apt::Apt, gfx::Gfx}; + use bitflags::bitflags; use std::{ffi::CString, fmt}; @@ -252,15 +254,18 @@ impl MiiSelector { /// Launch the Mii Selector. /// - /// Depending on the configuration, the Mii Selector window will appear either on the bottom screen (default behaviour) or the top screen (see [`Options::USE_TOP_SCREEN`]). - /// - /// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT. + /// Depending on the configuration, the Mii Selector window will appear either + /// on the bottom screen (default behaviour) or the top screen (see [`Options::USE_TOP_SCREEN`]). /// /// # Example /// /// ```no_run /// # 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::mii_selector::{MiiSelector, Options}; /// @@ -270,13 +275,13 @@ impl MiiSelector { /// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS; /// mii_selector.set_options(opts); /// - /// let result = mii_selector.launch()?; + /// let result = mii_selector.launch(&apt, &gfx)?; /// # /// # Ok(()) /// # } /// ``` #[doc(alias = "miiSelectorLaunch")] - pub fn launch(&mut self) -> Result { + pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result { let mut return_val = Box::::default(); unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) } diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 69d4329..41d40cd 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -1,25 +1,36 @@ //! Software Keyboard applet. //! //! This applet opens a virtual keyboard on the console's bottom screen which lets the user write UTF-16 valid text. -// TODO: Implement remaining functionality (password mode, filter callbacks, etc.). Also improve "max text length" API. Improve `number of buttons` API when creating a new SoftwareKeyboard. -// TODO: Split the Parental PIN lock operations into a different type. #![doc(alias = "keyboard")] +use crate::services::{apt::Apt, gfx::Gfx}; +use ctru_sys::{self, SwkbdState}; + use bitflags::bitflags; -use ctru_sys::{ - self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText, - swkbdSetInitialText, SwkbdState, -}; use libc; + +use std::ffi::{CStr, CString}; use std::fmt::Display; use std::iter::once; use std::str; +type CallbackFunction = dyn Fn(&CStr) -> (CallbackResult, Option); + /// Configuration structure to setup the Software Keyboard applet. #[doc(alias = "SwkbdState")] -#[derive(Clone)] pub struct SoftwareKeyboard { state: Box, + callback: Option>, + error_message: Option, +} + +/// Configuration structure to setup the Parental Lock applet. +/// +/// Internally, the Parental Lock is just a different kind of [`SoftwareKeyboard`]. +#[doc(alias = "SwkbdState")] +#[derive(Clone)] +pub struct ParentalLock { + state: Box, } /// The type of keyboard used by the [`SoftwareKeyboard`]. @@ -41,6 +52,21 @@ pub enum Kind { Western = ctru_sys::SWKBD_TYPE_WESTERN, } +/// The type of result returned by a custom filter callback. +/// +/// The custom callback can be set using [`SoftwareKeyboard::set_filter_callback()`]. +#[doc(alias = "SwkbdCallbackResult")] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum CallbackResult { + /// The callback yields a positive result. + Ok = ctru_sys::SWKBD_CALLBACK_OK, + /// The callback finds the input invalid, but lets the user try again. + Retry = ctru_sys::SWKBD_CALLBACK_CONTINUE, + /// The callback finds the input invalid and closes the Software Keyboard view. + Close = ctru_sys::SWKBD_CALLBACK_CLOSE, +} + /// Represents which button the user pressed to close the [`SoftwareKeyboard`]. /// /// Button text and behaviour can be customized with [`SoftwareKeyboard::configure_button()`]. @@ -56,6 +82,33 @@ pub enum Button { Right = ctru_sys::SWKBD_BUTTON_RIGHT, } +/// Represents the password mode to conceal the input text for the [`SoftwareKeyboard`]. +/// +/// Can be set using [`SoftwareKeyboard::set_password_mode()`]. +#[doc(alias = "SwkbdPasswordMode")] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum PasswordMode { + /// The input text will not be concealed. + None = ctru_sys::SWKBD_PASSWORD_NONE, + /// The input text will be concealed immediately after typing. + Hide = ctru_sys::SWKBD_PASSWORD_HIDE, + /// The input text will be concealed a second after typing. + HideDelay = ctru_sys::SWKBD_PASSWORD_HIDE_DELAY, +} + +/// Configuration to setup the on-screen buttons to exit the [`SoftwareKeyboard`] prompt. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(i32)] +pub enum ButtonConfig { + /// 1 Button: considered the right button. + Right = 1, + /// 2 Buttons: left and right buttons. + LeftRight = 2, + /// 3 Buttons: left, middle and right buttons. + LeftMiddleRight = 3, +} + /// Error returned by an unsuccessful [`SoftwareKeyboard::get_string()`]. #[doc(alias = "SwkbdResult")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -73,15 +126,22 @@ pub enum Error { PowerPressed = ctru_sys::SWKBD_POWERPRESSED, /// The parental lock PIN was correct. /// - /// While this variant isn't *technically* considerable an error - /// the result of a Parental PIN operation won't return a string to the program, thus it's still exceptional behaviour. + /// This variant should never be returned by normal operations made using this module, + /// and is listed here only for compatibility purposes. + /// Refer to the return value of [`ParentalLock::launch()`] to confirm the outcome + /// of the Parental Lock PIN operation. ParentalOk = ctru_sys::SWKBD_PARENTAL_OK, /// The parental lock PIN was incorrect. + /// + /// Refer to the return value of [`ParentalLock::launch()`] to confirm the outcome + /// of the Parental Lock PIN operation. ParentalFail = ctru_sys::SWKBD_PARENTAL_FAIL, /// Input triggered the filter. /// /// You can have a look at [`Filters`] to activate custom filters. BannedInput = ctru_sys::SWKBD_BANNED_INPUT, + /// An on-screen button was pressed to exit the prompt. + ButtonPressed = ctru_sys::SWKBD_D0_CLICK, } /// Restrictions to enforce rules on the keyboard input. @@ -107,12 +167,6 @@ bitflags! { /// Special features that can be activated via [`SoftwareKeyboard::set_features()`]. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct Features: u32 { - /// Parental PIN mode. - /// - /// # Notes - /// - /// Refer to [`Error::ParentalOk`] and [`Error::ParentalFail`] to check whether the Parental PIN lock was successfully opened. - const PARENTAL_PIN = ctru_sys::SWKBD_PARENTAL; /// Darken top screen while the [`SoftwareKeyboard`] is active. const DARKEN_TOP_SCREEN = ctru_sys::SWKBD_DARKEN_TOP_SCREEN; /// Enable predictive input (necessary for Kanji on JPN consoles). @@ -149,10 +203,6 @@ bitflags! { const BACKSLASH = ctru_sys::SWKBD_FILTER_BACKSLASH; /// Disallow the use of profanity via Nintendo's profanity filter. const PROFANITY = ctru_sys::SWKBD_FILTER_PROFANITY; - /// Use a custom callback in order to filter the input. - /// - /// TODO: It's currently impossible to setup a custom filter callback. - const CALLBACK = ctru_sys::SWKBD_FILTER_CALLBACK; } } @@ -165,21 +215,25 @@ impl SoftwareKeyboard { /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # - /// use ctru::applets::swkbd::{SoftwareKeyboard, Kind}; + /// use ctru::applets::swkbd::{SoftwareKeyboard, ButtonConfig, Kind}; /// - /// // Standard keyboard. - /// let keyboard = SoftwareKeyboard::new(Kind::Normal, 2); + /// // Standard keyboard. Equivalent to `SoftwareKeyboard::default()`. + /// let keyboard = SoftwareKeyboard::new(Kind::Normal, ButtonConfig::LeftRight); /// /// // Numpad (with only the "confirm" button). - /// let keyboard = SoftwareKeyboard::new(Kind::Numpad, 1); + /// let keyboard = SoftwareKeyboard::new(Kind::Numpad, ButtonConfig::Right); /// # /// # } #[doc(alias = "swkbdInit")] - pub fn new(keyboard_type: Kind, num_buttons: i32) -> Self { + pub fn new(keyboard_type: Kind, buttons: ButtonConfig) -> Self { unsafe { let mut state = Box::::default(); - swkbdInit(state.as_mut(), keyboard_type.into(), num_buttons, -1); - SoftwareKeyboard { state } + ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1); + Self { + state, + callback: None, + error_message: None, + } } } @@ -188,8 +242,7 @@ impl SoftwareKeyboard { /// # Notes /// /// The text received from the keyboard will be truncated if it is longer than `max_bytes`. - /// - /// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT. + /// Use [`SoftwareKeyboard::set_max_text_len()`] to make sure the buffer can contain the input text. /// /// # Example /// @@ -197,23 +250,32 @@ impl SoftwareKeyboard { /// # 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 (text, button) = keyboard.get_string(2048)?; + /// let (text, button) = keyboard.get_string(2048, &apt, &gfx)?; /// # /// # Ok(()) /// # } /// ``` #[doc(alias = "swkbdInputText")] - pub fn get_string(&mut self, max_bytes: usize) -> Result<(String, Button), Error> { + 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)?; + 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 @@ -234,30 +296,39 @@ impl SoftwareKeyboard { /// If the buffer is too small to contain the entire sequence received from the keyboard, /// the output will be truncated. /// - /// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT. - /// /// # 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)?; + /// let button = keyboard.write_exact(&mut buffer, &apt, &gfx)?; /// # /// # Ok(()) /// # } /// ``` #[doc(alias = "swkbdInputText")] - pub fn write_exact(&mut self, buf: &mut [u8]) -> Result { + pub fn write_exact(&mut self, buf: &mut [u8], _apt: &Apt, _gfx: &Gfx) -> Result { unsafe { - match swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) { - ctru_sys::SWKBD_BUTTON_NONE => Err(self.parse_swkbd_error()), + // 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), @@ -284,7 +355,7 @@ impl SoftwareKeyboard { /// # } #[doc(alias = "swkbdSetFeatures")] pub fn set_features(&mut self, features: Features) { - unsafe { swkbdSetFeatures(self.state.as_mut(), features.bits()) } + unsafe { ctru_sys::swkbdSetFeatures(self.state.as_mut(), features.bits()) } } /// Configure input validation for this keyboard. @@ -311,6 +382,82 @@ impl SoftwareKeyboard { self.state.filter_flags = filters.bits(); } + /// Configure a custom filtering function to validate the input. + /// + /// The callback function must return a [`CallbackResult`] and the error message to display when the input is invalid. + /// + /// # Notes + /// + /// Passing [`None`] will unbind the custom filter callback. + /// + /// The error message returned by the callback should be shorter than `256` characters, otherwise it will be truncated. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # 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()), + /// ); + /// } + /// + /// (CallbackResult::Ok, None) + /// }))); + /// # + /// # } + pub fn set_filter_callback(&mut self, callback: Option>) { + 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() + } + } + /// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled. /// /// # Example @@ -357,7 +504,7 @@ impl SoftwareKeyboard { pub fn set_initial_text(&mut self, text: &str) { unsafe { let nul_terminated: String = text.chars().chain(once('\0')).collect(); - swkbdSetInitialText(self.state.as_mut(), nul_terminated.as_ptr()); + ctru_sys::swkbdSetInitialText(self.state.as_mut(), nul_terminated.as_ptr()); } } @@ -381,7 +528,70 @@ impl SoftwareKeyboard { pub fn set_hint_text(&mut self, text: &str) { unsafe { let nul_terminated: String = text.chars().chain(once('\0')).collect(); - swkbdSetHintText(self.state.as_mut(), nul_terminated.as_ptr()); + ctru_sys::swkbdSetHintText(self.state.as_mut(), nul_terminated.as_ptr()); + } + } + + /// Set a password mode for this software keyboard. + /// + /// Depending on the selected mode the input text will be concealed. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, PasswordMode}; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// keyboard.set_password_mode(PasswordMode::Hide); + /// # + /// # } + #[doc(alias = "swkbdSetPasswordMode")] + pub fn set_password_mode(&mut self, mode: PasswordMode) { + unsafe { + ctru_sys::swkbdSetPasswordMode(self.state.as_mut(), mode.into()); + } + } + + /// Set the 2 custom characters to add to the keyboard while using [`Kind::Numpad`]. + /// + /// These characters will appear in their own buttons right next to the `0` key. + /// + /// # Notes + /// + /// If [`None`] is passed as either key, that button will not be shown to the user. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, Kind, ButtonConfig}; + /// let mut keyboard = SoftwareKeyboard::new(Kind::Numpad, ButtonConfig::LeftRight); + /// + /// keyboard.set_numpad_keys(Some('#'), Some('.')); + /// + /// // The right numpad key will not be shown. + /// keyboard.set_numpad_keys(Some('!'), None); + /// # + /// # } + #[doc(alias = "swkbdSetNumpadKeys")] + pub fn set_numpad_keys(&mut self, left_key: Option, right_key: Option) { + let mut keys = (0, 0); + + if let Some(k) = left_key { + keys.0 = k as i32; + } + + if let Some(k) = right_key { + keys.1 = k as i32; + } + + unsafe { + ctru_sys::swkbdSetNumpadKeys(self.state.as_mut(), keys.0, keys.1); } } @@ -399,10 +609,10 @@ impl SoftwareKeyboard { /// # let _runner = test_runner::GdbRunner::default(); /// # fn main() { /// # - /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind}; + /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, ButtonConfig, Kind}; /// /// // We create a `SoftwareKeyboard` with left and right buttons. - /// let mut keyboard = SoftwareKeyboard::new(Kind::Normal, 2); + /// let mut keyboard = SoftwareKeyboard::new(Kind::Normal, ButtonConfig::LeftRight); /// /// // Set the left button text to "Cancel" and pressing it will NOT return the user's input. /// keyboard.configure_button(Button::Left, "Cancel", false); @@ -415,7 +625,7 @@ impl SoftwareKeyboard { pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) { unsafe { let nul_terminated: String = text.chars().chain(once('\0')).collect(); - swkbdSetButton( + ctru_sys::swkbdSetButton( self.state.as_mut(), button.into(), nul_terminated.as_ptr(), @@ -429,6 +639,8 @@ impl SoftwareKeyboard { /// /// # Notes /// + /// This action will overwrite any previously submitted [`ValidInput`] validation. + /// /// 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()`]. @@ -448,19 +660,69 @@ impl SoftwareKeyboard { /// # } pub fn set_max_text_len(&mut self, len: u16) { self.state.max_text_len = len; + + // Activate the specific validation rule for maximum length. + self.state.valid_input = ValidInput::FixedLen.into(); } +} - fn parse_swkbd_error(&self) -> Error { - match self.state.result { - ctru_sys::SWKBD_INVALID_INPUT => Error::InvalidParameters, - ctru_sys::SWKBD_OUTOFMEM => Error::OutOfMem, - ctru_sys::SWKBD_HOMEPRESSED => Error::HomePressed, - ctru_sys::SWKBD_RESETPRESSED => Error::ResetPressed, - ctru_sys::SWKBD_POWERPRESSED => Error::PowerPressed, - ctru_sys::SWKBD_PARENTAL_OK => Error::ParentalOk, - ctru_sys::SWKBD_PARENTAL_FAIL => Error::ParentalFail, - ctru_sys::SWKBD_BANNED_INPUT => Error::BannedInput, - _ => unreachable!(), +impl ParentalLock { + /// Initialize a new configuration for the Parental Lock applet. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # fn main() { + /// # + /// use ctru::applets::swkbd::ParentalLock; + /// + /// let parental_lock = ParentalLock::new(); + /// # + /// # } + #[doc(alias = "swkbdInit")] + pub fn new() -> Self { + unsafe { + let mut state = Box::::default(); + ctru_sys::swkbdInit(state.as_mut(), Kind::Normal.into(), 1, -1); + ctru_sys::swkbdSetFeatures(state.as_mut(), ctru_sys::SWKBD_PARENTAL); + Self { state } + } + } + + /// Launch the Parental Lock applet based on the configuration and return a result depending on whether the operation was successful or not. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # fn main() { + /// # use ctru::services::{apt::Apt, gfx::Gfx}; + /// # + /// # let gfx = Gfx::new().unwrap(); + /// # let apt = Apt::new().unwrap(); + /// use ctru::applets::swkbd::{ParentalLock, Error}; + /// + /// let mut parental_lock = ParentalLock::new(); + /// + /// match parental_lock.launch(&apt, &gfx) { + /// Ok(_) => println!("You can access parental-only features and settings."), + /// Err(Error::ParentalFail) => println!("Is a kid trying to access this?"), + /// Err(_) => println!("Something wrong happened during the parental lock prompt.") + /// } + /// # + /// # } + #[doc(alias = "swkbdInputText")] + pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(), Error> { + unsafe { + let mut buf = [0; 0]; + ctru_sys::swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), 0); + let e = self.state.result.into(); + + match e { + Error::ParentalOk => Ok(()), + _ => Err(e), + } } } } @@ -468,7 +730,13 @@ impl SoftwareKeyboard { /// Creates a new [`SoftwareKeyboard`] configuration set to using a [`Kind::Normal`] keyboard and 2 [`Button`]s. impl Default for SoftwareKeyboard { fn default() -> Self { - SoftwareKeyboard::new(Kind::Normal, 2) + SoftwareKeyboard::new(Kind::Normal, ButtonConfig::LeftRight) + } +} + +impl Default for ParentalLock { + fn default() -> Self { + ParentalLock::new() } } @@ -497,13 +765,40 @@ impl Display for Error { f, "input given to the software keyboard triggered the active filters" ), + Self::ButtonPressed => write!(f, "on-screen button was pressed to exit the prompt"), } } } impl std::error::Error for Error {} +impl From for Error { + fn from(value: ctru_sys::SwkbdResult) -> Self { + match value { + ctru_sys::SWKBD_INVALID_INPUT => Error::InvalidParameters, + ctru_sys::SWKBD_OUTOFMEM => Error::OutOfMem, + ctru_sys::SWKBD_HOMEPRESSED => Error::HomePressed, + ctru_sys::SWKBD_RESETPRESSED => Error::ResetPressed, + ctru_sys::SWKBD_POWERPRESSED => Error::PowerPressed, + ctru_sys::SWKBD_PARENTAL_OK => Error::ParentalOk, + ctru_sys::SWKBD_PARENTAL_FAIL => Error::ParentalFail, + ctru_sys::SWKBD_BANNED_INPUT => Error::BannedInput, + ctru_sys::SWKBD_D0_CLICK => Error::ButtonPressed, + ctru_sys::SWKBD_D1_CLICK0 => Error::ButtonPressed, + ctru_sys::SWKBD_D1_CLICK1 => Error::ButtonPressed, + ctru_sys::SWKBD_D2_CLICK0 => Error::ButtonPressed, + ctru_sys::SWKBD_D2_CLICK1 => Error::ButtonPressed, + ctru_sys::SWKBD_D2_CLICK2 => Error::ButtonPressed, + _ => unreachable!(), + } + } +} + from_impl!(Kind, ctru_sys::SwkbdType); from_impl!(Button, ctru_sys::SwkbdButton); from_impl!(Error, ctru_sys::SwkbdResult); from_impl!(ValidInput, i32); +from_impl!(ValidInput, u32); +from_impl!(ButtonConfig, i32); +from_impl!(PasswordMode, u32); +from_impl!(CallbackResult, u32);