ctru/applets/
swkbd.rs

1//! Software Keyboard applet.
2//!
3//! This applet opens a virtual keyboard on the console's bottom screen which lets the user write UTF-16 valid text.
4#![doc(alias = "keyboard")]
5
6use crate::Utf16Writer;
7use crate::services::{apt::Apt, gfx::Gfx};
8
9use ctru_sys::{
10    APPID_SOFTWARE_KEYBOARD, APT_SendParameter, APTCMD_MESSAGE, NS_APPID, SwkbdButton,
11    SwkbdDictWord, SwkbdLearningData, SwkbdState, SwkbdStatusData, aptLaunchLibraryApplet,
12    aptSetMessageCallback, envGetAptAppId, svcCloseHandle, svcCreateMemoryBlock,
13};
14
15use bitflags::bitflags;
16
17use std::borrow::Cow;
18use std::fmt::{Display, Write};
19use std::str;
20
21type CallbackFunction = dyn FnMut(&str) -> CallbackResult;
22
23/// Configuration structure to setup the Software Keyboard applet.
24#[doc(alias = "SwkbdState")]
25pub struct SoftwareKeyboard {
26    state: Box<SwkbdState>,
27    filter_callback: Option<Box<CallbackFunction>>,
28    initial_text: Option<Cow<'static, str>>,
29}
30
31/// Configuration structure to setup the Parental Lock applet.
32///
33/// Internally, the Parental Lock is just a different kind of [`SoftwareKeyboard`].
34#[doc(alias = "SwkbdState")]
35#[derive(Clone)]
36pub struct ParentalLock {
37    state: Box<SwkbdState>,
38}
39
40/// The type of keyboard used by the [`SoftwareKeyboard`].
41///
42/// Can be set with [`SoftwareKeyboard::new()`]
43#[doc(alias = "SwkbdType")]
44#[derive(Copy, Clone, Debug, PartialEq, Eq)]
45#[repr(u8)]
46pub enum Kind {
47    /// Normal keyboard composed of several pages (QWERTY, accents, symbols, mobile).
48    Normal = ctru_sys::SWKBD_TYPE_NORMAL,
49    /// Only QWERTY keyboard.
50    Qwerty = ctru_sys::SWKBD_TYPE_QWERTY,
51    /// Only number pad.
52    Numpad = ctru_sys::SWKBD_TYPE_NUMPAD,
53    /// On JPN systems: a keyboard without japanese input capabilities.
54    ///
55    /// On any other region: same as [`Normal`](Kind::Normal).
56    Western = ctru_sys::SWKBD_TYPE_WESTERN,
57}
58
59/// The type of result returned by a custom filter callback.
60///
61/// The custom callback can be set using [`SoftwareKeyboard::set_filter_callback()`].
62#[doc(alias = "SwkbdCallbackResult")]
63#[derive(Clone, Debug, PartialEq, Eq)]
64#[repr(u8)]
65pub enum CallbackResult {
66    /// The callback yields a positive result.
67    Ok = ctru_sys::SWKBD_CALLBACK_OK,
68    /// The callback finds the input invalid, but lets the user try again.
69    Retry(Cow<'static, str>) = ctru_sys::SWKBD_CALLBACK_CONTINUE,
70    /// The callback finds the input invalid and closes the Software Keyboard view.
71    Close(Cow<'static, str>) = ctru_sys::SWKBD_CALLBACK_CLOSE,
72}
73
74impl CallbackResult {
75    fn discriminant(&self) -> u8 {
76        // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` `union`
77        // between `repr(C)` structs, each of which has the `u8` discriminant as its first
78        // field, so we can read the discriminant without offsetting the pointer.
79        unsafe { *(self as *const Self).cast() }
80    }
81}
82
83/// Represents which button the user pressed to close the [`SoftwareKeyboard`].
84///
85/// Button text and behaviour can be customized with [`SoftwareKeyboard::configure_button()`].
86#[doc(alias = "SwkbdButton")]
87#[derive(Copy, Clone, Debug, PartialEq, Eq)]
88#[repr(u8)]
89pub enum Button {
90    /// Left button. Usually corresponds to "Cancel".
91    Left = ctru_sys::SWKBD_BUTTON_LEFT,
92    /// Middle button. Usually corresponds to "I Forgot".
93    Middle = ctru_sys::SWKBD_BUTTON_MIDDLE,
94    /// Right button. Usually corresponds to "OK".
95    Right = ctru_sys::SWKBD_BUTTON_RIGHT,
96}
97
98/// Represents the password mode to conceal the input text for the [`SoftwareKeyboard`].
99///
100/// Can be set using [`SoftwareKeyboard::set_password_mode()`].
101#[doc(alias = "SwkbdPasswordMode")]
102#[derive(Copy, Clone, Debug, PartialEq, Eq)]
103#[repr(u8)]
104pub enum PasswordMode {
105    /// The input text will not be concealed.
106    None = ctru_sys::SWKBD_PASSWORD_NONE,
107    /// The input text will be concealed immediately after typing.
108    Hide = ctru_sys::SWKBD_PASSWORD_HIDE,
109    /// The input text will be concealed a second after typing.
110    HideDelay = ctru_sys::SWKBD_PASSWORD_HIDE_DELAY,
111}
112
113/// Configuration to setup the on-screen buttons to exit the [`SoftwareKeyboard`] prompt.
114#[derive(Copy, Clone, Debug, PartialEq, Eq)]
115#[repr(i32)]
116pub enum ButtonConfig {
117    /// 1 Button: considered the right button.
118    Right = 1,
119    /// 2 Buttons: left and right buttons.
120    LeftRight = 2,
121    /// 3 Buttons: left, middle and right buttons.
122    LeftMiddleRight = 3,
123}
124
125/// Error returned by an unsuccessful [`SoftwareKeyboard::launch()`].
126#[doc(alias = "SwkbdResult")]
127#[derive(Copy, Clone, Debug, PartialEq, Eq)]
128#[repr(i8)]
129pub enum Error {
130    /// Invalid parameters given to the [`SoftwareKeyboard`] configuration.
131    InvalidParameters = ctru_sys::SWKBD_INVALID_INPUT,
132    /// [`SoftwareKeyboard`] ran out of memory.
133    OutOfMem = ctru_sys::SWKBD_OUTOFMEM,
134    /// Home button was pressed while [`SoftwareKeyboard`] was running.
135    HomePressed = ctru_sys::SWKBD_HOMEPRESSED,
136    /// Reset button was pressed while [`SoftwareKeyboard`] was running.
137    ResetPressed = ctru_sys::SWKBD_RESETPRESSED,
138    /// Power button was pressed while [`SoftwareKeyboard`] was running.
139    PowerPressed = ctru_sys::SWKBD_POWERPRESSED,
140    /// The parental lock PIN was correct.
141    ///
142    /// This variant should never be returned by normal operations made using this module,
143    /// and is listed here only for compatibility purposes.
144    /// Refer to the return value of [`ParentalLock::launch()`] to confirm the outcome
145    /// of the Parental Lock PIN operation.
146    ParentalOk = ctru_sys::SWKBD_PARENTAL_OK,
147    /// The parental lock PIN was incorrect.
148    ///
149    /// Refer to the return value of [`ParentalLock::launch()`] to confirm the outcome
150    /// of the Parental Lock PIN operation.
151    ParentalFail = ctru_sys::SWKBD_PARENTAL_FAIL,
152    /// Input triggered the filter.
153    ///
154    /// You can have a look at [`Filters`] to activate custom filters.
155    BannedInput = ctru_sys::SWKBD_BANNED_INPUT,
156    /// An on-screen button was pressed to exit the prompt.
157    ButtonPressed = ctru_sys::SWKBD_D0_CLICK,
158}
159
160/// Restrictions to enforce rules on the keyboard input.
161///
162/// See [`SoftwareKeyboard::set_validation()`]
163#[doc(alias = "SwkbdValidInput")]
164#[derive(Copy, Clone, Debug, PartialEq, Eq)]
165#[repr(u8)]
166pub enum ValidInput {
167    /// All inputs are accepted.
168    Anything = ctru_sys::SWKBD_ANYTHING,
169    /// Empty inputs are not accepted.
170    NotEmpty = ctru_sys::SWKBD_NOTEMPTY,
171    /// Blank inputs (consisting only of whitespaces) are not accepted.
172    NotBlank = ctru_sys::SWKBD_NOTBLANK,
173    /// Neither empty inputs nor blank inputs are accepted.
174    NotEmptyNotBlank = ctru_sys::SWKBD_NOTEMPTY_NOTBLANK,
175    /// Input must have a fixed length. Maximum length can be specified with [`SoftwareKeyboard::set_max_text_len()`];
176    FixedLen = ctru_sys::SWKBD_FIXEDLEN,
177}
178
179bitflags! {
180    /// Special features that can be activated via [`SoftwareKeyboard::set_features()`].
181    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
182    pub struct Features: u16 {
183        /// Darken top screen while the [`SoftwareKeyboard`] is active.
184        const DARKEN_TOP_SCREEN = ctru_sys::SWKBD_DARKEN_TOP_SCREEN;
185        /// Enable predictive input (necessary for Kanji on JPN consoles).
186        const PREDICTIVE_INPUT  = ctru_sys::SWKBD_PREDICTIVE_INPUT;
187        /// Enable multiline input.
188        const MULTILINE         = ctru_sys::SWKBD_MULTILINE;
189        /// Enable fixed-width mode.
190        const FIXED_WIDTH       = ctru_sys::SWKBD_FIXED_WIDTH;
191        /// Allow the usage of the Home Button while the [`SoftwareKeyboard`] is running.
192        const ALLOW_HOME        = ctru_sys::SWKBD_ALLOW_HOME;
193        /// Allow the usage of the Reset Button while the [`SoftwareKeyboard`] is running.
194        const ALLOW_RESET       = ctru_sys::SWKBD_ALLOW_RESET;
195        /// Allow the usage of the Power Button while the [`SoftwareKeyboard`] is running.
196        const ALLOW_POWER       = ctru_sys::SWKBD_ALLOW_POWER;
197        /// Default to the QWERTY page when the [`SoftwareKeyboard`] is shown.
198        const DEFAULT_QWERTY    = ctru_sys::SWKBD_DEFAULT_QWERTY;
199    }
200
201    /// Availble filters to disallow some types of input for the [`SoftwareKeyboard`].
202    ///
203    /// See [`SoftwareKeyboard::set_validation()`]
204    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
205    pub struct Filters: u8 {
206        /// Disallow the usage of numerical digits.
207        ///
208        /// The maximum number of digits that are allowed to be used while this filter is active
209        /// can be configured with [`SoftwareKeyboard::set_max_digits()`] (default is 0).
210        const DIGITS    = ctru_sys::SWKBD_FILTER_DIGITS;
211        /// Disallow the usage of the "at" (@) sign.
212        const AT        = ctru_sys::SWKBD_FILTER_AT;
213        /// Disallow the usage of the "percent" (%) sign.
214        const PERCENT   = ctru_sys::SWKBD_FILTER_PERCENT;
215        /// Disallow the usage of the "backslash" (\) sign.
216        const BACKSLASH = ctru_sys::SWKBD_FILTER_BACKSLASH;
217        /// Disallow the use of profanity via Nintendo's profanity filter.
218        const PROFANITY = ctru_sys::SWKBD_FILTER_PROFANITY;
219    }
220}
221
222// Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard.
223struct MessageCallbackData {
224    filter_callback: *mut Box<CallbackFunction>,
225    swkbd_shared_mem_ptr: *mut libc::c_void,
226}
227
228impl SoftwareKeyboard {
229    /// Initialize a new configuration for the Software Keyboard applet depending on how many "exit" buttons are available to the user (1, 2 or 3).
230    ///
231    /// # Example
232    ///
233    /// ```
234    /// # let _runner = test_runner::GdbRunner::default();
235    /// # fn main() {
236    /// #
237    /// use ctru::applets::swkbd::{SoftwareKeyboard, ButtonConfig, Kind};
238    ///
239    /// // Standard keyboard. Equivalent to `SoftwareKeyboard::default()`.
240    /// let keyboard = SoftwareKeyboard::new(Kind::Normal, ButtonConfig::LeftRight);
241    ///
242    /// // Numpad (with only the "confirm" button).
243    /// let keyboard = SoftwareKeyboard::new(Kind::Numpad, ButtonConfig::Right);
244    /// #
245    /// # }
246    #[doc(alias = "swkbdInit")]
247    pub fn new(keyboard_type: Kind, buttons: ButtonConfig) -> Self {
248        unsafe {
249            let mut state = Box::<SwkbdState>::default();
250            ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1);
251            Self {
252                state,
253                filter_callback: None,
254                initial_text: None,
255            }
256        }
257    }
258
259    /// Launches the applet based on the given configuration and returns a string containing the text input.
260    ///
261    /// # Example
262    ///
263    /// ```
264    /// # let _runner = test_runner::GdbRunner::default();
265    /// # use std::error::Error;
266    /// # fn main() -> Result<(), Box<dyn Error>> {
267    /// # use ctru::services::{apt::Apt, gfx::Gfx};
268    /// #
269    /// # let gfx = Gfx::new().unwrap();
270    /// # let apt = Apt::new().unwrap();
271    /// #
272    /// use ctru::applets::swkbd::SoftwareKeyboard;
273    /// let mut keyboard = SoftwareKeyboard::default();
274    ///
275    /// let (text, button) = keyboard.launch(&apt, &gfx)?;
276    /// #
277    /// # Ok(())
278    /// # }
279    /// ```
280    #[doc(alias = "swkbdInputText")]
281    pub fn launch(&mut self, apt: &Apt, gfx: &Gfx) -> Result<(String, Button), Error> {
282        let mut output = String::new();
283
284        match self.swkbd_input_text(&mut output, apt, gfx) {
285            ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()),
286            ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)),
287            ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)),
288            ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)),
289            _ => unreachable!(),
290        }
291    }
292
293    /// Set special features for this keyboard.
294    ///
295    /// # Example
296    ///
297    /// ```
298    /// # let _runner = test_runner::GdbRunner::default();
299    /// # fn main() {
300    /// #
301    /// use ctru::applets::swkbd::{SoftwareKeyboard, Features};
302    ///
303    /// let mut keyboard = SoftwareKeyboard::default();
304    ///
305    /// let features = Features::DARKEN_TOP_SCREEN & Features::MULTILINE;
306    /// keyboard.set_features(features);
307    /// #
308    /// # }
309    #[doc(alias = "swkbdSetFeatures")]
310    pub fn set_features(&mut self, features: Features) {
311        unsafe { ctru_sys::swkbdSetFeatures(self.state.as_mut(), features.bits().into()) }
312    }
313
314    /// Configure input validation for this keyboard.
315    ///
316    /// # Example
317    ///
318    /// ```
319    /// # let _runner = test_runner::GdbRunner::default();
320    /// # fn main() {
321    /// #
322    /// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters};
323    /// let mut keyboard = SoftwareKeyboard::default();
324    ///
325    /// // Disallow empty or blank input.
326    /// let validation = ValidInput::NotEmptyNotBlank;
327    ///
328    /// // Disallow the use of numerical digits and profanity.
329    /// let filters = Filters::DIGITS & Filters::PROFANITY;
330    /// keyboard.set_validation(validation, filters);
331    /// #
332    /// # }
333    pub fn set_validation(&mut self, validation: ValidInput, filters: Filters) {
334        self.state.valid_input = validation.into();
335        self.state.filter_flags = filters.bits().into();
336    }
337
338    /// Configure a custom filtering function to validate the input.
339    ///
340    /// The callback function must return a [`CallbackResult`] and the error message to display when the input is invalid.
341    ///
342    /// # Notes
343    ///
344    /// Passing [`None`] will unbind the custom filter callback.
345    ///
346    /// The error message returned by the callback should be shorter than `256` characters, otherwise it will be truncated.
347    ///
348    /// # Example
349    ///
350    /// ```
351    /// # let _runner = test_runner::GdbRunner::default();
352    /// # fn main() {
353    /// #
354    /// use std::borrow::Cow;
355    /// use ctru::applets::swkbd::{SoftwareKeyboard, CallbackResult};
356    ///
357    /// let mut keyboard = SoftwareKeyboard::default();
358    ///
359    /// keyboard.set_filter_callback(Some(Box::new(move |input| {
360    ///     if input.contains("boo") {
361    ///         CallbackResult::Retry("Aaaah, you scared me!".into())
362    ///     } else {
363    ///         CallbackResult::Ok
364    ///     }
365    /// })));
366    /// #
367    /// # }
368    pub fn set_filter_callback(&mut self, callback: Option<Box<CallbackFunction>>) {
369        self.filter_callback = callback;
370    }
371
372    /// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled.
373    ///
374    /// # Example
375    ///
376    /// ```
377    /// # let _runner = test_runner::GdbRunner::default();
378    /// # fn main() {
379    /// #
380    /// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters};
381    /// let mut keyboard = SoftwareKeyboard::default();
382    ///
383    /// // Disallow empty or blank input.
384    /// let validation = ValidInput::NotEmptyNotBlank;
385    ///
386    /// // Disallow the use of numerical digits. This filter is customizable thanks to `set_max_digits`.
387    /// let filters = Filters::DIGITS;
388    /// keyboard.set_validation(validation, filters);
389    ///
390    /// // No more than 3 numbers are accepted.
391    /// keyboard.set_max_digits(3);
392    /// #
393    /// # }
394    pub fn set_max_digits(&mut self, digits: u16) {
395        self.state.max_digits = digits;
396    }
397
398    /// Set the initial text for this software keyboard.
399    ///
400    /// The initial text is the text already written when you open the software keyboard.
401    ///
402    /// # Notes
403    ///
404    /// Passing [`None`] will clear the initial text.
405    ///
406    /// # Example
407    ///
408    /// ```
409    /// # let _runner = test_runner::GdbRunner::default();
410    /// # fn main() {
411    /// #
412    /// use ctru::applets::swkbd::SoftwareKeyboard;
413    /// let mut keyboard = SoftwareKeyboard::default();
414    ///
415    /// keyboard.set_initial_text(Some("Write here what you like!".into()));
416    /// #
417    /// # }
418    #[doc(alias = "swkbdSetInitialText")]
419    pub fn set_initial_text(&mut self, text: Option<Cow<'static, str>>) {
420        self.initial_text = text;
421    }
422
423    /// Set the hint text for this software keyboard.
424    ///
425    /// The hint text is the text shown in gray before any text gets written in the input box.
426    ///
427    /// # Notes
428    ///
429    /// Passing [`None`] will clear the hint text.
430    ///
431    /// The hint text will be converted to UTF-16 when passed to the software keyboard, and the text will be truncated
432    /// if the length exceeds 64 code units after conversion.
433    ///
434    /// # Example
435    ///
436    /// ```
437    /// # let _runner = test_runner::GdbRunner::default();
438    /// # fn main() {
439    /// #
440    /// use ctru::applets::swkbd::SoftwareKeyboard;
441    /// let mut keyboard = SoftwareKeyboard::default();
442    ///
443    /// keyboard.set_hint_text(Some("Write here what you like!"));
444    /// #
445    /// # }
446    #[doc(alias = "swkbdSetHintText")]
447    pub fn set_hint_text(&mut self, text: Option<&str>) {
448        if let Some(text) = text {
449            let mut writer = Utf16Writer::new(&mut self.state.hint_text);
450
451            let _ = writer.write_str(text);
452        } else {
453            self.state.hint_text[0] = 0;
454        }
455    }
456
457    /// Set a password mode for this software keyboard.
458    ///
459    /// Depending on the selected mode the input text will be concealed.
460    ///
461    /// # Example
462    ///
463    /// ```
464    /// # let _runner = test_runner::GdbRunner::default();
465    /// # fn main() {
466    /// #
467    /// use ctru::applets::swkbd::{SoftwareKeyboard, PasswordMode};
468    /// let mut keyboard = SoftwareKeyboard::default();
469    ///
470    /// keyboard.set_password_mode(PasswordMode::Hide);
471    /// #
472    /// # }
473    #[doc(alias = "swkbdSetPasswordMode")]
474    pub fn set_password_mode(&mut self, mode: PasswordMode) {
475        unsafe {
476            ctru_sys::swkbdSetPasswordMode(self.state.as_mut(), mode as _);
477        }
478    }
479
480    /// Set the 2 custom characters to add to the keyboard while using [`Kind::Numpad`].
481    ///
482    /// These characters will appear in their own buttons right next to the `0` key.
483    ///
484    /// # Notes
485    ///
486    /// If [`None`] is passed as either key, that button will not be shown to the user.
487    ///
488    /// # Example
489    ///
490    /// ```
491    /// # let _runner = test_runner::GdbRunner::default();
492    /// # fn main() {
493    /// #
494    /// use ctru::applets::swkbd::{SoftwareKeyboard, Kind, ButtonConfig};
495    /// let mut keyboard = SoftwareKeyboard::new(Kind::Numpad, ButtonConfig::LeftRight);
496    ///
497    /// keyboard.set_numpad_keys(Some('#'), Some('.'));
498    ///
499    /// // The right numpad key will not be shown.
500    /// keyboard.set_numpad_keys(Some('!'), None);
501    /// #
502    /// # }
503    #[doc(alias = "swkbdSetNumpadKeys")]
504    pub fn set_numpad_keys(&mut self, left_key: Option<char>, right_key: Option<char>) {
505        let mut keys = (0, 0);
506
507        if let Some(k) = left_key {
508            keys.0 = k as i32;
509        }
510
511        if let Some(k) = right_key {
512            keys.1 = k as i32;
513        }
514
515        unsafe {
516            ctru_sys::swkbdSetNumpadKeys(self.state.as_mut(), keys.0, keys.1);
517        }
518    }
519
520    /// Configure the look and behavior of a button for this keyboard.
521    ///
522    /// # Arguments
523    ///
524    /// - `button` - the [`Button`] to be configured based on the position.
525    /// - `text` - the text displayed in the button.
526    /// - `submit` - whether pressing the button will accept the keyboard's input or discard it.
527    ///
528    /// # Example
529    ///
530    /// ```
531    /// # let _runner = test_runner::GdbRunner::default();
532    /// # fn main() {
533    /// #
534    /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, ButtonConfig, Kind};
535    ///
536    /// // We create a `SoftwareKeyboard` with left and right buttons.
537    /// let mut keyboard = SoftwareKeyboard::new(Kind::Normal, ButtonConfig::LeftRight);
538    ///
539    /// // Set the left button text to "Cancel" and pressing it will NOT return the user's input.
540    /// keyboard.configure_button(Button::Left, "Cancel", false);
541    ///
542    /// // Set the right button text to "Ok" and pressing it will return the user's input.
543    /// keyboard.configure_button(Button::Right, "Ok", true);
544    /// #
545    /// # }
546    #[doc(alias = "swkbdSetButton")]
547    pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) {
548        let button_text = &mut self.state.button_text[button as usize];
549
550        let mut writer = Utf16Writer::new(button_text);
551
552        let _ = writer.write_str(text);
553
554        self.state.button_submits_text[button as usize] = submit;
555    }
556
557    /// Configure the maximum number of UTF-16 code units that can be entered into the software
558    /// keyboard. By default the limit is `65000` code units.
559    ///
560    /// # Notes
561    ///
562    /// This action will overwrite any previously submitted [`ValidInput`] validation.
563    ///
564    /// Keyboard input is converted from UTF-16 to UTF-8 before being handed to Rust,
565    /// so this code point limit does not necessarily equal the max number of UTF-8 code points
566    /// receivable by [`SoftwareKeyboard::launch()`].
567    ///
568    /// # Example
569    ///
570    /// ```
571    /// # let _runner = test_runner::GdbRunner::default();
572    /// # fn main() {
573    /// #
574    /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind};
575    /// let mut keyboard = SoftwareKeyboard::default();
576    ///
577    /// // Set the maximum text length to 18 UTF-16 code units.
578    /// keyboard.set_max_text_len(18);
579    /// #
580    /// # }
581    pub fn set_max_text_len(&mut self, len: u16) {
582        self.state.max_text_len = len;
583
584        // Activate the specific validation rule for maximum length.
585        self.state.valid_input = ValidInput::FixedLen.into();
586    }
587
588    // A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to fix various
589    // API nits and get rid of awkward type conversions when interacting with the Software Keyboard.
590    fn swkbd_input_text(&mut self, output: &mut String, _apt: &Apt, _gfx: &Gfx) -> SwkbdButton {
591        use ctru_sys::{
592            MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE,
593            SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0,
594            SWKBD_D1_CLICK1, SWKBD_D2_CLICK0, SWKBD_D2_CLICK1, SWKBD_D2_CLICK2,
595            SWKBD_FILTER_CALLBACK, SWKBD_OUTOFMEM,
596        };
597
598        let swkbd = self.state.as_mut();
599        let extra = unsafe { swkbd.__bindgen_anon_1.extra };
600
601        // Calculate shared mem size
602        let mut shared_mem_size = 0;
603
604        shared_mem_size += (std::mem::size_of::<u16>() * (swkbd.max_text_len as usize + 1))
605            .next_multiple_of(std::mem::size_of::<usize>());
606
607        let dict_off = shared_mem_size;
608
609        shared_mem_size += (std::mem::size_of::<SwkbdDictWord>() * swkbd.dict_word_count as usize)
610            .next_multiple_of(std::mem::size_of::<usize>());
611
612        let status_off = shared_mem_size;
613
614        shared_mem_size += if swkbd.initial_learning_offset >= 0 {
615            std::mem::size_of::<SwkbdStatusData>()
616        } else {
617            0
618        };
619
620        let learning_off = shared_mem_size;
621
622        shared_mem_size += if swkbd.initial_learning_offset >= 0 {
623            std::mem::size_of::<SwkbdLearningData>()
624        } else {
625            0
626        };
627
628        if swkbd.save_state_flags & (1 << 0) != 0 {
629            swkbd.status_offset = shared_mem_size as _;
630            shared_mem_size += std::mem::size_of::<SwkbdStatusData>();
631        }
632
633        if swkbd.save_state_flags & (1 << 1) != 0 {
634            swkbd.learning_offset = shared_mem_size as _;
635            shared_mem_size += std::mem::size_of::<SwkbdLearningData>();
636        }
637
638        shared_mem_size = shared_mem_size.next_multiple_of(0x1000);
639
640        swkbd.shared_memory_size = shared_mem_size;
641
642        // Allocate shared mem
643        let swkbd_shared_mem_ptr = unsafe { libc::memalign(0x1000, shared_mem_size) };
644
645        let mut swkbd_shared_mem_handle = 0;
646
647        if swkbd_shared_mem_ptr.is_null() {
648            swkbd.result = SWKBD_OUTOFMEM;
649            return SWKBD_BUTTON_NONE;
650        }
651
652        let res = unsafe {
653            svcCreateMemoryBlock(
654                &mut swkbd_shared_mem_handle,
655                swkbd_shared_mem_ptr as _,
656                shared_mem_size as _,
657                MEMPERM_READ | MEMPERM_WRITE,
658                MEMPERM_READ | MEMPERM_WRITE,
659            )
660        };
661
662        if R_FAILED(res) {
663            unsafe {
664                libc::free(swkbd_shared_mem_ptr);
665                swkbd.result = SWKBD_OUTOFMEM;
666                return SWKBD_BUTTON_NONE;
667            }
668        }
669
670        // Copy stuff to shared mem
671        if let Some(initial_text) = self.initial_text.as_deref() {
672            let slice = unsafe {
673                std::slice::from_raw_parts_mut(swkbd_shared_mem_ptr.cast(), initial_text.len())
674            };
675
676            let mut writer = Utf16Writer::new(slice);
677
678            let _ = writer.write_str(initial_text);
679        }
680
681        if !extra.dict.is_null() {
682            swkbd.dict_offset = dict_off as _;
683            unsafe {
684                std::ptr::copy_nonoverlapping(
685                    extra.dict,
686                    swkbd_shared_mem_ptr.add(dict_off).cast(),
687                    swkbd.dict_word_count as _,
688                )
689            };
690        }
691
692        if swkbd.initial_status_offset >= 0 {
693            swkbd.initial_status_offset = status_off as _;
694            unsafe {
695                std::ptr::copy_nonoverlapping(
696                    extra.status_data,
697                    swkbd_shared_mem_ptr.add(status_off).cast(),
698                    1,
699                )
700            };
701        }
702
703        if swkbd.initial_learning_offset >= 0 {
704            swkbd.initial_learning_offset = learning_off as _;
705            unsafe {
706                std::ptr::copy_nonoverlapping(
707                    extra.learning_data,
708                    swkbd_shared_mem_ptr.add(learning_off).cast(),
709                    1,
710                )
711            };
712        }
713
714        if self.filter_callback.is_some() {
715            swkbd.filter_flags |= u32::from(SWKBD_FILTER_CALLBACK);
716        } else {
717            swkbd.filter_flags &= !u32::from(SWKBD_FILTER_CALLBACK);
718        }
719
720        // Launch swkbd
721        unsafe {
722            swkbd.__bindgen_anon_1.reserved.fill(0);
723
724            // We need to pass a thin pointer to the boxed closure over FFI. Since we know that the message callback will finish before
725            // `self` is allowed to be moved again, we can safely use a pointer to the local value contained in `self.filter_callback`
726            // The cast here is also sound since the pointer will only be read from if `self.filter_callback.is_some()` returns true.
727            let mut data = MessageCallbackData {
728                filter_callback: (&raw mut self.filter_callback).cast(),
729                swkbd_shared_mem_ptr,
730            };
731
732            if self.filter_callback.is_some() {
733                aptSetMessageCallback(Some(Self::swkbd_message_callback), (&raw mut data).cast())
734            }
735
736            aptLaunchLibraryApplet(
737                APPID_SOFTWARE_KEYBOARD,
738                (swkbd as *mut SwkbdState).cast(),
739                std::mem::size_of::<SwkbdState>(),
740                swkbd_shared_mem_handle,
741            );
742
743            if self.filter_callback.is_some() {
744                aptSetMessageCallback(None, std::ptr::null_mut());
745            }
746
747            let _ = svcCloseHandle(swkbd_shared_mem_handle);
748        }
749
750        let button = match swkbd.result {
751            SWKBD_D1_CLICK0 | SWKBD_D2_CLICK0 => SWKBD_BUTTON_LEFT,
752            SWKBD_D2_CLICK1 => SWKBD_BUTTON_MIDDLE,
753            SWKBD_D0_CLICK | SWKBD_D1_CLICK1 | SWKBD_D2_CLICK2 => SWKBD_BUTTON_RIGHT,
754            _ => SWKBD_BUTTON_NONE,
755        };
756
757        if swkbd.text_length > 0 {
758            let text16 = unsafe {
759                std::slice::from_raw_parts(
760                    swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(),
761                    swkbd.text_length as _,
762                )
763            };
764
765            *output = String::from_utf16(text16).unwrap();
766        }
767
768        if swkbd.save_state_flags & (1 << 0) != 0 {
769            unsafe {
770                std::ptr::copy_nonoverlapping(
771                    swkbd_shared_mem_ptr.add(swkbd.status_offset as _).cast(),
772                    extra.status_data,
773                    1,
774                )
775            };
776        }
777
778        if swkbd.save_state_flags & (1 << 1) != 0 {
779            unsafe {
780                std::ptr::copy_nonoverlapping(
781                    swkbd_shared_mem_ptr.add(swkbd.learning_offset as _).cast(),
782                    extra.learning_data,
783                    1,
784                )
785            };
786        }
787
788        unsafe { libc::free(swkbd_shared_mem_ptr) };
789
790        button
791    }
792
793    // A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`.
794    // This function sets up and then calls the filter callback
795    unsafe extern "C" fn swkbd_message_callback(
796        user: *mut libc::c_void,
797        sender: NS_APPID,
798        msg: *mut libc::c_void,
799        msg_size: libc::size_t,
800    ) {
801        if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD
802            || msg_size != std::mem::size_of::<SwkbdState>()
803        {
804            return;
805        }
806
807        let swkbd = unsafe { &mut *msg.cast::<SwkbdState>() };
808
809        let data = unsafe { &*user.cast::<MessageCallbackData>() };
810
811        let text16 = unsafe {
812            std::slice::from_raw_parts(
813                data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(),
814                swkbd.text_length as _,
815            )
816        };
817
818        let text8 = String::from_utf16(text16).unwrap();
819
820        let result = unsafe { &mut **data.filter_callback }(&text8);
821
822        swkbd.callback_result = result.discriminant().into();
823
824        if let CallbackResult::Retry(msg) | CallbackResult::Close(msg) = result {
825            let mut writer = Utf16Writer::new(&mut swkbd.callback_msg);
826
827            let _ = writer.write_str(&msg);
828        }
829
830        let _ = unsafe {
831            APT_SendParameter(
832                envGetAptAppId() as _,
833                sender,
834                APTCMD_MESSAGE,
835                (swkbd as *mut SwkbdState).cast(),
836                std::mem::size_of::<SwkbdState>() as _,
837                0,
838            )
839        };
840    }
841}
842
843impl ParentalLock {
844    /// Initialize a new configuration for the Parental Lock applet.
845    ///
846    /// # Example
847    ///
848    /// ```
849    /// # let _runner = test_runner::GdbRunner::default();
850    /// # fn main() {
851    /// #
852    /// use ctru::applets::swkbd::ParentalLock;
853    ///
854    /// let parental_lock = ParentalLock::new();
855    /// #
856    /// # }
857    #[doc(alias = "swkbdInit")]
858    pub fn new() -> Self {
859        unsafe {
860            let mut state = Box::<SwkbdState>::default();
861            ctru_sys::swkbdInit(state.as_mut(), Kind::Normal.into(), 1, -1);
862            ctru_sys::swkbdSetFeatures(state.as_mut(), ctru_sys::SWKBD_PARENTAL.into());
863            Self { state }
864        }
865    }
866
867    /// Launch the Parental Lock applet based on the configuration and return a result depending on whether the operation was successful or not.
868    ///
869    /// # Example
870    ///
871    /// ```
872    /// # let _runner = test_runner::GdbRunner::default();
873    /// # fn main() {
874    /// # use ctru::services::{apt::Apt, gfx::Gfx};
875    /// #
876    /// # let gfx = Gfx::new().unwrap();
877    /// # let apt = Apt::new().unwrap();
878    /// use ctru::applets::swkbd::{ParentalLock, Error};
879    ///
880    /// let mut parental_lock = ParentalLock::new();
881    ///
882    /// match parental_lock.launch(&apt, &gfx) {
883    ///     Ok(_) => println!("You can access parental-only features and settings."),
884    ///     Err(Error::ParentalFail) => println!("Is a kid trying to access this?"),
885    ///     Err(_) => println!("Something wrong happened during the parental lock prompt.")
886    /// }
887    /// #
888    /// # }
889    #[doc(alias = "swkbdInputText")]
890    pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(), Error> {
891        unsafe {
892            let mut buf = [0; 0];
893            ctru_sys::swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), 0);
894            let e = self.state.result.into();
895
896            match e {
897                Error::ParentalOk => Ok(()),
898                _ => Err(e),
899            }
900        }
901    }
902}
903
904/// Creates a new [`SoftwareKeyboard`] configuration set to using a [`Kind::Normal`] keyboard and 2 [`Button`]s.
905impl Default for SoftwareKeyboard {
906    fn default() -> Self {
907        SoftwareKeyboard::new(Kind::Normal, ButtonConfig::LeftRight)
908    }
909}
910
911impl Default for ParentalLock {
912    fn default() -> Self {
913        ParentalLock::new()
914    }
915}
916
917impl Display for Error {
918    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
919        match self {
920            Self::InvalidParameters => write!(
921                f,
922                "software keyboard was configured with invalid parameters"
923            ),
924            Self::OutOfMem => write!(f, "software keyboard ran out of memory"),
925            Self::HomePressed => {
926                write!(f, "home button pressed while software keyboard was running")
927            }
928            Self::ResetPressed => write!(
929                f,
930                "reset button pressed while software keyboard was running"
931            ),
932            Self::PowerPressed => write!(
933                f,
934                "power button pressed while software keyboard was running"
935            ),
936            Self::ParentalOk => write!(f, "parental lock pin was correct"),
937            Self::ParentalFail => write!(f, "parental lock pin was incorrect"),
938            Self::BannedInput => write!(
939                f,
940                "input given to the software keyboard triggered the active filters"
941            ),
942            Self::ButtonPressed => write!(f, "on-screen button was pressed to exit the prompt"),
943        }
944    }
945}
946
947impl std::error::Error for Error {}
948
949impl From<ctru_sys::SwkbdResult> for Error {
950    fn from(value: ctru_sys::SwkbdResult) -> Self {
951        match value {
952            ctru_sys::SWKBD_INVALID_INPUT => Error::InvalidParameters,
953            ctru_sys::SWKBD_OUTOFMEM => Error::OutOfMem,
954            ctru_sys::SWKBD_HOMEPRESSED => Error::HomePressed,
955            ctru_sys::SWKBD_RESETPRESSED => Error::ResetPressed,
956            ctru_sys::SWKBD_POWERPRESSED => Error::PowerPressed,
957            ctru_sys::SWKBD_PARENTAL_OK => Error::ParentalOk,
958            ctru_sys::SWKBD_PARENTAL_FAIL => Error::ParentalFail,
959            ctru_sys::SWKBD_BANNED_INPUT => Error::BannedInput,
960            ctru_sys::SWKBD_D0_CLICK => Error::ButtonPressed,
961            ctru_sys::SWKBD_D1_CLICK0 => Error::ButtonPressed,
962            ctru_sys::SWKBD_D1_CLICK1 => Error::ButtonPressed,
963            ctru_sys::SWKBD_D2_CLICK0 => Error::ButtonPressed,
964            ctru_sys::SWKBD_D2_CLICK1 => Error::ButtonPressed,
965            ctru_sys::SWKBD_D2_CLICK2 => Error::ButtonPressed,
966            _ => unreachable!(),
967        }
968    }
969}
970
971from_impl!(Kind, ctru_sys::SwkbdType);
972from_impl!(Button, ctru_sys::SwkbdButton);
973from_impl!(Error, ctru_sys::SwkbdResult);
974from_impl!(ValidInput, i32);
975from_impl!(ValidInput, u32);
976from_impl!(ButtonConfig, i32);
977from_impl!(PasswordMode, u32);