Skip to content

Commit 92ee28f

Browse files
authored
Merge pull request #232 from FenrirWolf/utf16_writer
Add helper type for writing to UTF-16 buffers
2 parents 17373fd + 6df1731 commit 92ee28f

File tree

4 files changed

+92
-88
lines changed

4 files changed

+92
-88
lines changed

ctru-rs/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ pthread-3ds = { workspace = true }
2424
libc = { workspace = true, default-features = true }
2525
bitflags = "2.6.0"
2626
macaddr = "1.0.1"
27-
widestring = "1.1.0"
2827

2928
[build-dependencies]
3029
toml = "0.9.4"

ctru-rs/src/applets/error.rs

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! This applet displays error text as a pop-up message on the lower screen.
44
5+
use crate::Utf16Writer;
56
use crate::services::{apt::Apt, gfx::Gfx};
67

78
use ctru_sys::{errorConf, errorDisp, errorInit};
@@ -51,22 +52,31 @@ impl PopUp {
5152
Self { state }
5253
}
5354

54-
/// Sets the error text to display.
55+
/// Returns a [`Utf16Writer`] that writes its output to the [`PopUp`]'s internal text buffer.
5556
///
5657
/// # Notes
5758
///
58-
/// The text will be converted to UTF-16 for display with the applet, and the message will be truncated if it exceeds
59-
/// 1900 UTF-16 code units in length after conversion.
59+
/// The input will be converted to UTF-16 for display with the applet, and the message will be
60+
/// truncated if it exceeds 1900 UTF-16 code units in length after conversion.
61+
///
62+
/// # Example
63+
///
64+
/// ```
65+
/// # let _runner = test_runner::GdbRunner::default();
66+
/// # fn main() {
67+
/// #
68+
/// use ctru::applets::error::{PopUp, WordWrap};
69+
/// use std::fmt::Write;
70+
///
71+
/// let mut popup = PopUp::new(WordWrap::Enabled);
72+
///
73+
/// let _ = write!(popup.writer(), "Look mom, I'm a custom error message!");
74+
/// #
75+
/// # }
76+
/// ```
6077
#[doc(alias = "errorText")]
61-
pub fn set_text(&mut self, text: &str) {
62-
for (idx, code_unit) in text
63-
.encode_utf16()
64-
.take(self.state.Text.len() - 1)
65-
.chain(std::iter::once(0))
66-
.enumerate()
67-
{
68-
self.state.Text[idx] = code_unit;
69-
}
78+
pub fn writer(&mut self) -> Utf16Writer<'_> {
79+
Utf16Writer::new(&mut self.state.Text)
7080
}
7181

7282
/// Launches the error applet.
@@ -94,31 +104,6 @@ impl PopUp {
94104
}
95105
}
96106

