Compare commits

..

1 Commits

Author SHA1 Message Date
Ian Chamberlain 892a29b039
Test CI with vulkan runner 10 months ago
  1. 9
      .github/workflows/ci.yml
  2. 2
      Cargo.toml
  3. 9
      ctru-rs/Cargo.toml
  4. 301
      ctru-rs/examples/local-networking.rs
  5. 12
      ctru-rs/examples/software-keyboard.rs
  6. 153
      ctru-rs/src/applets/error.rs
  7. 1
      ctru-rs/src/applets/mod.rs
  8. 237
      ctru-rs/src/applets/swkbd.rs
  9. 1
      ctru-rs/src/console.rs
  10. 2
      ctru-rs/src/lib.rs
  11. 2
      ctru-rs/src/linear.rs
  12. 5
      ctru-rs/src/services/am.rs
  13. 87
      ctru-rs/src/services/apt.rs
  14. 2
      ctru-rs/src/services/gfx.rs
  15. 4
      ctru-rs/src/services/ir_user.rs
  16. 1
      ctru-rs/src/services/mod.rs
  17. 1
      ctru-rs/src/services/ndsp/mod.rs
  18. 13
      ctru-rs/src/services/svc.rs
  19. 1382
      ctru-rs/src/services/uds.rs
  20. 6
      ctru-sys/Cargo.toml
  21. 4
      ctru-sys/build.rs
  22. 18
      ctru-sys/src/lib.rs

9
.github/workflows/ci.yml

@ -43,16 +43,11 @@ jobs: @@ -43,16 +43,11 @@ jobs:
- name: Check formatting
run: cargo fmt --all --verbose -- --check
# We always run the next two steps, so that even if formatting fails we
# still get compilation errors for the same run (mainly for matchers).
- name: Cargo check ctru-sys (without tests)
run: cargo 3ds clippy --package ctru-sys --color=always --verbose
if: success() || failure()
- name: Cargo check ctru-rs (including tests)
run: cargo 3ds clippy --package ctru-rs --color=always --verbose --all-targets
if: success() || failure()
test:
strategy:
@ -83,12 +78,12 @@ jobs: @@ -83,12 +78,12 @@ jobs:
run: cargo 3ds test --no-run --tests
- name: Run lib and integration tests
uses: rust3ds/test-runner/run-tests@v1
uses: rust3ds/test-runner/run-tests@98a8ed44057c2100447f0f6547c0de1e14be8022
with:
args: --tests
- name: Build and run doc tests
uses: rust3ds/test-runner/run-tests@v1
uses: rust3ds/test-runner/run-tests@98a8ed44057c2100447f0f6547c0de1e14be8022
with:
args: --doc

2
Cargo.toml

@ -3,7 +3,7 @@ members = ["ctru-rs", "ctru-sys"] @@ -3,7 +3,7 @@ members = ["ctru-rs", "ctru-sys"]
default-members = ["ctru-rs", "ctru-sys"]
resolver = "2"
[patch.'https://git.xenua.me/rust3ds/ctru-rs']
[patch.'https://github.com/rust3ds/ctru-rs']
# Make sure all dependencies use the local packages. This is needed for things
# like pthread-3ds that rely on ctru-sys, and test-runner which relies on ctru-rs
ctru-rs = { path = "ctru-rs" }

9
ctru-rs/Cargo.toml

@ -3,7 +3,7 @@ name = "ctru-rs" @@ -3,7 +3,7 @@ name = "ctru-rs"
version = "0.7.1"
authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"]
description = "A safe wrapper around libctru"
repository = "https://git.xenua.me/rust3ds/ctru-rs"
repository = "https://github.com/rust3ds/ctru-rs"
documentation = "https://rust3ds.github.io/ctru-rs/crates/ctru"
keywords = ["3ds", "libctru"]
categories = ["os", "api-bindings", "hardware-support"]
@ -20,11 +20,10 @@ name = "ctru" @@ -20,11 +20,10 @@ name = "ctru"
cfg-if = "1.0"
ctru-sys = { path = "../ctru-sys", version = "0.5.0" }
const-zero = "0.1.0"
shim-3ds = { git = "https://git.xenua.me/rust3ds/shim-3ds.git" }
pthread-3ds = { git = "https://git.xenua.me/rust3ds/pthread-3ds.git" }
shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" }
pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" }
libc = "0.2.121"
bitflags = "2.3.3"
macaddr = "1.0.1"
widestring = "1.0.2"
[build-dependencies]
@ -36,7 +35,7 @@ cfg-if = "1.0.0" @@ -36,7 +35,7 @@ cfg-if = "1.0.0"
ferris-says = "0.2.1"
futures = "0.3"
lewton = "0.10.2"
test-runner = { git = "https://git.xenua.me/rust3ds/test-runner.git" }
test-runner = { git = "https://github.com/rust3ds/test-runner.git" }
time = "0.3.7"
tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] }

301
ctru-rs/examples/local-networking.rs

