diff --git a/ctru-rs/src/applets/mod.rs b/ctru-rs/src/applets/mod.rs new file mode 100644 index 0000000..73469a3 --- /dev/null +++ b/ctru-rs/src/applets/mod.rs @@ -0,0 +1 @@ +pub mod swkbd; diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs new file mode 100644 index 0000000..515dd92 --- /dev/null +++ b/ctru-rs/src/applets/swkbd.rs @@ -0,0 +1,211 @@ +use std::iter::once; +use std::mem; +use std::str; + +use libctru::{self, SwkbdState, swkbdInit, swkbdSetFeatures, swkbdSetHintText, swkbdInputText, + swkbdSetButton}; + +use libc; + +/// An instance of the software keyboard. +pub struct Swkbd { + state: Box, +} + +/// The kind of keyboard to be initialized. +/// +/// Normal is the full keyboard with several pages (QUERTY/accents/symbol/mobile) +/// Querty is a QUERTY-only keyboard. +/// Numpad is a number pad. +/// Western is a text keyboard without japanese symbols (only applies to JPN systems). For other +/// systems it's the same as a Normal keyboard. +#[derive(Copy, Clone, Debug)] +pub enum Kind { + Normal, + Querty, + Numpad, + Western, +} + +/// Represents which button the user pressed to close the software keyboard. +#[derive(Copy, Clone, Debug)] +pub enum Button { + Left, + Middle, + Right, +} + +/// Error type for the software keyboard. +#[derive(Copy, Clone, Debug)] +pub enum Error { + InvalidInput, + OutOfMem, + HomePressed, + ResetPressed, + PowerPressed, + ParentalOk, + ParentalFail, + BannedInput, +} + +/// Restrictions on keyboard input +#[derive(Copy, Clone, Debug)] +pub enum ValidInput { + Anything, + NotEmpty, + NotEmptyNotBlank, + NotBlank, + FixedLen, +} + +/// Keyboard feature flags +bitflags! { + pub struct Features: u32 { + const PARENTAL_PIN = 1 << 0; + const DARKEN_TOP_SCREEN = 1 << 1; + const PREDICTIVE_INPUT = 1 << 2; + const MULTILINE = 1 << 3; + const FIXED_WIDTH = 1 << 4; + const ALLOW_HOME = 1 << 5; + const ALLOW_RESET = 1 << 6; + const ALLOW_POWER = 1 << 7; + const DEFAULT_QUERTY = 1 << 8; + } +} + +/// Keyboard input filtering flags +bitflags! { + pub struct Filters: u32 { + const DIGITS = 1 << 0; + const AT = 1 << 1; + const PERCENT = 1 << 2; + const BACKSLASH = 1 << 3; + const PROFANITY = 1 << 4; + const CALLBACK = 1 << 5; + } +} + +impl Swkbd { + /// Initializes a software keyboard of the specified type and the chosen number of buttons + /// (from 1-3). + pub fn init(keyboard_type: Kind, num_buttons: i32) -> Self { + unsafe { + let mut state = Box::new(mem::uninitialized::()); + swkbdInit(state.as_mut(), keyboard_type as u32, num_buttons, -1); + Swkbd { state } + } + } + + /// Gets input from this keyboard and appends it to the provided string. + /// + /// The text received from the keyboard will be truncated if it is greater than 2048 bytes + /// in length. + pub fn get_utf8(&mut self, buf: &mut String) -> Result { + // 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. + const MAX_BYTES: usize = 2048; + let mut tmp = [0u8; MAX_BYTES]; + let button = self.get_bytes(&mut tmp)?; + + // libctru does, however, seem to ensure that the buffer will always contain a properly + // terminated UTF-8 sequence even if the input has to be truncated, so these operations + // should be safe. + let len = unsafe { libc::strlen(tmp.as_ptr()) }; + let utf8 = unsafe { str::from_utf8_unchecked(&tmp[..len]) }; + + // Copy the input into the user's `String` + *buf += utf8; + Ok(button) + } + + /// Fills the provided buffer with a UTF-8 encoded, NUL-terminated sequence of bytes from + /// this software keyboard. + /// + /// If the buffer is too small to contain the entire sequence received from the keyboard, + /// the output will be truncated but should still be well-formed UTF-8 + pub fn get_bytes(&mut self, buf: &mut [u8]) -> Result { + unsafe { + match swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) { + libctru::SWKBD_BUTTON_NONE => Err(self.parse_swkbd_error()), + libctru::SWKBD_BUTTON_LEFT => Ok(Button::Left), + libctru::SWKBD_BUTTON_MIDDLE => Ok(Button::Middle), + libctru::SWKBD_BUTTON_RIGHT => Ok(Button::Right), + _ => unreachable!(), + } + } + } + + /// Sets special features for this keyboard + pub fn set_features(&mut self, features: Features) { + unsafe { + swkbdSetFeatures(self.state.as_mut(), features.bits) + } + } + + /// Configures input validation for this keyboard + pub fn set_validation(&mut self, validation: ValidInput, + filters: Filters) { + self.state.valid_input = validation as i32; + self.state.filter_flags = filters.bits; + } + + /// Configures the maximum number of digits that can be entered in the keyboard when the + /// `Filters::DIGITS` flag is enabled + pub fn set_max_digits(&mut self, digits: u16) { + self.state.max_digits = digits; + } + + /// Sets the hint text for this software keyboard (that is, the help text that is displayed + /// when the textbox is empty) + 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()); + } + } + + /// Configures the look and behavior of a button for this keyboard. + /// + /// `button` is the `Button` to be configured + /// `text` configures the display text for the button + /// `submit` configures whether pressing the button will accept the keyboard's input or + /// discard it. + pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) { + unsafe { + let nul_terminated: String = text.chars().chain(once('\0')).collect(); + swkbdSetButton(self.state.as_mut(), button as u32, nul_terminated.as_ptr(), submit); + } + } + + /// Configures the maximum number of UTF-16 code units that can be entered into the software + /// keyboard. By default the limit is 0xFDE8 code units. + /// + /// Note that 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 the `get_utf8` and `get_bytes` functions. + pub fn set_max_text_len(&mut self, len: u16) { + self.state.max_text_len = len; + } + + fn parse_swkbd_error(&self) -> Error { + match self.state.result { + libctru::SWKBD_INVALID_INPUT => Error::InvalidInput, + libctru::SWKBD_OUTOFMEM => Error::OutOfMem, + libctru::SWKBD_HOMEPRESSED => Error::HomePressed, + libctru::SWKBD_RESETPRESSED => Error::ResetPressed, + libctru::SWKBD_POWERPRESSED => Error::PowerPressed, + libctru::SWKBD_PARENTAL_OK => Error::ParentalOk, + libctru::SWKBD_PARENTAL_FAIL => Error::ParentalFail, + libctru::SWKBD_BANNED_INPUT => Error::BannedInput, + _ => unreachable!(), + } + } +} + +impl Default for Swkbd { + fn default() -> Self { + Swkbd::init(Kind::Normal, 2) + } +} diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 56ac370..c3caf62 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -11,6 +11,7 @@ extern crate widestring; extern crate ctru_sys as libctru; +pub mod applets; pub mod console; pub mod error; pub mod srv; diff --git a/examples/src/bin/software-keyboard.rs b/examples/src/bin/software-keyboard.rs new file mode 100644 index 0000000..cb1b058 --- /dev/null +++ b/examples/src/bin/software-keyboard.rs @@ -0,0 +1,47 @@ +extern crate ctru; + +use ctru::gfx::Gfx; +use ctru::console::Console; +use ctru::services::apt::Apt; +use ctru::services::hid::{Hid, KeyPad}; +use ctru::applets::swkbd::{Swkbd, Button}; + +fn main() { + let apt = Apt::init().unwrap(); + let hid = Hid::init().unwrap(); + let gfx = Gfx::default(); + let _console = Console::default(); + + println!("Press A to enter some text or press Start to quit"); + + while apt.main_loop() { + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::KEY_A) { + // Prepares a software keyboard with two buttons: One to cancel input and one + // to accept it. You can also use `Swkbd::init()` to launch the keyboard in different + // configurations. + let mut keyboard = Swkbd::default(); + + // String used to store text received from the keyboard + let mut text = String::new(); + + // Raise the software keyboard. You can perform different actions depending on which + // software button the user pressed + match keyboard.get_utf8(&mut text) { + Ok(Button::Right) => println!("You entered: {}", text), + Ok(Button::Left) => println!("Cancelled"), + Ok(Button::Middle) => println!("How did you even press this?"), + Err(_) => println!("Oh noes, an error happened!"), + } + } + + if hid.keys_down().contains(KeyPad::KEY_START) { + break; + } + } +}