diff --git a/ctru-rs/examples/audio-filters.rs b/ctru-rs/examples/audio-filters.rs index 75941b7..1dfde5a 100644 --- a/ctru-rs/examples/audio-filters.rs +++ b/ctru-rs/examples/audio-filters.rs @@ -40,8 +40,6 @@ fn fill_buffer(audio_data: &mut [u8], frequency: f32) { } fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index 0de9e7c..258a715 100644 --- a/ctru-rs/examples/buttons.rs +++ b/ctru-rs/examples/buttons.rs @@ -5,8 +5,6 @@ use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - let apt = Apt::new().unwrap(); let mut hid = Hid::new().unwrap(); let gfx = Gfx::new().unwrap(); @@ -25,6 +23,12 @@ fn main() { // Get information about which keys were held down on this frame. 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 // from what they were on the previous frame. if keys != old_keys { @@ -44,7 +48,7 @@ fn main() { // and the `.intersects()` method checks for any of the provided keys. // // You can also use the `.bits()` method to do direct comparisons on - // the underlying bits + // the underlying bits. if keys.contains(KeyPad::A) { println!("You held A!"); diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 851aa2a..4084f21 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/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. use ctru::prelude::*; -use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize}; -use ctru::services::gfx::{Flush, Screen, Swap}; +use ctru::services::cam::{ + Cam, Camera, OutputFormat, ShutterSound, Trimming, ViewSize, WhiteBalance, +}; +use ctru::services::gfx::{Flush, Screen, Swap, TopScreen3D}; use ctru::services::gspgpu::FramebufferFormat; 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); fn main() { - ctru::use_panic_handler(); - let apt = Apt::new().expect("Failed to initialize Apt service."); let mut hid = Hid::new().expect("Failed to initialize Hid service."); let gfx = Gfx::new().expect("Failed to initialize GFX service."); - let mut top_screen = gfx.top_screen.borrow_mut(); - top_screen.set_double_buffering(true); - top_screen.set_framebuffer_format(FramebufferFormat::Rgb565); + gfx.top_screen.borrow_mut().set_double_buffering(true); + gfx.top_screen + .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()); @@ -35,9 +32,8 @@ fn main() { let mut cam = Cam::new().expect("Failed to initialize CAM service."); // Camera setup. + let camera = &mut cam.both_outer_cams; { - let camera = &mut cam.outer_right_cam; - camera .set_view_size(ViewSize::TopLCD) .expect("Failed to set camera size"); @@ -51,14 +47,17 @@ fn main() { .set_auto_exposure(true) .expect("Failed to enable auto exposure"); camera - .set_auto_white_balance(true) + .set_white_balance(WhiteBalance::Auto) .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 - .set_trimming(false) - .expect("Failed to disable trimming"); + .set_trimming(Trimming::new_centered_with_view(ViewSize::TopLCD)) + .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!("Press Start to exit"); @@ -75,28 +74,42 @@ fn main() { if keys_down.contains(KeyPad::R) { 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. camera - .take_picture( - &mut buf, - WIDTH.try_into().unwrap(), - HEIGHT.try_into().unwrap(), - WAIT_TIMEOUT, - ) + .take_picture(&mut buf, WAIT_TIMEOUT) .expect("Failed to take picture"); + let (width, height) = camera.final_view_size(); + // Play the normal shutter sound. cam.play_shutter_sound(ShutterSound::Normal) .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`. - top_screen.flush_buffers(); - top_screen.swap_buffers(); + top_screen_3d.flush_buffers(); + top_screen_3d.swap_buffers(); gfx.wait_for_vblank(); } @@ -125,8 +138,8 @@ fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: u unsafe { // 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. - let pixel_pointer = framebuf.offset(draw_index as isize); - pixel_pointer.copy_from(src.as_ptr().offset(read_index as isize), 2); + let pixel_pointer = framebuf.add(draw_index); + pixel_pointer.copy_from(src.as_ptr().add(read_index), 2); } } } diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 6fa1183..3ad37c9 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -11,8 +11,6 @@ use std::os::horizon::fs::MetadataExt; use std::path::{Path, PathBuf}; fn main() { - ctru::use_panic_handler(); - let apt = Apt::new().unwrap(); let mut hid = Hid::new().unwrap(); let gfx = Gfx::new().unwrap(); diff --git a/ctru-rs/examples/futures-basic.rs b/ctru-rs/examples/futures-basic.rs index c41245c..5eb1c2a 100644 --- a/ctru-rs/examples/futures-basic.rs +++ b/ctru-rs/examples/futures-basic.rs @@ -13,8 +13,6 @@ use futures::StreamExt; use std::os::horizon::thread::BuilderExt; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/futures-tokio.rs b/ctru-rs/examples/futures-tokio.rs index 986e930..06a7cff 100644 --- a/ctru-rs/examples/futures-tokio.rs +++ b/ctru-rs/examples/futures-tokio.rs @@ -6,8 +6,6 @@ use std::os::horizon::thread::BuilderExt; use std::time::Duration; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/gfx-3d-mode.rs b/ctru-rs/examples/gfx-3d-mode.rs index 7176085..a5f3b1c 100644 --- a/ctru-rs/examples/gfx-3d-mode.rs +++ b/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()]; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/gfx-bitmap.rs b/ctru-rs/examples/gfx-bitmap.rs index 8efb2be..e775d8e 100644 --- a/ctru-rs/examples/gfx-bitmap.rs +++ b/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"); fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/gfx-wide-mode.rs b/ctru-rs/examples/gfx-wide-mode.rs index 109169d..5cae138 100644 --- a/ctru-rs/examples/gfx-wide-mode.rs +++ b/ctru-rs/examples/gfx-wide-mode.rs @@ -8,8 +8,6 @@ use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - let apt = Apt::new().unwrap(); let mut hid = Hid::new().unwrap(); let gfx = Gfx::new().unwrap(); diff --git a/ctru-rs/examples/hashmaps.rs b/ctru-rs/examples/hashmaps.rs index e563068..97f42ae 100644 --- a/ctru-rs/examples/hashmaps.rs +++ b/ctru-rs/examples/hashmaps.rs @@ -9,8 +9,6 @@ use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - // HashMaps generate hashes thanks to the 3DS' cryptografically secure generator. // This generator is only active when activating the `PS` service. // This service is automatically initialized. diff --git a/ctru-rs/examples/hello-both-screens.rs b/ctru-rs/examples/hello-both-screens.rs index 1f2b383..d7dc798 100644 --- a/ctru-rs/examples/hello-both-screens.rs +++ b/ctru-rs/examples/hello-both-screens.rs @@ -5,8 +5,6 @@ use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - let apt = Apt::new().unwrap(); let mut hid = Hid::new().unwrap(); let gfx = Gfx::new().unwrap(); diff --git a/ctru-rs/examples/hello-world.rs b/ctru-rs/examples/hello-world.rs index 9210484..348f96d 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -6,10 +6,6 @@ use ctru::prelude::*; use std::io::BufWriter; 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. // These is standard setup any app would need. let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); diff --git a/ctru-rs/examples/linear-memory.rs b/ctru-rs/examples/linear-memory.rs index de653e6..8b386fb 100644 --- a/ctru-rs/examples/linear-memory.rs +++ b/ctru-rs/examples/linear-memory.rs @@ -10,8 +10,6 @@ use ctru::linear::LinearAllocator; use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/mii-selector.rs b/ctru-rs/examples/mii-selector.rs index db3a9ea..3b2b0ef 100644 --- a/ctru-rs/examples/mii-selector.rs +++ b/ctru-rs/examples/mii-selector.rs @@ -6,8 +6,6 @@ use ctru::applets::mii_selector::{Error, MiiSelector, Options}; use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - 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"); @@ -19,7 +17,7 @@ fn main() { mii_selector.set_options(Options::ENABLE_CANCEL); mii_selector.set_initial_index(3); // 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!"); // Launch the Mii Selector and use its result to print the selected Mii's information. diff --git a/ctru-rs/examples/movement.rs b/ctru-rs/examples/movement.rs new file mode 100644 index 0000000..af83488 --- /dev/null +++ b/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(); + } +} diff --git a/ctru-rs/examples/network-sockets.rs b/ctru-rs/examples/network-sockets.rs index d55e29c..9689500 100644 --- a/ctru-rs/examples/network-sockets.rs +++ b/ctru-rs/examples/network-sockets.rs @@ -9,8 +9,6 @@ use std::net::{Shutdown, TcpListener}; use std::time::Duration; fn main() { - ctru::use_panic_handler(); - let gfx = Gfx::new().unwrap(); let mut hid = Hid::new().unwrap(); let apt = Apt::new().unwrap(); diff --git a/ctru-rs/examples/output-3dslink.rs b/ctru-rs/examples/output-3dslink.rs index 9df3f3d..fb1194a 100644 --- a/ctru-rs/examples/output-3dslink.rs +++ b/ctru-rs/examples/output-3dslink.rs @@ -13,8 +13,6 @@ use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/romfs.rs b/ctru-rs/examples/romfs.rs index 490a785..e9c7f04 100644 --- a/ctru-rs/examples/romfs.rs +++ b/ctru-rs/examples/romfs.rs @@ -5,8 +5,6 @@ use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index e754781..61c32db 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -6,8 +6,6 @@ use ctru::applets::swkbd::{Button, SoftwareKeyboard}; use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - let apt = Apt::new().unwrap(); let mut hid = Hid::new().unwrap(); let gfx = Gfx::new().unwrap(); diff --git a/ctru-rs/examples/system-configuration.rs b/ctru-rs/examples/system-configuration.rs index f1748f7..6060066 100644 --- a/ctru-rs/examples/system-configuration.rs +++ b/ctru-rs/examples/system-configuration.rs @@ -7,8 +7,6 @@ use ctru::prelude::*; use ctru::services::cfgu::Cfgu; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/thread-basic.rs b/ctru-rs/examples/thread-basic.rs index 3e4604b..58474ba 100644 --- a/ctru-rs/examples/thread-basic.rs +++ b/ctru-rs/examples/thread-basic.rs @@ -6,8 +6,6 @@ use std::os::horizon::thread::BuilderExt; use std::time::Duration; fn main() { - ctru::use_panic_handler(); - let apt = Apt::new().unwrap(); let mut hid = Hid::new().unwrap(); let gfx = Gfx::new().unwrap(); diff --git a/ctru-rs/examples/thread-info.rs b/ctru-rs/examples/thread-info.rs index 46d34d3..4069bc3 100644 --- a/ctru-rs/examples/thread-info.rs +++ b/ctru-rs/examples/thread-info.rs @@ -7,8 +7,6 @@ use ctru::prelude::*; use std::os::horizon::thread::BuilderExt; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/thread-locals.rs b/ctru-rs/examples/thread-locals.rs index 72458c9..50e24a3 100644 --- a/ctru-rs/examples/thread-locals.rs +++ b/ctru-rs/examples/thread-locals.rs @@ -10,8 +10,6 @@ std::thread_local! { } fn main() { - ctru::use_panic_handler(); - let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); gfx.top_screen.borrow_mut().set_wide_mode(true); let mut hid = Hid::new().expect("Couldn't obtain HID controller"); diff --git a/ctru-rs/examples/time-rtc.rs b/ctru-rs/examples/time-rtc.rs index ca4709a..f05cd64 100644 --- a/ctru-rs/examples/time-rtc.rs +++ b/ctru-rs/examples/time-rtc.rs @@ -6,8 +6,6 @@ use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index d2898ac..03fe08f 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -8,8 +8,6 @@ use ctru::services::am::Am; use ctru::services::fs::FsMediaType; fn main() { - ctru::use_panic_handler(); - 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"); @@ -58,19 +56,17 @@ fn main() { if hid.keys_down().intersects(KeyPad::DOWN) { if offset + 1 < cur_list.len() { - offset = offset + 1; - refresh = true; - } - } else if hid.keys_down().intersects(KeyPad::UP) { - if offset > 0 { - offset = offset - 1; + offset += 1; refresh = true; } + } else if hid.keys_down().intersects(KeyPad::UP) && offset > 0 { + offset -= 1; + refresh = true; } // Render the title list via a scrollable text UI. 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. top_screen.select(); diff --git a/ctru-rs/examples/touch-screen.rs b/ctru-rs/examples/touch-screen.rs index fe2409d..8475eac 100644 --- a/ctru-rs/examples/touch-screen.rs +++ b/ctru-rs/examples/touch-screen.rs @@ -5,8 +5,6 @@ use ctru::prelude::*; fn main() { - ctru::use_panic_handler(); - 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"); diff --git a/ctru-rs/src/applets/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs index bd0e4b4..2353d7d 100644 --- a/ctru-rs/src/applets/mii_selector.rs +++ b/ctru-rs/src/applets/mii_selector.rs @@ -1,7 +1,7 @@ //! Mii Selector applet. //! //! 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 bitflags::bitflags; @@ -9,7 +9,7 @@ use std::{ffi::CString, fmt}; /// 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)] pub enum Index { /// Specific Mii index. @@ -136,11 +136,11 @@ impl MiiSelector { 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 /// - /// 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. /// /// # Example @@ -151,12 +151,12 @@ impl MiiSelector { /// use ctru::applets::mii_selector::{Index, MiiSelector}; /// let mut mii_selector = MiiSelector::new(); /// - /// // Whitelist the guest Mii at index 2. - /// mii_selector.whitelist_guest_mii(Index::Index(2)); + /// // Allowlist the guest Mii at index 2. + /// mii_selector.allowlist_guest_mii(Index::Index(2)); /// # } /// ``` #[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 { Index::Index(i) => i, Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS, @@ -165,11 +165,11 @@ impl MiiSelector { 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 /// - /// 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. /// /// # Example @@ -180,12 +180,12 @@ impl MiiSelector { /// use ctru::applets::mii_selector::{Index, MiiSelector}; /// let mut mii_selector = MiiSelector::new(); /// - /// // Blacklist the guest Mii at index 1 so that it cannot be selected. - /// mii_selector.blacklist_guest_mii(Index::Index(1)); + /// // Blocklist the guest Mii at index 1 so that it cannot be selected. + /// mii_selector.blocklist_guest_mii(Index::Index(1)); /// # } /// ``` #[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 { Index::Index(i) => i, Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS, @@ -194,7 +194,7 @@ impl MiiSelector { 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 /// @@ -204,12 +204,12 @@ impl MiiSelector { /// use ctru::applets::mii_selector::{Index, MiiSelector}; /// let mut mii_selector = MiiSelector::new(); /// - /// // Whitelist the user-created Mii at index 0. - /// mii_selector.whitelist_user_mii(Index::Index(0)); + /// // Allowlist the user-created Mii at index 0. + /// mii_selector.allowlist_user_mii(Index::Index(0)); /// # } /// ``` #[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 { Index::Index(i) => i, Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS, @@ -218,7 +218,7 @@ impl MiiSelector { 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 /// @@ -228,12 +228,12 @@ impl MiiSelector { /// use ctru::applets::mii_selector::{Index, MiiSelector}; /// let mut mii_selector = MiiSelector::new(); /// - /// // Blacklist all user-created Miis so that they cannot be selected. - /// mii_selector.blacklist_user_mii(Index::All); + /// // Blocklist all user-created Miis so that they cannot be selected. + /// mii_selector.blocklist_user_mii(Index::All); /// # } /// ``` #[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 { Index::Index(i) => i, 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. #[doc(alias = "miiSelectorSetInitialIndex")] pub fn set_initial_index(&mut self, index: usize) { - // This function is static inline in libctru - // https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155 - self.config.initial_index = index as u32; + unsafe { ctru_sys::miiSelectorSetInitialIndex(self.config.as_mut(), index as u32) }; } /// Launch the Mii Selector. diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 6d25d93..5a8c772 100644 --- a/ctru-rs/src/console.rs +++ b/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) }; +/// 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. /// /// [`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")] pub struct Console<'screen> { context: Box, - _screen: RefMut<'screen, dyn Screen>, + screen: RefMut<'screen, dyn 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. /// + /// # 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 /// /// ``` @@ -60,7 +90,7 @@ impl<'screen> Console<'screen> { /// use ctru::console::Console; /// use ctru::services::gfx::Gfx; /// - /// // Initialize graphics. + /// // Initialize graphics (using framebuffers allocated on the HEAP). /// let gfx = Gfx::new()?; /// /// // 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()) }; - Console { - context, - _screen: screen, - } + Console { context, screen } } /// 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`], /// 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 /// /// ``` @@ -177,16 +201,126 @@ impl<'screen> Console<'screen> { /// # Notes /// /// 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. /// - /// # Safety + /// # Example /// - /// This function is unsafe because it does not validate whether the input will produce - /// a console that actually fits on the screen. - // TODO: Wrap this safely. + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// # 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); + /// + /// println!("I'm becoming claustrophobic in here!"); + /// # + /// # Ok(()) + /// # } + /// ``` #[doc(alias = "consoleSetWindow")] - pub unsafe fn set_window(&mut self, x: i32, y: i32, width: i32, height: i32) { - consoleSetWindow(self.context.as_mut(), x, y, width, height); + pub fn set_window(&mut self, x: u8, y: u8, width: u8, height: u8) -> Result<(), Error> { + 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> { + /// # + /// # 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> { + /// # + /// # 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 {} diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index dce7011..58537ee 100644 --- a/ctru-rs/src/lib.rs +++ b/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 console; pub mod error; diff --git a/ctru-rs/src/linear.rs b/ctru-rs/src/linear.rs index 269e5dd..927d556 100644 --- a/ctru-rs/src/linear.rs +++ b/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, // 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. #[derive(Copy, Clone, Default, Debug)] diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index 449972d..dc76cf2 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -6,12 +6,18 @@ use crate::error::{Error, ResultCode}; use crate::services::gspgpu::FramebufferFormat; +use crate::services::ServiceReference; use ctru_sys::Handle; +use private::Configuration; + +use std::sync::Mutex; use std::time::Duration; +static CAM_ACTIVE: Mutex<()> = Mutex::new(()); + /// Handle to the Camera service. -#[non_exhaustive] pub struct Cam { + _service_handler: ServiceReference, /// Inside-facing camera. pub inner_cam: InwardCam, /// Outside-facing right camera. @@ -105,7 +111,7 @@ pub enum FrameRate { /// White balance settings. /// -/// See [`Camera::set_white_balance()`] and [`Camera::set_white_balance_without_base_up()`] to learn how to use this. +/// See [`Camera::set_white_balance()`] to learn how to use this. #[doc(alias = "CAMU_WhiteBalance")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] @@ -224,83 +230,127 @@ pub enum ShutterSound { MovieEnd = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE_END, } -/// Parameters to handle image trimming. +/// Configuration to handle image trimming. /// -/// See [`Camera::set_trimming_params()`] to learn how to use this. +/// See [`Trimming::new_centered()`] and the other associated methods for controlled +/// ways of configuring trimming. +#[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct TrimmingParams { - x_start: i16, - y_start: i16, - x_end: i16, - y_end: i16, -} - -impl TrimmingParams { - /// Creates a new [`TrimmingParams`] and guarantees the start coordinates are less than or - /// equal to the end coordinates. - /// - /// # Panics - /// - /// This function panics if the start coordinates are larger than the end coordinates (for each axis). - pub fn new(x_start: i16, y_start: i16, x_end: i16, y_end: i16) -> TrimmingParams { - assert!(x_start <= x_end && y_start <= y_end); - Self { - x_start, - y_start, - x_end, - y_end, - } - } +pub enum Trimming { + /// Trimming configuration relatively to the center of the image. + #[allow(missing_docs)] + Centered { width: i16, height: i16 }, + /// Trimming disabled. + Off, } /// Data used by the camera to calibrate image quality for a single camera. +// TODO: Implement Image quality calibration. #[doc(alias = "CAMU_ImageQualityCalibrationData")] #[derive(Default, Clone, Copy, Debug)] -pub struct ImageQualityCalibrationData(pub ctru_sys::CAMU_ImageQualityCalibrationData); +pub struct ImageQualityCalibration(pub ctru_sys::CAMU_ImageQualityCalibrationData); /// Data used by the camera to calibrate image quality when using both outward cameras. // TODO: Implement Stereo camera calibration. #[doc(alias = "CAMU_StereoCameraCalibrationData")] #[derive(Default, Clone, Copy, Debug)] -pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData); +pub struct StereoCameraCalibration(ctru_sys::CAMU_StereoCameraCalibrationData); /// Inward camera representation (facing the user of the 3DS). /// /// Usually used for selfies. -#[non_exhaustive] -pub struct InwardCam; - -impl Camera for InwardCam { - fn camera_as_raw(&self) -> ctru_sys::u32_ { - ctru_sys::SELECT_IN1 - } +pub struct InwardCam { + configuration: Configuration, } /// Right-side outward camera representation. -#[non_exhaustive] -pub struct OutwardRightCam; - -impl Camera for OutwardRightCam { - fn camera_as_raw(&self) -> ctru_sys::u32_ { - ctru_sys::SELECT_OUT1 - } +pub struct OutwardRightCam { + configuration: Configuration, } /// Left-side outward camera representation. -#[non_exhaustive] -pub struct OutwardLeftCam; - -impl Camera for OutwardLeftCam { - fn camera_as_raw(&self) -> ctru_sys::u32_ { - ctru_sys::SELECT_OUT2 - } +pub struct OutwardLeftCam { + configuration: Configuration, } /// Both outer cameras combined. /// /// Usually used for 3D photos. -#[non_exhaustive] -pub struct BothOutwardCam; +pub struct BothOutwardCam { + configuration: Configuration, +} + +mod private { + use super::{BothOutwardCam, InwardCam, OutwardLeftCam, OutwardRightCam, Trimming, ViewSize}; + + /// Basic configuration needed to properly use the built-in cameras. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct Configuration { + pub view_size: ViewSize, + pub trimming: Trimming, + } + + impl Configuration { + pub fn new() -> Self { + Self::default() + } + } + + impl Default for Configuration { + fn default() -> Self { + Self { + view_size: ViewSize::TopLCD, + trimming: Trimming::Off, + } + } + } + + pub trait ConfigurableCamera { + fn configuration(&self) -> &Configuration; + + fn configuration_mut(&mut self) -> &mut Configuration; + } + + impl ConfigurableCamera for InwardCam { + fn configuration(&self) -> &Configuration { + &self.configuration + } + + fn configuration_mut(&mut self) -> &mut Configuration { + &mut self.configuration + } + } + + impl ConfigurableCamera for OutwardRightCam { + fn configuration(&self) -> &Configuration { + &self.configuration + } + + fn configuration_mut(&mut self) -> &mut Configuration { + &mut self.configuration + } + } + + impl ConfigurableCamera for OutwardLeftCam { + fn configuration(&self) -> &Configuration { + &self.configuration + } + + fn configuration_mut(&mut self) -> &mut Configuration { + &mut self.configuration + } + } + + impl ConfigurableCamera for BothOutwardCam { + fn configuration(&self) -> &Configuration { + &self.configuration + } + + fn configuration_mut(&mut self) -> &mut Configuration { + &mut self.configuration + } + } +} impl BothOutwardCam { /// Set whether to enable or disable brightness synchronization between the two cameras. @@ -313,8 +363,62 @@ impl BothOutwardCam { ResultCode(ctru_sys::CAMU_SetBrightnessSynchronization( brightness_synchronization, ))?; - Ok(()) } + + Ok(()) + } + + #[doc(alias = "CAMU_GetStereoCameraCalibrationData")] + /// Returns the currently set [`StereoCameraCalibration`]. + pub fn stereo_calibration(&self) -> crate::Result { + let mut calibration = StereoCameraCalibration::default(); + + unsafe { + ResultCode(ctru_sys::CAMU_GetStereoCameraCalibrationData( + &mut calibration.0, + ))?; + } + + Ok(calibration) + } + + #[doc(alias = "CAMU_SetStereoCameraCalibrationData")] + /// Set the [`StereoCameraCalibration`]. + // TODO: This seems to have no effect. + pub fn set_stereo_calibration( + &mut self, + mut stereo_calibration: StereoCameraCalibration, + ) -> crate::Result<()> { + let view_size = self.final_view_size(); + + stereo_calibration.0.imageWidth = view_size.0; + stereo_calibration.0.imageHeight = view_size.1; + + unsafe { + ResultCode(ctru_sys::CAMU_SetStereoCameraCalibrationData( + stereo_calibration.0, + ))?; + } + + Ok(()) + } +} + +impl Camera for InwardCam { + fn camera_as_raw(&self) -> ctru_sys::u32_ { + ctru_sys::SELECT_IN1 + } +} + +impl Camera for OutwardRightCam { + fn camera_as_raw(&self) -> ctru_sys::u32_ { + ctru_sys::SELECT_OUT1 + } +} + +impl Camera for OutwardLeftCam { + fn camera_as_raw(&self) -> ctru_sys::u32_ { + ctru_sys::SELECT_OUT2 } } @@ -326,14 +430,132 @@ impl Camera for BothOutwardCam { fn port_as_raw(&self) -> ctru_sys::u32_ { ctru_sys::PORT_BOTH } + + fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> { + // Check whether the provided buffer is big enough to store the image. + let max_size = self.final_byte_length(); + if buffer.len() < max_size { + return Err(Error::BufferTooShort { + provided: buffer.len(), + wanted: max_size, + }); + } + + let final_view = self.final_view_size(); + + // The transfer unit is NOT the "max number of bytes" or whatever the docs make you think it is... + let transfer_unit = unsafe { + let mut transfer_unit = 0; + + ResultCode(ctru_sys::CAMU_GetMaxBytes( + &mut transfer_unit, + final_view.0, + final_view.1, + ))?; + + transfer_unit + }; + + unsafe { + ResultCode(ctru_sys::CAMU_SetTransferBytes( + self.port_as_raw(), + transfer_unit, + final_view.0, + final_view.1, + ))?; + }; + + unsafe { + ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?; + ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?; + }; + + // Synchronize the two cameras. + unsafe { + ResultCode(ctru_sys::CAMU_SynchronizeVsyncTiming( + ctru_sys::SELECT_OUT1, + ctru_sys::SELECT_OUT2, + ))?; + } + + // Start capturing with the camera. + unsafe { + ResultCode(ctru_sys::CAMU_StartCapture(self.port_as_raw()))?; + }; + + let receive_event_1 = unsafe { + let mut completion_handle: Handle = 0; + + ResultCode(ctru_sys::CAMU_SetReceiving( + &mut completion_handle, + buffer.as_mut_ptr().cast(), + ctru_sys::PORT_CAM1, + (max_size / 2) as u32, + transfer_unit.try_into().unwrap(), + ))?; + + completion_handle + }; + + let receive_event_2 = unsafe { + let mut completion_handle: Handle = 0; + + ResultCode(ctru_sys::CAMU_SetReceiving( + &mut completion_handle, + buffer[max_size / 2..].as_mut_ptr().cast(), + ctru_sys::PORT_CAM2, + (max_size / 2) as u32, + transfer_unit.try_into().unwrap(), + ))?; + + completion_handle + }; + + unsafe { + // Panicking without closing an SVC handle causes an ARM exception, we have to handle it carefully. + let wait_result_1 = ResultCode(ctru_sys::svcWaitSynchronization( + receive_event_1, + timeout.as_nanos().try_into().unwrap(), + )); + + let wait_result_2 = ResultCode(ctru_sys::svcWaitSynchronization( + receive_event_2, + timeout.as_nanos().try_into().unwrap(), + )); + + // We close everything first, then we check for possible errors + let _ = ctru_sys::svcCloseHandle(receive_event_1); // We wouldn't return the error even if there was one, so no use of ResultCode is needed. + let _ = ctru_sys::svcCloseHandle(receive_event_2); + + // Camera state cleanup + ResultCode(ctru_sys::CAMU_StopCapture(self.port_as_raw()))?; + ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?; + ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE))?; + + wait_result_1?; + wait_result_2?; + }; + + Ok(()) + } } /// Generic functionality common to all cameras. -// TODO: Change "set true/set parameters" scheme (classic of C code) into a single "set parameter" scheme using enums. This is valid for stuff such as [`TrimmingParams`] -pub trait Camera { +pub trait Camera: private::ConfigurableCamera { /// Returns the raw value of the selected camera. fn camera_as_raw(&self) -> ctru_sys::u32_; + /// Returns view size of the selected camera. + /// + /// # Notes + /// + /// This view is the full resolution at which the camera will take the photo. + /// If you are interested in the final image's size, calculated while taking into account all processing and modifications, + /// have a look at [`Camera::final_view_size()`]. + fn view_size(&self) -> ViewSize { + self.configuration().view_size + } + /// Returns the raw port of the selected camera. fn port_as_raw(&self) -> ctru_sys::u32_ { ctru_sys::PORT_CAM1 @@ -368,9 +590,14 @@ pub trait Camera { } } - /// Returns the maximum amount of transfer bytes based on the view size, trimming, and other + /// Returns the maximum amount of bytes the final image will occupy in memory based on the view size, trimming, pixel depth and other /// modifications set to the camera. /// + /// # Notes + /// + /// The value returned will be double the image size if requested by [`BothOutwardCam`]. + /// Remember to query this information again if *any* changes are applied to the [`Camera`] configuration! + /// /// # Example /// /// ``` @@ -383,115 +610,114 @@ pub trait Camera { /// /// let inward = &cam.inner_cam; /// - /// // Inward cam is not busy since it is not being used. - /// let transfer_count = inward.transfer_byte_count(); + /// let transfer_count = inward.final_byte_length(); /// # /// # Ok(()) /// # } /// ``` - #[doc(alias = "CAMU_GetTransferBytes")] - fn transfer_byte_count(&self) -> crate::Result { - unsafe { - let mut transfer_bytes = 0; - ResultCode(ctru_sys::CAMU_GetTransferBytes( - &mut transfer_bytes, - self.port_as_raw(), - ))?; - Ok(transfer_bytes) - } - } + fn final_byte_length(&self) -> usize { + let size = self.final_view_size(); - /// Set whether or not the camera should trim the image. - /// - /// [`TrimmingParams`] can be set via [`Camera::set_trimming_params`]. - #[doc(alias = "CAMU_SetTrimming")] - fn set_trimming(&mut self, enabled: bool) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), enabled))?; - Ok(()) - } - } + let mut res: usize = (size.0 as usize * size.1 as usize) * std::mem::size_of::(); - /// Returns whether or not trimming is currently enabled for the camera. - #[doc(alias = "CAMU_IsTrimming")] - fn is_trimming_enabled(&self) -> crate::Result { - unsafe { - let mut trimming = false; - ResultCode(ctru_sys::CAMU_IsTrimming(&mut trimming, self.port_as_raw()))?; - Ok(trimming) + // If we are taking a picture using both outwards cameras, we need to expect 2 images, rather than just 1 + if self.port_as_raw() == ctru_sys::PORT_BOTH { + res *= 2; } + + res } - /// Set trimming bounds based on image coordinates. + /// Returns the dimensions of the final image based on the view size, trimming and other + /// modifications set to the camera. /// - /// For trimming to take effect it is required to pass `true` into [`Camera::set_trimming()`]. - #[doc(alias = "CAMU_SetTrimmingParams")] - fn set_trimming_params(&mut self, params: TrimmingParams) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetTrimmingParams( - self.port_as_raw(), - params.x_start, - params.y_start, - params.x_end, - params.y_end, - ))?; - Ok(()) + /// # Notes + /// + /// Remember to query this information again if *any* changes are applied to the [`Camera`] configuration! + /// + /// # Example + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cam::{Cam, Camera, Trimming, ViewSize}; + /// let mut cam = Cam::new()?; + /// + /// let mut inward = &mut cam.inner_cam; + /// + /// // We trim the image down so that it fits on a DS screen! + /// inward.set_trimming(Trimming::new_centered_with_view(ViewSize::DS)); + /// + /// // This result will take into account the trimming. + /// let final_resolution = inward.final_view_size(); + /// # + /// # Ok(()) + /// # } + /// ``` + fn final_view_size(&self) -> (i16, i16) { + match self.trimming() { + Trimming::Centered { width, height } => (width, height), + Trimming::Off => self.view_size().into(), } } - /// Returns the [`TrimmingParams`] currently set. - #[doc(alias = "CAMU_GetTrimmingParams")] - fn trimming_params(&self) -> crate::Result { - unsafe { - let mut x_start = 0; - let mut y_start = 0; - let mut x_end = 0; - let mut y_end = 0; - ResultCode(ctru_sys::CAMU_GetTrimmingParams( - &mut x_start, - &mut y_start, - &mut x_end, - &mut y_end, - self.port_as_raw(), - ))?; - - Ok(TrimmingParams { - x_start, - y_start, - x_end, - y_end, - }) - } + /// Returns the [`Trimming`] configuration currently set. + fn trimming(&self) -> Trimming { + self.configuration().trimming } - /// Set the trimming bounds relatively to the center of the image. + /// Set trimming bounds to trim the camera photo. /// /// # Notes /// - /// The new width will be `trim_width / 2` to the left and right of the center. - /// The new height will be `trim_height / 2` above and below the center. - // TODO: This function doesn't use `TrimmingParams`. It'd be better to merge it with `set_trimming_params()` and change the `TrimmingParams` representation. - #[doc(alias = "CAMU_SetTrimmingParamsCenter")] - fn set_trimming_params_center( - &mut self, - trim_width: i16, - trim_height: i16, - cam_width: i16, - cam_height: i16, - ) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter( - self.port_as_raw(), - trim_width, - trim_height, - cam_width, - cam_height, - ))?; - Ok(()) + /// The trimmed image must have a pixel area of (`width * height`) multiple of 128. + /// If not, a raw `libctru` error may be returned. + /// + /// # Panics + /// + /// Setting up a [`Trimming`] configurations that exceeds the bounds of the original + /// image's size will result in a panic. + #[doc(alias = "CAMU_SetTrimming")] + fn set_trimming(&mut self, trimming: Trimming) -> crate::Result<()> { + match trimming { + Trimming::Centered { width, height } => unsafe { + let view_size: (i16, i16) = self.view_size().into(); + let trim_size: (i16, i16) = (width, height); + + // Check whether the trim size is within the view. + assert!( + trim_size.0 <= view_size.0 && trim_size.1 <= view_size.1, + "trimmed view is bigger than the camera view", + ); + + ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; + + ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter( + self.port_as_raw(), + trim_size.0, + trim_size.1, + view_size.0, + view_size.1, + ))?; + }, + Trimming::Off => unsafe { + ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?; + }, } + + self.configuration_mut().trimming = trimming; + + Ok(()) } - /// Set the exposure level of the camera.å + /// Returns whether or not trimming is currently enabled for the camera. + #[doc(alias = "CAMU_IsTrimming")] + fn is_trimming(&self) -> bool { + matches!(self.trimming(), Trimming::Off) + } + + /// Set the exposure level of the camera. #[doc(alias = "CAMU_SetExposure")] fn set_exposure(&mut self, exposure: i8) -> crate::Result<()> { unsafe { @@ -512,22 +738,6 @@ pub trait Camera { } } - /// Set the white balance of the camera. - // TODO: Explain what "without base up" means. - #[doc(alias = "CAMU_SetWhiteBalanceWithoutBaseUp")] - fn set_white_balance_without_base_up( - &mut self, - white_balance: WhiteBalance, - ) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetWhiteBalanceWithoutBaseUp( - self.camera_as_raw(), - white_balance.into(), - ))?; - Ok(()) - } - } - /// Set the sharpness of the camera. #[doc(alias = "CAMU_SetSharpness")] fn set_sharpness(&mut self, sharpness: i8) -> crate::Result<()> { @@ -562,31 +772,6 @@ pub trait Camera { } } - /// Set whether auto white balance is enabled or disabled for the camera. - #[doc(alias = "CAMU_SetAutoWhiteBalance")] - fn set_auto_white_balance(&mut self, enabled: bool) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetAutoWhiteBalance( - self.camera_as_raw(), - enabled, - ))?; - Ok(()) - } - } - - /// Returns `true` if auto white balance is enabled for the camera. - #[doc(alias = "CAMU_IsAutoWhiteBalance")] - fn is_auto_white_balance_enabled(&self) -> crate::Result { - unsafe { - let mut enabled = false; - ResultCode(ctru_sys::CAMU_IsAutoWhiteBalance( - &mut enabled, - self.camera_as_raw(), - ))?; - Ok(enabled) - } - } - /// Set the flip mode of the camera's image. #[doc(alias = "CAMU_FlipImage")] fn flip_image(&mut self, flip: FlipMode) -> crate::Result<()> { @@ -600,43 +785,11 @@ pub trait Camera { } } - /// Set the image resolution of the camera in detail. - /// - /// # Errors - /// - /// This function will error if the coordinates of the first crop point are greater than the - /// coordinates of the second crop point. + /// Set the view size of the camera. /// - /// # Arguments + /// # Notes /// - /// * `width` - Width of the image - /// * `height` - height of the image - /// * `crop_0` - The first crop point in which the image will be trimmed - /// * `crop_1` - The second crop point in which the image will be trimmed - #[doc(alias = "CAMU_SetDetailSize")] - fn set_detail_size( - &mut self, - width: i16, - height: i16, - crop_0: (i16, i16), - crop_1: (i16, i16), - ) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetDetailSize( - self.camera_as_raw(), - width, - height, - crop_0.0, - crop_0.1, - crop_1.0, - crop_1.1, - ctru_sys::CONTEXT_A, - ))?; - Ok(()) - } - } - - /// Set the view size of the camera. + /// Calling this function will reset the trimming configuration. #[doc(alias = "CAMU_SetSize")] fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { unsafe { @@ -645,8 +798,13 @@ pub trait Camera { size.into(), ctru_sys::CONTEXT_A, ))?; - Ok(()) } + + self.configuration_mut().view_size = size; + + self.set_trimming(Trimming::Off)?; + + Ok(()) } /// Set the frame rate of the camera. @@ -677,8 +835,7 @@ pub trait Camera { /// /// # Notes /// - /// This operation will override any previously set [`Effect`]s. - /// Multiple effects can be set at once by combining the bitflags of [`Effect`]. + /// This operation will override any previously set [`Effect`]. #[doc(alias = "CAMU_SetEffect")] fn set_effect(&mut self, effect: Effect) -> crate::Result<()> { unsafe { @@ -797,11 +954,11 @@ pub trait Camera { } } - /// Set the [`ImageQualityCalibrationData`] for the camera. + /// Set the [`ImageQualityCalibration`] for the camera. #[doc(alias = "CAMU_SetImageQualityCalibrationData")] - fn set_image_quality_calibration_data( + fn set_image_quality_calibration( &mut self, - data: ImageQualityCalibrationData, + data: ImageQualityCalibration, ) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetImageQualityCalibrationData(data.0))?; @@ -809,37 +966,27 @@ pub trait Camera { } } - /// Returns the current [`ImageQualityCalibrationData`] for the camera. + /// Returns the current [`ImageQualityCalibration`] for the camera. #[doc(alias = "CAMU_GetImageQualityCalibrationData")] - fn image_quality_calibration_data(&self) -> crate::Result { + fn image_quality_calibration(&self) -> crate::Result { unsafe { - let mut data = ImageQualityCalibrationData::default(); + let mut data = ImageQualityCalibration::default(); ResultCode(ctru_sys::CAMU_GetImageQualityCalibrationData(&mut data.0))?; Ok(data) } } - /// Set the camera as the current sleep camera. - // TODO: Explain sleep camera - #[doc(alias = "CAMU_SetSleepCamera")] - fn set_sleep_camera(&mut self) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetSleepCamera(self.camera_as_raw()))?; - Ok(()) - } - } - /// Request the camera to take a picture and write it in a buffer. /// /// # Errors /// - /// This function will return an error if the camera is busy or if the timeout duration gets reached. + /// This function will return an error if the camera is already busy or if the timeout duration is reached. /// - /// # Arguments + /// # Notes /// - /// * `width` - Width of the desired image - /// * `height` - Height of the desired image - /// * `timeout` - Duration to wait for the image + /// If the picture is taken using [`BothOutwardCam`], the buffer will have to be able to hold both images + /// (from each camera), which will be written into it sequentially. + /// Use [`Camera::final_byte_length()`] to know how big the buffer needs to be to hold your next image. /// /// # Example /// @@ -849,91 +996,98 @@ pub trait Camera { /// # use std::time::Duration; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::cam::{Cam, Camera, OutputFormat, ViewSize}; + /// use ctru::services::cam::{Cam, Camera, ViewSize, OutputFormat, WhiteBalance}; /// let mut cam = Cam::new()?; /// /// // We borrow the inward facing `Camera`. - /// let inward = &mut cam.inner_cam; + /// let camera = &mut cam.inner_cam; /// - /// inward.set_view_size(ViewSize::TopLCD)?; - /// inward.set_output_format(OutputFormat::Rgb565)?; - /// inward.set_noise_filter(true)?; - /// inward.set_auto_exposure(true)?; - /// inward.set_auto_white_balance(true)?; + /// camera.set_view_size(ViewSize::TopLCD)?; + /// camera.set_output_format(OutputFormat::Rgb565)?; + /// camera.set_noise_filter(true)?; + /// camera.set_auto_exposure(true)?; + /// camera.set_white_balance(WhiteBalance::Auto)?; /// /// // Size of the top screen buffer at 2 bytes per pixel (RGB565). - /// let mut buffer = vec![0; 400*240*2]; + /// let mut buffer = vec![0; camera.final_byte_length()]; /// /// // Take picture with 3 seconds of timeout. - /// inward.take_picture(&mut buffer, 400, 240, Duration::from_secs(3)); + /// camera.take_picture(&mut buffer, Duration::from_secs(3)); /// # /// # Ok(()) /// # } /// ``` - // TODO: This should use the value passed within `set_view_size` rather than arbitrary `width` and `height` values. - // Furthermore, it's pretty unclear what the "default" view size is. What happens if the user doesn't set it before taking the picture? - fn take_picture( - &mut self, - buffer: &mut [u8], - width: u16, - height: u16, - timeout: Duration, - ) -> crate::Result<()> { + fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> { + // Check whether the provided buffer is big enough to store the image. + let max_size = self.final_byte_length(); + if buffer.len() < max_size { + return Err(Error::BufferTooShort { + provided: buffer.len(), + wanted: max_size, + }); + } + + let final_view = self.final_view_size(); + + // The transfer unit is NOT the "max number of bytes" or whatever the docs make you think it is... let transfer_unit = unsafe { - let mut buf_size = 0; + let mut transfer_unit = 0; + ResultCode(ctru_sys::CAMU_GetMaxBytes( - &mut buf_size, - width as i16, - height as i16, + &mut transfer_unit, + final_view.0, + final_view.1, ))?; - Ok::(buf_size) - }?; + + transfer_unit + }; unsafe { ResultCode(ctru_sys::CAMU_SetTransferBytes( self.port_as_raw(), transfer_unit, - width as i16, - height as i16, + final_view.0, + final_view.1, ))?; }; - let screen_size: usize = usize::from(width) * usize::from(height) * 2; - if buffer.len() < screen_size { - return Err(Error::BufferTooShort { - provided: buffer.len(), - wanted: screen_size, - }); - } - unsafe { ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?; ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?; - ResultCode(ctru_sys::CAMU_StartCapture(self.port_as_raw()))?; }; let receive_event = unsafe { let mut completion_handle: Handle = 0; + ResultCode(ctru_sys::CAMU_SetReceiving( &mut completion_handle, buffer.as_mut_ptr().cast(), self.port_as_raw(), - screen_size as u32, + max_size as u32, transfer_unit.try_into().unwrap(), ))?; - Ok::(completion_handle) - }?; + completion_handle + }; + + // Start capturing with the camera. unsafe { - // Panicking without closing an SVC handle causes an ARM exception, we have to handle it carefully (TODO: SVC module) + ResultCode(ctru_sys::CAMU_StartCapture(self.port_as_raw()))?; + }; + + unsafe { + // Panicking without closing an SVC handle causes an ARM exception, we have to handle it carefully. let wait_result = ResultCode(ctru_sys::svcWaitSynchronization( receive_event, timeout.as_nanos().try_into().unwrap(), )); // We close everything first, then we check for possible errors - let _ = ctru_sys::svcCloseHandle(receive_event); // We wouldn't return the error even if there was one, so no use of ResultCode is needed + let _ = ctru_sys::svcCloseHandle(receive_event); // We wouldn't return the error even if there was one, so no use of ResultCode is needed. + + // Camera state cleanup ResultCode(ctru_sys::CAMU_StopCapture(self.port_as_raw()))?; + ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?; ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE))?; wait_result?; @@ -943,9 +1097,39 @@ pub trait Camera { } } +impl Trimming { + /// Create a new [`Trimming`] configuration using width and height centered to the original image. + /// + /// # Panics + /// + /// This function will panic if the pixel area of the new configuration (`width * height`) + /// is not a multiple of 128. + pub fn new_centered(width: i16, height: i16) -> Self { + // Pixel area must be a multiple of 128. + assert!((width * height) % 128 == 0); + + Self::Centered { width, height } + } + + /// Create a new [`Trimming`] configuration using a standard view size centered to the original image. + pub fn new_centered_with_view(size: ViewSize) -> Self { + let size: (i16, i16) = size.into(); + + Self::Centered { + width: size.0, + height: size.1, + } + } +} + impl Cam { /// Initialize a new service handle. /// + /// # Notes + /// + /// All cameras default to taking photos with [`ViewSize::TopLCD`] and [`OutputFormat::Yuv422`]. + /// Have a look at [`Camera::set_view_size()`] and [`Camera::set_output_format()`] to change these settings. + /// /// # Errors /// /// This function will return an error if the service was unable to be initialized. @@ -968,15 +1152,37 @@ impl Cam { /// ``` #[doc(alias = "camInit")] pub fn new() -> crate::Result { - unsafe { - ResultCode(ctru_sys::camInit())?; - Ok(Cam { - inner_cam: InwardCam, - outer_right_cam: OutwardRightCam, - outer_left_cam: OutwardLeftCam, - both_outer_cams: BothOutwardCam, - }) - } + let _service_handler = ServiceReference::new( + &CAM_ACTIVE, + || { + ResultCode(unsafe { ctru_sys::camInit() })?; + + Ok(()) + }, + || unsafe { + ctru_sys::camExit(); + }, + )?; + + let configuration = Configuration::new(); + + let mut inner_cam = InwardCam { configuration }; + let mut outer_right_cam = OutwardRightCam { configuration }; + let mut outer_left_cam = OutwardLeftCam { configuration }; + let mut both_outer_cams = BothOutwardCam { configuration }; + + inner_cam.set_view_size(ViewSize::TopLCD)?; + outer_right_cam.set_view_size(ViewSize::TopLCD)?; + outer_left_cam.set_view_size(ViewSize::TopLCD)?; + both_outer_cams.set_view_size(ViewSize::TopLCD)?; + + Ok(Cam { + _service_handler, + inner_cam, + outer_right_cam, + outer_left_cam, + both_outer_cams, + }) } /// Play the specified sound based on the [`ShutterSound`] argument @@ -1012,13 +1218,6 @@ impl Cam { } } -impl Drop for Cam { - #[doc(alias = "camExit")] - fn drop(&mut self) { - unsafe { ctru_sys::camExit() }; - } -} - impl TryFrom for OutputFormat { type Error = (); @@ -1041,6 +1240,21 @@ impl TryFrom for FramebufferFormat { } } +impl From for (i16, i16) { + fn from(value: ViewSize) -> Self { + match value { + ViewSize::TopLCD => (400, 240), + ViewSize::BottomLCD => (320, 240), + ViewSize::Vga => (640, 480), + ViewSize::QQVga => (160, 120), + ViewSize::Cif => (352, 288), + ViewSize::QCif => (176, 144), + ViewSize::DS => (256, 192), + ViewSize::DSX4 => (512, 384), + } + } +} + from_impl!(FlipMode, ctru_sys::CAMU_Flip); from_impl!(ViewSize, ctru_sys::CAMU_Size); from_impl!(FrameRate, ctru_sys::CAMU_FrameRate); diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 4c64c2b..6eb04f6 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -37,10 +37,16 @@ pub trait Screen: private::Sealed { /// Returns the Screen side (left or right). 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 - /// change after each call to this function if double buffering is enabled. + /// # Notes + /// + /// 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")] fn raw_framebuffer(&mut self) -> RawFrameBuffer { let mut width: u16 = 0; @@ -244,14 +250,15 @@ pub struct Gfx { _service_handler: ServiceReference, } -static GFX_ACTIVE: Mutex = Mutex::new(0); +static GFX_ACTIVE: Mutex<()> = Mutex::new(()); impl Gfx { /// Initialize a new default service handle. /// /// # 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(); @@ -261,12 +268,14 @@ impl Gfx { /// # use ctru::services::gfx::Gfx; /// # use ctru::services::gspgpu::FramebufferFormat; /// # - /// Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)?; + /// Gfx::with_formats_shared(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8)?; /// # /// # Ok(()) /// # } /// ``` /// + /// Have a look at [`Gfx::with_formats_vram()`] if you aren't interested in manipulating the framebuffers using the CPU. + /// /// # Example /// /// ``` @@ -283,10 +292,10 @@ impl Gfx { /// ``` #[doc(alias = "gfxInit")] pub fn new() -> Result { - 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 /// @@ -302,22 +311,66 @@ impl Gfx { /// /// // Top screen uses RGBA8, bottom screen uses RGB565. /// // 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(()) /// # } /// ``` #[doc(alias = "gfxInit")] - pub fn with_formats( + pub fn with_formats_shared( + top_fb_fmt: FramebufferFormat, + bottom_fb_fmt: FramebufferFormat, + ) -> Result { + 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> { + /// # + /// 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::with_configuration(top_fb_fmt, bottom_fb_fmt, true) + } + + // Internal function to handle the initialization of `Gfx`. + fn with_configuration( top_fb_fmt: FramebufferFormat, bottom_fb_fmt: FramebufferFormat, - use_vram_buffers: bool, + vram_buffer: bool, ) -> Result { let handler = ServiceReference::new( &GFX_ACTIVE, - false, || 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(()) }, diff --git a/ctru-rs/src/services/gspgpu.rs b/ctru-rs/src/services/gspgpu.rs index 81872d0..34d867f 100644 --- a/ctru-rs/src/services/gspgpu.rs +++ b/ctru-rs/src/services/gspgpu.rs @@ -5,19 +5,19 @@ #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum Event { - /// Memory fill completed. + /// Memory fill 1 completed. Psc0 = ctru_sys::GSPGPU_EVENT_PSC0, - /// TODO: Unknown. + /// Memory fill 2 completed. Psc1 = ctru_sys::GSPGPU_EVENT_PSC1, - /// TODO: Unknown. + /// Top screen VBlank. VBlank0 = ctru_sys::GSPGPU_EVENT_VBlank0, - /// TODO: Unknown. + /// Bottom screen VBlank. VBlank1 = ctru_sys::GSPGPU_EVENT_VBlank1, - /// Display transfer finished. + /// Display transfer completed. PPF = ctru_sys::GSPGPU_EVENT_PPF, - /// Command list processing finished. + /// Command list processing completed. P3D = ctru_sys::GSPGPU_EVENT_P3D, - /// TODO: Unknown. + /// Direct Memory Access requested. DMA = ctru_sys::GSPGPU_EVENT_DMA, } diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 1e0ceee..10d89c4 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -1,15 +1,21 @@ //! 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), -//! and [circle pad information](Hid::circlepad_position). It also provides information from the sound volume slider, the accelerometer, and the gyroscope. -// TODO: Implement volume slider, accelerometer and gyroscope + any other missing functionality. +//! and [circle pad information](Hid::circlepad_position). It also provides information from the [volume slider](Hid::volume_slider()), +//! the [accelerometer](Hid::accelerometer_vector()), and the [gyroscope](Hid::gyroscope_rate()). #![doc(alias = "input")] #![doc(alias = "controller")] #![doc(alias = "gamepad")] +use std::sync::Mutex; + use crate::error::ResultCode; +use crate::services::ServiceReference; + use bitflags::bitflags; +static HID_ACTIVE: Mutex<()> = Mutex::new(()); + bitflags! { /// 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)] @@ -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. -pub struct Hid(()); +pub struct Hid { + active_accelerometer: bool, + active_gyroscope: bool, + _service_handler: ServiceReference, +} impl Hid { /// Initialize a new service handle. @@ -101,10 +143,26 @@ impl Hid { /// ``` #[doc(alias = "hidInit")] pub fn new() -> crate::Result { - unsafe { - ResultCode(ctru_sys::hidInit())?; - Ok(Hid(())) - } + let handler = ServiceReference::new( + &HID_ACTIVE, + || { + 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. @@ -289,11 +347,219 @@ impl Hid { (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> { + /// # + /// 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> { + /// # + /// 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() })?; + } + + self.active_accelerometer = enabled; + + Ok(()) + } + + /// Activate/deactivate the console's gyroscopic sensor. + /// + /// # Example + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// 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> { + /// # + /// 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 { + 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> { + /// # + /// 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 { + 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 Drop for Hid { - #[doc(alias = "hidExit")] - fn drop(&mut self) { - unsafe { ctru_sys::hidExit() }; +impl From for (i16, i16, i16) { + fn from(value: Acceleration) -> (i16, i16, i16) { + (value.x, value.y, value.z) } } + +impl From 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 {} diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs index 987881a..3e96184 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -61,6 +61,16 @@ pub struct AudioMix { 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. #[doc(alias = "ndspInterpType")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -75,8 +85,9 @@ pub enum InterpolationType { } /// Errors returned by [`ndsp`](self) functions. +#[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum NdspError { +pub enum Error { /// Channel with the specified ID does not exist. InvalidChannel(u8), /// 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 } -static NDSP_ACTIVE: Mutex = Mutex::new(0); +static NDSP_ACTIVE: Mutex<()> = Mutex::new(()); /// Handle to the DSP service. /// @@ -143,7 +154,6 @@ impl Ndsp { pub fn new() -> crate::Result { let _service_handler = ServiceReference::new( &NDSP_ACTIVE, - false, || { ResultCode(unsafe { ctru_sys::ndspInit() })?; @@ -180,7 +190,7 @@ impl Ndsp { /// # Ok(()) /// # } /// ``` - pub fn channel(&self, id: u8) -> std::result::Result { + pub fn channel(&self, id: u8) -> std::result::Result { let in_bounds = self.channel_flags.get(id as usize); match in_bounds { @@ -188,10 +198,10 @@ impl Ndsp { let flag = ref_cell.try_borrow_mut(); match flag { 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. // These "alive wave" shenanigans are the most substantial reason why I'd like to fully re-write this service in Rust. #[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() { - 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). - pub fn aux_front(&self, id: usize) -> (f32, f32) { - if id > 1 { - panic!("invalid auxiliary output device index") - } - - let index = 4 + id * 4; + pub fn aux_front(&self, id: AuxDevice) -> (f32, f32) { + let index = 4 + (id as usize * 4); (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). - pub fn aux_back(&self, id: usize) -> (f32, f32) { - if id > 1 { - panic!("invalid auxiliary output device index") - } - - let index = 6 + id * 4; + pub fn aux_back(&self, id: AuxDevice) -> (f32, f32) { + let index = 6 + (id as usize * 4); (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. /// However, an [`AudioMix`] instance with larger/smaller values is valid. - pub fn set_aux_front(&mut self, left: f32, right: f32, id: usize) { - if id > 1 { - panic!("invalid auxiliary output device index") - } - - let index = 4 + id * 4; + pub fn set_aux_front(&mut self, left: f32, right: f32, id: AuxDevice) { + let index = 4 + (id as usize * 4); self.raw[index] = left; self.raw[index + 1] = right; @@ -738,12 +736,8 @@ impl AudioMix { /// /// [`Channel`] will normalize the mix values to be within 0 and 1. /// However, an [`AudioMix`] instance with larger/smaller values is valid. - pub fn set_aux_back(&mut self, left: f32, right: f32, id: usize) { - if id > 1 { - panic!("invalid auxiliary output device index") - } - - let index = 6 + id * 4; + pub fn set_aux_back(&mut self, left: f32, right: f32, id: AuxDevice) { + let index = 6 + (id as usize * 4); self.raw[index] = left; 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 { match self { 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 { #[doc(alias = "ndspExit")] diff --git a/ctru-rs/src/services/ndsp/wave.rs b/ctru-rs/src/services/ndsp/wave.rs index 9c467e0..5389ebd 100644 --- a/ctru-rs/src/services/ndsp/wave.rs +++ b/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. -use super::{AudioFormat, NdspError}; +use super::{AudioFormat, Error}; use crate::linear::LinearAllocator; /// 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, /// 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() { Status::Playing | Status::Queued => { - Err(NdspError::WaveBusy(self.played_on_channel.unwrap())) + Err(Error::WaveBusy(self.played_on_channel.unwrap())) } _ => 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 /// 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() { 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(); 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; diff --git a/ctru-rs/src/services/reference.rs b/ctru-rs/src/services/reference.rs index 41319a7..0fad37d 100644 --- a/ctru-rs/src/services/reference.rs +++ b/ctru-rs/src/services/reference.rs @@ -1,35 +1,37 @@ use crate::Error; -use std::sync::Mutex; +use std::sync::{Mutex, MutexGuard, TryLockError}; + pub(crate) struct ServiceReference { - counter: &'static Mutex, + _guard: MutexGuard<'static, ()>, close: Box, } impl ServiceReference { - pub fn new( - counter: &'static Mutex, - allow_multiple: bool, - start: S, - close: E, - ) -> crate::Result + pub fn new(counter: &'static Mutex<()>, start: S, close: E) -> crate::Result where S: FnOnce() -> crate::Result<()>, E: Fn() + Send + Sync + 'static, { - let mut value = counter - .lock() - .expect("Mutex Counter for ServiceReference is poisoned"); // todo: handle poisoning + let _guard = match counter.try_lock() { + Ok(lock) => lock, + 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 { - start()?; - } else if !allow_multiple { - return Err(Error::ServiceAlreadyActive); - } + guard.into_inner() + } + TryLockError::WouldBlock => return Err(Error::ServiceAlreadyActive), + }, + }; - *value += 1; + start()?; Ok(Self { - counter, + _guard, close: Box::new(close), }) } @@ -37,13 +39,6 @@ impl ServiceReference { impl Drop for ServiceReference { 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)(); } } diff --git a/ctru-rs/src/services/romfs.rs b/ctru-rs/src/services/romfs.rs index 4018a7c..e7f992c 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -38,7 +38,7 @@ pub struct RomFS { _service_handler: ServiceReference, } -static ROMFS_ACTIVE: Mutex = Mutex::new(0); +static ROMFS_ACTIVE: Mutex<()> = Mutex::new(()); impl RomFS { /// Mount the bundled RomFS archive as a virtual drive. @@ -64,7 +64,6 @@ impl RomFS { pub fn new() -> crate::Result { let _service_handler = ServiceReference::new( &ROMFS_ACTIVE, - true, || { let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); ResultCode(unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) })?; @@ -84,19 +83,15 @@ impl RomFS { mod tests { use super::*; - #[test] // 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 - fn romfs_counter() { - let _romfs = RomFS::new().unwrap(); - let value = *ROMFS_ACTIVE.lock().unwrap(); - - assert_eq!(value, 1); - - drop(_romfs); + #[test] + #[should_panic] + fn romfs_lock() { + let romfs = RomFS::new().unwrap(); - let value = *ROMFS_ACTIVE.lock().unwrap(); + ROMFS_ACTIVE.try_lock().unwrap(); - assert_eq!(value, 0); + drop(romfs); } } diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index 548e44d..dd7b601 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -19,7 +19,7 @@ pub struct Soc { sock_3dslink: libc::c_int, } -static SOC_ACTIVE: Mutex = Mutex::new(0); +static SOC_ACTIVE: Mutex<()> = Mutex::new(()); impl Soc { /// 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 { let _service_handler = ServiceReference::new( &SOC_ACTIVE, - false, || { let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32; ResultCode(unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) })?;