@ -1,301 +0,0 @@ @@ -1,301 +0,0 @@
//! Local networking example.
//!
//! This example showcases local networking using the UDS module.
use ctru::prelude::*;
use ctru::services::uds::*;
fn handle_status_event(uds: &Uds, prev_node_mask: u16) -> ctru::Result<u16> {
println!("Connection status event signalled");
let status = uds.connection_status()?;
println!("Status: {status:#02X?}");
let left = prev_node_mask & (status.node_bitmask() ^ prev_node_mask);
let joined = status.node_bitmask() & (status.node_bitmask() ^ prev_node_mask);
for i in 0..16 {
if left & (1 << i) != 0 {
println!("Node {} disconnected", i + 1);
}
}
for i in 0..16 {
if joined & (1 << i) != 0 {
println!(
"Node {} connected: {:?}",
i + 1,
uds.node_info(NodeID::Node(i + 1))
);
}
}
Ok(status.node_bitmask())
}
fn main() -> Result<(), Error> {
let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap();
let console = Console::new(gfx.top_screen.borrow_mut());
println!("Local networking demo");
let mut uds = Uds::new(None).unwrap();
println!("UDS initialised");
enum State {
Initialised,
Scanning,
DrawList,
List,
Connect,
Connected,
Create,
Created,
}
let mut state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
let mut networks = vec![];
let mut selected_network = 0;
let mut mode = ConnectionType::Client;
let mut channel = 0;
let data_channel = 1;
let mut prev_node_mask = 0;
while apt.main_loop() {
gfx.wait_for_vblank();
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
match state {
State::Initialised => {
if hid.keys_down().contains(KeyPad::A) {
state = State::Scanning;
console.clear();
prev_node_mask = 0;
} else if hid.keys_down().contains(KeyPad::B) {
state = State::Create;
console.clear();
prev_node_mask = 0;
}
}
State::Scanning => {
println!("Scanning...");
let nwks = uds.scan(b"HBW\x10", None, None);
match nwks {
Ok(n) => {
if n.is_empty() {
state = State::Initialised;
console.clear();
println!("Scanned successfully; no networks found");
println!("Press A to start scanning or B to create a new network");
} else {
networks = n;
selected_network = 0;
state = State::DrawList;
}
}
Err(e) => {
state = State::Initialised;
console.clear();
eprintln!("Error while scanning: {e}");
println!("Press A to start scanning or B to create a new network");
}
}
}
State::DrawList => {
console.clear();
println!(
"Scanned successfully; {} network{} found",
networks.len(),
if networks.len() == 1 { "" } else { "s" }
);
println!("D-Pad to select, A to connect as client, R + A to connect as spectator, B to create a new network");
for (index, n) in networks.iter().enumerate() {
println!(
"{} Username: {}",
if index == selected_network { ">" } else { " " },
n.nodes()[0].unwrap().username()
);
}
state = State::List;
}
State::List => {
if hid.keys_down().contains(KeyPad::UP) && selected_network > 0 {
selected_network -= 1;
state = State::DrawList;
} else if hid.keys_down().contains(KeyPad::DOWN)
&& selected_network < networks.len() - 1
{
selected_network += 1;
state = State::DrawList;
} else if hid.keys_down().contains(KeyPad::A) {
state = State::Connect;
mode = if hid.keys_held().contains(KeyPad::R) {
ConnectionType::Spectator
} else {
ConnectionType::Client
};
} else if hid.keys_down().contains(KeyPad::B) {
state = State::Create;
}
}
State::Connect => {
let appdata = uds.network_appdata(&networks[selected_network], None)?;
println!("App data: {:02X?}", appdata);
if let Err(e) = uds.connect_network(
&networks[selected_network],
b"udsdemo passphrase c186093cd2652741\0",
mode,
data_channel,
) {
console.clear();
eprintln!("Error while connecting to network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
} else {
channel = uds.channel()?;
println!("Connected using channel {}", channel);
let appdata = uds.appdata(None)?;
println!("App data: {:02X?}", appdata);
if uds.wait_status_event(false, false)? {
prev_node_mask = handle_status_event(&uds, prev_node_mask)?;
}
println!("Press A to stop data transfer");
state = State::Connected;
}
}
State::Connected => {
let packet = uds.pull_packet();
match packet {
Ok(p) => {
if let Some((pkt, node)) = p {
println!(
"{:02X}{:02X}{:02X}{:02X} from {:?}",
pkt[0], pkt[1], pkt[2], pkt[3], node
);
}
if uds.wait_status_event(false, false)? {
prev_node_mask = handle_status_event(&uds, prev_node_mask)?;
}
if hid.keys_down().contains(KeyPad::A) {
uds.disconnect_network()?;
state = State::Initialised;
console.clear();
println!("Press A to start scanning or B to create a new network");
} else if !hid.keys_down().is_empty() || !hid.keys_up().is_empty() {
let transfer_data = hid.keys_held().bits();
if mode != ConnectionType::Spectator {
uds.send_packet(
&transfer_data.to_le_bytes(),
NodeID::Broadcast,
data_channel,
SendFlags::Default,
)?;
}
}
}
Err(e) => {
uds.disconnect_network()?;
console.clear();
eprintln!("Error while grabbing packet from network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
}
}
}
State::Create => {
console.clear();
println!("Creating network...");
match uds.create_network(
b"HBW\x10",
None,
None,
b"udsdemo passphrase c186093cd2652741\0",
data_channel,
) {
Ok(_) => {
let appdata = [0x69u8, 0x8a, 0x05, 0x5c]
.into_iter()
.chain((*b"Test appdata.").into_iter())
.chain(std::iter::repeat(0).take(3))
.collect::<Vec<_>>();
uds.set_appdata(&appdata)?;
println!("Press A to stop data transfer");
state = State::Created;
}
Err(e) => {
console.clear();
eprintln!("Error while creating network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
}
}
}
State::Created => {
let packet = uds.pull_packet();
match packet {
Ok(p) => {
if let Some((pkt, node)) = p {
println!(
"{:02X}{:02X}{:02X}{:02X} from {:?}",
pkt[0], pkt[1], pkt[2], pkt[3], node
);
}
if uds.wait_status_event(false, false)? {
prev_node_mask = handle_status_event(&uds, prev_node_mask)?;
}
if hid.keys_down().contains(KeyPad::A) {
uds.destroy_network()?;
state = State::Initialised;
console.clear();
println!("Press A to start scanning or B to create a new network");
} else if !hid.keys_down().is_empty() || !hid.keys_up().is_empty() {
let transfer_data = hid.keys_held().bits();
uds.send_packet(
&transfer_data.to_le_bytes(),
NodeID::Broadcast,
data_channel,
SendFlags::Default,
)?;
}
}
Err(e) => {
uds.destroy_network()?;
console.clear();
eprintln!("Error while grabbing packet from network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
}
}
}
}
}
Ok(())
}

12
ctru-rs/examples/software-keyboard.rs

@ -5,6 +5,8 @@ @@ -5,6 +5,8 @@
use ctru::applets::swkbd::{Button, CallbackResult, SoftwareKeyboard};
use ctru::prelude::*;
use std::ffi::CString;
fn main() {
let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap();
@ -19,9 +21,13 @@ fn main() { @@ -19,9 +21,13 @@ fn main() {
// Custom filter callback to handle the given input.
// Using this callback it's possible to integrate the applet
// with custom error messages when the input is incorrect.
keyboard.set_filter_callback(Some(Box::new(move |str| {
if str.contains("boo") {
return (CallbackResult::Retry, Some("Ah, you scared me!".into()));
keyboard.set_filter_callback(Some(Box::new(|str| {
// The string is guaranteed to contain valid Unicode text, so we can safely unwrap and use it as a normal `&str`.
if str.to_str().unwrap().contains("boo") {
return (
CallbackResult::Retry,
Some(CString::new("Ah, you scared me!").unwrap()),
);
}
(CallbackResult::Ok, None)

153
ctru-rs/src/applets/error.rs

@ -1,153 +0,0 @@ @@ -1,153 +0,0 @@
//! Error applet.
//!
//! This applet displays error text as a pop-up message on the lower screen.
use crate::services::{apt::Apt, gfx::Gfx};
use ctru_sys::errorConf;
/// Configuration struct to set up the Error applet.
#[doc(alias = "errorConf")]
pub struct PopUp {
state: Box<errorConf>,
}
/// Determines whether the Error applet will use word wrapping when displaying a message.
#[doc(alias = "errorType")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum WordWrap {
/// Error text is centered in the error applet window and does not use word wrapping.
Disabled = ctru_sys::ERROR_TEXT,
/// Error text starts at the top of the error applet window and uses word wrapping.
Enabled = ctru_sys::ERROR_TEXT_WORD_WRAP,
}
/// Error returned by an unsuccessful [`PopUp::launch()`].
#[doc(alias = "errorReturnCode")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i8)]
pub enum Error {
/// Unknown error occurred.
Unknown = ctru_sys::ERROR_UNKNOWN,
/// Operation not supported.
NotSupported = ctru_sys::ERROR_NOT_SUPPORTED,
/// Home button pressed while [`PopUp`] was running.
HomePressed = ctru_sys::ERROR_HOME_BUTTON,
/// Power button pressed while [`PopUp`] was running.
PowerPressed = ctru_sys::ERROR_POWER_BUTTON,
/// Reset button pressed while [`PopUp`] was running.
ResetPressed = ctru_sys::ERROR_SOFTWARE_RESET,
}
impl PopUp {
/// Initializes the error applet with the provided word wrap setting.
#[doc(alias = "errorInit")]
pub fn new(word_wrap: WordWrap) -> Self {
let mut state = Box::<errorConf>::default();
unsafe { ctru_sys::errorInit(state.as_mut(), word_wrap as _, 0) };
Self { state }
}
/// Sets the error text to display.
///
/// # 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.
#[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;
}
}
/// Launches the error applet.
#[doc(alias = "errorDisp")]
pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(), Error> {
unsafe { self.launch_unchecked() }
}
/// Launches the error applet without requiring an [`Apt`] or [`Gfx`] handle.
///
/// # Safety
///
/// Potentially leads to undefined behavior if the aforementioned services are not actually active when the applet launches.
unsafe fn launch_unchecked(&mut self) -> Result<(), Error> {
unsafe { ctru_sys::errorDisp(self.state.as_mut()) };
match self.state.returnCode {
ctru_sys::ERROR_NONE | ctru_sys::ERROR_SUCCESS => Ok(()),
ctru_sys::ERROR_NOT_SUPPORTED => Err(Error::NotSupported),
ctru_sys::ERROR_HOME_BUTTON => Err(Error::HomePressed),
ctru_sys::ERROR_POWER_BUTTON => Err(Error::PowerPressed),
ctru_sys::ERROR_SOFTWARE_RESET => Err(Error::ResetPressed),
_ => Err(Error::Unknown),
}
}
}
/// Sets a custom [panic hook](https://doc.rust-lang.org/std/panic/fn.set_hook.html) that uses the error applet to display panic messages.
///
/// You can also choose to have the previously registered panic hook called along with the error applet popup, which can be useful
/// if you want to use output redirection to display panic messages over `3dslink` or `GDB`.
///
/// You can use [`std::panic::take_hook`](https://doc.rust-lang.org/std/panic/fn.take_hook.html) to unregister the panic hook
/// set by this function.
///
/// # Notes
///
/// * If the [`Gfx`] service is not initialized during a panic, the error applet will not be displayed and the old panic hook will be called.
pub fn set_panic_hook(call_old_hook: bool) {
use crate::services::gfx::GFX_ACTIVE;
use std::sync::TryLockError;
let old_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
// If we get a `WouldBlock` error, we know that the `Gfx` service has been initialized.
// Otherwise fallback to using the old panic hook.
if let (Err(TryLockError::WouldBlock), Ok(_apt)) = (GFX_ACTIVE.try_lock(), Apt::new()) {
if call_old_hook {
old_hook(panic_info);
}
let thread = std::thread::current();
let name = thread.name().unwrap_or("<unnamed>");
let message = format!("thread '{name}' {panic_info}");
let mut popup = PopUp::new(WordWrap::Enabled);
popup.set_text(&message);
unsafe {
let _ = popup.launch_unchecked();
}
} else {
old_hook(panic_info);
}
}));
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotSupported => write!(f, "operation not supported"),
Self::HomePressed => write!(f, "home button pressed while error applet was running"),
Self::PowerPressed => write!(f, "power button pressed while error applet was running"),
Self::ResetPressed => write!(f, "reset button pressed while error applet was running"),
Self::Unknown => write!(f, "an unknown error occurred"),
}
}
}
impl std::error::Error for Error {}

1
ctru-rs/src/applets/mod.rs

@ -8,6 +8,5 @@ @@ -8,6 +8,5 @@
//!
//! Applets block execution of the thread that launches them as long as the user doesn't close the applet.
pub mod error;
pub mod mii_selector;
pub mod swkbd;

237
ctru-rs/src/applets/swkbd.rs

@ -6,25 +6,27 @@ @@ -6,25 +6,27 @@
use crate::services::{apt::Apt, gfx::Gfx};
use ctru_sys::{
aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle,
svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdLearningData,
SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, NS_APPID,
svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdExtra,
SwkbdLearningData, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE,
NS_APPID,
};
use bitflags::bitflags;
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::fmt::Display;
use std::iter::once;
use std::str;
type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option<Cow<'static, str>>);
type CallbackFunction = dyn Fn(&CStr) -> (CallbackResult, Option<CString>);
/// Configuration structure to setup the Software Keyboard applet.
#[doc(alias = "SwkbdState")]
pub struct SoftwareKeyboard {
state: Box<SwkbdState>,
filter_callback: Option<Box<CallbackFunction>>,
initial_text: Option<Cow<'static, str>>,
callback: Option<Box<CallbackFunction>>,
error_message: Option<CString>,
initial_text: Option<CString>,
}
/// Configuration structure to setup the Parental Lock applet.
@ -210,9 +212,9 @@ bitflags! { @@ -210,9 +212,9 @@ bitflags! {
}
// Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard.
#[derive(Copy, Clone)]
// We only need this because libctru doesn't keep a pointer to the shared memory block in `SwkbdExtra` for whatever reason
struct MessageCallbackData {
filter_callback: *const Box<CallbackFunction>,
extra: *mut SwkbdExtra,
swkbd_shared_mem_ptr: *mut libc::c_void,
}
@ -241,7 +243,8 @@ impl SoftwareKeyboard { @@ -241,7 +243,8 @@ impl SoftwareKeyboard {
ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1);
Self {
state,
filter_callback: None,
callback: None,
error_message: None,
initial_text: None,
}
}
@ -269,10 +272,18 @@ impl SoftwareKeyboard { @@ -269,10 +272,18 @@ impl SoftwareKeyboard {
/// # }
/// ```
#[doc(alias = "swkbdInputText")]
pub fn launch(&mut self, apt: &Apt, gfx: &Gfx) -> Result<(String, Button), Error> {
pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> {
let mut output = String::new();
match self.swkbd_input_text(&mut output, apt, gfx) {
unsafe {
// The filter callback gets reset every time the SoftwareKeyboard is used.
ctru_sys::swkbdSetFilterCallback(
self.state.as_mut(),
Some(Self::internal_callback),
(self as *mut Self).cast(),
);
match self.swkbd_input_text(&mut output) {
ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()),
ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)),
ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)),
@ -280,6 +291,7 @@ impl SoftwareKeyboard { @@ -280,6 +291,7 @@ impl SoftwareKeyboard {
_ => unreachable!(),
}
}
}
/// Set special features for this keyboard.
///
@ -343,13 +355,17 @@ impl SoftwareKeyboard { @@ -343,13 +355,17 @@ impl SoftwareKeyboard {
/// # fn main() {
/// #
/// use std::borrow::Cow;
/// use std::ffi::CString;
/// use ctru::applets::swkbd::{SoftwareKeyboard, CallbackResult};
///
/// let mut keyboard = SoftwareKeyboard::default();
///
/// keyboard.set_filter_callback(Some(Box::new(move |str| {
/// if str.contains("boo") {
/// return (CallbackResult::Retry, Some("Ah, you scared me!".into()));
/// keyboard.set_filter_callback(Some(Box::new(|str| {
/// if str.to_str().unwrap().contains("boo") {
/// return (
/// CallbackResult::Retry,
/// Some(CString::new("Ah, you scared me!").unwrap()),
/// );
/// }
///
/// (CallbackResult::Ok, None)
@ -357,7 +373,45 @@ impl SoftwareKeyboard { @@ -357,7 +373,45 @@ impl SoftwareKeyboard {
/// #
/// # }
pub fn set_filter_callback(&mut self, callback: Option<Box<CallbackFunction>>) {
self.filter_callback = callback;
self.callback = callback;
}
/// Internal function called by the filter callback.
extern "C" fn internal_callback(
user: *mut libc::c_void,
pp_message: *mut *const libc::c_char,
text: *const libc::c_char,
_text_size: libc::size_t,
) -> ctru_sys::SwkbdCallbackResult {
let this: *mut SoftwareKeyboard = user.cast();
unsafe {
// Reset any leftover error message.
(*this).error_message = None;
let text = CStr::from_ptr(text);
let result = {
// Run the callback if still available.
if let Some(callback) = &mut (*this).callback {
let (res, cstr) = callback(text);
// Due to how `libctru` operates, the user is expected to keep the error message alive until
// the end of the Software Keyboard prompt. We ensure that happens by saving it within the configuration.
(*this).error_message = cstr;
if let Some(newstr) = &(*this).error_message {
*pp_message = newstr.as_ptr();
}
res
} else {
CallbackResult::Ok
}
};
result.into()
}
}
/// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled.
@ -390,10 +444,6 @@ impl SoftwareKeyboard { @@ -390,10 +444,6 @@ impl SoftwareKeyboard {
///
/// The initial text is the text already written when you open the software keyboard.
///
/// # Notes
///
/// Passing [`None`] will clear the initial text.
///
/// # Example
///
/// ```
@ -403,25 +453,30 @@ impl SoftwareKeyboard { @@ -403,25 +453,30 @@ impl SoftwareKeyboard {
/// use ctru::applets::swkbd::SoftwareKeyboard;
/// let mut keyboard = SoftwareKeyboard::default();
///
/// keyboard.set_initial_text(Some("Write here what you like!".into()));
/// keyboard.set_initial_text(Some("Write here what you like!"));
/// #
/// # }
#[doc(alias = "swkbdSetInitialText")]
pub fn set_initial_text(&mut self, text: Option<Cow<'static, str>>) {
self.initial_text = text;
pub fn set_initial_text(&mut self, text: Option<&str>) {
if let Some(text) = text {
let initial_text = CString::new(text).unwrap();
unsafe {
ctru_sys::swkbdSetInitialText(self.state.as_mut(), initial_text.as_ptr());
}
self.initial_text = Some(initial_text);
} else {
unsafe { ctru_sys::swkbdSetInitialText(self.state.as_mut(), std::ptr::null()) };
self.initial_text = None;
}
}
/// Set the hint text for this software keyboard.
///
/// The hint text is the text shown in gray before any text gets written in the input box.
///
/// # Notes
///
/// Passing [`None`] will clear the hint text.
///
/// The hint text will be converted to UTF-16 when passed to the software keyboard, and the text will be truncated
/// if the length exceeds 64 code units after conversion.
///
/// # Example
///
/// ```
@ -431,22 +486,14 @@ impl SoftwareKeyboard { @@ -431,22 +486,14 @@ impl SoftwareKeyboard {
/// use ctru::applets::swkbd::SoftwareKeyboard;
/// let mut keyboard = SoftwareKeyboard::default();
///
/// keyboard.set_hint_text(Some("Write here what you like!"));
/// keyboard.set_hint_text("Write here what you like!");
/// #
/// # }
#[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;
}
} else {
self.state.hint_text[0] = 0;
pub fn set_hint_text(&mut self, text: &str) {
unsafe {
let nul_terminated: String = text.chars().chain(once('\0')).collect();
ctru_sys::swkbdSetHintText(self.state.as_mut(), nul_terminated.as_ptr());
}
}
@ -541,18 +588,15 @@ impl SoftwareKeyboard { @@ -541,18 +588,15 @@ impl SoftwareKeyboard {
/// # }
#[doc(alias = "swkbdSetButton")]
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;
unsafe {
let nul_terminated: String = text.chars().chain(once('\0')).collect();
ctru_sys::swkbdSetButton(
self.state.as_mut(),
button.into(),
nul_terminated.as_ptr(),
submit,
);
}
self.state.button_submits_text[button as usize] = submit;
}
/// Configure the maximum number of UTF-16 code units that can be entered into the software
@ -586,9 +630,10 @@ impl SoftwareKeyboard { @@ -586,9 +630,10 @@ impl SoftwareKeyboard {
self.state.valid_input = ValidInput::FixedLen.into();
}
// A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to fix various
// API nits and get rid of awkward type conversions when interacting with the Software Keyboard.
fn swkbd_input_text(&mut self, output: &mut String, _apt: &Apt, _gfx: &Gfx) -> SwkbdButton {
// 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,
@ -597,7 +642,7 @@ impl SoftwareKeyboard { @@ -597,7 +642,7 @@ impl SoftwareKeyboard {
};
let swkbd = self.state.as_mut();
let extra = unsafe { swkbd.__bindgen_anon_1.extra };
let mut extra = unsafe { swkbd.__bindgen_anon_1.extra };
// Calculate shared mem size
let mut shared_mem_size = 0;
@ -669,17 +714,19 @@ impl SoftwareKeyboard { @@ -669,17 +714,19 @@ impl SoftwareKeyboard {
}
// Copy stuff to shared mem
if let Some(initial_text) = self.initial_text.as_deref() {
if !extra.initial_text.is_null() {
swkbd.initial_text_offset = 0;
let mut initial_text_cursor = swkbd_shared_mem_ptr.cast();
for code_unit in initial_text
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))
{
unsafe {
.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);
}
@ -719,7 +766,7 @@ impl SoftwareKeyboard { @@ -719,7 +766,7 @@ impl SoftwareKeyboard {
};
}
if self.filter_callback.is_some() {
if extra.callback.is_some() {
swkbd.filter_flags |= SWKBD_FILTER_CALLBACK;
} else {
swkbd.filter_flags &= !SWKBD_FILTER_CALLBACK;
@ -729,19 +776,16 @@ impl SoftwareKeyboard { @@ -729,19 +776,16 @@ impl SoftwareKeyboard {
unsafe {
swkbd.__bindgen_anon_1.reserved.fill(0);
// We need to pass a thin pointer to the boxed closure over FFI. Since we know that the message callback will finish before
// `self` is allowed to be moved again, we can safely use a pointer to the local value contained in `self.filter_callback`
// The cast here is also sound since the pointer will only be read from if `self.filter_callback.is_some()` returns true.
let mut data = MessageCallbackData {
filter_callback: std::ptr::addr_of!(self.filter_callback).cast(),
let mut callback_data = MessageCallbackData {
extra: std::ptr::addr_of_mut!(extra),
swkbd_shared_mem_ptr,
};
if self.filter_callback.is_some() {
if extra.callback.is_some() {
aptSetMessageCallback(
Some(Self::swkbd_message_callback),
std::ptr::addr_of_mut!(data).cast(),
)
std::ptr::addr_of_mut!(callback_data).cast(),
);
}
aptLaunchLibraryApplet(
@ -751,7 +795,7 @@ impl SoftwareKeyboard { @@ -751,7 +795,7 @@ impl SoftwareKeyboard {
swkbd_shared_mem_handle,
);
if self.filter_callback.is_some() {
if extra.callback.is_some() {
aptSetMessageCallback(None, std::ptr::null_mut());
}
@ -802,46 +846,63 @@ impl SoftwareKeyboard { @@ -802,46 +846,63 @@ impl SoftwareKeyboard {
}
// A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`.
// This function sets up and then calls the filter callback
// 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 swkbd = unsafe { &mut *msg.cast::<SwkbdState>() };
let data = unsafe { *user.cast::<MessageCallbackData>() };
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 _,
swkbd.text_length as usize + 1,
))
};
let text8 = text16.to_string();
let filter_callback = unsafe { &**data.filter_callback };
let mut retmsg = std::ptr::null();
let (result, retmsg) = filter_callback(&text8);
if let Some(cb) = extra.callback {
swkbd.callback_result = unsafe {
cb(
extra.callback_user,
&mut retmsg,
text8.as_ptr(),
text8.len(),
)
} as _
};
swkbd.callback_result = result 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"
};
if let Some(msg) = retmsg.as_deref() {
for (idx, code_unit) in msg
let callback_msg = &mut swkbd.callback_msg;
for (idx, code_unit) in retmsg
.encode_utf16()
.take(swkbd.callback_msg.len() - 1)
.chain(once(0))
.take(callback_msg.len() - 1)
.enumerate()
{
swkbd.callback_msg[idx] = code_unit;
}
callback_msg[idx] = code_unit;
}
let _ = unsafe {

1
ctru-rs/src/console.rs

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
//! Have a look at [`Soc::redirect_to_3dslink()`](crate::services::soc::Soc::redirect_to_3dslink) for a better alternative when debugging applications.
use std::cell::{RefMut, UnsafeCell};
use std::default::Default;
use ctru_sys::{consoleClear, consoleInit, consoleSelect, consoleSetWindow, PrintConsole};

2
ctru-rs/src/lib.rs

@ -18,11 +18,9 @@ @@ -18,11 +18,9 @@
#![crate_type = "rlib"]
#![crate_name = "ctru"]
#![warn(missing_docs)]
#![deny(unsafe_op_in_unsafe_fn)]
#![feature(custom_test_frameworks)]
#![feature(try_trait_v2)]
#![feature(allocator_api)]
#![feature(new_uninit)]
#![test_runner(test_runner::run_gdb)] // TODO: does this make sense to have configurable?
#![doc(
html_favicon_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png"

2
ctru-rs/src/linear.rs

@ -42,8 +42,6 @@ unsafe impl Allocator for LinearAllocator { @@ -42,8 +42,6 @@ unsafe impl Allocator for LinearAllocator {
#[doc(alias = "linearFree")]
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
unsafe {
ctru_sys::linearFree(ptr.as_ptr().cast());
}
}
}

5
ctru-rs/src/services/am.rs

@ -51,11 +51,6 @@ impl<'a> Title<'a> { @@ -51,11 +51,6 @@ impl<'a> Title<'a> {
pub fn version(&self) -> u16 {
self.version
}
/// Returns this title's media type
pub fn media_type(&self) -> MediaType {
self.mediatype
}
}
/// Handle to the Application Manager service.

87
ctru-rs/src/services/apt.rs

@ -82,48 +82,6 @@ impl Apt { @@ -82,48 +82,6 @@ impl Apt {
Ok(())
}
}
/// Set if the console is allowed to enter sleep mode.
///
/// You can check whether the console is allowed to sleep with [Apt::is_sleep_allowed].
#[doc(alias = "aptSetSleepAllowed")]
pub fn set_sleep_allowed(&mut self, allowed: bool) {
unsafe {
ctru_sys::aptSetSleepAllowed(allowed);
}
}
/// Check if the console is allowed to enter sleep mode.
///
/// You can set whether the console is allowed to sleep with [Apt::set_sleep_allowed].
#[doc(alias = "aptIsSleepAllowed")]
pub fn is_sleep_allowed(&self) -> bool {
unsafe { ctru_sys::aptIsSleepAllowed() }
}
/// Set if the console is allowed to enter the home menu.
///
/// You can check whether the console is allowed to enter the home menu with [Apt::is_home_allowed].
#[doc(alias = "aptSetHomeAllowed")]
pub fn set_home_allowed(&mut self, allowed: bool) {
unsafe {
ctru_sys::aptSetHomeAllowed(allowed);
}
}
/// Check if the console is allowed to enter the home menu.
///
/// You can set whether the console is allowed to enter the home menu with [Apt::set_home_allowed].
#[doc(alias = "aptIsHomeAllowed")]
pub fn is_home_allowed(&self) -> bool {
unsafe { ctru_sys::aptIsHomeAllowed() }
}
/// Immediately jumps to the home menu.
#[doc(alias = "aptJumpToHomeMenu")]
pub fn jump_to_home_menu(&mut self) {
unsafe { ctru_sys::aptJumpToHomeMenu() }
}
}
impl Drop for Apt {
@ -132,48 +90,3 @@ impl Drop for Apt { @@ -132,48 +90,3 @@ impl Drop for Apt {
unsafe { ctru_sys::aptExit() };
}
}
/// Can launch other applications when the current one exits.
pub struct Chainloader<'a> {
_apt: &'a Apt,
}
impl<'a> Chainloader<'a> {
/// Gets a handle to the chainloader
pub fn new(apt: &'a Apt) -> Self {
Self { _apt: apt }
}
/// Checks if the chainloader is set
#[doc(alias = "aptIsChainload")]
pub fn is_set(&self) -> bool {
// static funtion not exported
unsafe { (ctru_sys::envGetSystemRunFlags() & ctru_sys::RUNFLAG_APTCHAINLOAD) != 0 }
}
/// Clears the chainloader state.
#[doc(alias = "aptClearChainloader")]
pub fn clear(&mut self) {
unsafe { ctru_sys::aptClearChainloader() }
}
/// Configures the chainloader to launch a specific application.
///
/// See also [`Title`](crate::services::am::Title]
#[doc(alias = "aptSetChainloader")]
pub fn set(&mut self, title: &super::am::Title<'_>) {
unsafe { ctru_sys::aptSetChainloader(title.id(), title.media_type() as u8) }
}
/// Configures the chainloader to launch the previous application.
#[doc(alias = "aptSetChainloaderToCaller")]
pub fn set_to_caller(&mut self) {
unsafe { ctru_sys::aptSetChainloaderToCaller() }
}
/// Configures the chainloader to relaunch the current application (i.e. soft-reset)
#[doc(alias = "aptSetChainloaderToSelf")]
pub fn set_to_self(&mut self) {
unsafe { ctru_sys::aptSetChainloaderToSelf() }
}
}

2
ctru-rs/src/services/gfx.rs

@ -239,7 +239,7 @@ pub struct Gfx { @@ -239,7 +239,7 @@ pub struct Gfx {
_service_handler: ServiceReference,
}
pub(crate) static GFX_ACTIVE: Mutex<()> = Mutex::new(());
static GFX_ACTIVE: Mutex<()> = Mutex::new(());
impl Gfx {
/// Initialize a new default service handle.

4
ctru-rs/src/services/ir_user.rs

@ -150,7 +150,7 @@ impl IrUser { @@ -150,7 +150,7 @@ impl IrUser {
shared_mem.shared_memory_layout,
);
Ok::<_, Error>(())
Ok(())
})()
.unwrap();
},
@ -347,13 +347,11 @@ impl IrUser { @@ -347,13 +347,11 @@ impl IrUser {
let mut shared_mem_guard = IR_USER_STATE.lock().unwrap();
let shared_mem = shared_mem_guard.as_mut().unwrap();
unsafe {
shared_mem
.service_handle
.send_service_request(request, expected_response_len)
}
}
}
// Internal helper for rounding up a value to a multiple of another value.
fn round_up(value: usize, multiple: usize) -> usize {

1
ctru-rs/src/services/mod.rs

@ -26,7 +26,6 @@ mod reference; @@ -26,7 +26,6 @@ mod reference;
pub mod soc;
pub mod sslc;
pub mod svc;
pub mod uds;
cfg_if::cfg_if! {
if #[cfg(all(feature = "romfs", romfs_exists))] {

1
ctru-rs/src/services/ndsp/mod.rs

@ -21,6 +21,7 @@ use crate::error::ResultCode; @@ -21,6 +21,7 @@ use crate::error::ResultCode;
use crate::services::ServiceReference;
use std::cell::{RefCell, RefMut};
use std::default::Default;
use std::error;
use std::fmt;
use std::sync::Mutex;

13
ctru-rs/src/services/svc.rs

@ -50,28 +50,19 @@ impl HandleExt for Handle { @@ -50,28 +50,19 @@ impl HandleExt for Handle {
) -> crate::Result<Vec<u32>> {
// Copy over the request
let cmd_buffer_ptr = unsafe { ctru_sys::getThreadCommandBuffer() };
unsafe {
std::ptr::copy_nonoverlapping(request.as_ptr(), cmd_buffer_ptr, request.len());
// Send the request
ResultCode(ctru_sys::svcSendSyncRequest(self))?;
// Handle the result returned by the service
let result = std::ptr::read(cmd_buffer_ptr.add(1));
let result = unsafe { std::ptr::read(cmd_buffer_ptr.add(1)) };
ResultCode(result as ctru_sys::Result)?;
}
// Copy back the response
request.clear();
request.resize(expected_response_len, 0);
unsafe {
std::ptr::copy_nonoverlapping(
cmd_buffer_ptr,
request.as_mut_ptr(),
expected_response_len,
);
}
std::ptr::copy_nonoverlapping(cmd_buffer_ptr, request.as_mut_ptr(), expected_response_len);
Ok(request)
}

1382
ctru-rs/src/services/uds.rs

File diff suppressed because it is too large Load Diff

6
ctru-sys/Cargo.toml

@ -3,7 +3,7 @@ name = "ctru-sys" @@ -3,7 +3,7 @@ name = "ctru-sys"
version = "0.5.0"
authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"]
description = "Raw bindings to libctru"
repository = "https://git.xenua.me/rust3ds/ctru-rs"
repository = "https://github.com/rust3ds/ctru-rs"
documentation = "https://rust3ds.github.io/ctru-rs/crates/ctru_sys"
keywords = ["3ds", "libctru"]
categories = ["os", "external-ffi-bindings", "no-std", "hardware-support"]
@ -23,8 +23,8 @@ itertools = "0.11.0" @@ -23,8 +23,8 @@ itertools = "0.11.0"
which = "4.4.0"
[dev-dependencies]
shim-3ds = { git = "https://git.xenua.me/rust3ds/shim-3ds.git" }
test-runner = { git = "https://git.xenua.me/rust3ds/test-runner.git" }
shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" }
test-runner = { git = "https://github.com/rust3ds/test-runner.git" }
[package.metadata.docs.rs]
default-target = "armv6k-nintendo-3ds"

4
ctru-sys/build.rs

@ -73,14 +73,12 @@ fn main() { @@ -73,14 +73,12 @@ fn main() {
.use_core()
.trust_clang_mangling(false)
.must_use_type("Result")
.layout_tests(true)
.layout_tests(false)
.ctypes_prefix("::libc")
.prepend_enum_name(false)
.blocklist_type("u(8|16|32|64)")
.blocklist_type("__builtin_va_list")
.blocklist_type("__va_list")
.blocklist_type("errorReturnCode")
.blocklist_type("errorScreenFlag")
.opaque_type("MiiData")
.derive_default(true)
.wrap_static_fns(true)

18
ctru-sys/src/lib.rs

@ -16,24 +16,6 @@ @@ -16,24 +16,6 @@
pub mod result;
pub use result::*;
// Fun fact: C compilers are allowed to represent enums as the smallest integer type that can hold all of its variants,
// meaning that enums are allowed to be the size of a `c_short` or a `c_char` rather than the size of a `c_int`.
// Libctru's `errorConf` struct contains two enums that depend on this narrowing property for size and alignment purposes,
// and since `bindgen` generates all enums with `c_int` sizing, we have to blocklist those types and manually define them
// here with the proper size.
pub type errorReturnCode = libc::c_schar;
pub const ERROR_UNKNOWN: errorReturnCode = -1;
pub const ERROR_NONE: errorReturnCode = 0;
pub const ERROR_SUCCESS: errorReturnCode = 1;
pub const ERROR_NOT_SUPPORTED: errorReturnCode = 2;
pub const ERROR_HOME_BUTTON: errorReturnCode = 10;
pub const ERROR_SOFTWARE_RESET: errorReturnCode = 11;
pub const ERROR_POWER_BUTTON: errorReturnCode = 12;
pub type errorScreenFlag = libc::c_char;
pub const ERROR_NORMAL: errorScreenFlag = 0;
pub const ERROR_STEREO: errorScreenFlag = 1;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
/// In lieu of a proper errno function exposed by libc

Loading…
Cancel
Save