FenrirWolf
7 years ago
committed by
GitHub
4 changed files with 260 additions and 0 deletions
@ -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<SwkbdState>, |
||||||
|
} |
||||||
|
|
||||||
|
/// 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::<SwkbdState>()); |
||||||
|
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<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.
|
||||||
|
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<Button, Error> { |
||||||
|
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) |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue