Browse Source

Merge pull request #137 from rust3ds/camera-and-more

Camera rework and small fixes
pull/145/head
Meziu 1 year ago committed by GitHub
parent
commit
9406f5d402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ctru-rs/examples/audio-filters.rs
  2. 10
      ctru-rs/examples/buttons.rs
  3. 77
      ctru-rs/examples/camera-image.rs
  4. 2
      ctru-rs/examples/file-explorer.rs
  5. 2
      ctru-rs/examples/futures-basic.rs
  6. 2
      ctru-rs/examples/futures-tokio.rs
  7. 2
      ctru-rs/examples/gfx-3d-mode.rs
  8. 2
      ctru-rs/examples/gfx-bitmap.rs
  9. 2
      ctru-rs/examples/gfx-wide-mode.rs
  10. 2
      ctru-rs/examples/hashmaps.rs
  11. 2
      ctru-rs/examples/hello-both-screens.rs
  12. 4
      ctru-rs/examples/hello-world.rs
  13. 2
      ctru-rs/examples/linear-memory.rs
  14. 4
      ctru-rs/examples/mii-selector.rs
  15. 52
      ctru-rs/examples/movement.rs
  16. 2
      ctru-rs/examples/network-sockets.rs
  17. 2
      ctru-rs/examples/output-3dslink.rs
  18. 2
      ctru-rs/examples/romfs.rs
  19. 2
      ctru-rs/examples/software-keyboard.rs
  20. 2
      ctru-rs/examples/system-configuration.rs
  21. 2
      ctru-rs/examples/thread-basic.rs
  22. 2
      ctru-rs/examples/thread-info.rs
  23. 2
      ctru-rs/examples/thread-locals.rs
  24. 2
      ctru-rs/examples/time-rtc.rs
  25. 12
      ctru-rs/examples/title-info.rs
  26. 2
      ctru-rs/examples/touch-screen.rs
  27. 44
      ctru-rs/src/applets/mii_selector.rs
  28. 199
      ctru-rs/src/console.rs
  29. 49
      ctru-rs/src/lib.rs
  30. 2
      ctru-rs/src/linear.rs
  31. 798
      ctru-rs/src/services/cam.rs
  32. 79
      ctru-rs/src/services/gfx.rs
  33. 14
      ctru-rs/src/services/gspgpu.rs
  34. 288
      ctru-rs/src/services/hid.rs
  35. 62
      ctru-rs/src/services/ndsp/mod.rs
  36. 12
      ctru-rs/src/services/ndsp/wave.rs
  37. 43
      ctru-rs/src/services/reference.rs
  38. 19
      ctru-rs/src/services/romfs.rs
  39. 3
      ctru-rs/src/services/soc.rs

2
ctru-rs/examples/audio-filters.rs

@ -40,8 +40,6 @@ fn fill_buffer(audio_data: &mut [u8], frequency: f32) {
} }
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

10
ctru-rs/examples/buttons.rs

@ -5,8 +5,6 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();
@ -25,6 +23,12 @@ fn main() {
// Get information about which keys were held down on this frame. // Get information about which keys were held down on this frame.
let keys = hid.keys_held(); let keys = hid.keys_held();
// Print the status of the volume slider.
println!(
"\x1b[20;0HVolume slider: {} ",
hid.volume_slider()
);
// We only want to print when the keys we're holding now are different // We only want to print when the keys we're holding now are different
// from what they were on the previous frame. // from what they were on the previous frame.
if keys != old_keys { if keys != old_keys {
@ -44,7 +48,7 @@ fn main() {
// and the `.intersects()` method checks for any of the provided keys. // and the `.intersects()` method checks for any of the provided keys.
// //
// You can also use the `.bits()` method to do direct comparisons on // You can also use the `.bits()` method to do direct comparisons on
// the underlying bits // the underlying bits.
if keys.contains(KeyPad::A) { if keys.contains(KeyPad::A) {
println!("You held A!"); println!("You held A!");

77
ctru-rs/examples/camera-image.rs

@ -3,30 +3,27 @@
//! This example demonstrates how to use the built-in cameras to take a picture and display it to the screen. //! This example demonstrates how to use the built-in cameras to take a picture and display it to the screen.
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize}; use ctru::services::cam::{
use ctru::services::gfx::{Flush, Screen, Swap}; Cam, Camera, OutputFormat, ShutterSound, Trimming, ViewSize, WhiteBalance,
};
use ctru::services::gfx::{Flush, Screen, Swap, TopScreen3D};
use ctru::services::gspgpu::FramebufferFormat; use ctru::services::gspgpu::FramebufferFormat;
use std::time::Duration; use std::time::Duration;
const WIDTH: usize = 400;
const HEIGHT: usize = 240;
// The screen size is the width and height multiplied by 2 (RGB565 store pixels in 2 bytes).
const BUF_SIZE: usize = WIDTH * HEIGHT * 2;
const WAIT_TIMEOUT: Duration = Duration::from_millis(300); const WAIT_TIMEOUT: Duration = Duration::from_millis(300);
fn main() { fn main() {
ctru::use_panic_handler();
let apt = Apt::new().expect("Failed to initialize Apt service."); let apt = Apt::new().expect("Failed to initialize Apt service.");
let mut hid = Hid::new().expect("Failed to initialize Hid service."); let mut hid = Hid::new().expect("Failed to initialize Hid service.");
let gfx = Gfx::new().expect("Failed to initialize GFX service."); let gfx = Gfx::new().expect("Failed to initialize GFX service.");
let mut top_screen = gfx.top_screen.borrow_mut(); gfx.top_screen.borrow_mut().set_double_buffering(true);
top_screen.set_double_buffering(true); gfx.top_screen
top_screen.set_framebuffer_format(FramebufferFormat::Rgb565); .borrow_mut()
.set_framebuffer_format(FramebufferFormat::Rgb565);
let mut top_screen_3d = TopScreen3D::from(&gfx.top_screen);
let _console = Console::new(gfx.bottom_screen.borrow_mut()); let _console = Console::new(gfx.bottom_screen.borrow_mut());
@ -35,9 +32,8 @@ fn main() {
let mut cam = Cam::new().expect("Failed to initialize CAM service."); let mut cam = Cam::new().expect("Failed to initialize CAM service.");
// Camera setup. // Camera setup.
let camera = &mut cam.both_outer_cams;
{ {
let camera = &mut cam.outer_right_cam;
camera camera
.set_view_size(ViewSize::TopLCD) .set_view_size(ViewSize::TopLCD)
.expect("Failed to set camera size"); .expect("Failed to set camera size");
@ -51,14 +47,17 @@ fn main() {
.set_auto_exposure(true) .set_auto_exposure(true)
.expect("Failed to enable auto exposure"); .expect("Failed to enable auto exposure");
camera camera
.set_auto_white_balance(true) .set_white_balance(WhiteBalance::Auto)
.expect("Failed to enable auto white balance"); .expect("Failed to enable auto white balance");
// This line has no effect on the camera since the photos are already shot with `TopLCD` size.
camera camera
.set_trimming(false) .set_trimming(Trimming::new_centered_with_view(ViewSize::TopLCD))
.expect("Failed to disable trimming"); .expect("Failed to enable trimming");
} }
let mut buf = vec![0u8; BUF_SIZE]; // We don't intend on making any other modifications to the camera, so this size should be enough.
let len = camera.final_byte_length();
let mut buf = vec![0u8; len];
println!("\nPress R to take a new picture"); println!("\nPress R to take a new picture");
println!("Press Start to exit"); println!("Press Start to exit");
@ -75,28 +74,42 @@ fn main() {
if keys_down.contains(KeyPad::R) { if keys_down.contains(KeyPad::R) {
println!("Capturing new image"); println!("Capturing new image");
let camera = &mut cam.outer_right_cam; let camera = &mut cam.both_outer_cams;
// Take a picture and write it to the buffer. // Take a picture and write it to the buffer.
camera camera
.take_picture( .take_picture(&mut buf, WAIT_TIMEOUT)
&mut buf,
WIDTH.try_into().unwrap(),
HEIGHT.try_into().unwrap(),
WAIT_TIMEOUT,
)
.expect("Failed to take picture"); .expect("Failed to take picture");
let (width, height) = camera.final_view_size();
// Play the normal shutter sound. // Play the normal shutter sound.
cam.play_shutter_sound(ShutterSound::Normal) cam.play_shutter_sound(ShutterSound::Normal)
.expect("Failed to play shutter sound"); .expect("Failed to play shutter sound");
// Rotate the image and correctly display it on the screen. {
rotate_image_to_screen(&buf, top_screen.raw_framebuffer().ptr, WIDTH, HEIGHT); let (mut left_side, mut right_side) = top_screen_3d.split_mut();
// Rotate the left image and correctly display it on the screen.
rotate_image_to_screen(
&buf,
left_side.raw_framebuffer().ptr,
width as usize,
height as usize,
);
// Rotate the right image and correctly display it on the screen.
rotate_image_to_screen(
&buf[len / 2..],
right_side.raw_framebuffer().ptr,
width as usize,
height as usize,
);
}
// We will only flush and swap the "camera" screen, since the other screen is handled by the `Console`. // We will only flush and swap the "camera" screen, since the other screen is handled by the `Console`.
top_screen.flush_buffers(); top_screen_3d.flush_buffers();
top_screen.swap_buffers(); top_screen_3d.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
@ -125,8 +138,8 @@ fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: u
unsafe { unsafe {
// We'll work with pointers since the framebuffer is a raw pointer regardless. // We'll work with pointers since the framebuffer is a raw pointer regardless.
// The offsets are completely safe as long as the width and height are correct. // The offsets are completely safe as long as the width and height are correct.
let pixel_pointer = framebuf.offset(draw_index as isize); let pixel_pointer = framebuf.add(draw_index);
pixel_pointer.copy_from(src.as_ptr().offset(read_index as isize), 2); pixel_pointer.copy_from(src.as_ptr().add(read_index), 2);
} }
} }
} }

2
ctru-rs/examples/file-explorer.rs

@ -11,8 +11,6 @@ use std::os::horizon::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
fn main() { fn main() {
ctru::use_panic_handler();
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();

2
ctru-rs/examples/futures-basic.rs

@ -13,8 +13,6 @@ use futures::StreamExt;
use std::os::horizon::thread::BuilderExt; use std::os::horizon::thread::BuilderExt;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

2
ctru-rs/examples/futures-tokio.rs

@ -6,8 +6,6 @@ use std::os::horizon::thread::BuilderExt;
use std::time::Duration; use std::time::Duration;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

2
ctru-rs/examples/gfx-3d-mode.rs

@ -17,8 +17,6 @@ const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb");
static ZERO: &[u8] = &[0; IMAGE.len()]; static ZERO: &[u8] = &[0; IMAGE.len()];
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

2
ctru-rs/examples/gfx-bitmap.rs

@ -18,8 +18,6 @@ use ctru::services::gfx::{Flush, Screen, Swap};
static IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); static IMAGE: &[u8] = include_bytes!("assets/ferris.rgb");
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

2
ctru-rs/examples/gfx-wide-mode.rs

@ -8,8 +8,6 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();

2
ctru-rs/examples/hashmaps.rs

@ -9,8 +9,6 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
// HashMaps generate hashes thanks to the 3DS' cryptografically secure generator. // HashMaps generate hashes thanks to the 3DS' cryptografically secure generator.
// This generator is only active when activating the `PS` service. // This generator is only active when activating the `PS` service.
// This service is automatically initialized. // This service is automatically initialized.

2
ctru-rs/examples/hello-both-screens.rs

@ -5,8 +5,6 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();

4
ctru-rs/examples/hello-world.rs

@ -6,10 +6,6 @@ use ctru::prelude::*;
use std::io::BufWriter; use std::io::BufWriter;
fn main() { fn main() {
// Setup the custom panic handler in case any errors arise.
// Thanks to it the user will get promptly notified of any panics.
ctru::use_panic_handler();
// Setup Graphics, Controller Inputs, Application runtime. // Setup Graphics, Controller Inputs, Application runtime.
// These is standard setup any app would need. // These is standard setup any app would need.
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");

2
ctru-rs/examples/linear-memory.rs

@ -10,8 +10,6 @@ use ctru::linear::LinearAllocator;
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

4
ctru-rs/examples/mii-selector.rs

@ -6,8 +6,6 @@ use ctru::applets::mii_selector::{Error, MiiSelector, Options};
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");
@ -19,7 +17,7 @@ fn main() {
mii_selector.set_options(Options::ENABLE_CANCEL); mii_selector.set_options(Options::ENABLE_CANCEL);
mii_selector.set_initial_index(3); mii_selector.set_initial_index(3);
// The first user-made Mii cannot be used. // The first user-made Mii cannot be used.
mii_selector.blacklist_user_mii(0.into()); mii_selector.blocklist_user_mii(0.into());
mii_selector.set_title("Great Mii Selector!"); mii_selector.set_title("Great Mii Selector!");
// Launch the Mii Selector and use its result to print the selected Mii's information. // Launch the Mii Selector and use its result to print the selected Mii's information.

52
ctru-rs/examples/movement.rs

@ -0,0 +1,52 @@
//! Movement example.
//!
//! Simple application to showcase the use of the accelerometer and gyroscope.
use ctru::prelude::*;
fn main() {
let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
println!("Move the console around!");
println!("\x1b[29;16HPress Start to exit");
// Activate the accelerometer and the gyroscope.
// Because of the complex nature of the movement sensors, they aren't activated by default with the `Hid` service.
// However, they can simply be turned on and off whenever necessary.
hid.set_accelerometer(true)
.expect("Couldn't activate accelerometer");
hid.set_gyroscope(true)
.expect("Couldn't activate gyroscope");
while apt.main_loop() {
// Scan all the controller inputs.
// Accelerometer and gyroscope require this step to update the readings.
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Be careful: reading without activating the sensors (as done before this loop) will result in a panic.
println!(
"\x1b[3;0HAcceleration: {:?} ",
<(i16, i16, i16)>::from(
hid.accelerometer_vector()
.expect("could not retrieve acceleration vector")
)
);
println!(
"\x1b[4;0HGyroscope angular rate: {:?} ",
Into::<(i16, i16, i16)>::into(
hid.gyroscope_rate()
.expect("could not retrieve angular rate")
)
);
gfx.wait_for_vblank();
}
}

2
ctru-rs/examples/network-sockets.rs

@ -9,8 +9,6 @@ use std::net::{Shutdown, TcpListener};
use std::time::Duration; use std::time::Duration;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();

2
ctru-rs/examples/output-3dslink.rs

@ -13,8 +13,6 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

2
ctru-rs/examples/romfs.rs

@ -5,8 +5,6 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

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

@ -6,8 +6,6 @@ use ctru::applets::swkbd::{Button, SoftwareKeyboard};
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();

2
ctru-rs/examples/system-configuration.rs

@ -7,8 +7,6 @@ use ctru::prelude::*;
use ctru::services::cfgu::Cfgu; use ctru::services::cfgu::Cfgu;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

2
ctru-rs/examples/thread-basic.rs

@ -6,8 +6,6 @@ use std::os::horizon::thread::BuilderExt;
use std::time::Duration; use std::time::Duration;
fn main() { fn main() {
ctru::use_panic_handler();
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();

2
ctru-rs/examples/thread-info.rs

@ -7,8 +7,6 @@ use ctru::prelude::*;
use std::os::horizon::thread::BuilderExt; use std::os::horizon::thread::BuilderExt;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

2
ctru-rs/examples/thread-locals.rs

@ -10,8 +10,6 @@ std::thread_local! {
} }
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
gfx.top_screen.borrow_mut().set_wide_mode(true); gfx.top_screen.borrow_mut().set_wide_mode(true);
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");

2
ctru-rs/examples/time-rtc.rs

@ -6,8 +6,6 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

12
ctru-rs/examples/title-info.rs

@ -8,8 +8,6 @@ use ctru::services::am::Am;
use ctru::services::fs::FsMediaType; use ctru::services::fs::FsMediaType;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");
@ -58,19 +56,17 @@ fn main() {
if hid.keys_down().intersects(KeyPad::DOWN) { if hid.keys_down().intersects(KeyPad::DOWN) {
if offset + 1 < cur_list.len() { if offset + 1 < cur_list.len() {
offset = offset + 1; offset += 1;
refresh = true; refresh = true;
} }
} else if hid.keys_down().intersects(KeyPad::UP) { } else if hid.keys_down().intersects(KeyPad::UP) && offset > 0 {
if offset > 0 { offset -= 1;
offset = offset - 1;
refresh = true; refresh = true;
} }
}
// Render the title list via a scrollable text UI. // Render the title list via a scrollable text UI.
if refresh { if refresh {
let mut selected_title = cur_list.iter().skip(offset).next().unwrap(); let mut selected_title = cur_list.get(offset).unwrap();
// Clear the top screen and write title IDs to it. // Clear the top screen and write title IDs to it.
top_screen.select(); top_screen.select();

2
ctru-rs/examples/touch-screen.rs

@ -5,8 +5,6 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");

44
ctru-rs/src/applets/mii_selector.rs

@ -1,7 +1,7 @@
//! Mii Selector applet. //! Mii Selector applet.
//! //!
//! This applet opens a window which lets the player/user choose a Mii from the ones present on their console. //! This applet opens a window which lets the player/user choose a Mii from the ones present on their console.
//! The selected Mii is readable as a [`Mii`](crate::mii::Mii). //! The selected Mii is readable as a [`Mii`].
use crate::mii::Mii; use crate::mii::Mii;
use bitflags::bitflags; use bitflags::bitflags;
@ -9,7 +9,7 @@ use std::{ffi::CString, fmt};
/// Index of a Mii on the [`MiiSelector`] interface. /// Index of a Mii on the [`MiiSelector`] interface.
/// ///
/// See [`MiiSelector::whitelist_user_mii()`] and related functions for more information. /// See [`MiiSelector::allowlist_user_mii()`] and related functions for more information.
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Index { pub enum Index {
/// Specific Mii index. /// Specific Mii index.
@ -136,11 +136,11 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits()) } unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits()) }
} }
/// Whitelist a guest Mii based on its index. /// Allowlist a guest Mii based on its index.
/// ///
/// # Notes /// # Notes
/// ///
/// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`]. /// Guest Mii's won't be available regardless of their allowlist/blocklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`].
/// Look into [`MiiSelector::set_options()`] to see how to work with options. /// Look into [`MiiSelector::set_options()`] to see how to work with options.
/// ///
/// # Example /// # Example
@ -151,12 +151,12 @@ impl MiiSelector {
/// use ctru::applets::mii_selector::{Index, MiiSelector}; /// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new(); /// let mut mii_selector = MiiSelector::new();
/// ///
/// // Whitelist the guest Mii at index 2. /// // Allowlist the guest Mii at index 2.
/// mii_selector.whitelist_guest_mii(Index::Index(2)); /// mii_selector.allowlist_guest_mii(Index::Index(2));
/// # } /// # }
/// ``` /// ```
#[doc(alias = "miiSelectorWhitelistGuestMii")] #[doc(alias = "miiSelectorWhitelistGuestMii")]
pub fn whitelist_guest_mii(&mut self, mii_index: Index) { pub fn allowlist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
Index::Index(i) => i, Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS, Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
@ -165,11 +165,11 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) } unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) }
} }
/// Blacklist a guest Mii based on its index. /// Blocklist a guest Mii based on its index.
/// ///
/// # Notes /// # Notes
/// ///
/// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`]. /// Guest Mii's won't be available regardless of their allowlist/blocklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`].
/// Look into [`MiiSelector::set_options()`] to see how to work with options. /// Look into [`MiiSelector::set_options()`] to see how to work with options.
/// ///
/// # Example /// # Example
@ -180,12 +180,12 @@ impl MiiSelector {
/// use ctru::applets::mii_selector::{Index, MiiSelector}; /// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new(); /// let mut mii_selector = MiiSelector::new();
/// ///
/// // Blacklist the guest Mii at index 1 so that it cannot be selected. /// // Blocklist the guest Mii at index 1 so that it cannot be selected.
/// mii_selector.blacklist_guest_mii(Index::Index(1)); /// mii_selector.blocklist_guest_mii(Index::Index(1));
/// # } /// # }
/// ``` /// ```
#[doc(alias = "miiSelectorBlacklistGuestMii")] #[doc(alias = "miiSelectorBlacklistGuestMii")]
pub fn blacklist_guest_mii(&mut self, mii_index: Index) { pub fn blocklist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
Index::Index(i) => i, Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS, Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
@ -194,7 +194,7 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) } unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) }
} }
/// Whitelist a user-created Mii based on its index. /// Allowlist a user-created Mii based on its index.
/// ///
/// # Example /// # Example
/// ///
@ -204,12 +204,12 @@ impl MiiSelector {
/// use ctru::applets::mii_selector::{Index, MiiSelector}; /// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new(); /// let mut mii_selector = MiiSelector::new();
/// ///
/// // Whitelist the user-created Mii at index 0. /// // Allowlist the user-created Mii at index 0.
/// mii_selector.whitelist_user_mii(Index::Index(0)); /// mii_selector.allowlist_user_mii(Index::Index(0));
/// # } /// # }
/// ``` /// ```
#[doc(alias = "miiSelectorWhitelistUserMii")] #[doc(alias = "miiSelectorWhitelistUserMii")]
pub fn whitelist_user_mii(&mut self, mii_index: Index) { pub fn allowlist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
Index::Index(i) => i, Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS, Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
@ -218,7 +218,7 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) } unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) }
} }
/// Blacklist a user-created Mii based on its index. /// Blocklist a user-created Mii based on its index.
/// ///
/// # Example /// # Example
/// ///
@ -228,12 +228,12 @@ impl MiiSelector {
/// use ctru::applets::mii_selector::{Index, MiiSelector}; /// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new(); /// let mut mii_selector = MiiSelector::new();
/// ///
/// // Blacklist all user-created Miis so that they cannot be selected. /// // Blocklist all user-created Miis so that they cannot be selected.
/// mii_selector.blacklist_user_mii(Index::All); /// mii_selector.blocklist_user_mii(Index::All);
/// # } /// # }
/// ``` /// ```
#[doc(alias = "miiSelectorBlacklistUserMii")] #[doc(alias = "miiSelectorBlacklistUserMii")]
pub fn blacklist_user_mii(&mut self, mii_index: Index) { pub fn blocklist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
Index::Index(i) => i, Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS, Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
@ -247,9 +247,7 @@ impl MiiSelector {
/// If there's no Mii at that index, the cursor will start at the Mii with the index 0. /// If there's no Mii at that index, the cursor will start at the Mii with the index 0.
#[doc(alias = "miiSelectorSetInitialIndex")] #[doc(alias = "miiSelectorSetInitialIndex")]
pub fn set_initial_index(&mut self, index: usize) { pub fn set_initial_index(&mut self, index: usize) {
// This function is static inline in libctru unsafe { ctru_sys::miiSelectorSetInitialIndex(self.config.as_mut(), index as u32) };
// https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155
self.config.initial_index = index as u32;
} }
/// Launch the Mii Selector. /// Launch the Mii Selector.

199
ctru-rs/src/console.rs

@ -14,6 +14,31 @@ use crate::services::gfx::Screen;
static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintConsole) }; static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintConsole) };
/// Error enum for generic errors within [`Console`].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Error {
/// The coordinate specified on the given axis exceeds the limits imposed by the [`Console`] window.
CoordinateOutOfBounds(Axis),
/// The size specified for the given dimension exceeds the limits imposed by the [`Console`] window.
DimensionOutOfBounds(Dimension),
}
/// 2D coordinate axes.
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Axis {
X,
Y,
}
/// 2D dimensions.
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Dimension {
Width,
Height,
}
/// Virtual text console. /// Virtual text console.
/// ///
/// [`Console`] lets the application redirect `stdout` and `stderr` to a simple text displayer on the 3DS screen. /// [`Console`] lets the application redirect `stdout` and `stderr` to a simple text displayer on the 3DS screen.
@ -35,7 +60,7 @@ static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintC
#[doc(alias = "PrintConsole")] #[doc(alias = "PrintConsole")]
pub struct Console<'screen> { pub struct Console<'screen> {
context: Box<PrintConsole>, context: Box<PrintConsole>,
_screen: RefMut<'screen, dyn Screen>, screen: RefMut<'screen, dyn Screen>,
} }
impl<'screen> Console<'screen> { impl<'screen> Console<'screen> {
@ -50,6 +75,11 @@ impl<'screen> Console<'screen> {
/// ///
/// [`Console`] automatically takes care of flushing and swapping buffers for its screen when printing. /// [`Console`] automatically takes care of flushing and swapping buffers for its screen when printing.
/// ///
/// # Panics
///
/// If the [`Gfx`](crate::services::gfx::Gfx) service was initialised via [`Gfx::with_formats_vram()`](crate::services::gfx::Gfx::with_formats_vram)
/// this function will crash the program with an ARM exception.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -60,7 +90,7 @@ impl<'screen> Console<'screen> {
/// use ctru::console::Console; /// use ctru::console::Console;
/// use ctru::services::gfx::Gfx; /// use ctru::services::gfx::Gfx;
/// ///
/// // Initialize graphics. /// // Initialize graphics (using framebuffers allocated on the HEAP).
/// let gfx = Gfx::new()?; /// let gfx = Gfx::new()?;
/// ///
/// // Create a `Console` that takes control of the upper LCD screen. /// // Create a `Console` that takes control of the upper LCD screen.
@ -77,10 +107,7 @@ impl<'screen> Console<'screen> {
unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; unsafe { consoleInit(screen.as_raw(), context.as_mut()) };
Console { Console { context, screen }
context,
_screen: screen,
}
} }
/// Returns `true` if a valid [`Console`] to print on is currently selected. /// Returns `true` if a valid [`Console`] to print on is currently selected.
@ -90,9 +117,6 @@ impl<'screen> Console<'screen> {
/// This function is used to check whether one of the two screens has an existing (and selected) [`Console`], /// This function is used to check whether one of the two screens has an existing (and selected) [`Console`],
/// so that the program can be sure its output will be shown *somewhere*. /// so that the program can be sure its output will be shown *somewhere*.
/// ///
/// The main use of this is within the [`ctru::use_panic_handler()`](crate::use_panic_handler()) hook,
/// since it will only stop the program's execution if the user is able to see the panic information output on screen.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -177,16 +201,126 @@ impl<'screen> Console<'screen> {
/// # Notes /// # Notes
/// ///
/// The first two arguments are the desired coordinates of the top-left corner /// The first two arguments are the desired coordinates of the top-left corner
/// of the console, and the second pair is the new width and height. /// of the new window based on the row/column coordinates of a full-screen console.
/// The second pair is the new width and height.
///
/// # Example
/// ///
/// # Safety /// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// # use ctru::services::gfx::Gfx;
/// # let gfx = Gfx::new()?;
/// #
/// # use ctru::console::Console;
/// #
/// let mut top_console = Console::new(gfx.top_screen.borrow_mut());
/// top_console.set_window(10, 10, 16, 6);
/// ///
/// This function is unsafe because it does not validate whether the input will produce /// println!("I'm becoming claustrophobic in here!");
/// a console that actually fits on the screen. /// #
// TODO: Wrap this safely. /// # Ok(())
/// # }
/// ```
#[doc(alias = "consoleSetWindow")] #[doc(alias = "consoleSetWindow")]
pub unsafe fn set_window(&mut self, x: i32, y: i32, width: i32, height: i32) { pub fn set_window(&mut self, x: u8, y: u8, width: u8, height: u8) -> Result<(), Error> {
consoleSetWindow(self.context.as_mut(), x, y, width, height); let height_limit = 30;
let length_limit = self.max_width();
if x >= length_limit {
return Err(Error::CoordinateOutOfBounds(Axis::X));
}
if y >= height_limit {
return Err(Error::CoordinateOutOfBounds(Axis::Y));
}
if (x + width) > length_limit {
return Err(Error::DimensionOutOfBounds(Dimension::Width));
}
if (y + height) > height_limit {
return Err(Error::DimensionOutOfBounds(Dimension::Height));
}
unsafe {
consoleSetWindow(
self.context.as_mut(),
x.into(),
y.into(),
width.into(),
height.into(),
)
};
Ok(())
}
/// Reset the window's size to default parameters.
///
/// This can be used to undo the changes made by [`set_window()`](Console::set_window()).
///
/// # Example
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// # use ctru::services::gfx::Gfx;
/// # let gfx = Gfx::new()?;
/// #
/// # use ctru::console::Console;
/// #
/// let mut top_console = Console::new(gfx.top_screen.borrow_mut());
/// top_console.set_window(15, 15, 8, 10);
///
/// println!("It's really jammed in here!");
///
/// top_console.reset_window();
///
/// println!("Phew, finally a breath of fresh air.");
/// #
/// # Ok(())
/// # }
/// ```
pub fn reset_window(&mut self) {
let width = self.max_width();
self.set_window(0, 0, width, 30).unwrap();
}
/// Returns this [`Console`]'s maximum character width depending on the screen used.
///
/// # Example
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// # use ctru::services::gfx::Gfx;
/// # use ctru::console::Console;
/// #
/// let gfx = Gfx::new()?;
///
/// let top_console = Console::new(gfx.top_screen.borrow_mut());
///
/// // The maximum width for the top screen (without any alterations) is 50 characters.
/// assert_eq!(top_console.max_width(), 50);
/// #
/// # Ok(())
/// # }
/// ```
pub fn max_width(&self) -> u8 {
match self.screen.as_raw() {
ctru_sys::GFX_TOP => {
if unsafe { ctru_sys::gfxIsWide() } {
100
} else {
50
}
}
ctru_sys::GFX_BOTTOM => 40,
_ => unreachable!(),
}
} }
} }
@ -215,3 +349,36 @@ impl Drop for Console<'_> {
} }
} }
} }
impl std::fmt::Display for Axis {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::X => write!(f, "x"),
Self::Y => write!(f, "y"),
}
}
}
impl std::fmt::Display for Dimension {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Width => write!(f, "width"),
Self::Height => write!(f, "height"),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CoordinateOutOfBounds(a) => {
write!(f, "coordinate specified for the {a} axis is out of bounds")
}
Self::DimensionOutOfBounds(d) => {
write!(f, "size specified for the {d} is out of bounds")
}
}
}
}
impl std::error::Error for Error {}

