From c18bb0d4cb301c422e6f1312c77a3bc5831cd3c2 Mon Sep 17 00:00:00 2001 From: Fenrir Date: Sat, 28 Apr 2018 22:27:17 -0600 Subject: [PATCH] Initial software keyboard support --- ctru-rs/src/applet/mod.rs | 1 + ctru-rs/src/applet/swkbd.rs | 193 ++++++++++++++++++++++++++++++++++++ ctru-rs/src/lib.rs | 1 + 3 files changed, 195 insertions(+) create mode 100644 ctru-rs/src/applet/mod.rs create mode 100644 ctru-rs/src/applet/swkbd.rs diff --git a/ctru-rs/src/applet/mod.rs b/ctru-rs/src/applet/mod.rs new file mode 100644 index 0000000..73469a3 --- /dev/null +++ b/ctru-rs/src/applet/mod.rs @@ -0,0 +1 @@ +pub mod swkbd; diff --git a/ctru-rs/src/applet/swkbd.rs b/ctru-rs/src/applet/swkbd.rs new file mode 100644 index 0000000..27b08ee --- /dev/null +++ b/ctru-rs/src/applet/swkbd.rs @@ -0,0 +1,193 @@ +use std::mem; +use std::str; + +use libctru::{self, SwkbdState, swkbdInit, swkbdSetFeatures, swkbdInputText}; + +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 can be up to 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 = unsafe { self.get_bytes(&mut tmp)? }; + + // Make sure we haven't overflowed our buffer. libctru might already check this, + // but we'll do it here too just in case + let len = unsafe { libc::strlen(tmp.as_ptr()) }; + assert!(len <= MAX_BYTES); + + // Not sure if this is falliable or not in this stage of the process, + // but catch any decoding errors to be sure + let utf8 = match str::from_utf8(&tmp[..len]) { + Ok(parsed) => parsed, + Err(_) => return Err(Error::InvalidInput), + }; + + // Finally, copy the validated input into the user's `String` + *buf += utf8; + Ok(button) + } + + /// Fills the provided buffer with a NUL-terminated sequence of bytes from the software + /// keyboard + /// + /// # Unsafety + /// + /// The received bytes are nominally UTF-8 formatted, but the provided buffer must be large + /// enough to receive both the text from the software keyboard along with a NUL-terminator. + /// Otherwise undefined behavior can result. + pub unsafe fn get_bytes(&mut self, buf: &mut [u8]) -> Result { + 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; + } + + /// 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..c98ca65 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 applet; pub mod console; pub mod error; pub mod srv;