97-
struct ErrorConfWriter<'a> {
98-
error_conf: &'a mut errorConf,
99-
index: usize,
100-
}
101-
102-
impl std::fmt::Write for ErrorConfWriter<'_> {
103-
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
104-
let max = self.error_conf.Text.len() - 1;
105-
106-
for code_unit in s.encode_utf16() {
107-
if self.index == max {
108-
self.error_conf.Text[self.index] = 0;
109-
return Err(std::fmt::Error);
110-
} else {
111-
self.error_conf.Text[self.index] = code_unit;
112-
self.index += 1;
113-
}
114-
}
115-
116-
self.error_conf.Text[self.index] = 0;
117-
118-
Ok(())
119-
}
120-
}
121-
122107
pub(crate) fn set_panic_hook(call_old_hook: bool) {
123108
use crate::services::gfx::GFX_ACTIVE;
124109
use std::fmt::Write;
@@ -144,10 +129,7 @@ pub(crate) fn set_panic_hook(call_old_hook: bool) {
144129

145130
let error_conf = &mut *lock;
146131

147-
let mut writer = ErrorConfWriter {
148-
error_conf,
149-
index: 0,
150-
};
132+
let mut writer = Utf16Writer::new(&mut error_conf.Text);
151133

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

ctru-rs/src/applets/swkbd.rs

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
//! This applet opens a virtual keyboard on the console's bottom screen which lets the user write UTF-16 valid text.
44
#![doc(alias = "keyboard")]
55

6+
use crate::Utf16Writer;
67
use crate::services::{apt::Apt, gfx::Gfx};
8+
79
use ctru_sys::{
810
APPID_SOFTWARE_KEYBOARD, APT_SendParameter, APTCMD_MESSAGE, NS_APPID, SwkbdButton,
911
SwkbdDictWord, SwkbdLearningData, SwkbdState, SwkbdStatusData, aptLaunchLibraryApplet,
@@ -13,8 +15,7 @@ use ctru_sys::{
1315
use bitflags::bitflags;
1416

1517
use std::borrow::Cow;
16-
use std::fmt::Display;
17-
use std::iter::once;
18+
use std::fmt::{Display, Write};
1819
use std::str;
1920

2021
type CallbackFunction = dyn FnMut(&str) -> CallbackResult;
@@ -445,14 +446,9 @@ impl SoftwareKeyboard {
445446
#[doc(alias = "swkbdSetHintText")]
446447
pub fn set_hint_text(&mut self, text: Option<&str>) {
447448
if let Some(text) = text {
448-
for (idx, code_unit) in text
449-
.encode_utf16()
450-
.take(self.state.hint_text.len() - 1)
451-
.chain(once(0))
452-
.enumerate()
453-
{
454-
self.state.hint_text[idx] = code_unit;
455-
}
449+
let mut writer = Utf16Writer::new(&mut self.state.hint_text);
450+
451+
let _ = writer.write_str(text);
456452
} else {
457453
self.state.hint_text[0] = 0;
458454
}
@@ -551,14 +547,9 @@ impl SoftwareKeyboard {
551547
pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) {
552548
let button_text = &mut self.state.button_text[button as usize];
553549

554-
for (idx, code_unit) in text
555-
.encode_utf16()
556-
.take(button_text.len() - 1)
557-
.chain(once(0))
558-
.enumerate()
559-
{
560-
button_text[idx] = code_unit;
561-
}
550+
let mut writer = Utf16Writer::new(button_text);
551+
552+
let _ = writer.write_str(text);
562553

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

679670
// Copy stuff to shared mem
680671
if let Some(initial_text) = self.initial_text.as_deref() {
681-
swkbd.initial_text_offset = 0;
682-
683-
let mut initial_text_cursor = swkbd_shared_mem_ptr.cast();
684-
685-
for code_unit in initial_text
686-
.encode_utf16()
687-
.take(swkbd.max_text_len as _)
688-
.chain(once(0))
689-
{
690-
unsafe {
691-
*initial_text_cursor = code_unit;
692-
initial_text_cursor = initial_text_cursor.add(1);
693-
}
694-
}
672+
let slice = unsafe {
673+
std::slice::from_raw_parts_mut(swkbd_shared_mem_ptr.cast(), initial_text.len())
674+
};
675+
676+
let mut writer = Utf16Writer::new(slice);
677+
678+
let _ = writer.write_str(initial_text);
695679
}
696680

697681
if !extra.dict.is_null() {
@@ -772,13 +756,13 @@ impl SoftwareKeyboard {
772756

773757
if swkbd.text_length > 0 {
774758
let text16 = unsafe {
775-
widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts(
759+
std::slice::from_raw_parts(
776760
swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(),
777761
swkbd.text_length as _,
778-
))
762+
)
779763
};
780764

781-
*output = text16.to_string();
765+
*output = String::from_utf16(text16).unwrap();
782766
}
783767

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

827811
let text16 = unsafe {
828-
widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts(
812+
std::slice::from_raw_parts(
829813
data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(),
830814
swkbd.text_length as _,
831-
))
815+
)
832816
};
833817

834-
let text8 = text16.to_string();
818+
let text8 = String::from_utf16(text16).unwrap();
835819

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

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

840824
if let CallbackResult::Retry(msg) | CallbackResult::Close(msg) = result {
841-
for (idx, code_unit) in msg
842-
.encode_utf16()
843-
.take(swkbd.callback_msg.len() - 1)
844-
.chain(once(0))
845-
.enumerate()
846-
{
847-
swkbd.callback_msg[idx] = code_unit;
848-
}
825+
let mut writer = Utf16Writer::new(&mut swkbd.callback_msg);
826+
827+
let _ = writer.write_str(&msg);
849828
}
850829

851830
let _ = unsafe {

ctru-rs/src/lib.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,47 @@ pub use crate::error::{Error, Result};
8484
pub fn set_panic_hook(call_old_hook: bool) {
8585
crate::applets::error::set_panic_hook(call_old_hook);
8686
}
87+
88+
/// A helper type for writing string data into `[u16]` buffers.
89+
///
90+
/// This type is mainly useful for interop with `libctru` APIs that expect UTF-16 text as input. The writer implements the
91+
/// [`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.
92+
///
93+
/// # Notes
94+
///
95+
/// 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
96+
/// beginning of the buffer, simply create a new `Utf16Writer`.
97+
///
98+
/// 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
99+
/// still be written.
100+
pub struct Utf16Writer<'a> {
101+
buf: &'a mut [u16],
102+
index: usize,
103+
}
104+
105+
impl Utf16Writer<'_> {
106+
/// Creates a new [Utf16Writer] that writes its output into the provided buffer.
107+
pub fn new(buf: &mut [u16]) -> Utf16Writer<'_> {
108+
Utf16Writer { buf, index: 0 }
109+
}
110+
}
111+
112+
impl std::fmt::Write for Utf16Writer<'_> {
113+
fn write_str(&mut self, s: &str) -> std::fmt::Result {
114+
let max = self.buf.len() - 1;
115+
116+
for code_unit in s.encode_utf16() {
117+
if self.index == max {
118+
self.buf[self.index] = 0;
119+
return Err(std::fmt::Error);
120+
} else {
121+
self.buf[self.index] = code_unit;
122+
self.index += 1;
123+
}
124+
}
125+
126+
self.buf[self.index] = 0;
127+
128+
Ok(())
129+
}
130+
}

0 commit comments

Comments
 (0)