Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion ctru-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ pthread-3ds = { workspace = true }
libc = { workspace = true, default-features = true }
bitflags = "2.6.0"
macaddr = "1.0.1"
widestring = "1.1.0"

[build-dependencies]
toml = "0.9.4"
Expand Down
64 changes: 23 additions & 41 deletions ctru-rs/src/applets/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//!
//! This applet displays error text as a pop-up message on the lower screen.

use crate::Utf16Writer;
use crate::services::{apt::Apt, gfx::Gfx};

use ctru_sys::{errorConf, errorDisp, errorInit};
Expand Down Expand Up @@ -51,22 +52,31 @@ impl PopUp {
Self { state }
}

/// Sets the error text to display.
/// Returns a [`Utf16Writer`] that writes its output to the [`PopUp`]'s internal text buffer.
///
/// # Notes
///
/// The text will be converted to UTF-16 for display with the applet, and the message will be truncated if it exceeds
/// 1900 UTF-16 code units in length after conversion.
/// The input will be converted to UTF-16 for display with the applet, and the message will be
/// truncated if it exceeds 1900 UTF-16 code units in length after conversion.
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # fn main() {
/// #
/// use ctru::applets::error::{PopUp, WordWrap};
/// use std::fmt::Write;
///
/// let mut popup = PopUp::new(WordWrap::Enabled);
///
/// let _ = write!(popup.writer(), "Look mom, I'm a custom error message!");
/// #
/// # }
/// ```
#[doc(alias = "errorText")]
pub fn set_text(&mut self, text: &str) {
for (idx, code_unit) in text
.encode_utf16()
.take(self.state.Text.len() - 1)
.chain(std::iter::once(0))
.enumerate()
{
self.state.Text[idx] = code_unit;
}
pub fn writer(&mut self) -> Utf16Writer<'_> {
Utf16Writer::new(&mut self.state.Text)
}

/// Launches the error applet.
Expand Down Expand Up @@ -94,31 +104,6 @@ impl PopUp {
}
}

struct ErrorConfWriter<'a> {
error_conf: &'a mut errorConf,
index: usize,
}

impl std::fmt::Write for ErrorConfWriter<'_> {
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
let max = self.error_conf.Text.len() - 1;

for code_unit in s.encode_utf16() {
if self.index == max {
self.error_conf.Text[self.index] = 0;
return Err(std::fmt::Error);
} else {
self.error_conf.Text[self.index] = code_unit;
self.index += 1;
}
}

self.error_conf.Text[self.index] = 0;

Ok(())
}
}