49
ctru-rs/src/lib.rs

@ -56,55 +56,6 @@ macro_rules! from_impl {
}; };
} }
/// Activate the custom [`ctru-rs`](crate) panic handler.
///
/// With this implementation, the main thread will stop and try to print debug info to an available [`Console`](console::Console).
/// In case it fails to find an active [`Console`](console::Console) the program will just exit.
///
/// # Notes
///
/// When `test` is enabled, this function will not do anything, as its behaviour should be overridden by the `test` environment.
pub fn use_panic_handler() {
#[cfg(not(test))]
panic_hook_setup();
}
/// Internal protocol to activate the custom panic handler hook.
///
/// # Notes
///
/// When `test` is enabled, this function will be ignored.
#[cfg(not(test))]
fn panic_hook_setup() {
use crate::services::hid::{Hid, KeyPad};
use std::panic::PanicInfo;
let main_thread = std::thread::current().id();
// Panic Hook setup
let default_hook = std::panic::take_hook();
let new_hook = Box::new(move |info: &PanicInfo| {
default_hook(info);
// Only for panics in the main thread
if main_thread == std::thread::current().id() && console::Console::exists() {
println!("\nPress SELECT to exit the software");
match Hid::new() {
Ok(mut hid) => loop {
hid.scan_input();
let keys = hid.keys_down();
if keys.contains(KeyPad::SELECT) {
break;
}
},
Err(e) => println!("Error while intializing Hid controller during panic: {e}"),
}
}
});
std::panic::set_hook(new_hook);
}
pub mod applets; pub mod applets;
pub mod console; pub mod console;
pub mod error; pub mod error;

