|
|
@ -4,10 +4,14 @@ |
|
|
|
#![doc(alias = "keyboard")] |
|
|
|
#![doc(alias = "keyboard")] |
|
|
|
|
|
|
|
|
|
|
|
use crate::services::{apt::Apt, gfx::Gfx}; |
|
|
|
use crate::services::{apt::Apt, gfx::Gfx}; |
|
|
|
use ctru_sys::{self, SwkbdState}; |
|
|
|
use ctru_sys::{ |
|
|
|
|
|
|
|
aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle, |
|
|
|
|
|
|
|
svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdExtra, |
|
|
|
|
|
|
|
SwkbdLearningData, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, |
|
|
|
|
|
|
|
NS_APPID, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
use bitflags::bitflags; |
|
|
|
use bitflags::bitflags; |
|
|
|
use libc; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use std::ffi::{CStr, CString}; |
|
|
|
use std::ffi::{CStr, CString}; |
|
|
|
use std::fmt::Display; |
|
|
|
use std::fmt::Display; |
|
|
@ -110,7 +114,7 @@ pub enum ButtonConfig { |
|
|
|
LeftMiddleRight = 3, |
|
|
|
LeftMiddleRight = 3, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Error returned by an unsuccessful [`SoftwareKeyboard::get_string()`].
|
|
|
|
/// Error returned by an unsuccessful [`SoftwareKeyboard::launch()`].
|
|
|
|
#[doc(alias = "SwkbdResult")] |
|
|
|
#[doc(alias = "SwkbdResult")] |
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)] |
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)] |
|
|
|
#[repr(i32)] |
|
|
|
#[repr(i32)] |
|
|
@ -207,6 +211,13 @@ bitflags! { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard.
|
|
|
|
|
|
|
|
// We only need this because libctru doesn't keep a pointer to the shared memory block in `SwkbdExtra` for whatever reason
|
|
|
|
|
|
|
|
struct MessageCallbackData { |
|
|
|
|
|
|
|
extra: *mut SwkbdExtra, |
|
|
|
|
|
|
|
swkbd_shared_mem_ptr: *mut libc::c_void, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl SoftwareKeyboard { |
|
|
|
impl SoftwareKeyboard { |
|
|
|
/// Initialize a new configuration for the Software Keyboard applet depending on how many "exit" buttons are available to the user (1, 2 or 3).
|
|
|
|
/// Initialize a new configuration for the Software Keyboard applet depending on how many "exit" buttons are available to the user (1, 2 or 3).
|
|
|
|
///
|
|
|
|
///
|
|
|
@ -241,11 +252,6 @@ impl SoftwareKeyboard { |
|
|
|
|
|
|
|
|
|
|
|
/// Launches the applet based on the given configuration and returns a string containing the text input.
|
|
|
|
/// Launches the applet based on the given configuration and returns a string containing the text input.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// # Notes
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// The text received from the keyboard will be truncated if it is longer than `max_bytes`.
|
|
|
|
|
|
|
|
/// Use [`SoftwareKeyboard::set_max_text_len()`] to make sure the buffer can contain the input text.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// # Example
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
@ -260,67 +266,15 @@ impl SoftwareKeyboard { |
|
|
|
/// use ctru::applets::swkbd::SoftwareKeyboard;
|
|
|
|
/// use ctru::applets::swkbd::SoftwareKeyboard;
|
|
|
|
/// let mut keyboard = SoftwareKeyboard::default();
|
|
|
|
/// let mut keyboard = SoftwareKeyboard::default();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// let (text, button) = keyboard.get_string(2048, &apt, &gfx)?;
|
|
|
|
/// let (text, button) = keyboard.launch(&apt, &gfx)?;
|
|
|
|
/// #
|
|
|
|
/// #
|
|
|
|
/// # Ok(())
|
|
|
|
/// # Ok(())
|
|
|
|
/// # }
|
|
|
|
/// # }
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
#[doc(alias = "swkbdInputText")] |
|
|
|
#[doc(alias = "swkbdInputText")] |
|
|
|
pub fn get_string( |
|
|
|
pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> { |
|
|
|
&mut self, |
|
|
|
let mut output = String::new(); |
|
|
|
max_bytes: usize, |
|
|
|
|
|
|
|
apt: &Apt, |
|
|
|
|
|
|
|
gfx: &Gfx, |
|
|
|
|
|
|
|
) -> Result<(String, 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.
|
|
|
|
|
|
|
|
let mut tmp = vec![0u8; max_bytes]; |
|
|
|
|
|
|
|
let button = self.write_exact(&mut tmp, apt, gfx)?; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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()) }; |
|
|
|
|
|
|
|
tmp.truncate(len); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let res = unsafe { String::from_utf8_unchecked(tmp) }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ok((res, button)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Fills the provided buffer with a UTF-8 encoded, NUL-terminated sequence of bytes from
|
|
|
|
|
|
|
|
/// this software keyboard.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// # Notes
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// If the buffer is too small to contain the entire sequence received from the keyboard,
|
|
|
|
|
|
|
|
/// the output will be truncated.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// # Example
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
|
|
/// # let _runner = test_runner::GdbRunner::default();
|
|
|
|
|
|
|
|
/// # use std::error::Error;
|
|
|
|
|
|
|
|
/// # fn main() -> Result<(), Box<dyn Error>> {
|
|
|
|
|
|
|
|
/// # use ctru::services::{apt::Apt, gfx::Gfx};
|
|
|
|
|
|
|
|
/// #
|
|
|
|
|
|
|
|
/// # let gfx = Gfx::new().unwrap();
|
|
|
|
|
|
|
|
/// # let apt = Apt::new().unwrap();
|
|
|
|
|
|
|
|
/// #
|
|
|
|
|
|
|
|
/// use ctru::applets::swkbd::SoftwareKeyboard;
|
|
|
|
|
|
|
|
/// let mut keyboard = SoftwareKeyboard::default();
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// let mut buffer = vec![0; 100];
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// let button = keyboard.write_exact(&mut buffer, &apt, &gfx)?;
|
|
|
|
|
|
|
|
/// #
|
|
|
|
|
|
|
|
/// # Ok(())
|
|
|
|
|
|
|
|
/// # }
|
|
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
|
|
#[doc(alias = "swkbdInputText")] |
|
|
|
|
|
|
|
pub fn write_exact(&mut self, buf: &mut [u8], _apt: &Apt, _gfx: &Gfx) -> Result<Button, Error> { |
|
|
|
|
|
|
|
unsafe { |
|
|
|
unsafe { |
|
|
|
// The filter callback gets reset every time the SoftwareKeyboard is used.
|
|
|
|
// The filter callback gets reset every time the SoftwareKeyboard is used.
|
|
|
|
ctru_sys::swkbdSetFilterCallback( |
|
|
|
ctru_sys::swkbdSetFilterCallback( |
|
|
@ -329,11 +283,11 @@ impl SoftwareKeyboard { |
|
|
|
(self as *mut Self).cast(), |
|
|
|
(self as *mut Self).cast(), |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
match ctru_sys::swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) { |
|
|
|
match self.swkbd_input_text(&mut output) { |
|
|
|
ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), |
|
|
|
ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), |
|
|
|
ctru_sys::SWKBD_BUTTON_LEFT => Ok(Button::Left), |
|
|
|
ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)), |
|
|
|
ctru_sys::SWKBD_BUTTON_MIDDLE => Ok(Button::Middle), |
|
|
|
ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)), |
|
|
|
ctru_sys::SWKBD_BUTTON_RIGHT => Ok(Button::Right), |
|
|
|
ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)), |
|
|
|
_ => unreachable!(), |
|
|
|
_ => unreachable!(), |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -654,7 +608,7 @@ impl SoftwareKeyboard { |
|
|
|
///
|
|
|
|
///
|
|
|
|
/// Keyboard input is converted from UTF-16 to UTF-8 before being handed to Rust,
|
|
|
|
/// 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
|
|
|
|
/// so this code point limit does not necessarily equal the max number of UTF-8 code points
|
|
|
|
/// receivable by [`SoftwareKeyboard::get_string()`] and [`SoftwareKeyboard::write_exact()`].
|
|
|
|
/// receivable by [`SoftwareKeyboard::launch()`].
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
///
|
|
|
@ -675,6 +629,293 @@ impl SoftwareKeyboard { |
|
|
|
// Activate the specific validation rule for maximum length.
|
|
|
|
// Activate the specific validation rule for maximum length.
|
|
|
|
self.state.valid_input = ValidInput::FixedLen.into(); |
|
|
|
self.state.valid_input = ValidInput::FixedLen.into(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to
|
|
|
|
|
|
|
|
// get text from the software keyboard and put it directly into a `String` without requiring
|
|
|
|
|
|
|
|
// an intermediate fixed-size buffer
|
|
|
|
|
|
|
|
fn swkbd_input_text(&mut self, output: &mut String) -> SwkbdButton { |
|
|
|
|
|
|
|
use ctru_sys::{ |
|
|
|
|
|
|
|
MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, |
|
|
|
|
|
|
|
SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0, |
|
|
|
|
|
|
|
SWKBD_D1_CLICK1, SWKBD_D2_CLICK0, SWKBD_D2_CLICK1, SWKBD_D2_CLICK2, |
|
|
|
|
|
|
|
SWKBD_FILTER_CALLBACK, SWKBD_OUTOFMEM, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let swkbd = self.state.as_mut(); |
|
|
|
|
|
|
|
let mut extra = unsafe { swkbd.__bindgen_anon_1.extra }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate shared mem size
|
|
|
|
|
|
|
|
let mut shared_mem_size = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shared_mem_size += (std::mem::size_of::<u16>() * (swkbd.max_text_len as usize + 1)) |
|
|
|
|
|
|
|
.next_multiple_of(std::mem::size_of::<usize>()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let dict_off = shared_mem_size; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shared_mem_size += (std::mem::size_of::<SwkbdDictWord>() * swkbd.dict_word_count as usize) |
|
|
|
|
|
|
|
.next_multiple_of(std::mem::size_of::<usize>()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let status_off = shared_mem_size; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shared_mem_size += if swkbd.initial_learning_offset >= 0 { |
|
|
|
|
|
|
|
std::mem::size_of::<SwkbdStatusData>() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
0 |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let learning_off = shared_mem_size; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shared_mem_size += if swkbd.initial_learning_offset >= 0 { |
|
|
|
|
|
|
|
std::mem::size_of::<SwkbdLearningData>() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
0 |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if swkbd.save_state_flags & (1 << 0) != 0 { |
|
|
|
|
|
|
|
swkbd.status_offset = shared_mem_size as _; |
|
|
|
|
|
|
|
shared_mem_size += std::mem::size_of::<SwkbdStatusData>(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if swkbd.save_state_flags & (1 << 1) != 0 { |
|
|
|
|
|
|
|
swkbd.learning_offset = shared_mem_size as _; |
|
|
|
|
|
|
|
shared_mem_size += std::mem::size_of::<SwkbdLearningData>(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shared_mem_size = shared_mem_size.next_multiple_of(0x1000); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
swkbd.shared_memory_size = shared_mem_size; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Allocate shared mem
|
|
|
|
|
|
|
|
let swkbd_shared_mem_ptr = unsafe { libc::memalign(0x1000, shared_mem_size) }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut swkbd_shared_mem_handle = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if swkbd_shared_mem_ptr.is_null() { |
|
|
|
|
|
|
|
swkbd.result = SWKBD_OUTOFMEM; |
|
|
|
|
|
|
|
return SWKBD_BUTTON_NONE; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let res = unsafe { |
|
|
|
|
|
|
|
svcCreateMemoryBlock( |
|
|
|
|
|
|
|
&mut swkbd_shared_mem_handle, |
|
|
|
|
|
|
|
swkbd_shared_mem_ptr as _, |
|
|
|
|
|
|
|
shared_mem_size as _, |
|
|
|
|
|
|
|
MEMPERM_READ | MEMPERM_WRITE, |
|
|
|
|
|
|
|
MEMPERM_READ | MEMPERM_WRITE, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if R_FAILED(res) { |
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
libc::free(swkbd_shared_mem_ptr); |
|
|
|
|
|
|
|
swkbd.result = SWKBD_OUTOFMEM; |
|
|
|
|
|
|
|
return SWKBD_BUTTON_NONE; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Copy stuff to shared mem
|
|
|
|
|
|
|
|
if !extra.initial_text.is_null() { |
|
|
|
|
|
|
|
swkbd.initial_text_offset = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
let utf16_iter = |
|
|
|
|
|
|
|
str::from_utf8_unchecked(CStr::from_ptr(extra.initial_text).to_bytes()) |
|
|
|
|
|
|
|
.encode_utf16() |
|
|
|
|
|
|
|
.take(swkbd.max_text_len as _) |
|
|
|
|
|
|
|
.chain(once(0)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut initial_text_cursor = swkbd_shared_mem_ptr.cast(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for code_unit in utf16_iter { |
|
|
|
|
|
|
|
*initial_text_cursor = code_unit; |
|
|
|
|
|
|
|
initial_text_cursor = initial_text_cursor.add(1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if !extra.dict.is_null() { |
|
|
|
|
|
|
|
swkbd.dict_offset = dict_off as _; |
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
std::ptr::copy_nonoverlapping( |
|
|
|
|
|
|
|
extra.dict, |
|
|
|
|
|
|
|
swkbd_shared_mem_ptr.add(dict_off).cast(), |
|
|
|
|
|
|
|
swkbd.dict_word_count as _, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if swkbd.initial_status_offset >= 0 { |
|
|
|
|
|
|
|
swkbd.initial_status_offset = status_off as _; |
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
std::ptr::copy_nonoverlapping( |
|
|
|
|
|
|
|
extra.status_data, |
|
|
|
|
|
|
|
swkbd_shared_mem_ptr.add(status_off).cast(), |
|
|
|
|
|
|
|
1, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if swkbd.initial_learning_offset >= 0 { |
|
|
|
|
|
|
|
swkbd.initial_learning_offset = learning_off as _; |
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
std::ptr::copy_nonoverlapping( |
|
|
|
|
|
|
|
extra.learning_data, |
|
|
|
|
|
|
|
swkbd_shared_mem_ptr.add(learning_off).cast(), |
|
|
|
|
|
|
|
1, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if extra.callback.is_some() { |
|
|
|
|
|
|
|
swkbd.filter_flags |= SWKBD_FILTER_CALLBACK; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
swkbd.filter_flags &= !SWKBD_FILTER_CALLBACK; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Launch swkbd
|
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
swkbd.__bindgen_anon_1.reserved.fill(0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut callback_data = MessageCallbackData { |
|
|
|
|
|
|
|
extra: std::ptr::addr_of_mut!(extra), |
|
|
|
|
|
|
|
swkbd_shared_mem_ptr, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if extra.callback.is_some() { |
|
|
|
|
|
|
|
aptSetMessageCallback( |
|
|
|
|
|
|
|
Some(Self::swkbd_message_callback), |
|
|
|
|
|
|
|
std::ptr::addr_of_mut!(callback_data).cast(), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
aptLaunchLibraryApplet( |
|
|
|
|
|
|
|
APPID_SOFTWARE_KEYBOARD, |
|
|
|
|
|
|
|
(swkbd as *mut SwkbdState).cast(), |
|
|
|
|
|
|
|
std::mem::size_of::<SwkbdState>(), |
|
|
|
|
|
|
|
swkbd_shared_mem_handle, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if extra.callback.is_some() { |
|
|
|
|
|
|
|
aptSetMessageCallback(None, std::ptr::null_mut()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let _ = svcCloseHandle(swkbd_shared_mem_handle); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let button = match swkbd.result { |
|
|
|
|
|
|
|
SWKBD_D1_CLICK0 | SWKBD_D2_CLICK0 => SWKBD_BUTTON_LEFT, |
|
|
|
|
|
|
|
SWKBD_D2_CLICK1 => SWKBD_BUTTON_MIDDLE, |
|
|
|
|
|
|
|
SWKBD_D0_CLICK | SWKBD_D1_CLICK1 | SWKBD_D2_CLICK2 => SWKBD_BUTTON_RIGHT, |
|
|
|
|
|
|
|
_ => SWKBD_BUTTON_NONE, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if swkbd.text_length > 0 { |
|
|
|
|
|
|
|
let text16 = unsafe { |
|
|
|
|
|
|
|
widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( |
|
|
|
|
|
|
|
swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(), |
|
|
|
|
|
|
|
swkbd.text_length as _, |
|
|
|
|
|
|
|
)) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*output = text16.to_string(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if swkbd.save_state_flags & (1 << 0) != 0 { |
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
std::ptr::copy_nonoverlapping( |
|
|
|
|
|
|
|
swkbd_shared_mem_ptr.add(swkbd.status_offset as _).cast(), |
|
|
|
|
|
|
|
extra.status_data, |
|
|
|
|
|
|
|
1, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if swkbd.save_state_flags & (1 << 1) != 0 { |
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
std::ptr::copy_nonoverlapping( |
|
|
|
|
|
|
|
swkbd_shared_mem_ptr.add(swkbd.learning_offset as _).cast(), |
|
|
|
|
|
|
|
extra.learning_data, |
|
|
|
|
|
|
|
1, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsafe { libc::free(swkbd_shared_mem_ptr) }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
button |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`.
|
|
|
|
|
|
|
|
// This function sets up and then calls the callback set by `swkbdSetFilterCallback`
|
|
|
|
|
|
|
|
#[deny(unsafe_op_in_unsafe_fn)] |
|
|
|
|
|
|
|
unsafe extern "C" fn swkbd_message_callback( |
|
|
|
|
|
|
|
user: *mut libc::c_void, |
|
|
|
|
|
|
|
sender: NS_APPID, |
|
|
|
|
|
|
|
msg: *mut libc::c_void, |
|
|
|
|
|
|
|
msg_size: libc::size_t, |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
let data = unsafe { &mut *user.cast::<MessageCallbackData>() }; |
|
|
|
|
|
|
|
let swkbd = unsafe { &mut *msg.cast::<SwkbdState>() }; |
|
|
|
|
|
|
|
let extra = unsafe { &mut *data.extra }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD |
|
|
|
|
|
|
|
|| msg_size != std::mem::size_of::<SwkbdState>() |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let text16 = unsafe { |
|
|
|
|
|
|
|
widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( |
|
|
|
|
|
|
|
data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(), |
|
|
|
|
|
|
|
swkbd.text_length as usize + 1, |
|
|
|
|
|
|
|
)) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let text8 = text16.to_string(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut retmsg = std::ptr::null(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(cb) = extra.callback { |
|
|
|
|
|
|
|
swkbd.callback_result = unsafe { |
|
|
|
|
|
|
|
cb( |
|
|
|
|
|
|
|
extra.callback_user, |
|
|
|
|
|
|
|
&mut retmsg, |
|
|
|
|
|
|
|
text8.as_ptr(), |
|
|
|
|
|
|
|
text8.len(), |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} as _ |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let retmsg = if !retmsg.is_null() { |
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
let len = libc::strlen(retmsg) + 1; |
|
|
|
|
|
|
|
std::str::from_utf8_unchecked(std::slice::from_raw_parts(retmsg, len)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
"\0" |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let callback_msg = &mut swkbd.callback_msg; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (idx, code_unit) in retmsg |
|
|
|
|
|
|
|
.encode_utf16() |
|
|
|
|
|
|
|
.take(callback_msg.len() - 1) |
|
|
|
|
|
|
|
.enumerate() |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
callback_msg[idx] = code_unit; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let _ = unsafe { |
|
|
|
|
|
|
|
APT_SendParameter( |
|
|
|
|
|
|
|
envGetAptAppId(), |
|
|
|
|
|
|
|
sender, |
|
|
|
|
|
|
|
APTCMD_MESSAGE, |
|
|
|
|
|
|
|
(swkbd as *mut SwkbdState).cast(), |
|
|
|
|
|
|
|
std::mem::size_of::<SwkbdState>() as _, |
|
|
|
|
|
|
|
0, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl ParentalLock { |
|
|
|
impl ParentalLock { |
|
|
|