pub(crate) fn set_panic_hook(call_old_hook: bool) {
use crate::services::gfx::GFX_ACTIVE;
use std::fmt::Write;
Expand All @@ -144,10 +129,7 @@ pub(crate) fn set_panic_hook(call_old_hook: bool) {

let error_conf = &mut *lock;

let mut writer = ErrorConfWriter {
error_conf,
index: 0,
};
let mut writer = Utf16Writer::new(&mut error_conf.Text);

let thread = std::thread::current();

Expand Down
71 changes: 25 additions & 46 deletions ctru-rs/src/applets/swkbd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
//! This applet opens a virtual keyboard on the console's bottom screen which lets the user write UTF-16 valid text.
#![doc(alias = "keyboard")]

use crate::Utf16Writer;
use crate::services::{apt::Apt, gfx::Gfx};

use ctru_sys::{
APPID_SOFTWARE_KEYBOARD, APT_SendParameter, APTCMD_MESSAGE, NS_APPID, SwkbdButton,
SwkbdDictWord, SwkbdLearningData, SwkbdState, SwkbdStatusData, aptLaunchLibraryApplet,
Expand All @@ -13,8 +15,7 @@ use ctru_sys::{
use bitflags::bitflags;

use std::borrow::Cow;
use std::fmt::Display;
use std::iter::once;
use std::fmt::{Display, Write};
use std::str;

type CallbackFunction = dyn FnMut(&str) -> CallbackResult;
Expand Down Expand Up @@ -445,14 +446,9 @@ impl SoftwareKeyboard {
#[doc(alias = "swkbdSetHintText")]
pub fn set_hint_text(&mut self, text: Option<&str>) {
if let Some(text) = text {
for (idx, code_unit) in text
.encode_utf16()
.take(self.state.hint_text.len() - 1)
.chain(once(0))
.enumerate()
{
self.state.hint_text[idx] = code_unit;
}
let mut writer = Utf16Writer::new(&mut self.state.hint_text);

let _ = writer.write_str(text);
} else {
self.state.hint_text[0] = 0;
}
Expand Down Expand Up @@ -551,14 +547,9 @@ impl SoftwareKeyboard {
pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) {
let button_text = &mut self.state.button_text[button as usize];

for (idx, code_unit) in text
.encode_utf16()
.take(button_text.len() - 1)
.chain(once(0))
.enumerate()
{
button_text[idx] = code_unit;
}
let mut writer = Utf16Writer::new(button_text);

let _ = writer.write_str(text);

self.state.button_submits_text[button as usize] = submit;
}
Expand Down Expand Up @@ -678,20 +669,13 @@ impl SoftwareKeyboard {

// Copy stuff to shared mem
if let Some(initial_text) = self.initial_text.as_deref() {
swkbd.initial_text_offset = 0;

let mut initial_text_cursor = swkbd_shared_mem_ptr.cast();

for code_unit in initial_text
.encode_utf16()
.take(swkbd.max_text_len as _)
.chain(once(0))
{
unsafe {
*initial_text_cursor = code_unit;
initial_text_cursor = initial_text_cursor.add(1);
}
}
let slice = unsafe {
std::slice::from_raw_parts_mut(swkbd_shared_mem_ptr.cast(), initial_text.len())
};

let mut writer = Utf16Writer::new(slice);

let _ = writer.write_str(initial_text);
}

if !extra.dict.is_null() {
Expand Down Expand Up @@ -772,13 +756,13 @@ impl SoftwareKeyboard {

if swkbd.text_length > 0 {
let text16 = unsafe {
widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts(
std::slice::from_raw_parts(
swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(),
swkbd.text_length as _,
))
)
};

*output = text16.to_string();
*output = String::from_utf16(text16).unwrap();
}

if swkbd.save_state_flags & (1 << 0) != 0 {
Expand Down Expand Up @@ -825,27 +809,22 @@ impl SoftwareKeyboard {
let data = unsafe { &*user.cast::<MessageCallbackData>() };

let text16 = unsafe {
widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts(
std::slice::from_raw_parts(
data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(),
swkbd.text_length as _,
))
)
};

let text8 = text16.to_string();
let text8 = String::from_utf16(text16).unwrap();

let result = unsafe { &mut **data.filter_callback }(&text8);

swkbd.callback_result = result.discriminant().into();

if let CallbackResult::Retry(msg) | CallbackResult::Close(msg) = result {
for (idx, code_unit) in msg
.encode_utf16()
.take(swkbd.callback_msg.len() - 1)
.chain(once(0))
.enumerate()
{
swkbd.callback_msg[idx] = code_unit;
}
let mut writer = Utf16Writer::new(&mut swkbd.callback_msg);

let _ = writer.write_str(&msg);
}

let _ = unsafe {
Expand Down
44 changes: 44 additions & 0 deletions ctru-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,47 @@ pub use crate::error::{Error, Result};
pub fn set_panic_hook(call_old_hook: bool) {
crate::applets::error::set_panic_hook(call_old_hook);
}

/// A helper type for writing string data into `[u16]` buffers.
///
/// This type is mainly useful for interop with `libctru` APIs that expect UTF-16 text as input. The writer implements the
/// [`std::fmt::Write`](https://doc.rust-lang.org/std/fmt/trait.Write.html) trait and ensures that the text is written in-bounds and properly nul-terminated.
///
/// # Notes
///
/// Subsequent writes to the same `Utf16Writer` will append to the buffer instead of overwriting the existing contents. If you want to start over from the
/// beginning of the buffer, simply create a new `Utf16Writer`.
///
/// If a write causes the buffer to reach the end of its capacity, `std::fmt::Error` will be returned, but all string data up until the end of the capacity will
/// still be written.
pub struct Utf16Writer<'a> {
buf: &'a mut [u16],
index: usize,
}

impl Utf16Writer<'_> {
/// Creates a new [Utf16Writer] that writes its output into the provided buffer.
pub fn new(buf: &mut [u16]) -> Utf16Writer<'_> {
Utf16Writer { buf, index: 0 }
}
}

impl std::fmt::Write for Utf16Writer<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
let max = self.buf.len() - 1;

for code_unit in s.encode_utf16() {
if self.index == max {
self.buf[self.index] = 0;
return Err(std::fmt::Error);
} else {
self.buf[self.index] = code_unit;
self.index += 1;
}
}

self.buf[self.index] = 0;

Ok(())
}
}
Loading