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);