2
ctru-rs/src/linear.rs

@ -16,7 +16,7 @@ use std::ptr::NonNull;
// Sadly the linear memory allocator included in `libctru` doesn't implement `linearRealloc` at the time of these additions, // Sadly the linear memory allocator included in `libctru` doesn't implement `linearRealloc` at the time of these additions,
// but the default fallback of the `std` will take care of that for us. // but the default fallback of the `std` will take care of that for us.
/// [`Allocator`](std::alloc::Allocator) struct for LINEAR memory. /// [`Allocator`] struct for LINEAR memory.
/// ///
/// To use this struct the main crate must activate the `allocator_api` unstable feature. /// To use this struct the main crate must activate the `allocator_api` unstable feature.
#[derive(Copy, Clone, Default, Debug)] #[derive(Copy, Clone, Default, Debug)]

798
ctru-rs/src/services/cam.rs

File diff suppressed because it is too large Load Diff

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

@ -37,10 +37,16 @@ pub trait Screen: private::Sealed {
/// Returns the Screen side (left or right). /// Returns the Screen side (left or right).
fn side(&self) -> Side; fn side(&self) -> Side;
/// Returns a [`RawFrameBuffer`] for the screen. /// Returns a [`RawFrameBuffer`] for the screen (if the framebuffer was allocated on the HEAP).
/// ///
/// Note that the pointer of the framebuffer returned by this function can /// # Notes
/// change after each call to this function if double buffering is enabled. ///
/// The pointer of the framebuffer returned by this function can change after each call
/// to this function if double buffering is enabled, so it's suggested to NOT save it for later use.
///
/// # Panics
///
/// If the [`Gfx`] service was initialised via [`Gfx::with_formats_vram()`] this function will crash the program with an ARM exception.
#[doc(alias = "gfxGetFramebuffer")] #[doc(alias = "gfxGetFramebuffer")]
fn raw_framebuffer(&mut self) -> RawFrameBuffer { fn raw_framebuffer(&mut self) -> RawFrameBuffer {
let mut width: u16 = 0; let mut width: u16 = 0;
@ -244,14 +250,15 @@ pub struct Gfx {
_service_handler: ServiceReference, _service_handler: ServiceReference,
} }
static GFX_ACTIVE: Mutex<usize> = Mutex::new(0); static GFX_ACTIVE: Mutex<()> = Mutex::new(());
impl Gfx { impl Gfx {
/// Initialize a new default service handle. /// Initialize a new default service handle.
/// ///
/// # Notes /// # Notes
/// ///
/// It's the same as calling: /// The new `Gfx` instance will allocate the needed framebuffers in the CPU-GPU shared memory region (to ensure compatibiltiy with all possible uses of the `Gfx` service).
/// As such, it's the same as calling:
/// ///
/// ``` /// ```
/// # let _runner = test_runner::GdbRunner::default(); /// # let _runner = test_runner::GdbRunner::default();
@ -261,12 +268,14 @@ impl Gfx {
/// # use ctru::services::gfx::Gfx; /// # use ctru::services::gfx::Gfx;
/// # use ctru::services::gspgpu::FramebufferFormat; /// # use ctru::services::gspgpu::FramebufferFormat;
/// # /// #
/// Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)?; /// Gfx::with_formats_shared(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8)?;
/// # /// #
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
/// ///
/// Have a look at [`Gfx::with_formats_vram()`] if you aren't interested in manipulating the framebuffers using the CPU.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -283,10 +292,10 @@ impl Gfx {
/// ``` /// ```
#[doc(alias = "gfxInit")] #[doc(alias = "gfxInit")]
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) Gfx::with_formats_shared(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8)
} }
/// Initialize a new service handle with the chosen framebuffer formats for the top and bottom screens. /// Initialize a new service handle with the chosen framebuffer formats on the HEAP for the top and bottom screens.
/// ///
/// Use [`Gfx::new()`] instead of this function to initialize the module with default parameters /// Use [`Gfx::new()`] instead of this function to initialize the module with default parameters
/// ///
@ -302,22 +311,66 @@ impl Gfx {
/// ///
/// // Top screen uses RGBA8, bottom screen uses RGB565. /// // Top screen uses RGBA8, bottom screen uses RGB565.
/// // The screen buffers are allocated in the standard HEAP memory, and not in VRAM. /// // The screen buffers are allocated in the standard HEAP memory, and not in VRAM.
/// let gfx = Gfx::with_formats(FramebufferFormat::Rgba8, FramebufferFormat::Rgb565, false)?; /// let gfx = Gfx::with_formats_shared(FramebufferFormat::Rgba8, FramebufferFormat::Rgb565)?;
/// # /// #
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[doc(alias = "gfxInit")] #[doc(alias = "gfxInit")]
pub fn with_formats( pub fn with_formats_shared(
top_fb_fmt: FramebufferFormat,
bottom_fb_fmt: FramebufferFormat,
) -> Result<Self> {
Self::with_configuration(top_fb_fmt, bottom_fb_fmt, false)
}
/// Initialize a new service handle with the chosen framebuffer formats on the VRAM for the top and bottom screens.
///
/// # Notes
///
/// Though unsafe to do so, it's suggested to use VRAM buffers when working exclusively with the GPU,
/// since they result in faster performance and less memory waste.
///
/// # Safety
///
/// By initializing the [`Gfx`] service as such, all functionality that relies on CPU manipulation of the framebuffers will
/// be completely unavailable (usually resulting in an ARM panic if wrongly used).
///
/// Usage of functionality such as [`Console`](crate::console::Console) and [`Screen::raw_framebuffer()`] will result in ARM exceptions.
///
/// # Example
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::{gfx::Gfx, gspgpu::FramebufferFormat};
///
/// // Top screen uses RGBA8, bottom screen uses RGB565.
/// // The screen buffers are allocated in the in VRAM, so they will NOT be accessible from the CPU.
/// let gfx = unsafe { Gfx::with_formats_vram(FramebufferFormat::Rgba8, FramebufferFormat::Rgb565)? };
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "gfxInit")]
pub unsafe fn with_formats_vram(
top_fb_fmt: FramebufferFormat,
bottom_fb_fmt: FramebufferFormat,
) -> Result<Self> {
Self::with_configuration(top_fb_fmt, bottom_fb_fmt, true)
}
// Internal function to handle the initialization of `Gfx`.
fn with_configuration(
top_fb_fmt: FramebufferFormat, top_fb_fmt: FramebufferFormat,
bottom_fb_fmt: FramebufferFormat, bottom_fb_fmt: FramebufferFormat,
use_vram_buffers: bool, vram_buffer: bool,
) -> Result<Self> { ) -> Result<Self> {
let handler = ServiceReference::new( let handler = ServiceReference::new(
&GFX_ACTIVE, &GFX_ACTIVE,
false,
|| unsafe { || unsafe {
ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), vram_buffer);
Ok(()) Ok(())
}, },

14
ctru-rs/src/services/gspgpu.rs

@ -5,19 +5,19 @@
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Event { pub enum Event {
/// Memory fill completed. /// Memory fill 1 completed.
Psc0 = ctru_sys::GSPGPU_EVENT_PSC0, Psc0 = ctru_sys::GSPGPU_EVENT_PSC0,
/// TODO: Unknown. /// Memory fill 2 completed.
Psc1 = ctru_sys::GSPGPU_EVENT_PSC1, Psc1 = ctru_sys::GSPGPU_EVENT_PSC1,
/// TODO: Unknown. /// Top screen VBlank.
VBlank0 = ctru_sys::GSPGPU_EVENT_VBlank0, VBlank0 = ctru_sys::GSPGPU_EVENT_VBlank0,
/// TODO: Unknown. /// Bottom screen VBlank.
VBlank1 = ctru_sys::GSPGPU_EVENT_VBlank1, VBlank1 = ctru_sys::GSPGPU_EVENT_VBlank1,
/// Display transfer finished. /// Display transfer completed.
PPF = ctru_sys::GSPGPU_EVENT_PPF, PPF = ctru_sys::GSPGPU_EVENT_PPF,
/// Command list processing finished. /// Command list processing completed.
P3D = ctru_sys::GSPGPU_EVENT_P3D, P3D = ctru_sys::GSPGPU_EVENT_P3D,
/// TODO: Unknown. /// Direct Memory Access requested.
DMA = ctru_sys::GSPGPU_EVENT_DMA, DMA = ctru_sys::GSPGPU_EVENT_DMA,
} }

288
ctru-rs/src/services/hid.rs

@ -1,15 +1,21 @@
//! Human Interface Device service. //! Human Interface Device service.
//! //!
//! The HID service provides read access to user input such as [button presses](Hid::keys_down), [touch screen presses](Hid::touch_position), //! The HID service provides read access to user input such as [button presses](Hid::keys_down), [touch screen presses](Hid::touch_position),
//! and [circle pad information](Hid::circlepad_position). It also provides information from the sound volume slider, the accelerometer, and the gyroscope. //! and [circle pad information](Hid::circlepad_position). It also provides information from the [volume slider](Hid::volume_slider()),
// TODO: Implement volume slider, accelerometer and gyroscope + any other missing functionality. //! the [accelerometer](Hid::accelerometer_vector()), and the [gyroscope](Hid::gyroscope_rate()).
#![doc(alias = "input")] #![doc(alias = "input")]
#![doc(alias = "controller")] #![doc(alias = "controller")]
#![doc(alias = "gamepad")] #![doc(alias = "gamepad")]
use std::sync::Mutex;
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::services::ServiceReference;
use bitflags::bitflags; use bitflags::bitflags;
static HID_ACTIVE: Mutex<()> = Mutex::new(());
bitflags! { bitflags! {
/// A set of flags corresponding to the button and directional pad inputs present on the 3DS. /// A set of flags corresponding to the button and directional pad inputs present on the 3DS.
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
@ -74,8 +80,44 @@ bitflags! {
} }
} }
/// Error enum for generic errors within the [`Hid`] service.
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Error {
/// An attempt was made to access the accelerometer while disabled.
UnavailableAccelerometer,
/// An attempt was made to access the gyroscope while disabled.
UnavailableGyroscope,
}
/// Representation of the acceleration vector read by the accelerometer.
///
/// Have a look at [`Hid::set_accelerometer()`] for more information.
#[allow(missing_docs)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
pub struct Acceleration {
x: i16,
y: i16,
z: i16,
}
/// Representation of the angular rate read by the gyroscope.
///
/// Have a look at [`Hid::set_gyroscope()`] for more information.
#[allow(missing_docs)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
pub struct AngularRate {
roll: i16,
pitch: i16,
yaw: i16,
}
/// Handle to the HID service. /// Handle to the HID service.
pub struct Hid(()); pub struct Hid {
active_accelerometer: bool,
active_gyroscope: bool,
_service_handler: ServiceReference,
}
impl Hid { impl Hid {
/// Initialize a new service handle. /// Initialize a new service handle.
@ -101,10 +143,26 @@ impl Hid {
/// ``` /// ```
#[doc(alias = "hidInit")] #[doc(alias = "hidInit")]
pub fn new() -> crate::Result<Hid> { pub fn new() -> crate::Result<Hid> {
unsafe { let handler = ServiceReference::new(
ResultCode(ctru_sys::hidInit())?; &HID_ACTIVE,
Ok(Hid(())) || {
} ResultCode(unsafe { ctru_sys::hidInit() })?;
Ok(())
},
|| unsafe {
let _ = ctru_sys::HIDUSER_DisableGyroscope();
let _ = ctru_sys::HIDUSER_DisableAccelerometer();
ctru_sys::hidExit();
},
)?;
Ok(Self {
active_accelerometer: false,
active_gyroscope: false,
_service_handler: handler,
})
} }
/// Scan the HID service for all user input occurring on the current frame. /// Scan the HID service for all user input occurring on the current frame.
@ -289,11 +347,219 @@ impl Hid {
(res.dx, res.dy) (res.dx, res.dy)
} }
/// Returns the current volume slider position (between 0 and 1).
///
/// # Notes
///
/// The [`ndsp`](crate::services::ndsp) service automatically uses the volume slider's position to handle audio mixing.
/// As such this method should not be used to programmatically change the volume.
///
/// Its purpose is only to inform the program of the volume slider's position (e.g. checking if the user has muted the audio).
///
/// # Example
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::hid::Hid;
/// let mut hid = Hid::new()?;
///
/// hid.scan_input();
///
/// let volume = hid.volume_slider();
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "HIDUSER_GetSoundVolume")]
pub fn volume_slider(&self) -> f32 {
let mut slider = 0;
unsafe {
let _ = ctru_sys::HIDUSER_GetSoundVolume(&mut slider);
}
(slider as f32) / 63.
}
/// Activate/deactivate the console's acceleration sensor.
///
/// # Example
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::hid::Hid;
/// let mut hid = Hid::new()?;
///
/// // The accelerometer will start to register movements.
/// hid.set_accelerometer(true).unwrap();
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "HIDUSER_EnableAccelerometer")]
#[doc(alias = "HIDUSER_DisableAccelerometer")]
pub fn set_accelerometer(&mut self, enabled: bool) -> crate::Result<()> {
if enabled {
ResultCode(unsafe { ctru_sys::HIDUSER_EnableAccelerometer() })?;
} else {
ResultCode(unsafe { ctru_sys::HIDUSER_DisableAccelerometer() })?;
} }
impl Drop for Hid { self.active_accelerometer = enabled;
#[doc(alias = "hidExit")]
fn drop(&mut self) { Ok(())
unsafe { ctru_sys::hidExit() };
} }
/// Activate/deactivate the console's gyroscopic sensor.
///
/// # Example
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::hid::Hid;
/// let mut hid = Hid::new()?;
///
/// // The gyroscope will start to register positions.
/// hid.set_gyroscope(true).unwrap();
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "HIDUSER_EnableGyroscope")]
#[doc(alias = "HIDUSER_DisableGyroscope")]
pub fn set_gyroscope(&mut self, enabled: bool) -> crate::Result<()> {
if enabled {
ResultCode(unsafe { ctru_sys::HIDUSER_EnableGyroscope() })?;
} else {
ResultCode(unsafe { ctru_sys::HIDUSER_DisableGyroscope() })?;
} }
self.active_gyroscope = enabled;
Ok(())
}
/// Returns the acceleration vector (x, y, z) registered by the accelerometer.
///
/// # Errors
///
/// This function will return an error if the accelerometer was not previously enabled.
/// Have a look at [`Hid::set_accelerometer()`] to enable the accelerometer.
///
/// # Example
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::hid::Hid;
/// let mut hid = Hid::new()?;
///
/// // The accelerometer will start to register movements.
/// hid.set_accelerometer(true).unwrap();
///
/// // It's necessary to run `scan_input()` to update the accelerometer's readings.
/// hid.scan_input();
///
/// // This call fails if the accelerometer was not previously enabled.
/// let acceleration = hid.accelerometer_vector()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "hidAccelRead")]
pub fn accelerometer_vector(&self) -> Result<Acceleration, Error> {
if !self.active_accelerometer {
return Err(Error::UnavailableAccelerometer);
}
let mut res = ctru_sys::accelVector { x: 0, y: 0, z: 0 };
unsafe {
ctru_sys::hidAccelRead(&mut res);
}
Ok(Acceleration {
x: res.x,
y: res.y,
z: res.z,
})
}
/// Returns the angular rate registered by the gyroscope.
///
/// # Errors
///
/// This function returns an error if the gyroscope was not previously enabled.
/// Have a look at [`Hid::set_gyroscope()`].
///
/// # Example
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::hid::Hid;
/// let mut hid = Hid::new()?;
///
/// // The gyroscope will start to register positions.
/// hid.set_gyroscope(true).unwrap();
///
/// // It's necessary to run `scan_input()` to update the gyroscope's readings.
/// hid.scan_input();
///
/// // This call fails if the gyroscope was not previously enabled.
/// let angular_rate = hid.gyroscope_rate()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "hidGyroRead")]
pub fn gyroscope_rate(&self) -> Result<AngularRate, Error> {
if !self.active_gyroscope {
return Err(Error::UnavailableGyroscope);
}
let mut res = ctru_sys::angularRate { x: 0, y: 0, z: 0 };
unsafe {
ctru_sys::hidGyroRead(&mut res);
}
Ok(AngularRate {
roll: res.x,
pitch: res.y,
yaw: res.z,
})
}
}
impl From<Acceleration> for (i16, i16, i16) {
fn from(value: Acceleration) -> (i16, i16, i16) {
(value.x, value.y, value.z)
}
}
impl From<AngularRate> for (i16, i16, i16) {
fn from(value: AngularRate) -> (i16, i16, i16) {
(value.roll, value.pitch, value.yaw)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnavailableAccelerometer => write!(f, "tried using accelerometer while disabled"),
Self::UnavailableGyroscope => write!(f, "tried using gyroscope while disabled"),
}
}
}
impl std::error::Error for Error {}

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

