aboutsummaryrefslogtreecommitdiff
path: root/ctru-rs/src/applets/swkbd.rs
blob: 54098c748185cfcc798c0b6b1d3cca508ac0b53a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
use std::iter::once;
use std::str;
use std::convert::TryInto;

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 (QWERTY/accents/symbol/mobile)
/// Qwerty is a QWERTY-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,
    Qwerty,
    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_QWERTY    = 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(SwkbdState::default());
            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().try_into().unwrap()) {
                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)
    }
}