@ -61,6 +61,16 @@ pub struct AudioMix {
raw: [f32; 12], raw: [f32; 12],
} }
/// Auxiliary Device index.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(usize)]
pub enum AuxDevice {
/// Aux device with index 0.
Zero = 0,
/// Aux device with index 1.
One = 1,
}
/// Interpolation used between audio frames. /// Interpolation used between audio frames.
#[doc(alias = "ndspInterpType")] #[doc(alias = "ndspInterpType")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -75,8 +85,9 @@ pub enum InterpolationType {
} }
/// Errors returned by [`ndsp`](self) functions. /// Errors returned by [`ndsp`](self) functions.
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NdspError { pub enum Error {
/// Channel with the specified ID does not exist. /// Channel with the specified ID does not exist.
InvalidChannel(u8), InvalidChannel(u8),
/// Channel with the specified ID is already being used. /// Channel with the specified ID is already being used.
@ -107,7 +118,7 @@ pub struct Channel<'ndsp> {
_rf: RefMut<'ndsp, ()>, // we don't need to hold any data _rf: RefMut<'ndsp, ()>, // we don't need to hold any data
} }
static NDSP_ACTIVE: Mutex<usize> = Mutex::new(0); static NDSP_ACTIVE: Mutex<()> = Mutex::new(());
/// Handle to the DSP service. /// Handle to the DSP service.
/// ///
@ -143,7 +154,6 @@ impl Ndsp {
pub fn new() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&NDSP_ACTIVE, &NDSP_ACTIVE,
false,
|| { || {
ResultCode(unsafe { ctru_sys::ndspInit() })?; ResultCode(unsafe { ctru_sys::ndspInit() })?;
@ -180,7 +190,7 @@ impl Ndsp {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn channel(&self, id: u8) -> std::result::Result<Channel, NdspError> { pub fn channel(&self, id: u8) -> std::result::Result<Channel, Error> {
let in_bounds = self.channel_flags.get(id as usize); let in_bounds = self.channel_flags.get(id as usize);
match in_bounds { match in_bounds {
@ -188,10 +198,10 @@ impl Ndsp {
let flag = ref_cell.try_borrow_mut(); let flag = ref_cell.try_borrow_mut();
match flag { match flag {
Ok(_rf) => Ok(Channel { id, _rf }), Ok(_rf) => Ok(Channel { id, _rf }),
Err(_) => Err(NdspError::ChannelAlreadyInUse(id)), Err(_) => Err(Error::ChannelAlreadyInUse(id)),
} }
} }
None => Err(NdspError::InvalidChannel(id)), None => Err(Error::InvalidChannel(id)),
} }
} }
@ -528,9 +538,9 @@ impl Channel<'_> {
// TODO: Find a better way to handle the wave lifetime problem. // TODO: Find a better way to handle the wave lifetime problem.
// These "alive wave" shenanigans are the most substantial reason why I'd like to fully re-write this service in Rust. // These "alive wave" shenanigans are the most substantial reason why I'd like to fully re-write this service in Rust.
#[doc(alias = "ndspChnWaveBufAdd")] #[doc(alias = "ndspChnWaveBufAdd")]
pub fn queue_wave(&mut self, wave: &mut Wave) -> std::result::Result<(), NdspError> { pub fn queue_wave(&mut self, wave: &mut Wave) -> std::result::Result<(), Error> {
match wave.status() { match wave.status() {
Status::Playing | Status::Queued => return Err(NdspError::WaveBusy(self.id)), Status::Playing | Status::Queued => return Err(Error::WaveBusy(self.id)),
_ => (), _ => (),
} }
@ -672,23 +682,15 @@ impl AudioMix {
} }
/// Returns the values set for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). /// Returns the values set for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
pub fn aux_front(&self, id: usize) -> (f32, f32) { pub fn aux_front(&self, id: AuxDevice) -> (f32, f32) {
if id > 1 { let index = 4 + (id as usize * 4);
panic!("invalid auxiliary output device index")
}
let index = 4 + id * 4;
(self.raw[index], self.raw[index + 1]) (self.raw[index], self.raw[index + 1])
} }
/// Returns the values set for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). /// Returns the values set for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
pub fn aux_back(&self, id: usize) -> (f32, f32) { pub fn aux_back(&self, id: AuxDevice) -> (f32, f32) {
if id > 1 { let index = 6 + (id as usize * 4);
panic!("invalid auxiliary output device index")
}
let index = 6 + id * 4;
(self.raw[index], self.raw[index + 1]) (self.raw[index], self.raw[index + 1])
} }
@ -721,12 +723,8 @@ impl AudioMix {
/// ///
/// [`Channel`] will normalize the mix values to be within 0 and 1. /// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [`AudioMix`] instance with larger/smaller values is valid. /// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_aux_front(&mut self, left: f32, right: f32, id: usize) { pub fn set_aux_front(&mut self, left: f32, right: f32, id: AuxDevice) {
if id > 1 { let index = 4 + (id as usize * 4);
panic!("invalid auxiliary output device index")
}
let index = 4 + id * 4;
self.raw[index] = left; self.raw[index] = left;
self.raw[index + 1] = right; self.raw[index + 1] = right;
@ -738,12 +736,8 @@ impl AudioMix {
/// ///
/// [`Channel`] will normalize the mix values to be within 0 and 1. /// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [`AudioMix`] instance with larger/smaller values is valid. /// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_aux_back(&mut self, left: f32, right: f32, id: usize) { pub fn set_aux_back(&mut self, left: f32, right: f32, id: AuxDevice) {
if id > 1 { let index = 6 + (id as usize * 4);
panic!("invalid auxiliary output device index")
}
let index = 6 + id * 4;
self.raw[index] = left; self.raw[index] = left;
self.raw[index + 1] = right; self.raw[index + 1] = right;
@ -766,7 +760,7 @@ impl From<[f32; 12]> for AudioMix {
} }
} }
impl fmt::Display for NdspError { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::InvalidChannel(id) => write!(f, "audio Channel with ID {id} doesn't exist. Valid channels have an ID between 0 and 23"), Self::InvalidChannel(id) => write!(f, "audio Channel with ID {id} doesn't exist. Valid channels have an ID between 0 and 23"),
@ -777,7 +771,7 @@ impl fmt::Display for NdspError {
} }
} }
impl error::Error for NdspError {} impl error::Error for Error {}
impl Drop for Ndsp { impl Drop for Ndsp {
#[doc(alias = "ndspExit")] #[doc(alias = "ndspExit")]

12
ctru-rs/src/services/ndsp/wave.rs

@ -2,7 +2,7 @@
//! //!
//! This modules has all methods and structs required to work with audio waves meant to be played via the [`ndsp`](crate::services::ndsp) service. //! This modules has all methods and structs required to work with audio waves meant to be played via the [`ndsp`](crate::services::ndsp) service.
use super::{AudioFormat, NdspError}; use super::{AudioFormat, Error};
use crate::linear::LinearAllocator; use crate::linear::LinearAllocator;
/// Informational struct holding the raw audio data and playback info. /// Informational struct holding the raw audio data and playback info.
@ -98,10 +98,10 @@ impl Wave {
/// ///
/// This function will return an error if the [`Wave`] is currently busy, /// This function will return an error if the [`Wave`] is currently busy,
/// with the id to the channel in which it's queued. /// with the id to the channel in which it's queued.
pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> { pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], Error> {
match self.status() { match self.status() {
Status::Playing | Status::Queued => { Status::Playing | Status::Queued => {
Err(NdspError::WaveBusy(self.played_on_channel.unwrap())) Err(Error::WaveBusy(self.played_on_channel.unwrap()))
} }
_ => Ok(&mut self.buffer), _ => Ok(&mut self.buffer),
} }
@ -165,10 +165,10 @@ impl Wave {
/// ///
/// This function will return an error if the sample size exceeds the buffer's capacity /// This function will return an error if the sample size exceeds the buffer's capacity
/// or if the [`Wave`] is currently queued. /// or if the [`Wave`] is currently queued.
pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> { pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), Error> {
match self.status() { match self.status() {
Status::Playing | Status::Queued => { Status::Playing | Status::Queued => {
return Err(NdspError::WaveBusy(self.played_on_channel.unwrap())); return Err(Error::WaveBusy(self.played_on_channel.unwrap()));
} }
_ => (), _ => (),
} }
@ -176,7 +176,7 @@ impl Wave {
let max_count = self.buffer.len() / self.audio_format.size(); let max_count = self.buffer.len() / self.audio_format.size();
if sample_count > max_count { if sample_count > max_count {
return Err(NdspError::SampleCountOutOfBounds(sample_count, max_count)); return Err(Error::SampleCountOutOfBounds(sample_count, max_count));
} }
self.raw_data.nsamples = sample_count as u32; self.raw_data.nsamples = sample_count as u32;

43
ctru-rs/src/services/reference.rs

@ -1,35 +1,37 @@
use crate::Error; use crate::Error;
use std::sync::Mutex; use std::sync::{Mutex, MutexGuard, TryLockError};
pub(crate) struct ServiceReference { pub(crate) struct ServiceReference {
counter: &'static Mutex<usize>, _guard: MutexGuard<'static, ()>,
close: Box<dyn Fn() + Send + Sync>, close: Box<dyn Fn() + Send + Sync>,
} }
impl ServiceReference { impl ServiceReference {
pub fn new<S, E>( pub fn new<S, E>(counter: &'static Mutex<()>, start: S, close: E) -> crate::Result<Self>
counter: &'static Mutex<usize>,
allow_multiple: bool,
start: S,
close: E,
) -> crate::Result<Self>
where where
S: FnOnce() -> crate::Result<()>, S: FnOnce() -> crate::Result<()>,
E: Fn() + Send + Sync + 'static, E: Fn() + Send + Sync + 'static,
{ {
let mut value = counter let _guard = match counter.try_lock() {
.lock() Ok(lock) => lock,
.expect("Mutex Counter for ServiceReference is poisoned"); // todo: handle poisoning Err(e) => match e {
TryLockError::Poisoned(guard) => {
// If the MutexGuard is poisoned that means that the "other" service instance (of which the thread panicked)
// was NOT properly closed. To avoid any weird behaviour, we try closing the service now, to then re-open a fresh instance.
//
// It's up to our `close()` implementations to avoid panicking/doing weird stuff again.
close();
if *value == 0 { guard.into_inner()
start()?;
} else if !allow_multiple {
return Err(Error::ServiceAlreadyActive);
} }
TryLockError::WouldBlock => return Err(Error::ServiceAlreadyActive),
},
};
*value += 1; start()?;
Ok(Self { Ok(Self {
counter, _guard,
close: Box::new(close), close: Box::new(close),
}) })
} }
@ -37,13 +39,6 @@ impl ServiceReference {
impl Drop for ServiceReference { impl Drop for ServiceReference {
fn drop(&mut self) { fn drop(&mut self) {
let mut value = self
.counter
.lock()
.expect("Mutex Counter for ServiceReference is poisoned"); // todo: handle poisoning
*value -= 1;
if *value == 0 {
(self.close)(); (self.close)();
} }
} }
}

19
ctru-rs/src/services/romfs.rs

@ -38,7 +38,7 @@ pub struct RomFS {
_service_handler: ServiceReference, _service_handler: ServiceReference,
} }
static ROMFS_ACTIVE: Mutex<usize> = Mutex::new(0); static ROMFS_ACTIVE: Mutex<()> = Mutex::new(());
impl RomFS { impl RomFS {
/// Mount the bundled RomFS archive as a virtual drive. /// Mount the bundled RomFS archive as a virtual drive.
@ -64,7 +64,6 @@ impl RomFS {
pub fn new() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&ROMFS_ACTIVE, &ROMFS_ACTIVE,
true,
|| { || {
let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap();
ResultCode(unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) })?; ResultCode(unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) })?;
@ -84,19 +83,15 @@ impl RomFS {
mod tests { mod tests {
use super::*; use super::*;
#[test]
// NOTE: this test only passes when run with a .3dsx, which for now requires separate build // NOTE: this test only passes when run with a .3dsx, which for now requires separate build
// and run steps so the 3dsx is built before the runner looks for the executable // and run steps so the 3dsx is built before the runner looks for the executable
fn romfs_counter() { #[test]
let _romfs = RomFS::new().unwrap(); #[should_panic]
let value = *ROMFS_ACTIVE.lock().unwrap(); fn romfs_lock() {
let romfs = RomFS::new().unwrap();
assert_eq!(value, 1);
drop(_romfs);
let value = *ROMFS_ACTIVE.lock().unwrap(); ROMFS_ACTIVE.try_lock().unwrap();
assert_eq!(value, 0); drop(romfs);
} }
} }

3
ctru-rs/src/services/soc.rs

@ -19,7 +19,7 @@ pub struct Soc {
sock_3dslink: libc::c_int, sock_3dslink: libc::c_int,
} }
static SOC_ACTIVE: Mutex<usize> = Mutex::new(0); static SOC_ACTIVE: Mutex<()> = Mutex::new(());
impl Soc { impl Soc {
/// Initialize a new service handle using a socket buffer size of `0x100000` bytes. /// Initialize a new service handle using a socket buffer size of `0x100000` bytes.
@ -73,7 +73,6 @@ impl Soc {
pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result<Self> { pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&SOC_ACTIVE, &SOC_ACTIVE,
false,
|| { || {
let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32; let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32;
ResultCode(unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) })?; ResultCode(unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) })?;

Loading…
Cancel
Save