From 18924dfcf45aac087e08f0cf4139f6548fb46a5e Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Thu, 31 Aug 2023 18:25:03 +0200 Subject: [PATCH 01/29] Rename functions to allowlist/blocklist --- ctru-rs/examples/mii-selector.rs | 2 +- ctru-rs/src/applets/mii_selector.rs | 38 ++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ctru-rs/examples/mii-selector.rs b/ctru-rs/examples/mii-selector.rs index db3a9ea..2987970 100644 --- a/ctru-rs/examples/mii-selector.rs +++ b/ctru-rs/examples/mii-selector.rs @@ -19,7 +19,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/src/applets/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs index bd0e4b4..a2ef72d 100644 --- a/ctru-rs/src/applets/mii_selector.rs +++ b/ctru-rs/src/applets/mii_selector.rs @@ -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, From 45617265fe29b2a5f30fd235f69fb1235ebe0a44 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 3 Sep 2023 13:36:15 +0200 Subject: [PATCH 02/29] Safely wrap Console::set_window --- ctru-rs/src/console.rs | 126 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 11 deletions(-) diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 6768d47..e39f8d0 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -35,7 +35,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> { @@ -78,7 +78,7 @@ impl<'screen> Console<'screen> { Console { context, - _screen: screen, + screen, } } @@ -174,16 +174,120 @@ 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. - /// - /// # Safety - /// - /// 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. + /// of the new window based on the row/column coordinates of a full-screen console. + /// The second pair is the new width and height. + /// + /// # Panics + /// + /// This function will panic if the new window's position or size does not fit the screen. + /// + /// # Example + /// + /// ```no_run + /// # 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) { + let height_limit = 30; + let length_limit = self.max_width(); + + if x >= length_limit { + panic!("x coordinate of new console window out of bounds"); + } + if y >= height_limit { + panic!("y coordinate of new console window out of bounds"); + } + + if (x+width) > length_limit { + panic!("width of new console window out of bounds"); + } + if (y+height) > height_limit { + panic!("height of new console window out of bounds"); + } + + unsafe { consoleSetWindow(self.context.as_mut(), x.into(), y.into(), width.into(), height.into()) }; + } + + /// Reset the window's size to default parameters. + /// + /// This can be used to undo the changes made by [`set_window()`](Console::set_window()). + /// + /// # Example + /// + /// ```no_run + /// # 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(); + + unsafe { consoleSetWindow(self.context.as_mut(), 0, 0, width.into(), 30) }; + } + + /// Returns this [`Console`]'s maximum character width depending on the screen used. + /// + /// # Example + /// + /// ```no_run + /// # 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!(), + } } } From c3576c73bea9c41cbf426be8bb935684f9398477 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 3 Sep 2023 16:07:52 +0200 Subject: [PATCH 03/29] Better GSPGPU docs --- ctru-rs/src/services/gspgpu.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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, } From 1a704b0006dd5133e5d9782a0d0d100aca59ed8c Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Mon, 18 Sep 2023 19:42:54 +0200 Subject: [PATCH 04/29] Restructure ServiceReference and wip Hid --- ctru-rs/examples/buttons.rs | 9 +- ctru-rs/examples/movement.rs | 46 +++++++++ ctru-rs/src/console.rs | 53 +++++----- ctru-rs/src/lib.rs | 23 +++-- ctru-rs/src/services/gfx.rs | 3 +- ctru-rs/src/services/hid.rs | 161 +++++++++++++++++++++++++++--- ctru-rs/src/services/ndsp/mod.rs | 3 +- ctru-rs/src/services/reference.rs | 47 ++++----- ctru-rs/src/services/romfs.rs | 3 +- ctru-rs/src/services/soc.rs | 3 +- 10 files changed, 273 insertions(+), 78 deletions(-) create mode 100644 ctru-rs/examples/movement.rs diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index 0de9e7c..e92c349 100644 --- a/ctru-rs/examples/buttons.rs +++ b/ctru-rs/examples/buttons.rs @@ -25,6 +25,13 @@ fn main() { // Get information about which keys were held down on this frame. let keys = hid.keys_held(); + // Print the status of the 2 sliders. + println!( + "\x1b[20;0HVolume slider: {} ", + hid.slider_volume() + ); + println!("\x1b[21;0H3D slider: {} ", hid.slider_3d()); + // 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 +51,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/movement.rs b/ctru-rs/examples/movement.rs new file mode 100644 index 0000000..984b779 --- /dev/null +++ b/ctru-rs/examples/movement.rs @@ -0,0 +1,46 @@ +//! Movement example. +//! +//! Simple application to showcase the use of the accellerometer and gyroscope. + +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"); + + 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.enable_accellerometer(); + hid.enable_gyroscope(); + + while apt.main_loop() { + // Scan all the controller inputs. + // Accellerometer 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;0HAccelleration: {:?} ", + hid.accellerometer_vector() + ); + println!( + "\x1b[4;0HGyroscope angular rate: {:?} ", + hid.gyroscope_rate() + ); + + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index e39f8d0..d37ea06 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -76,10 +76,7 @@ impl<'screen> Console<'screen> { unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; - Console { - context, - screen, - } + Console { context, screen } } /// Returns `true` if a valid [`Console`] to print on is currently selected. @@ -176,13 +173,13 @@ impl<'screen> Console<'screen> { /// The first two arguments are the desired coordinates of the top-left corner /// of the new window based on the row/column coordinates of a full-screen console. /// The second pair is the new width and height. - /// + /// /// # Panics - /// + /// /// This function will panic if the new window's position or size does not fit the screen. - /// + /// /// # Example - /// + /// /// ```no_run /// # use std::error::Error; /// # fn main() -> Result<(), Box> { @@ -194,7 +191,7 @@ impl<'screen> Console<'screen> { /// # /// 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(()) @@ -202,7 +199,7 @@ impl<'screen> Console<'screen> { /// ``` #[doc(alias = "consoleSetWindow")] pub fn set_window(&mut self, x: u8, y: u8, width: u8, height: u8) { - let height_limit = 30; + let height_limit = 30; let length_limit = self.max_width(); if x >= length_limit { @@ -212,22 +209,30 @@ impl<'screen> Console<'screen> { panic!("y coordinate of new console window out of bounds"); } - if (x+width) > length_limit { + if (x + width) > length_limit { panic!("width of new console window out of bounds"); } - if (y+height) > height_limit { + if (y + height) > height_limit { panic!("height of new console window out of bounds"); } - - unsafe { consoleSetWindow(self.context.as_mut(), x.into(), y.into(), width.into(), height.into()) }; + + unsafe { + consoleSetWindow( + self.context.as_mut(), + x.into(), + y.into(), + width.into(), + height.into(), + ) + }; } /// Reset the window's size to default parameters. - /// + /// /// This can be used to undo the changes made by [`set_window()`](Console::set_window()). - /// + /// /// # Example - /// + /// /// ```no_run /// # use std::error::Error; /// # fn main() -> Result<(), Box> { @@ -239,11 +244,11 @@ impl<'screen> Console<'screen> { /// # /// 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(()) @@ -256,9 +261,9 @@ impl<'screen> Console<'screen> { } /// Returns this [`Console`]'s maximum character width depending on the screen used. - /// + /// /// # Example - /// + /// /// ```no_run /// # use std::error::Error; /// # fn main() -> Result<(), Box> { @@ -267,9 +272,9 @@ impl<'screen> Console<'screen> { /// # 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); /// # diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index ecc7dd9..1ba18a6 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -87,15 +87,24 @@ fn panic_hook_setup() { 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) { + // Due to how the Hid service operates, we can't safely use 2 handles to it at the same time. + // Furthermore, the panic hook runs before the panic cleanup is done, which means that any other handles + // to the service will still be alive during this process. + // Regardless, we can "unsafely" spin up a new instance, since the module won't be used any further from the main process, + // which is going to get cleaned up right after this loop. + unsafe { + let _ = ctru_sys::hidInit(); + + loop { + ctru_sys::hidScanInput(); + let keys = ctru_sys::hidKeysDown(); + + if KeyPad::from_bits_truncate(keys).contains(KeyPad::SELECT) { break; } - }, - Err(e) => println!("Error while intializing Hid controller during panic: {e}"), + } + + ctru_sys::hidExit(); } } }); diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 68ac957..c32a603 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -244,7 +244,7 @@ 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. @@ -311,7 +311,6 @@ impl Gfx { ) -> Result { let handler = ServiceReference::new( &GFX_ACTIVE, - false, || unsafe { ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 56a6035..16d10c5 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 [3D slider](Hid::slider_3d()), the [volume slider](Hid::slider_volume()), +//! the [accelerometer](Hid::accellerometer_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)] @@ -75,7 +81,11 @@ bitflags! { } /// Handle to the HID service. -pub struct Hid(()); +pub struct Hid { + active_accellerometer: bool, + active_gyroscope: bool, + _service_handler: ServiceReference, +} impl Hid { /// Initialize a new service handle. @@ -100,10 +110,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_accellerometer: false, + active_gyroscope: false, + _service_handler: handler, + }) } /// Scan the HID service for all user input occurring on the current frame. @@ -282,11 +308,122 @@ impl Hid { (res.dx, res.dy) } -} -impl Drop for Hid { - #[doc(alias = "hidExit")] - fn drop(&mut self) { - unsafe { ctru_sys::hidExit() }; + /// 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 + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::Hid; + /// let mut hid = Hid::new()?; + /// + /// hid.scan_input(); + /// + /// let volume = hid.slider_volume(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "HIDUSER_GetSoundVolume")] + pub fn slider_volume(&self) -> f32 { + let mut slider = 0; + + unsafe { + let _ = ctru_sys::HIDUSER_GetSoundVolume(&mut slider); + } + + (slider as f32) / 63. + } + + /// Returns the current 3D slider position (between 0 and 1). + /// + /// # Example + /// + /// ```no_run + /// # 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 = "osGet3DSliderState")] + pub fn slider_3d(&self) -> f32 { + // TODO: Replace with the static inline function `osGet3DSliderState`, which works the exact same way. + unsafe { (*(ctru_sys::OS_SHAREDCFG_VADDR as *mut ctru_sys::osSharedConfig_s)).slider_3d } + } + + #[doc(alias = "HIDUSER_EnableAccelerometer")] + pub fn enable_accellerometer(&mut self) { + let _ = unsafe { ctru_sys::HIDUSER_EnableAccelerometer() }; + + self.active_accellerometer = true; + } + + #[doc(alias = "HIDUSER_EnableGyroscope")] + pub fn enable_gyroscope(&mut self) { + let _ = unsafe { ctru_sys::HIDUSER_EnableGyroscope() }; + + self.active_gyroscope = true; + } + + #[doc(alias = "HIDUSER_DisableAccelerometer")] + pub fn disable_accellerometer(&mut self) { + let _ = unsafe { ctru_sys::HIDUSER_DisableAccelerometer() }; + + self.active_accellerometer = false; + } + + #[doc(alias = "HIDUSER_DisableGyroscope")] + pub fn disable_gyroscope(&mut self) { + let _ = unsafe { ctru_sys::HIDUSER_DisableGyroscope() }; + + self.active_gyroscope = false; + } + + #[doc(alias = "hidAccelRead")] + pub fn accellerometer_vector(&self) -> (i16, i16, i16) { + if !self.active_accellerometer { + panic!("tried to read accellerometer while disabled") + } + + let mut res = ctru_sys::accelVector { x: 0, y: 0, z: 0 }; + + unsafe { + ctru_sys::hidAccelRead(&mut res); + } + + (res.x, res.y, res.z) + } + + #[doc(alias = "hidGyroRead")] + pub fn gyroscope_rate(&self) -> (i16, i16, i16) { + if !self.active_gyroscope { + panic!("tried to read accellerometer while disabled") + } + + let mut res = ctru_sys::angularRate { x: 0, y: 0, z: 0 }; + + unsafe { + ctru_sys::hidGyroRead(&mut res); + } + + (res.x, res.y, res.z) } } diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs index 8cda802..1da683c 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -98,7 +98,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. /// @@ -133,7 +133,6 @@ impl Ndsp { pub fn new() -> crate::Result { let _service_handler = ServiceReference::new( &NDSP_ACTIVE, - false, || { ResultCode(unsafe { ctru_sys::ndspInit() })?; 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 e6f1c3b..aaf863f 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. @@ -63,7 +63,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()) })?; diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index d418919..5e61484 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. @@ -71,7 +71,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) })?; From d71774b6bf2f5333b2c040fb0ee936f98484e0ae Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Mon, 18 Sep 2023 20:37:29 +0200 Subject: [PATCH 05/29] Less panics, more errors --- ctru-rs/src/console.rs | 74 +++++++++++++++++++++++++++---- ctru-rs/src/lib.rs | 4 +- ctru-rs/src/services/hid.rs | 32 ++++++++++--- ctru-rs/src/services/ndsp/mod.rs | 58 +++++++++++------------- ctru-rs/src/services/ndsp/wave.rs | 12 ++--- ctru-rs/src/services/romfs.rs | 14 +++--- ctru-sys/build.rs | 2 +- 7 files changed, 131 insertions(+), 65 deletions(-) diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index d37ea06..ba85222 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. @@ -174,10 +199,6 @@ impl<'screen> Console<'screen> { /// of the new window based on the row/column coordinates of a full-screen console. /// The second pair is the new width and height. /// - /// # Panics - /// - /// This function will panic if the new window's position or size does not fit the screen. - /// /// # Example /// /// ```no_run @@ -198,22 +219,22 @@ impl<'screen> Console<'screen> { /// # } /// ``` #[doc(alias = "consoleSetWindow")] - pub fn set_window(&mut self, x: u8, y: u8, width: u8, height: u8) { + 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 { - panic!("x coordinate of new console window out of bounds"); + return Err(Error::CoordinateOutOfBounds(Axis::X)); } if y >= height_limit { - panic!("y coordinate of new console window out of bounds"); + return Err(Error::CoordinateOutOfBounds(Axis::Y)); } if (x + width) > length_limit { - panic!("width of new console window out of bounds"); + return Err(Error::DimensionOutOfBounds(Dimension::Width)); } if (y + height) > height_limit { - panic!("height of new console window out of bounds"); + return Err(Error::DimensionOutOfBounds(Dimension::Height)); } unsafe { @@ -225,6 +246,8 @@ impl<'screen> Console<'screen> { height.into(), ) }; + + Ok(()) } /// Reset the window's size to default parameters. @@ -321,3 +344,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 1ba18a6..329d063 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -73,7 +73,7 @@ pub fn use_panic_handler() { /// When `test` is enabled, this function will be ignored. #[cfg(not(test))] fn panic_hook_setup() { - use crate::services::hid::{Hid, KeyPad}; + use crate::services::hid::KeyPad; use std::panic::PanicInfo; let main_thread = std::thread::current().id(); @@ -103,7 +103,7 @@ fn panic_hook_setup() { break; } } - + ctru_sys::hidExit(); } } diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 16d10c5..1811c28 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -80,6 +80,15 @@ bitflags! { } } +/// Error enum for generic errors within the [`Hid`] service. +#[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, +} + /// Handle to the HID service. pub struct Hid { active_accellerometer: bool, @@ -398,9 +407,9 @@ impl Hid { } #[doc(alias = "hidAccelRead")] - pub fn accellerometer_vector(&self) -> (i16, i16, i16) { + pub fn accellerometer_vector(&self) -> Result<(i16, i16, i16), Error> { if !self.active_accellerometer { - panic!("tried to read accellerometer while disabled") + return Err(Error::UnavailableAccelerometer); } let mut res = ctru_sys::accelVector { x: 0, y: 0, z: 0 }; @@ -409,13 +418,13 @@ impl Hid { ctru_sys::hidAccelRead(&mut res); } - (res.x, res.y, res.z) + Ok((res.x, res.y, res.z)) } #[doc(alias = "hidGyroRead")] - pub fn gyroscope_rate(&self) -> (i16, i16, i16) { + pub fn gyroscope_rate(&self) -> Result<(i16, i16, i16), Error> { if !self.active_gyroscope { - panic!("tried to read accellerometer while disabled") + return Err(Error::UnavailableGyroscope); } let mut res = ctru_sys::angularRate { x: 0, y: 0, z: 0 }; @@ -424,6 +433,17 @@ impl Hid { ctru_sys::hidGyroRead(&mut res); } - (res.x, res.y, res.z) + Ok((res.x, res.y, res.z)) } } + +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 1da683c..78c744b 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -52,6 +52,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)] @@ -67,7 +77,7 @@ pub enum InterpolationType { /// Errors returned by [`ndsp`](self) functions. #[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. @@ -169,7 +179,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 { @@ -177,10 +187,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)), } } @@ -516,9 +526,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)), _ => (), } @@ -660,23 +670,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]) } @@ -709,12 +711,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; @@ -726,12 +724,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; @@ -754,7 +748,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"), @@ -765,7 +759,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 1a383ca..7f8c9f9 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. @@ -97,10 +97,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), } @@ -163,10 +163,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())); } _ => (), } @@ -174,7 +174,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/romfs.rs b/ctru-rs/src/services/romfs.rs index aaf863f..fd6064b 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -83,16 +83,12 @@ mod tests { use super::*; #[test] - fn romfs_counter() { - let _romfs = RomFS::new().unwrap(); - let value = *ROMFS_ACTIVE.lock().unwrap(); + #[should_panic] + fn romfs_lock() { + let romfs = RomFS::new().unwrap(); - assert_eq!(value, 1); + let _value = *ROMFS_ACTIVE.lock().unwrap(); - drop(_romfs); - - let value = *ROMFS_ACTIVE.lock().unwrap(); - - assert_eq!(value, 0); + drop(romfs); } } diff --git a/ctru-sys/build.rs b/ctru-sys/build.rs index 1643b8b..fa34560 100644 --- a/ctru-sys/build.rs +++ b/ctru-sys/build.rs @@ -169,7 +169,7 @@ fn check_libctru_version() -> Result<(String, String, String), Box> { .expect("crate version should have '+' delimeter"); if lib_version != crate_built_version { - return Err(format!( + Err(format!( "libctru version is {lib_version} but this crate was built for {crate_built_version}" ))?; } From 64d977749790ff1589ca7fe4385bf536e38a1f07 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sat, 23 Sep 2023 18:30:17 +0200 Subject: [PATCH 06/29] Fixed docs and typos --- ctru-rs/examples/movement.rs | 10 +-- ctru-rs/src/services/hid.rs | 138 ++++++++++++++++++++++++++++------- 2 files changed, 117 insertions(+), 31 deletions(-) diff --git a/ctru-rs/examples/movement.rs b/ctru-rs/examples/movement.rs index 984b779..b293c71 100644 --- a/ctru-rs/examples/movement.rs +++ b/ctru-rs/examples/movement.rs @@ -1,6 +1,6 @@ //! Movement example. //! -//! Simple application to showcase the use of the accellerometer and gyroscope. +//! Simple application to showcase the use of the accelerometer and gyroscope. use ctru::prelude::*; @@ -19,12 +19,12 @@ fn main() { // 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.enable_accellerometer(); + hid.enable_accelerometer(); hid.enable_gyroscope(); while apt.main_loop() { // Scan all the controller inputs. - // Accellerometer and gyroscope require this step to update the readings. + // Accelerometer and gyroscope require this step to update the readings. hid.scan_input(); if hid.keys_down().contains(KeyPad::START) { @@ -33,8 +33,8 @@ fn main() { // Be careful: reading without activating the sensors (as done before this loop) will result in a panic. println!( - "\x1b[3;0HAccelleration: {:?} ", - hid.accellerometer_vector() + "\x1b[3;0HAcceleration: {:?} ", + hid.accelerometer_vector() ); println!( "\x1b[4;0HGyroscope angular rate: {:?} ", diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 1811c28..0634091 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -2,7 +2,7 @@ //! //! 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 [3D slider](Hid::slider_3d()), the [volume slider](Hid::slider_volume()), -//! the [accelerometer](Hid::accellerometer_vector()), and the [gyroscope](Hid::gyroscope_rate()). +//! the [accelerometer](Hid::accelerometer_vector()), and the [gyroscope](Hid::gyroscope_rate()). #![doc(alias = "input")] #![doc(alias = "controller")] #![doc(alias = "gamepad")] @@ -91,7 +91,7 @@ pub enum Error { /// Handle to the HID service. pub struct Hid { - active_accellerometer: bool, + active_accelerometer: bool, active_gyroscope: bool, _service_handler: ServiceReference, } @@ -135,7 +135,7 @@ impl Hid { )?; Ok(Self { - active_accellerometer: false, + active_accelerometer: false, active_gyroscope: false, _service_handler: handler, }) @@ -367,7 +367,7 @@ impl Hid { /// /// hid.scan_input(); /// - /// let volume = hid.volume_slider(); + /// let volume = hid.slider_3d(); /// # /// # Ok(()) /// # } @@ -378,37 +378,95 @@ impl Hid { unsafe { (*(ctru_sys::OS_SHAREDCFG_VADDR as *mut ctru_sys::osSharedConfig_s)).slider_3d } } + /// Activate/deactivate the console's acceleration sensor. + /// + /// # Example + /// + /// ```no_run + /// # 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); + /// # + /// # Ok(()) + /// # } + /// ``` #[doc(alias = "HIDUSER_EnableAccelerometer")] - pub fn enable_accellerometer(&mut self) { - let _ = unsafe { ctru_sys::HIDUSER_EnableAccelerometer() }; - - self.active_accellerometer = true; - } - - #[doc(alias = "HIDUSER_EnableGyroscope")] - pub fn enable_gyroscope(&mut self) { - let _ = unsafe { ctru_sys::HIDUSER_EnableGyroscope() }; - - self.active_gyroscope = true; - } - #[doc(alias = "HIDUSER_DisableAccelerometer")] - pub fn disable_accellerometer(&mut self) { - let _ = unsafe { ctru_sys::HIDUSER_DisableAccelerometer() }; + pub fn set_accelerometer(&mut self, enabled: bool) { + if enabled { + let _ = unsafe { ctru_sys::HIDUSER_EnableAccelerometer() }; + } else { + let _ = unsafe { ctru_sys::HIDUSER_DisableAccelerometer() }; + } - self.active_accellerometer = false; + self.active_accelerometer = enabled; } - + + /// Activate/deactivate the console's gyroscopic sensor. + /// + /// # Example + /// + /// ```no_run + /// # 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); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "HIDUSER_EnableGyroscope")] #[doc(alias = "HIDUSER_DisableGyroscope")] - pub fn disable_gyroscope(&mut self) { - let _ = unsafe { ctru_sys::HIDUSER_DisableGyroscope() }; + pub fn set_gyroscope(&mut self, enabled: bool) { + if enabled { + let _ = unsafe { ctru_sys::HIDUSER_EnableGyroscope() }; + } else { + let _ = unsafe { ctru_sys::HIDUSER_DisableGyroscope() }; + } - self.active_gyroscope = false; + self.active_gyroscope = enabled; } + /// 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 + /// + /// ```no_run + /// # 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); + /// + /// // 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 accellerometer_vector(&self) -> Result<(i16, i16, i16), Error> { - if !self.active_accellerometer { + pub fn accelerometer_vector(&self) -> Result<(i16, i16, i16), Error> { + if !self.active_accelerometer { return Err(Error::UnavailableAccelerometer); } @@ -421,6 +479,34 @@ impl Hid { Ok((res.x, res.y, res.z)) } + /// Returns the angular rate (x,y,z) 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 + /// + /// ```no_run + /// # 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); + /// + /// // 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<(i16, i16, i16), Error> { if !self.active_gyroscope { From a84d2045f22678bf1a386c7b540ed242d73dcb53 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 24 Sep 2023 13:40:39 +0200 Subject: [PATCH 07/29] Fixed Screen unsafety on VRAM --- ctru-rs/src/console.rs | 7 ++- ctru-rs/src/services/gfx.rs | 81 ++++++++++++++++++++++++++++++----- ctru-rs/src/services/hid.rs | 22 +++++----- ctru-rs/src/services/romfs.rs | 2 +- 4 files changed, 88 insertions(+), 24 deletions(-) diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index ba85222..697b088 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -75,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 /// /// ```no_run @@ -84,7 +89,7 @@ impl<'screen> Console<'screen> { /// use ctru::services::gfx::Gfx; /// use ctru::console::Console; /// - /// // 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. diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index c32a603..dc1dca6 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; @@ -251,7 +257,8 @@ impl Gfx { /// /// # 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: /// /// ```no_run /// # use std::error::Error; @@ -260,12 +267,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 /// /// ```no_run @@ -281,10 +290,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 /// @@ -298,21 +307,71 @@ 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_shared( + top_fb_fmt: FramebufferFormat, + bottom_fb_fmt: FramebufferFormat, + ) -> Result { + let handler = ServiceReference::new( + &GFX_ACTIVE, + || unsafe { + ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), false); + + Ok(()) + }, + || unsafe { ctru_sys::gfxExit() }, + )?; + + Ok(Self { + top_screen: RefCell::new(TopScreen::new()), + bottom_screen: RefCell::new(BottomScreen), + _service_handler: handler, + }) + } + + /// 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). + /// + /// Things such as [`Console`](crate::console::Console) and [`Screen::raw_framebuffer()`] will result in ARM exceptions. + /// + /// # Example + /// + /// ```no_run + /// # 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 fn with_formats( + pub unsafe fn with_formats_vram( top_fb_fmt: FramebufferFormat, bottom_fb_fmt: FramebufferFormat, - use_vram_buffers: bool, ) -> Result { let handler = ServiceReference::new( &GFX_ACTIVE, || 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(), true); Ok(()) }, diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 0634091..5daabb1 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -400,13 +400,13 @@ impl Hid { pub fn set_accelerometer(&mut self, enabled: bool) { if enabled { let _ = unsafe { ctru_sys::HIDUSER_EnableAccelerometer() }; - } else { + } else { let _ = unsafe { ctru_sys::HIDUSER_DisableAccelerometer() }; } self.active_accelerometer = enabled; } - + /// Activate/deactivate the console's gyroscopic sensor. /// /// # Example @@ -429,7 +429,7 @@ impl Hid { pub fn set_gyroscope(&mut self, enabled: bool) { if enabled { let _ = unsafe { ctru_sys::HIDUSER_EnableGyroscope() }; - } else { + } else { let _ = unsafe { ctru_sys::HIDUSER_DisableGyroscope() }; } @@ -437,9 +437,9 @@ impl Hid { } /// 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. /// @@ -451,10 +451,10 @@ impl Hid { /// # /// use ctru::services::hid::Hid; /// let mut hid = Hid::new()?; - /// + /// /// // The accelerometer will start to register movements. /// hid.set_accelerometer(true); - /// + /// /// // It's necessary to run `scan_input()` to update the accelerometer's readings. /// hid.scan_input(); /// @@ -480,9 +480,9 @@ impl Hid { } /// Returns the angular rate (x,y,z) registered by the gyroscope. - /// + /// /// # Errors - /// + /// /// This function returns an error if the gyroscope was not previously enabled. /// Have a look at [`Hid::set_gyroscope()`]. /// @@ -494,10 +494,10 @@ impl Hid { /// # /// use ctru::services::hid::Hid; /// let mut hid = Hid::new()?; - /// + /// /// // The gyroscope will start to register positions. /// hid.set_gyroscope(true); - /// + /// /// // It's necessary to run `scan_input()` to update the gyroscope's readings. /// hid.scan_input(); /// diff --git a/ctru-rs/src/services/romfs.rs b/ctru-rs/src/services/romfs.rs index fd6064b..572aea6 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -87,7 +87,7 @@ mod tests { fn romfs_lock() { let romfs = RomFS::new().unwrap(); - let _value = *ROMFS_ACTIVE.lock().unwrap(); + let _value = *ROMFS_ACTIVE.try_lock().unwrap(); drop(romfs); } From b577a393236efbfa4faa0b6b873723526156804b Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 24 Sep 2023 13:40:56 +0200 Subject: [PATCH 08/29] Fixed doc lints --- ctru-rs/src/applets/mii_selector.rs | 2 +- ctru-rs/src/linear.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/applets/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs index a2ef72d..bdf661b 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; 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)] From c3021fe39d623ed12a0c02c61e6c8e0b6eb89ca5 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 24 Sep 2023 18:50:08 +0200 Subject: [PATCH 09/29] Translate impls to static inline --- ctru-rs/src/applets/mii_selector.rs | 4 +--- ctru-rs/src/services/hid.rs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ctru-rs/src/applets/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs index bdf661b..2353d7d 100644 --- a/ctru-rs/src/applets/mii_selector.rs +++ b/ctru-rs/src/applets/mii_selector.rs @@ -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/services/hid.rs b/ctru-rs/src/services/hid.rs index 5daabb1..808ff03 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -374,8 +374,7 @@ impl Hid { /// ``` #[doc(alias = "osGet3DSliderState")] pub fn slider_3d(&self) -> f32 { - // TODO: Replace with the static inline function `osGet3DSliderState`, which works the exact same way. - unsafe { (*(ctru_sys::OS_SHAREDCFG_VADDR as *mut ctru_sys::osSharedConfig_s)).slider_3d } + unsafe { ctru_sys::osGet3DSliderState() } } /// Activate/deactivate the console's acceleration sensor. From 45468d3aa51c5896648492fb9af69621c47590b8 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Wed, 4 Oct 2023 11:35:08 +0200 Subject: [PATCH 10/29] WIP camera service overhaul --- ctru-rs/examples/buttons.rs | 3 +- ctru-rs/examples/camera-image.rs | 82 ++-- ctru-rs/src/services/cam.rs | 690 ++++++++++++++++++++----------- ctru-rs/src/services/hid.rs | 29 +- 4 files changed, 494 insertions(+), 310 deletions(-) diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index e92c349..e7662a1 100644 --- a/ctru-rs/examples/buttons.rs +++ b/ctru-rs/examples/buttons.rs @@ -25,12 +25,11 @@ fn main() { // Get information about which keys were held down on this frame. let keys = hid.keys_held(); - // Print the status of the 2 sliders. + // Print the status of the volume slider. println!( "\x1b[20;0HVolume slider: {} ", hid.slider_volume() ); - println!("\x1b[21;0H3D slider: {} ", hid.slider_3d()); // We only want to print when the keys we're holding now are different // from what they were on the previous frame. diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 851aa2a..386f3b5 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -3,17 +3,16 @@ //! 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::cam::{ + Cam, Camera, OutputFormat, ShutterSound, Trimming, ViewSize, WhiteBalance, +}; use ctru::services::gfx::{Flush, Screen, Swap}; 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 WIDTH: usize = 200; +const HEIGHT: usize = 100; const WAIT_TIMEOUT: Duration = Duration::from_millis(300); @@ -35,30 +34,35 @@ fn main() { let mut cam = Cam::new().expect("Failed to initialize CAM service."); // Camera setup. - { - let camera = &mut cam.outer_right_cam; - - camera - .set_view_size(ViewSize::TopLCD) - .expect("Failed to set camera size"); - camera - .set_output_format(OutputFormat::Rgb565) - .expect("Failed to set camera output format"); - camera - .set_noise_filter(true) - .expect("Failed to enable noise filter"); - camera - .set_auto_exposure(true) - .expect("Failed to enable auto exposure"); - camera - .set_auto_white_balance(true) - .expect("Failed to enable auto white balance"); - camera - .set_trimming(false) - .expect("Failed to disable trimming"); - } - - let mut buf = vec![0u8; BUF_SIZE]; + let camera = &mut cam.outer_right_cam; + + camera + .set_view_size(ViewSize::TopLCD) + .expect("Failed to set camera size"); + camera + .set_output_format(OutputFormat::Rgb565) + .expect("Failed to set camera output format"); + camera + .set_noise_filter(true) + .expect("Failed to enable noise filter"); + camera + .set_auto_exposure(true) + .expect("Failed to enable auto exposure"); + camera + .set_white_balance(WhiteBalance::Auto) + .expect("Failed to enable auto white balance"); + camera + .set_trimming(Trimming::Centered { + width: WIDTH as i16, + height: HEIGHT as i16, + }) + .expect("Failed to disable trimming"); + + // We don't intend on making any other modifications to the camera, so this size should be enough. + let len = camera + .max_byte_count() + .expect("could not retrieve max image buffer size"); + let mut buf = vec![0u8; len]; println!("\nPress R to take a new picture"); println!("Press Start to exit"); @@ -79,20 +83,24 @@ fn main() { // 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 image_size = camera + .final_image_size() + .expect("could not retrieve final image 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); + rotate_image_to_screen( + &buf, + top_screen.raw_framebuffer().ptr, + image_size.0 as usize, + image_size.1 as usize, + ); // We will only flush and swap the "camera" screen, since the other screen is handled by the `Console`. top_screen.flush_buffers(); diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index 8d4af7b..fad9159 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -6,12 +6,17 @@ use crate::error::{Error, ResultCode}; use crate::services::gspgpu::FramebufferFormat; +use crate::services::ServiceReference; use ctru_sys::Handle; + +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. @@ -224,33 +229,25 @@ pub enum ShutterSound { MovieEnd = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE_END, } -/// Parameters to handle image trimming. -/// -/// See [`Camera::set_trimming_params()`] to learn how to use this. +/// Configuration to handle image trimming. #[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 based on absolute coordinates of the image. + Absolute { + /// Top-left corner coordinates (x,y) of the trimmed area. + top_left: (i16, i16), + /// Bottom-right corner coordinates (x,y) of the trimmed area. + bottom_right: (i16, i16), + }, + /// Trimming configuration relatively to the center of the image. + Centered { + /// Width of the trimmed area. + width: i16, + /// Height of the trimmed area. + height: i16, + }, + /// Trimming disabled. + Off, } /// Data used by the camera to calibrate image quality for a single camera. @@ -268,39 +265,33 @@ pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibratio /// /// 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 { + view_size: ViewSize, + trimming: Trimming, } /// 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 { + view_size: ViewSize, + trimming: Trimming, } /// 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 { + view_size: ViewSize, + trimming: Trimming, } /// Both outer cameras combined. /// /// Usually used for 3D photos. #[non_exhaustive] -pub struct BothOutwardCam; +pub struct BothOutwardCam { + view_size: ViewSize, + trimming: Trimming, +} impl BothOutwardCam { /// Set whether to enable or disable brightness synchronization between the two cameras. @@ -318,6 +309,148 @@ impl BothOutwardCam { } } +impl Camera for InwardCam { + fn camera_as_raw(&self) -> ctru_sys::u32_ { + ctru_sys::SELECT_IN1 + } + + fn view_size(&self) -> ViewSize { + self.view_size + } + + fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { + unsafe { + ResultCode(ctru_sys::CAMU_SetSize( + self.camera_as_raw(), + size.into(), + ctru_sys::CONTEXT_A, + ))?; + } + + self.view_size = size; + + self.set_trimming(Trimming::Off); + + Ok(()) + } + + fn trimming(&self) -> Trimming { + self.trimming + } + + fn set_trimming(&mut self, trimming: Trimming) { + match trimming { + Trimming::Absolute { + top_left, + bottom_right, + } => unsafe { + // Top left corner is "before" bottom right corner. + assert!(top_left.0 <= bottom_right.0 && top_left.1 <= bottom_right.1); + // All coordinates are positive. + assert!( + top_left.0 >= 0 + && top_left.1 >= 0 + && bottom_right.0 >= 0 + && bottom_right.1 >= 0 + ); + // All coordinates are within the view. + assert!( + top_left.0 < view_size.0 + && bottom_right.0 < view_size.0 + && top_left.1 < view_size.1 + && bottom_right.1 < view_size.1 + ); + + ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; + + ResultCode(ctru_sys::CAMU_SetTrimmingParams( + self.port_as_raw(), + top_left.0, + top_left.1, + bottom_right.0, + bottom_right.1, + ))?; + Ok(()) + }, + Trimming::Centered { width, height } => unsafe { + let view_size: (i16, i16) = self.view_size().into(); + + // Trim sizes are positive. + assert!(width >= 0 && height >= 0); + // Trim sizes are within the view. + assert!(width <= view_size.0 && height <= view_size.1); + + ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; + + ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter( + self.port_as_raw(), + width, + height, + view_size.0, + view_size.1, + ))?; + Ok(()) + }, + Trimming::Off => unsafe { + ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?; + Ok(()) + }, + } + } +} + +impl Camera for OutwardRightCam { + fn camera_as_raw(&self) -> ctru_sys::u32_ { + ctru_sys::SELECT_OUT1 + } + + fn view_size(&self) -> ViewSize { + self.view_size + } + + fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { + unsafe { + ResultCode(ctru_sys::CAMU_SetSize( + self.camera_as_raw(), + size.into(), + ctru_sys::CONTEXT_A, + ))?; + } + + self.view_size = size; + + self.set_trimming(Trimming::Off)?; + + Ok(()) + } +} + +impl Camera for OutwardLeftCam { + fn camera_as_raw(&self) -> ctru_sys::u32_ { + ctru_sys::SELECT_OUT2 + } + + fn view_size(&self) -> ViewSize { + self.view_size + } + + fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { + unsafe { + ResultCode(ctru_sys::CAMU_SetSize( + self.camera_as_raw(), + size.into(), + ctru_sys::CONTEXT_A, + ))?; + } + + self.view_size = size; + + self.set_trimming(Trimming::Off)?; + + Ok(()) + } +} + impl Camera for BothOutwardCam { fn camera_as_raw(&self) -> ctru_sys::u32_ { ctru_sys::SELECT_OUT1_OUT2 @@ -326,14 +459,41 @@ impl Camera for BothOutwardCam { fn port_as_raw(&self) -> ctru_sys::u32_ { ctru_sys::PORT_BOTH } + + fn view_size(&self) -> ViewSize { + self.view_size + } + + fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { + unsafe { + ResultCode(ctru_sys::CAMU_SetSize( + self.camera_as_raw(), + size.into(), + ctru_sys::CONTEXT_A, + ))?; + } + + self.view_size = size; + + self.set_trimming(Trimming::Off)?; + + 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 { /// 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 dimension, after all processing and modifications, have a look at [`Camera::final_image_size()`]. + fn view_size(&self) -> ViewSize; + /// Returns the raw port of the selected camera. fn port_as_raw(&self) -> ctru_sys::u32_ { ctru_sys::PORT_CAM1 @@ -367,7 +527,7 @@ 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 based on the view size, trimming, pixel depth and other /// modifications set to the camera. /// /// # Example @@ -381,35 +541,81 @@ 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.max_byte_count(); /// # /// # 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 max_byte_count(&self) -> crate::Result { + let size = self.final_image_size()?; + + let mut res: usize = (size.0 as usize * size.1 as usize) * std::mem::size_of::(); + + // 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 = res * 2; } + + Ok(res) } - /// Set whether or not the camera should trim the image. + /// Returns the dimensions of the final image based on the view size, trimming and other + /// modifications set to the camera. /// - /// [`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(()) + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cam::{Cam, Camera}; + /// let cam = Cam::new()?; + /// + /// let mut inward = &cam.inner_cam; + /// + /// inward.set_trimming(Trimming::Centered { + /// width: 100, + /// height: 100, + /// }); + /// + /// // This result will take into account the trimming. + /// let final_resolution = inward.final_image_size(); + /// # + /// # Ok(()) + /// # } + /// ``` + fn final_image_size(&self) -> crate::Result<(i16, i16)> { + match self.is_trimming_enabled()? { + // Take trimming into account + true => { + // This request should never fail, at least not under safe `ctru-rs` usage. + let trimming = self.trimming_configuration()?; + + let Trimming::Absolute { + top_left, + bottom_right, + } = trimming + else { + unreachable!() + }; + + let width = bottom_right.0 - top_left.0; + let height = bottom_right.1 - top_left.1; + + Ok((width, height)) + } + // Simply use the full view size. + false => Ok(self.view_size().into()), } } + fn trimming(&self) -> Trimming; + + /// Set trimming bounds to trim the camera photo. + #[doc(alias = "CAMU_SetTrimming")] + fn set_trimming(&mut self, trimming: Trimming); + /// Returns whether or not trimming is currently enabled for the camera. #[doc(alias = "CAMU_IsTrimming")] fn is_trimming_enabled(&self) -> crate::Result { @@ -420,76 +626,32 @@ pub trait Camera { } } - /// Set trimming bounds based on image coordinates. + /// Returns the [`Trimming`] configuration currently set. /// - /// 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(()) - } - } - - /// Returns the [`TrimmingParams`] currently set. + /// # Notes + /// + /// If successful, this function will always return a [`Trimming::Absolute`]. #[doc(alias = "CAMU_GetTrimmingParams")] - fn trimming_params(&self) -> crate::Result { + fn trimming_configuration(&self) -> crate::Result { unsafe { - let mut x_start = 0; - let mut y_start = 0; - let mut x_end = 0; - let mut y_end = 0; + let mut top_left = (0, 0); + let mut bottom_right = (0, 0); ResultCode(ctru_sys::CAMU_GetTrimmingParams( - &mut x_start, - &mut y_start, - &mut x_end, - &mut y_end, + &mut top_left.0, + &mut top_left.1, + &mut bottom_right.0, + &mut bottom_right.1, self.port_as_raw(), ))?; - Ok(TrimmingParams { - x_start, - y_start, - x_end, - y_end, + Ok(Trimming::Absolute { + top_left, + bottom_right, }) } } - /// Set the trimming bounds relatively to the center of the image. - /// - /// # 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(()) - } - } - - /// Set the exposure level of the camera.Ã¥ + /// Set the exposure level of the camera. #[doc(alias = "CAMU_SetExposure")] fn set_exposure(&mut self, exposure: i8) -> crate::Result<()> { unsafe { @@ -560,31 +722,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<()> { @@ -598,54 +735,13 @@ 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 { - ResultCode(ctru_sys::CAMU_SetSize( - self.camera_as_raw(), - size.into(), - ctru_sys::CONTEXT_A, - ))?; - Ok(()) - } - } + fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()>; /// Set the frame rate of the camera. #[doc(alias = "CAMU_SetFrameRate")] @@ -867,59 +963,119 @@ pub trait Camera { /// # 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<()> { - let transfer_unit = unsafe { - let mut buf_size = 0; - ResultCode(ctru_sys::CAMU_GetMaxBytes( - &mut buf_size, - width as i16, - height as i16, - ))?; - Ok::(buf_size) - }?; - - unsafe { - ResultCode(ctru_sys::CAMU_SetTransferBytes( - self.port_as_raw(), - transfer_unit, - width as i16, - height as i16, - ))?; - }; + fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> { + let full_view: (i16, i16) = self.view_size().into(); - let screen_size: usize = usize::from(width) * usize::from(height) * 2; - if buffer.len() < screen_size { + // Check whether the input buffer is big enough for the image. + let max_size = + (final_view.0 as usize * final_view.1 as usize) * std::mem::size_of::(); + if buffer.len() < max_size { return Err(Error::BufferTooShort { provided: buffer.len(), - wanted: screen_size, + wanted: max_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 transfer_unit = match self.trimming() { + Trimming::Absolute { + top_left, + bottom_right, + } => unsafe { + ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; + + ResultCode(ctru_sys::CAMU_SetTrimmingParams( + self.port_as_raw(), + top_left.0, + top_left.1, + bottom_right.0, + bottom_right.1, + ))?; + + // 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, + ))?; + }; + + transfer_unit + }, + Trimming::Centered { width, height } => unsafe { + ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; + + ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter( + self.port_as_raw(), + width, + height, + full_view.0, + full_view.1, + ))?; + + // 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, + width, + height, + ))?; + + transfer_unit + }; + + unsafe { + ResultCode(ctru_sys::CAMU_SetTransferBytes( + self.port_as_raw(), + transfer_unit, + width, + height, + ))?; + }; + + transfer_unit + }, + Trimming::Off => unsafe { + ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?; + }, }; 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 + }; + + unsafe { + 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 (TODO: SVC module) @@ -930,7 +1086,10 @@ pub trait Camera { // 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 + + // 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,6 +1102,11 @@ pub trait Camera { 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. @@ -964,15 +1128,43 @@ 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 mut inner_cam = InwardCam { + view_size: ViewSize::TopLCD, + }; + let mut outer_right_cam = OutwardRightCam { + view_size: ViewSize::TopLCD, + }; + let mut outer_left_cam = OutwardLeftCam { + view_size: ViewSize::TopLCD, + }; + let mut both_outer_cams = BothOutwardCam { + view_size: ViewSize::TopLCD, + }; + + 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 @@ -1007,13 +1199,6 @@ impl Cam { } } -impl Drop for Cam { - #[doc(alias = "camExit")] - fn drop(&mut self) { - unsafe { ctru_sys::camExit() }; - } -} - impl TryFrom for OutputFormat { type Error = (); @@ -1036,6 +1221,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/hid.rs b/ctru-rs/src/services/hid.rs index 808ff03..fadd9fd 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -1,7 +1,7 @@ //! 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 [3D slider](Hid::slider_3d()), the [volume slider](Hid::slider_volume()), +//! and [circle pad information](Hid::circlepad_position). It also provides information from the [volume slider](Hid::slider_volume()), //! the [accelerometer](Hid::accelerometer_vector()), and the [gyroscope](Hid::gyroscope_rate()). #![doc(alias = "input")] #![doc(alias = "controller")] @@ -344,7 +344,7 @@ impl Hid { /// # } /// ``` #[doc(alias = "HIDUSER_GetSoundVolume")] - pub fn slider_volume(&self) -> f32 { + pub fn volume_slider(&self) -> f32 { let mut slider = 0; unsafe { @@ -354,29 +354,6 @@ impl Hid { (slider as f32) / 63. } - /// Returns the current 3D slider position (between 0 and 1). - /// - /// # Example - /// - /// ```no_run - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// # - /// use ctru::services::hid::Hid; - /// let mut hid = Hid::new()?; - /// - /// hid.scan_input(); - /// - /// let volume = hid.slider_3d(); - /// # - /// # Ok(()) - /// # } - /// ``` - #[doc(alias = "osGet3DSliderState")] - pub fn slider_3d(&self) -> f32 { - unsafe { ctru_sys::osGet3DSliderState() } - } - /// Activate/deactivate the console's acceleration sensor. /// /// # Example @@ -478,7 +455,7 @@ impl Hid { Ok((res.x, res.y, res.z)) } - /// Returns the angular rate (x,y,z) registered by the gyroscope. + /// Returns the angular rate (roll,pitch,yaw) registered by the gyroscope. /// /// # Errors /// From b17bed0aeb2b2da0eeb2ab11c6d358504b0dcfd7 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sat, 7 Oct 2023 12:19:59 +0200 Subject: [PATCH 11/29] More CAM changes and WIP trimming --- ctru-rs/examples/camera-image.rs | 22 +- ctru-rs/src/services/cam.rs | 403 ++++++++++++++----------------- 2 files changed, 183 insertions(+), 242 deletions(-) diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 386f3b5..9f31f4e 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -11,8 +11,8 @@ use ctru::services::gspgpu::FramebufferFormat; use std::time::Duration; -const WIDTH: usize = 200; -const HEIGHT: usize = 100; +const WIDTH: i16 = 400; +const HEIGHT: i16 = 240; const WAIT_TIMEOUT: Duration = Duration::from_millis(300); @@ -51,17 +51,13 @@ fn main() { camera .set_white_balance(WhiteBalance::Auto) .expect("Failed to enable auto white balance"); - camera - .set_trimming(Trimming::Centered { - width: WIDTH as i16, - height: HEIGHT as i16, - }) - .expect("Failed to disable trimming"); + camera.set_trimming(Trimming::Centered { + width: WIDTH, + height: HEIGHT, + }); // We don't intend on making any other modifications to the camera, so this size should be enough. - let len = camera - .max_byte_count() - .expect("could not retrieve max image buffer size"); + let len = camera.max_byte_count(); let mut buf = vec![0u8; len]; println!("\nPress R to take a new picture"); @@ -86,9 +82,7 @@ fn main() { .take_picture(&mut buf, WAIT_TIMEOUT) .expect("Failed to take picture"); - let image_size = camera - .final_image_size() - .expect("could not retrieve final image size"); + let image_size = camera.final_image_size(); // Play the normal shutter sound. cam.play_shutter_sound(ShutterSound::Normal) diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index fad9159..de04ade 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -261,27 +261,40 @@ pub struct ImageQualityCalibrationData(pub ctru_sys::CAMU_ImageQualityCalibratio #[derive(Default, Clone, Copy, Debug)] pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData); +/// Basic configuration needed to properly use the built-in cameras. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct Configuration { + view_size: ViewSize, + trimming: Trimming, +} + +impl Configuration { + fn new() -> Self { + Self { + view_size: ViewSize::TopLCD, + trimming: Trimming::Off, + } + } +} + /// Inward camera representation (facing the user of the 3DS). /// /// Usually used for selfies. #[non_exhaustive] pub struct InwardCam { - view_size: ViewSize, - trimming: Trimming, + configuration: Configuration, } /// Right-side outward camera representation. #[non_exhaustive] pub struct OutwardRightCam { - view_size: ViewSize, - trimming: Trimming, + configuration: Configuration, } /// Left-side outward camera representation. #[non_exhaustive] pub struct OutwardLeftCam { - view_size: ViewSize, - trimming: Trimming, + configuration: Configuration, } /// Both outer cameras combined. @@ -289,8 +302,7 @@ pub struct OutwardLeftCam { /// Usually used for 3D photos. #[non_exhaustive] pub struct BothOutwardCam { - view_size: ViewSize, - trimming: Trimming, + configuration: Configuration, } impl BothOutwardCam { @@ -309,13 +321,52 @@ impl BothOutwardCam { } } +macro_rules! trimming_checks { + ($trimming:ident, $view_size:expr) => { + match $trimming { + Trimming::Absolute { + top_left, + bottom_right, + } => { + let view_size: (i16, i16) = $view_size; + + // Top left corner is "before" bottom right corner. + assert!(top_left.0 <= bottom_right.0 && top_left.1 <= bottom_right.1); + // All coordinates are positive. + assert!( + top_left.0 >= 0 + && top_left.1 >= 0 + && bottom_right.0 >= 0 + && bottom_right.1 >= 0 + ); + // All coordinates are within the view. + assert!( + top_left.0 < view_size.0 + && bottom_right.0 < view_size.0 + && top_left.1 < view_size.1 + && bottom_right.1 < view_size.1 + ); + } + Trimming::Centered { width, height } => { + let view_size: (i16, i16) = $view_size; + + // Trim sizes are positive. + assert!(width >= 0 && height >= 0); + // Trim sizes are within the view. + assert!(width <= view_size.0 && height <= view_size.1); + } + Trimming::Off => (), + } + }; +} + impl Camera for InwardCam { fn camera_as_raw(&self) -> ctru_sys::u32_ { ctru_sys::SELECT_IN1 } fn view_size(&self) -> ViewSize { - self.view_size + self.configuration.view_size } fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { @@ -327,7 +378,7 @@ impl Camera for InwardCam { ))?; } - self.view_size = size; + self.configuration.view_size = size; self.set_trimming(Trimming::Off); @@ -335,67 +386,14 @@ impl Camera for InwardCam { } fn trimming(&self) -> Trimming { - self.trimming + self.configuration.trimming } fn set_trimming(&mut self, trimming: Trimming) { - match trimming { - Trimming::Absolute { - top_left, - bottom_right, - } => unsafe { - // Top left corner is "before" bottom right corner. - assert!(top_left.0 <= bottom_right.0 && top_left.1 <= bottom_right.1); - // All coordinates are positive. - assert!( - top_left.0 >= 0 - && top_left.1 >= 0 - && bottom_right.0 >= 0 - && bottom_right.1 >= 0 - ); - // All coordinates are within the view. - assert!( - top_left.0 < view_size.0 - && bottom_right.0 < view_size.0 - && top_left.1 < view_size.1 - && bottom_right.1 < view_size.1 - ); - - ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; - - ResultCode(ctru_sys::CAMU_SetTrimmingParams( - self.port_as_raw(), - top_left.0, - top_left.1, - bottom_right.0, - bottom_right.1, - ))?; - Ok(()) - }, - Trimming::Centered { width, height } => unsafe { - let view_size: (i16, i16) = self.view_size().into(); - - // Trim sizes are positive. - assert!(width >= 0 && height >= 0); - // Trim sizes are within the view. - assert!(width <= view_size.0 && height <= view_size.1); - - ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; + // Run checks for all trimming possibilities. + trimming_checks!(trimming, self.view_size().into()); - ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter( - self.port_as_raw(), - width, - height, - view_size.0, - view_size.1, - ))?; - Ok(()) - }, - Trimming::Off => unsafe { - ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?; - Ok(()) - }, - } + self.configuration.trimming = trimming; } } @@ -405,7 +403,7 @@ impl Camera for OutwardRightCam { } fn view_size(&self) -> ViewSize { - self.view_size + self.configuration.view_size } fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { @@ -417,12 +415,23 @@ impl Camera for OutwardRightCam { ))?; } - self.view_size = size; + self.configuration.view_size = size; - self.set_trimming(Trimming::Off)?; + self.set_trimming(Trimming::Off); Ok(()) } + + fn trimming(&self) -> Trimming { + self.configuration.trimming + } + + fn set_trimming(&mut self, trimming: Trimming) { + // Run checks for all trimming possibilities. + trimming_checks!(trimming, self.view_size().into()); + + self.configuration.trimming = trimming; + } } impl Camera for OutwardLeftCam { @@ -431,7 +440,7 @@ impl Camera for OutwardLeftCam { } fn view_size(&self) -> ViewSize { - self.view_size + self.configuration.view_size } fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { @@ -443,12 +452,23 @@ impl Camera for OutwardLeftCam { ))?; } - self.view_size = size; + self.configuration.view_size = size; - self.set_trimming(Trimming::Off)?; + self.set_trimming(Trimming::Off); Ok(()) } + + fn trimming(&self) -> Trimming { + self.configuration.trimming + } + + fn set_trimming(&mut self, trimming: Trimming) { + // Run checks for all trimming possibilities. + trimming_checks!(trimming, self.view_size().into()); + + self.configuration.trimming = trimming; + } } impl Camera for BothOutwardCam { @@ -461,7 +481,7 @@ impl Camera for BothOutwardCam { } fn view_size(&self) -> ViewSize { - self.view_size + self.configuration.view_size } fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { @@ -473,12 +493,23 @@ impl Camera for BothOutwardCam { ))?; } - self.view_size = size; + self.configuration.view_size = size; - self.set_trimming(Trimming::Off)?; + self.set_trimming(Trimming::Off); Ok(()) } + + fn trimming(&self) -> Trimming { + self.configuration.trimming + } + + fn set_trimming(&mut self, trimming: Trimming) { + // Run checks for all trimming possibilities. + trimming_checks!(trimming, self.view_size().into()); + + self.configuration.trimming = trimming; + } } /// Generic functionality common to all cameras. @@ -547,8 +578,8 @@ pub trait Camera { /// # } /// ``` #[doc(alias = "CAMU_GetTransferBytes")] - fn max_byte_count(&self) -> crate::Result { - let size = self.final_image_size()?; + fn max_byte_count(&self) -> usize { + let size = self.final_image_size(); let mut res: usize = (size.0 as usize * size.1 as usize) * std::mem::size_of::(); @@ -557,7 +588,7 @@ pub trait Camera { res = res * 2; } - Ok(res) + res } /// Returns the dimensions of the final image based on the view size, trimming and other @@ -585,31 +616,18 @@ pub trait Camera { /// # Ok(()) /// # } /// ``` - fn final_image_size(&self) -> crate::Result<(i16, i16)> { - match self.is_trimming_enabled()? { - // Take trimming into account - true => { - // This request should never fail, at least not under safe `ctru-rs` usage. - let trimming = self.trimming_configuration()?; - - let Trimming::Absolute { - top_left, - bottom_right, - } = trimming - else { - unreachable!() - }; - - let width = bottom_right.0 - top_left.0; - let height = bottom_right.1 - top_left.1; - - Ok((width, height)) - } - // Simply use the full view size. - false => Ok(self.view_size().into()), + fn final_image_size(&self) -> (i16, i16) { + match self.trimming() { + Trimming::Absolute { + top_left, + bottom_right, + } => (bottom_right.0 - top_left.0, bottom_right.1 - top_left.1), + Trimming::Centered { width, height } => (width, height), + Trimming::Off => self.view_size().into(), } } + /// Returns the [`Trimming`] configuration currently set. fn trimming(&self) -> Trimming; /// Set trimming bounds to trim the camera photo. @@ -618,37 +636,8 @@ pub trait Camera { /// 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) - } - } - - /// Returns the [`Trimming`] configuration currently set. - /// - /// # Notes - /// - /// If successful, this function will always return a [`Trimming::Absolute`]. - #[doc(alias = "CAMU_GetTrimmingParams")] - fn trimming_configuration(&self) -> crate::Result { - unsafe { - let mut top_left = (0, 0); - let mut bottom_right = (0, 0); - ResultCode(ctru_sys::CAMU_GetTrimmingParams( - &mut top_left.0, - &mut top_left.1, - &mut bottom_right.0, - &mut bottom_right.1, - self.port_as_raw(), - ))?; - - Ok(Trimming::Absolute { - top_left, - bottom_right, - }) - } + fn is_trimming(&self) -> bool { + matches!(self.trimming(), Trimming::Off) } /// Set the exposure level of the camera. @@ -672,22 +661,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<()> { @@ -913,16 +886,6 @@ pub trait Camera { } } - /// 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 @@ -966,21 +929,13 @@ pub trait Camera { fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> { let full_view: (i16, i16) = self.view_size().into(); - // Check whether the input buffer is big enough for the image. - let max_size = - (final_view.0 as usize * final_view.1 as usize) * std::mem::size_of::(); - if buffer.len() < max_size { - return Err(Error::BufferTooShort { - provided: buffer.len(), - wanted: max_size, - }); - } - + // It seems like doing this as the first step gives the option to use trimming and get correct readings for the transfer bytes. unsafe { ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?; }; - let transfer_unit = match self.trimming() { + // Obtain the final view size and make the needed modifications to the camera. + let final_view: (i16, i16) = match self.trimming() { Trimming::Absolute { top_left, bottom_right, @@ -995,29 +950,7 @@ pub trait Camera { bottom_right.1, ))?; - // 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, - ))?; - }; - - transfer_unit + (bottom_right.0 - top_left.0, bottom_right.1 - top_left.1) }, Trimming::Centered { width, height } => unsafe { ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; @@ -1030,38 +963,57 @@ pub trait Camera { full_view.1, ))?; - // 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, - width, - height, - ))?; - - transfer_unit - }; - - unsafe { - ResultCode(ctru_sys::CAMU_SetTransferBytes( - self.port_as_raw(), - transfer_unit, - width, - height, - ))?; - }; - - transfer_unit + (width, height) }, Trimming::Off => unsafe { ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?; + + full_view }, }; + println!("CAMU_GetMaxBytes{:?}", final_view); + + // 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, + ))?; + }; + + // Check whether the input buffer is big enough for the image. + let max_size = (final_view.0 as usize * final_view.1 as usize) * std::mem::size_of::(); + if buffer.len() < max_size { + // We deactivate the camera prematurely. + // + // Note that it shouldn't be too important whether the camera closes or not here, + // since it only starts capturing later. + unsafe { ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE))? }; + + return Err(Error::BufferTooShort { + provided: buffer.len(), + wanted: max_size, + }); + } + let receive_event = unsafe { let mut completion_handle: Handle = 0; - + ResultCode(ctru_sys::CAMU_SetReceiving( &mut completion_handle, buffer.as_mut_ptr().cast(), @@ -1073,6 +1025,7 @@ pub trait Camera { completion_handle }; + // Start capturing with the camera. unsafe { ResultCode(ctru_sys::CAMU_StartCapture(self.port_as_raw()))?; }; @@ -1140,18 +1093,12 @@ impl Cam { }, )?; - let mut inner_cam = InwardCam { - view_size: ViewSize::TopLCD, - }; - let mut outer_right_cam = OutwardRightCam { - view_size: ViewSize::TopLCD, - }; - let mut outer_left_cam = OutwardLeftCam { - view_size: ViewSize::TopLCD, - }; - let mut both_outer_cams = BothOutwardCam { - view_size: ViewSize::TopLCD, - }; + 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)?; From 0067d55abf73f82064d937fcb7a4497e3e4a792c Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sat, 7 Oct 2023 12:21:49 +0200 Subject: [PATCH 12/29] More CAM changes and WIP trimming --- ctru-rs/src/services/cam.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index de04ade..008dbb4 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -980,8 +980,8 @@ pub trait Camera { ResultCode(ctru_sys::CAMU_GetMaxBytes( &mut transfer_unit, - final_view.0, - final_view.1, + full_view.0, + full_view.1, ))?; transfer_unit From 2453499d58d789a398bb7c427e9c68e00f294fae Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sat, 7 Oct 2023 13:35:09 +0200 Subject: [PATCH 13/29] Trimming works, finally --- ctru-rs/examples/camera-image.rs | 10 +- ctru-rs/src/services/cam.rs | 158 +++++++++++-------------------- ctru-rs/src/services/hid.rs | 4 +- 3 files changed, 58 insertions(+), 114 deletions(-) diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 9f31f4e..9155def 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -11,9 +11,6 @@ use ctru::services::gspgpu::FramebufferFormat; use std::time::Duration; -const WIDTH: i16 = 400; -const HEIGHT: i16 = 240; - const WAIT_TIMEOUT: Duration = Duration::from_millis(300); fn main() { @@ -51,10 +48,9 @@ fn main() { camera .set_white_balance(WhiteBalance::Auto) .expect("Failed to enable auto white balance"); - camera.set_trimming(Trimming::Centered { - width: WIDTH, - height: HEIGHT, - }); + + // Un-comment this line and see how it changes! + // camera.set_trimming(Trimming::Centered(ViewSize::BottomLCD)); // We don't intend on making any other modifications to the camera, so this size should be enough. let len = camera.max_byte_count(); diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index 008dbb4..f521d16 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -110,7 +110,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)] @@ -230,22 +230,11 @@ pub enum ShutterSound { } /// Configuration to handle image trimming. +#[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Trimming { - /// Trimming configuration based on absolute coordinates of the image. - Absolute { - /// Top-left corner coordinates (x,y) of the trimmed area. - top_left: (i16, i16), - /// Bottom-right corner coordinates (x,y) of the trimmed area. - bottom_right: (i16, i16), - }, /// Trimming configuration relatively to the center of the image. - Centered { - /// Width of the trimmed area. - width: i16, - /// Height of the trimmed area. - height: i16, - }, + Centered(ViewSize), /// Trimming disabled. Off, } @@ -324,36 +313,12 @@ impl BothOutwardCam { macro_rules! trimming_checks { ($trimming:ident, $view_size:expr) => { match $trimming { - Trimming::Absolute { - top_left, - bottom_right, - } => { + Trimming::Centered(trim_size) => { let view_size: (i16, i16) = $view_size; + let trim_size: (i16, i16) = trim_size.into(); - // Top left corner is "before" bottom right corner. - assert!(top_left.0 <= bottom_right.0 && top_left.1 <= bottom_right.1); - // All coordinates are positive. - assert!( - top_left.0 >= 0 - && top_left.1 >= 0 - && bottom_right.0 >= 0 - && bottom_right.1 >= 0 - ); - // All coordinates are within the view. - assert!( - top_left.0 < view_size.0 - && bottom_right.0 < view_size.0 - && top_left.1 < view_size.1 - && bottom_right.1 < view_size.1 - ); - } - Trimming::Centered { width, height } => { - let view_size: (i16, i16) = $view_size; - - // Trim sizes are positive. - assert!(width >= 0 && height >= 0); // Trim sizes are within the view. - assert!(width <= view_size.0 && height <= view_size.1); + assert!(trim_size.0 <= view_size.0 && trim_size.1 <= view_size.1); } Trimming::Off => (), } @@ -522,7 +487,8 @@ pub trait Camera { /// # Notes /// /// This view is the full resolution at which the camera will take the photo. - /// If you are interested in the final image dimension, after all processing and modifications, have a look at [`Camera::final_image_size()`]. + /// 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; /// Returns the raw port of the selected camera. @@ -558,9 +524,13 @@ pub trait Camera { } } - /// Returns the maximum amount of bytes the final image will occupy based on the view size, trimming, pixel depth 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 + /// + /// Remember to query this information again if *any* changes are applied to the [`Camera`] configuration! + /// /// # Example /// /// ```no_run @@ -572,20 +542,19 @@ pub trait Camera { /// /// let inward = &cam.inner_cam; /// - /// let transfer_count = inward.max_byte_count(); + /// let transfer_count = inward.final_byte_length(); /// # /// # Ok(()) /// # } /// ``` - #[doc(alias = "CAMU_GetTransferBytes")] - fn max_byte_count(&self) -> usize { - let size = self.final_image_size(); + fn final_byte_length(&self) -> usize { + let size = self.final_view_size(); let mut res: usize = (size.0 as usize * size.1 as usize) * std::mem::size_of::(); // 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 = res * 2; + res *= 2; } res @@ -594,35 +563,33 @@ pub trait Camera { /// Returns the dimensions of the final image based on the view size, trimming and other /// modifications set to the camera. /// + /// # Notes + /// + /// Remember to query this information again if *any* changes are applied to the [`Camera`] configuration! + /// /// # Example /// /// ```no_run /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::cam::{Cam, Camera}; - /// let cam = Cam::new()?; + /// use ctru::services::cam::{Cam, Camera, Trimming, ViewSize}; + /// let mut cam = Cam::new()?; /// - /// let mut inward = &cam.inner_cam; + /// let mut inward = &mut cam.inner_cam; /// - /// inward.set_trimming(Trimming::Centered { - /// width: 100, - /// height: 100, - /// }); + /// // We trim the image down so that it fits on a DS screen! + /// inward.set_trimming(Trimming::Centered(ViewSize::DS)); /// /// // This result will take into account the trimming. - /// let final_resolution = inward.final_image_size(); + /// let final_resolution = inward.final_view_size(); /// # /// # Ok(()) /// # } /// ``` - fn final_image_size(&self) -> (i16, i16) { + fn final_view_size(&self) -> (i16, i16) { match self.trimming() { - Trimming::Absolute { - top_left, - bottom_right, - } => (bottom_right.0 - top_left.0, bottom_right.1 - top_left.1), - Trimming::Centered { width, height } => (width, height), + Trimming::Centered(trim_size) => trim_size.into(), Trimming::Off => self.view_size().into(), } } @@ -905,74 +872,50 @@ pub trait Camera { /// # use std::time::Duration; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::cam::{Cam, Camera, ViewSize, OutputFormat}; + /// 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(()) /// # } /// ``` fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> { - let full_view: (i16, i16) = self.view_size().into(); - - // It seems like doing this as the first step gives the option to use trimming and get correct readings for the transfer bytes. - unsafe { - ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?; - }; - // Obtain the final view size and make the needed modifications to the camera. - let final_view: (i16, i16) = match self.trimming() { - Trimming::Absolute { - top_left, - bottom_right, - } => unsafe { + match self.trimming() { + Trimming::Centered(trim_size) => unsafe { ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; - ResultCode(ctru_sys::CAMU_SetTrimmingParams( - self.port_as_raw(), - top_left.0, - top_left.1, - bottom_right.0, - bottom_right.1, - ))?; - - (bottom_right.0 - top_left.0, bottom_right.1 - top_left.1) - }, - Trimming::Centered { width, height } => unsafe { - ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; + let full_view: (i16, i16) = self.view_size().into(); + let trim_size: (i16, i16) = trim_size.into(); ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter( self.port_as_raw(), - width, - height, + trim_size.0, + trim_size.1, full_view.0, full_view.1, ))?; - - (width, height) }, Trimming::Off => unsafe { ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?; - - full_view }, }; - println!("CAMU_GetMaxBytes{:?}", final_view); + 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 { @@ -980,8 +923,8 @@ pub trait Camera { ResultCode(ctru_sys::CAMU_GetMaxBytes( &mut transfer_unit, - full_view.0, - full_view.1, + final_view.0, + final_view.1, ))?; transfer_unit @@ -997,7 +940,7 @@ pub trait Camera { }; // Check whether the input buffer is big enough for the image. - let max_size = (final_view.0 as usize * final_view.1 as usize) * std::mem::size_of::(); + let max_size = self.final_byte_length(); if buffer.len() < max_size { // We deactivate the camera prematurely. // @@ -1011,6 +954,11 @@ pub trait Camera { }); } + unsafe { + ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?; + ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?; + }; + let receive_event = unsafe { let mut completion_handle: Handle = 0; diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index fadd9fd..7f6abc7 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -1,7 +1,7 @@ //! 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 [volume slider](Hid::slider_volume()), +//! 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")] @@ -338,7 +338,7 @@ impl Hid { /// /// hid.scan_input(); /// - /// let volume = hid.slider_volume(); + /// let volume = hid.volume_slider(); /// # /// # Ok(()) /// # } From ce8fc4ef838486af26556e406f870a9800d97ac7 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sat, 7 Oct 2023 16:54:49 +0200 Subject: [PATCH 14/29] Squash down camera functions --- ctru-rs/examples/camera-image.rs | 44 ++-- ctru-rs/src/services/cam.rs | 351 +++++++++++++------------------ 2 files changed, 166 insertions(+), 229 deletions(-) diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 9155def..7ee9b56 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -32,28 +32,30 @@ fn main() { // Camera setup. let camera = &mut cam.outer_right_cam; - - camera - .set_view_size(ViewSize::TopLCD) - .expect("Failed to set camera size"); - camera - .set_output_format(OutputFormat::Rgb565) - .expect("Failed to set camera output format"); - camera - .set_noise_filter(true) - .expect("Failed to enable noise filter"); - camera - .set_auto_exposure(true) - .expect("Failed to enable auto exposure"); - camera - .set_white_balance(WhiteBalance::Auto) - .expect("Failed to enable auto white balance"); - - // Un-comment this line and see how it changes! - // camera.set_trimming(Trimming::Centered(ViewSize::BottomLCD)); + { + camera + .set_view_size(ViewSize::TopLCD) + .expect("Failed to set camera size"); + camera + .set_output_format(OutputFormat::Rgb565) + .expect("Failed to set camera output format"); + camera + .set_noise_filter(true) + .expect("Failed to enable noise filter"); + camera + .set_auto_exposure(true) + .expect("Failed to enable auto exposure"); + camera + .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(Trimming::Centered(ViewSize::TopLCD)) + .expect("Failed to enable trimming"); + } // We don't intend on making any other modifications to the camera, so this size should be enough. - let len = camera.max_byte_count(); + let len = camera.final_byte_length(); let mut buf = vec![0u8; len]; println!("\nPress R to take a new picture"); @@ -78,7 +80,7 @@ fn main() { .take_picture(&mut buf, WAIT_TIMEOUT) .expect("Failed to take picture"); - let image_size = camera.final_image_size(); + let image_size = camera.final_view_size(); // Play the normal shutter sound. cam.play_shutter_sound(ShutterSound::Normal) diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index f521d16..614de33 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -8,6 +8,7 @@ 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; @@ -250,38 +251,19 @@ pub struct ImageQualityCalibrationData(pub ctru_sys::CAMU_ImageQualityCalibratio #[derive(Default, Clone, Copy, Debug)] pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData); -/// Basic configuration needed to properly use the built-in cameras. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct Configuration { - view_size: ViewSize, - trimming: Trimming, -} - -impl Configuration { - fn new() -> Self { - Self { - view_size: ViewSize::TopLCD, - trimming: Trimming::Off, - } - } -} - /// Inward camera representation (facing the user of the 3DS). /// /// Usually used for selfies. -#[non_exhaustive] pub struct InwardCam { configuration: Configuration, } /// Right-side outward camera representation. -#[non_exhaustive] pub struct OutwardRightCam { configuration: Configuration, } /// Left-side outward camera representation. -#[non_exhaustive] pub struct OutwardLeftCam { configuration: Configuration, } @@ -289,113 +271,107 @@ pub struct OutwardLeftCam { /// Both outer cameras combined. /// /// Usually used for 3D photos. -#[non_exhaustive] pub struct BothOutwardCam { configuration: Configuration, } -impl BothOutwardCam { - /// Set whether to enable or disable brightness synchronization between the two cameras. - #[doc(alias = "CAMU_SetBrightnessSynchronization")] - pub fn set_brightness_synchronization( - &mut self, - brightness_synchronization: bool, - ) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetBrightnessSynchronization( - brightness_synchronization, - ))?; - Ok(()) - } +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, } -} -macro_rules! trimming_checks { - ($trimming:ident, $view_size:expr) => { - match $trimming { - Trimming::Centered(trim_size) => { - let view_size: (i16, i16) = $view_size; - let trim_size: (i16, i16) = trim_size.into(); + impl Configuration { + pub fn new() -> Self { + Self::default() + } + } - // Trim sizes are within the view. - assert!(trim_size.0 <= view_size.0 && trim_size.1 <= view_size.1); + impl Default for Configuration { + fn default() -> Self { + Self { + view_size: ViewSize::TopLCD, + trimming: Trimming::Off, } - Trimming::Off => (), } - }; -} - -impl Camera for InwardCam { - fn camera_as_raw(&self) -> ctru_sys::u32_ { - ctru_sys::SELECT_IN1 } - fn view_size(&self) -> ViewSize { - self.configuration.view_size + pub trait ConfigurableCamera { + fn configuration(&self) -> &Configuration; + + fn configuration_mut(&mut self) -> &mut Configuration; } - fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetSize( - self.camera_as_raw(), - size.into(), - ctru_sys::CONTEXT_A, - ))?; + impl ConfigurableCamera for InwardCam { + fn configuration(&self) -> &Configuration { + &self.configuration } - self.configuration.view_size = size; - - self.set_trimming(Trimming::Off); - - Ok(()) + fn configuration_mut(&mut self) -> &mut Configuration { + &mut self.configuration + } } - fn trimming(&self) -> Trimming { - self.configuration.trimming + impl ConfigurableCamera for OutwardRightCam { + fn configuration(&self) -> &Configuration { + &self.configuration + } + + fn configuration_mut(&mut self) -> &mut Configuration { + &mut self.configuration + } } - fn set_trimming(&mut self, trimming: Trimming) { - // Run checks for all trimming possibilities. - trimming_checks!(trimming, self.view_size().into()); + impl ConfigurableCamera for OutwardLeftCam { + fn configuration(&self) -> &Configuration { + &self.configuration + } - self.configuration.trimming = trimming; + fn configuration_mut(&mut self) -> &mut Configuration { + &mut self.configuration + } } -} -impl Camera for OutwardRightCam { - fn camera_as_raw(&self) -> ctru_sys::u32_ { - ctru_sys::SELECT_OUT1 - } + impl ConfigurableCamera for BothOutwardCam { + fn configuration(&self) -> &Configuration { + &self.configuration + } - fn view_size(&self) -> ViewSize { - self.configuration.view_size + fn configuration_mut(&mut self) -> &mut Configuration { + &mut self.configuration + } } +} - fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { +impl BothOutwardCam { + /// Set whether to enable or disable brightness synchronization between the two cameras. + #[doc(alias = "CAMU_SetBrightnessSynchronization")] + pub fn set_brightness_synchronization( + &mut self, + brightness_synchronization: bool, + ) -> crate::Result<()> { unsafe { - ResultCode(ctru_sys::CAMU_SetSize( - self.camera_as_raw(), - size.into(), - ctru_sys::CONTEXT_A, + ResultCode(ctru_sys::CAMU_SetBrightnessSynchronization( + brightness_synchronization, ))?; + Ok(()) } - - self.configuration.view_size = size; - - self.set_trimming(Trimming::Off); - - Ok(()) } +} - fn trimming(&self) -> Trimming { - self.configuration.trimming +impl Camera for InwardCam { + fn camera_as_raw(&self) -> ctru_sys::u32_ { + ctru_sys::SELECT_IN1 } +} - fn set_trimming(&mut self, trimming: Trimming) { - // Run checks for all trimming possibilities. - trimming_checks!(trimming, self.view_size().into()); - - self.configuration.trimming = trimming; +impl Camera for OutwardRightCam { + fn camera_as_raw(&self) -> ctru_sys::u32_ { + ctru_sys::SELECT_OUT1 } } @@ -403,37 +379,6 @@ impl Camera for OutwardLeftCam { fn camera_as_raw(&self) -> ctru_sys::u32_ { ctru_sys::SELECT_OUT2 } - - fn view_size(&self) -> ViewSize { - self.configuration.view_size - } - - fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetSize( - self.camera_as_raw(), - size.into(), - ctru_sys::CONTEXT_A, - ))?; - } - - self.configuration.view_size = size; - - self.set_trimming(Trimming::Off); - - Ok(()) - } - - fn trimming(&self) -> Trimming { - self.configuration.trimming - } - - fn set_trimming(&mut self, trimming: Trimming) { - // Run checks for all trimming possibilities. - trimming_checks!(trimming, self.view_size().into()); - - self.configuration.trimming = trimming; - } } impl Camera for BothOutwardCam { @@ -444,41 +389,10 @@ impl Camera for BothOutwardCam { fn port_as_raw(&self) -> ctru_sys::u32_ { ctru_sys::PORT_BOTH } - - fn view_size(&self) -> ViewSize { - self.configuration.view_size - } - - fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::CAMU_SetSize( - self.camera_as_raw(), - size.into(), - ctru_sys::CONTEXT_A, - ))?; - } - - self.configuration.view_size = size; - - self.set_trimming(Trimming::Off); - - Ok(()) - } - - fn trimming(&self) -> Trimming { - self.configuration.trimming - } - - fn set_trimming(&mut self, trimming: Trimming) { - // Run checks for all trimming possibilities. - trimming_checks!(trimming, self.view_size().into()); - - self.configuration.trimming = trimming; - } } /// Generic functionality common to all cameras. -pub trait Camera { +pub trait Camera: private::ConfigurableCamera { /// Returns the raw value of the selected camera. fn camera_as_raw(&self) -> ctru_sys::u32_; @@ -489,7 +403,9 @@ pub trait Camera { /// 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; + 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_ { @@ -595,11 +511,50 @@ pub trait Camera { } /// Returns the [`Trimming`] configuration currently set. - fn trimming(&self) -> Trimming; + fn trimming(&self) -> Trimming { + self.configuration().trimming + } /// Set trimming bounds to trim the camera photo. + /// + /// # Notes + /// + /// Trimming the image view directly on the cameras can only work if the final image has the size of one of the supported image formats. + /// As such, even after trimming the image (which will result in a "zoomed in" view of the original image) + /// the resulting picture must still be of [`ViewSize`] dimensions. #[doc(alias = "CAMU_SetTrimming")] - fn set_trimming(&mut self, trimming: Trimming); + fn set_trimming(&mut self, trimming: Trimming) -> crate::Result<()> { + // Run checks for all trimming possibilities. + match trimming { + Trimming::Centered(trim_size) => unsafe { + let view_size: (i16, i16) = self.view_size().into(); + let trim_size: (i16, i16) = trim_size.into(); + + // Trim sizes are 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(()) + } /// Returns whether or not trimming is currently enabled for the camera. #[doc(alias = "CAMU_IsTrimming")] @@ -681,7 +636,21 @@ pub trait Camera { /// /// Calling this function will reset the trimming configuration. #[doc(alias = "CAMU_SetSize")] - fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()>; + fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { + unsafe { + ResultCode(ctru_sys::CAMU_SetSize( + self.camera_as_raw(), + size.into(), + ctru_sys::CONTEXT_A, + ))?; + } + + self.configuration_mut().view_size = size; + + self.set_trimming(Trimming::Off)?; + + Ok(()) + } /// Set the frame rate of the camera. #[doc(alias = "CAMU_SetFrameRate")] @@ -711,8 +680,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 { @@ -857,14 +825,7 @@ pub trait Camera { /// /// # Errors /// - /// This function will return an error if the camera is busy or if the timeout duration gets reached. - /// - /// # Arguments - /// - /// * `width` - Width of the desired image - /// * `height` - Height of the desired image - /// * `timeout` - Duration to wait for the image - /// + /// This function will return an error if the camera is already busy or if the timeout duration is reached. /// # Example /// /// ```no_run @@ -894,27 +855,16 @@ pub trait Camera { /// # } /// ``` fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> { - // Obtain the final view size and make the needed modifications to the camera. - match self.trimming() { - Trimming::Centered(trim_size) => unsafe { - ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; - - let full_view: (i16, i16) = self.view_size().into(); - let trim_size: (i16, i16) = trim_size.into(); - - ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter( - self.port_as_raw(), - trim_size.0, - trim_size.1, - full_view.0, - full_view.1, - ))?; - }, - Trimming::Off => unsafe { - ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?; - }, - }; + // 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... @@ -939,21 +889,6 @@ pub trait Camera { ))?; }; - // Check whether the input buffer is big enough for the image. - let max_size = self.final_byte_length(); - if buffer.len() < max_size { - // We deactivate the camera prematurely. - // - // Note that it shouldn't be too important whether the camera closes or not here, - // since it only starts capturing later. - unsafe { ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE))? }; - - return Err(Error::BufferTooShort { - provided: buffer.len(), - wanted: max_size, - }); - } - unsafe { ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?; ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?; @@ -979,14 +914,14 @@ pub trait Camera { }; unsafe { - // Panicking without closing an SVC handle causes an ARM exception, we have to handle it carefully (TODO: SVC module) + // 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()))?; From 0ec4c92fbc35b1159c428a5e0dcfdf2b368a5bd9 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 15 Oct 2023 13:43:19 +0200 Subject: [PATCH 15/29] 3D images without stereo calibration --- ctru-rs/examples/camera-image.rs | 45 +++++--- ctru-rs/src/services/cam.rs | 174 +++++++++++++++++++++++++++++-- 2 files changed, 193 insertions(+), 26 deletions(-) diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 7ee9b56..ff2339f 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -6,7 +6,7 @@ use ctru::prelude::*; use ctru::services::cam::{ Cam, Camera, OutputFormat, ShutterSound, Trimming, ViewSize, WhiteBalance, }; -use ctru::services::gfx::{Flush, Screen, Swap}; +use ctru::services::gfx::{Flush, Screen, Swap, TopScreen3D}; use ctru::services::gspgpu::FramebufferFormat; use std::time::Duration; @@ -20,9 +20,12 @@ fn main() { 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()); @@ -31,7 +34,7 @@ fn main() { let mut cam = Cam::new().expect("Failed to initialize CAM service."); // Camera setup. - let camera = &mut cam.outer_right_cam; + let camera = &mut cam.both_outer_cams; { camera .set_view_size(ViewSize::TopLCD) @@ -73,7 +76,7 @@ fn main() { if keys_down.contains(KeyPad::R) { println!("Capturing new image"); - let camera = &mut cam.outer_right_cam; + let mut camera = &mut cam.both_outer_cams; // Take a picture and write it to the buffer. camera @@ -86,17 +89,29 @@ fn main() { 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, - image_size.0 as usize, - image_size.1 as usize, - ); + { + 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, + image_size.0 as usize, + image_size.1 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, + image_size.0 as usize, + image_size.1 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(); } diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index 614de33..991db74 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -241,15 +241,16 @@ pub enum Trimming { } /// 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). /// @@ -358,8 +359,44 @@ 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(()) } } @@ -389,6 +426,114 @@ 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. @@ -445,6 +590,7 @@ pub trait Camera: private::ConfigurableCamera { /// /// # 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 @@ -799,11 +945,11 @@ pub trait Camera: private::ConfigurableCamera { } } - /// 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))?; @@ -811,11 +957,11 @@ pub trait Camera: private::ConfigurableCamera { } } - /// 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) } @@ -826,6 +972,13 @@ pub trait Camera: private::ConfigurableCamera { /// # Errors /// /// This function will return an error if the camera is already busy or if the timeout duration is reached. + /// + /// # Notes + /// + /// 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 /// /// ```no_run @@ -858,13 +1011,12 @@ pub trait Camera: private::ConfigurableCamera { // 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... From fdd836ba9d5d437717bc0f151a5e5c1412928ff0 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 15 Oct 2023 14:00:09 +0200 Subject: [PATCH 16/29] Fix lints --- ctru-rs/examples/buttons.rs | 2 +- ctru-rs/examples/camera-image.rs | 2 +- ctru-rs/examples/movement.rs | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index e7662a1..90862bf 100644 --- a/ctru-rs/examples/buttons.rs +++ b/ctru-rs/examples/buttons.rs @@ -28,7 +28,7 @@ fn main() { // Print the status of the volume slider. println!( "\x1b[20;0HVolume slider: {} ", - hid.slider_volume() + hid.volume_slider() ); // We only want to print when the keys we're holding now are different diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index ff2339f..ecf9f65 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -76,7 +76,7 @@ fn main() { if keys_down.contains(KeyPad::R) { println!("Capturing new image"); - let mut camera = &mut cam.both_outer_cams; + let camera = &mut cam.both_outer_cams; // Take a picture and write it to the buffer. camera diff --git a/ctru-rs/examples/movement.rs b/ctru-rs/examples/movement.rs index b293c71..a0aa2c8 100644 --- a/ctru-rs/examples/movement.rs +++ b/ctru-rs/examples/movement.rs @@ -19,8 +19,8 @@ fn main() { // 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.enable_accelerometer(); - hid.enable_gyroscope(); + hid.set_accelerometer(true); + hid.set_gyroscope(true); while apt.main_loop() { // Scan all the controller inputs. @@ -35,10 +35,12 @@ fn main() { println!( "\x1b[3;0HAcceleration: {:?} ", hid.accelerometer_vector() + .expect("could not retrieve acceleration vector") ); println!( "\x1b[4;0HGyroscope angular rate: {:?} ", hid.gyroscope_rate() + .expect("could not retrieve angular rate") ); gfx.wait_for_vblank(); From 6afad4dceb9b9badcb6fc5de588c842d1170aa14 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 15 Oct 2023 14:08:27 +0200 Subject: [PATCH 17/29] Even more lints --- ctru-rs/examples/camera-image.rs | 4 ++-- ctru-rs/examples/title-info.rs | 12 +++++------- ctru-rs/src/services/romfs.rs | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index ecf9f65..30938ce 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -140,8 +140,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/title-info.rs b/ctru-rs/examples/title-info.rs index d2898ac..b3b3f8e 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -58,19 +58,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.iter().nth(offset).unwrap(); // Clear the top screen and write title IDs to it. top_screen.select(); diff --git a/ctru-rs/src/services/romfs.rs b/ctru-rs/src/services/romfs.rs index 572aea6..1202f6d 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -87,7 +87,7 @@ mod tests { fn romfs_lock() { let romfs = RomFS::new().unwrap(); - let _value = *ROMFS_ACTIVE.try_lock().unwrap(); + *ROMFS_ACTIVE.try_lock().unwrap(); drop(romfs); } From c8e38a6fe42c312abb98524cc6a1ffeb89a12a9a Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 15 Oct 2023 16:27:29 +0200 Subject: [PATCH 18/29] Final lint fixing? --- ctru-rs/examples/title-info.rs | 2 +- ctru-rs/src/services/romfs.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index b3b3f8e..4332750 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -68,7 +68,7 @@ fn main() { // Render the title list via a scrollable text UI. if refresh { - let mut selected_title = cur_list.iter().nth(offset).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/src/services/romfs.rs b/ctru-rs/src/services/romfs.rs index 1202f6d..029525c 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -87,7 +87,7 @@ mod tests { fn romfs_lock() { let romfs = RomFS::new().unwrap(); - *ROMFS_ACTIVE.try_lock().unwrap(); + ROMFS_ACTIVE.try_lock().unwrap(); drop(romfs); } From 4b0b966026b8837e06713488afb90026589d2d92 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 12 Nov 2023 16:03:08 +0100 Subject: [PATCH 19/29] Fix errors --- ctru-rs/src/services/romfs.rs | 2 +- ctru-sys/build.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/services/romfs.rs b/ctru-rs/src/services/romfs.rs index a311e9c..e7f992c 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -89,7 +89,7 @@ mod tests { #[should_panic] fn romfs_lock() { let romfs = RomFS::new().unwrap(); - + ROMFS_ACTIVE.try_lock().unwrap(); drop(romfs); diff --git a/ctru-sys/build.rs b/ctru-sys/build.rs index 3ad5851..b1a48b3 100644 --- a/ctru-sys/build.rs +++ b/ctru-sys/build.rs @@ -169,7 +169,7 @@ fn check_libctru_version() -> Result<(String, String, String), Box> { .expect("crate version should have '+' delimeter"); if lib_version != crate_built_version { - Err(format!( + return Err(format!( "libctru version is {lib_version} but this crate was built for {crate_built_version}" ) .into()); From 079f3bdc3c7c70f06a07bea9f15dea5154cae892 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 12 Nov 2023 16:47:13 +0100 Subject: [PATCH 20/29] Fixed nits and suggestions --- ctru-rs/examples/camera-image.rs | 10 ++-- ctru-rs/examples/movement.rs | 18 ++++--- ctru-rs/src/console.rs | 8 +-- ctru-rs/src/lib.rs | 23 +-------- ctru-rs/src/services/cam.rs | 2 +- ctru-rs/src/services/gfx.rs | 4 +- ctru-rs/src/services/hid.rs | 89 ++++++++++++++++++++++++-------- ctru-rs/src/services/ndsp/mod.rs | 1 + 8 files changed, 95 insertions(+), 60 deletions(-) diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 30938ce..8395883 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -83,7 +83,7 @@ fn main() { .take_picture(&mut buf, WAIT_TIMEOUT) .expect("Failed to take picture"); - let image_size = camera.final_view_size(); + let (width, height) = camera.final_view_size(); // Play the normal shutter sound. cam.play_shutter_sound(ShutterSound::Normal) @@ -96,16 +96,16 @@ fn main() { rotate_image_to_screen( &buf, left_side.raw_framebuffer().ptr, - image_size.0 as usize, - image_size.1 as usize, + 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, - image_size.0 as usize, - image_size.1 as usize, + width as usize, + height as usize, ); } diff --git a/ctru-rs/examples/movement.rs b/ctru-rs/examples/movement.rs index a0aa2c8..1cbefc1 100644 --- a/ctru-rs/examples/movement.rs +++ b/ctru-rs/examples/movement.rs @@ -19,8 +19,10 @@ fn main() { // 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); - hid.set_gyroscope(true); + 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. @@ -34,13 +36,17 @@ fn main() { // Be careful: reading without activating the sensors (as done before this loop) will result in a panic. println!( "\x1b[3;0HAcceleration: {:?} ", - hid.accelerometer_vector() - .expect("could not retrieve acceleration vector") + Into::<(i16, i16, i16)>::into( + hid.accelerometer_vector() + .expect("could not retrieve acceleration vector") + ) ); println!( "\x1b[4;0HGyroscope angular rate: {:?} ", - hid.gyroscope_rate() - .expect("could not retrieve 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/src/console.rs b/ctru-rs/src/console.rs index f88e11b..5c9fba1 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -209,7 +209,7 @@ impl<'screen> Console<'screen> { /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -264,7 +264,7 @@ impl<'screen> Console<'screen> { /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -288,14 +288,14 @@ impl<'screen> Console<'screen> { pub fn reset_window(&mut self) { let width = self.max_width(); - unsafe { consoleSetWindow(self.context.as_mut(), 0, 0, width.into(), 30) }; + self.set_window(0, 0, width, 30).unwrap(); } /// Returns this [`Console`]'s maximum character width depending on the screen used. /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index a57cc00..f1569bb 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -76,7 +76,6 @@ pub fn use_panic_handler() { /// When `test` is enabled, this function will be ignored. #[cfg(not(test))] fn panic_hook_setup() { - use crate::services::hid::KeyPad; use std::panic::PanicInfo; let main_thread = std::thread::current().id(); @@ -88,27 +87,9 @@ fn panic_hook_setup() { // Only for panics in the main thread if main_thread == std::thread::current().id() && console::Console::exists() { - println!("\nPress SELECT to exit the software"); + println!("\nThe software will exit in 5 seconds"); - // Due to how the Hid service operates, we can't safely use 2 handles to it at the same time. - // Furthermore, the panic hook runs before the panic cleanup is done, which means that any other handles - // to the service will still be alive during this process. - // Regardless, we can "unsafely" spin up a new instance, since the module won't be used any further from the main process, - // which is going to get cleaned up right after this loop. - unsafe { - let _ = ctru_sys::hidInit(); - - loop { - ctru_sys::hidScanInput(); - let keys = ctru_sys::hidKeysDown(); - - if KeyPad::from_bits_truncate(keys).contains(KeyPad::SELECT) { - break; - } - } - - ctru_sys::hidExit(); - } + std::thread::sleep(std::time::Duration::from_secs(5)); } }); std::panic::set_hook(new_hook); diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index 3dd52ef..1964cb9 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -633,7 +633,7 @@ pub trait Camera: private::ConfigurableCamera { /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 25830a6..8c8764a 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -350,11 +350,11 @@ impl Gfx { /// 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). /// - /// Things such as [`Console`](crate::console::Console) and [`Screen::raw_framebuffer()`] will result in ARM exceptions. + /// Usage of functionality such as [`Console`](crate::console::Console) and [`Screen::raw_framebuffer()`] will result in ARM exceptions. /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 4997158..36588b0 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -81,6 +81,7 @@ 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. @@ -89,6 +90,28 @@ pub enum Error { UnavailableGyroscope, } +/// Representation of the acceleration vector read by the accelerometer. +/// +/// Have a look at [`Hid::enable_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::enable_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 { active_accelerometer: bool, @@ -336,7 +359,7 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -365,7 +388,7 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -373,28 +396,30 @@ impl Hid { /// let mut hid = Hid::new()?; /// /// // The accelerometer will start to register movements. - /// hid.set_accelerometer(true); + /// hid.set_accelerometer(true).unwrap(); /// # /// # Ok(()) /// # } /// ``` #[doc(alias = "HIDUSER_EnableAccelerometer")] #[doc(alias = "HIDUSER_DisableAccelerometer")] - pub fn set_accelerometer(&mut self, enabled: bool) { + pub fn set_accelerometer(&mut self, enabled: bool) -> crate::Result<()> { if enabled { - let _ = unsafe { ctru_sys::HIDUSER_EnableAccelerometer() }; + ResultCode(unsafe { ctru_sys::HIDUSER_EnableAccelerometer() })?; } else { - let _ = unsafe { ctru_sys::HIDUSER_DisableAccelerometer() }; + ResultCode(unsafe { ctru_sys::HIDUSER_DisableAccelerometer() })?; } self.active_accelerometer = enabled; + + Ok(()) } /// Activate/deactivate the console's gyroscopic sensor. /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -402,24 +427,26 @@ impl Hid { /// let mut hid = Hid::new()?; /// /// // The gyroscope will start to register positions. - /// hid.set_gyroscope(true); + /// hid.set_gyroscope(true).unwrap(); /// # /// # Ok(()) /// # } /// ``` #[doc(alias = "HIDUSER_EnableGyroscope")] #[doc(alias = "HIDUSER_DisableGyroscope")] - pub fn set_gyroscope(&mut self, enabled: bool) { + pub fn set_gyroscope(&mut self, enabled: bool) -> crate::Result<()> { if enabled { - let _ = unsafe { ctru_sys::HIDUSER_EnableGyroscope() }; + ResultCode(unsafe { ctru_sys::HIDUSER_EnableGyroscope() })?; } else { - let _ = unsafe { ctru_sys::HIDUSER_DisableGyroscope() }; + ResultCode(unsafe { ctru_sys::HIDUSER_DisableGyroscope() })?; } self.active_gyroscope = enabled; + + Ok(()) } - /// Returns the acceleration vector (x,y,z) registered by the accelerometer. + /// Returns the acceleration vector (x, y, z) registered by the accelerometer. /// /// # Errors /// @@ -428,7 +455,7 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -436,7 +463,7 @@ impl Hid { /// let mut hid = Hid::new()?; /// /// // The accelerometer will start to register movements. - /// hid.set_accelerometer(true); + /// hid.set_accelerometer(true).unwrap(); /// /// // It's necessary to run `scan_input()` to update the accelerometer's readings. /// hid.scan_input(); @@ -448,7 +475,7 @@ impl Hid { /// # } /// ``` #[doc(alias = "hidAccelRead")] - pub fn accelerometer_vector(&self) -> Result<(i16, i16, i16), Error> { + pub fn accelerometer_vector(&self) -> Result { if !self.active_accelerometer { return Err(Error::UnavailableAccelerometer); } @@ -459,10 +486,14 @@ impl Hid { ctru_sys::hidAccelRead(&mut res); } - Ok((res.x, res.y, res.z)) + Ok(Acceleration { + x: res.x, + y: res.y, + z: res.z, + }) } - /// Returns the angular rate (roll,pitch,yaw) registered by the gyroscope. + /// Returns the angular rate registered by the gyroscope. /// /// # Errors /// @@ -471,7 +502,7 @@ impl Hid { /// /// # Example /// - /// ```no_run + /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # @@ -479,7 +510,7 @@ impl Hid { /// let mut hid = Hid::new()?; /// /// // The gyroscope will start to register positions. - /// hid.set_gyroscope(true); + /// hid.set_gyroscope(true).unwrap(); /// /// // It's necessary to run `scan_input()` to update the gyroscope's readings. /// hid.scan_input(); @@ -491,7 +522,7 @@ impl Hid { /// # } /// ``` #[doc(alias = "hidGyroRead")] - pub fn gyroscope_rate(&self) -> Result<(i16, i16, i16), Error> { + pub fn gyroscope_rate(&self) -> Result { if !self.active_gyroscope { return Err(Error::UnavailableGyroscope); } @@ -502,7 +533,23 @@ impl Hid { ctru_sys::hidGyroRead(&mut res); } - Ok((res.x, res.y, res.z)) + Ok(AngularRate { + roll: res.x, + pitch: res.y, + yaw: res.z, + }) + } +} + +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) } } diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs index 4176f70..3e96184 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -85,6 +85,7 @@ pub enum InterpolationType { } /// Errors returned by [`ndsp`](self) functions. +#[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Error { /// Channel with the specified ID does not exist. From 99814fd5a1eef202646c02a119a77dab9f229f5b Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 19 Nov 2023 18:34:09 +0100 Subject: [PATCH 21/29] Finish up trimming --- ctru-rs/examples/camera-image.rs | 2 +- ctru-rs/src/services/cam.rs | 63 +++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 8395883..0c73f92 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -53,7 +53,7 @@ fn main() { .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(Trimming::Centered(ViewSize::TopLCD)) + .set_trimming(Trimming::new_centered_with_view(ViewSize::TopLCD)) .expect("Failed to enable trimming"); } diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index 1964cb9..0e31d13 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -231,11 +231,18 @@ pub enum ShutterSound { } /// Configuration to handle image trimming. +/// +/// See [`Trimming::new_centered()`] and the other associated methods for controlled +/// ways of configuring trimming. #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Trimming { /// Trimming configuration relatively to the center of the image. - Centered(ViewSize), + #[allow(missing_docs)] + Centered{ + width: i16, + height: i16, + }, /// Trimming disabled. Off, } @@ -653,7 +660,7 @@ pub trait Camera: private::ConfigurableCamera { /// ``` fn final_view_size(&self) -> (i16, i16) { match self.trimming() { - Trimming::Centered(trim_size) => trim_size.into(), + Trimming::Centered{width, height} => (width, height), Trimming::Off => self.view_size().into(), } } @@ -667,22 +674,24 @@ pub trait Camera: private::ConfigurableCamera { /// /// # Notes /// - /// Trimming the image view directly on the cameras can only work if the final image has the size of one of the supported image formats. - /// As such, even after trimming the image (which will result in a "zoomed in" view of the original image) - /// the resulting picture must still be of [`ViewSize`] dimensions. + /// Setting up a [`Trimming`] configurations that exceeds the bounds of the original + /// image's size will result in the trimming to be clamped to the maximum borders of the image. + /// Also, the final image size must have a pixel area multiple of 128, + /// otherwise the "status_changed" error may be returned. #[doc(alias = "CAMU_SetTrimming")] fn set_trimming(&mut self, trimming: Trimming) -> crate::Result<()> { - // Run checks for all trimming possibilities. match trimming { - Trimming::Centered(trim_size) => unsafe { + Trimming::Centered{width, height} => unsafe { let view_size: (i16, i16) = self.view_size().into(); - let trim_size: (i16, i16) = trim_size.into(); + let mut trim_size: (i16, i16) = (width, height); + + if trim_size.0 > view_size.0 { + trim_size.0 = view_size.0; + }; - // Trim sizes are within the view. - assert!( - trim_size.0 <= view_size.0 && trim_size.1 <= view_size.1, - "trimmed view is bigger than the camera view" - ); + if trim_size.1 > view_size.1 { + trim_size.1 = view_size.1; + }; ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?; @@ -1090,6 +1099,34 @@ pub trait Camera: private::ConfigurableCamera { } } +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. /// From 28a8b08f09ca82f155a662d0cdbdb7171d7f5b39 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 19 Nov 2023 18:42:14 +0100 Subject: [PATCH 22/29] Fmt and docs lints --- ctru-rs/src/services/cam.rs | 18 ++++++------------ ctru-rs/src/services/hid.rs | 4 ++-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index 0e31d13..a4c799d 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -239,10 +239,7 @@ pub enum ShutterSound { pub enum Trimming { /// Trimming configuration relatively to the center of the image. #[allow(missing_docs)] - Centered{ - width: i16, - height: i16, - }, + Centered { width: i16, height: i16 }, /// Trimming disabled. Off, } @@ -660,7 +657,7 @@ pub trait Camera: private::ConfigurableCamera { /// ``` fn final_view_size(&self) -> (i16, i16) { match self.trimming() { - Trimming::Centered{width, height} => (width, height), + Trimming::Centered { width, height } => (width, height), Trimming::Off => self.view_size().into(), } } @@ -681,7 +678,7 @@ pub trait Camera: private::ConfigurableCamera { #[doc(alias = "CAMU_SetTrimming")] fn set_trimming(&mut self, trimming: Trimming) -> crate::Result<()> { match trimming { - Trimming::Centered{width, height} => unsafe { + Trimming::Centered { width, height } => unsafe { let view_size: (i16, i16) = self.view_size().into(); let mut trim_size: (i16, i16) = (width, height); @@ -1103,17 +1100,14 @@ 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, - } + + Self::Centered { width, height } } /// Create a new [`Trimming`] configuration using a standard view size centered to the original image. diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 36588b0..10d89c4 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -92,7 +92,7 @@ pub enum Error { /// Representation of the acceleration vector read by the accelerometer. /// -/// Have a look at [`Hid::enable_accelerometer()`] for more information. +/// Have a look at [`Hid::set_accelerometer()`] for more information. #[allow(missing_docs)] #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] pub struct Acceleration { @@ -103,7 +103,7 @@ pub struct Acceleration { /// Representation of the angular rate read by the gyroscope. /// -/// Have a look at [`Hid::enable_gyroscope())`] for more information. +/// Have a look at [`Hid::set_gyroscope()`] for more information. #[allow(missing_docs)] #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] pub struct AngularRate { From 3e2861323f695f6782594bdec59015390be072ae Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 19 Nov 2023 18:46:23 +0100 Subject: [PATCH 23/29] Revert trim clamping --- ctru-rs/src/services/cam.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index a4c799d..e571aaa 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -669,26 +669,22 @@ pub trait Camera: private::ConfigurableCamera { /// Set trimming bounds to trim the camera photo. /// - /// # Notes + /// # Panics /// /// Setting up a [`Trimming`] configurations that exceeds the bounds of the original - /// image's size will result in the trimming to be clamped to the maximum borders of the image. - /// Also, the final image size must have a pixel area multiple of 128, - /// otherwise the "status_changed" error may be returned. + /// 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 mut trim_size: (i16, i16) = (width, height); - - if trim_size.0 > view_size.0 { - trim_size.0 = view_size.0; - }; + let trim_size: (i16, i16) = (width, height); - if trim_size.1 > view_size.1 { - trim_size.1 = view_size.1; - }; + // 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))?; From 8addde71b481919292c2a09514b118ba95777262 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 19 Nov 2023 18:48:19 +0100 Subject: [PATCH 24/29] Docs --- ctru-rs/src/services/cam.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index e571aaa..33a0753 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -669,6 +669,11 @@ pub trait Camera: private::ConfigurableCamera { /// Set trimming bounds to trim the camera photo. /// + /// # Notes + /// + /// 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 From 42b6f27fa6e1d9f1ec4ddc1b258ee340b04690ec Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 19 Nov 2023 18:55:43 +0100 Subject: [PATCH 25/29] Fix tests --- ctru-rs/src/services/cam.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs index 33a0753..dc76cf2 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -647,7 +647,7 @@ pub trait Camera: private::ConfigurableCamera { /// let mut inward = &mut cam.inner_cam; /// /// // We trim the image down so that it fits on a DS screen! - /// inward.set_trimming(Trimming::Centered(ViewSize::DS)); + /// 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(); From 73aac708f14325ac6f267f4bcefb9fe2b25de9d4 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sun, 19 Nov 2023 19:11:42 +0100 Subject: [PATCH 26/29] Fix gfx nits --- ctru-rs/src/services/gfx.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 8c8764a..6eb04f6 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -321,21 +321,7 @@ impl Gfx { top_fb_fmt: FramebufferFormat, bottom_fb_fmt: FramebufferFormat, ) -> Result { - let handler = ServiceReference::new( - &GFX_ACTIVE, - || unsafe { - ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), false); - - Ok(()) - }, - || unsafe { ctru_sys::gfxExit() }, - )?; - - Ok(Self { - top_screen: RefCell::new(TopScreen::new()), - bottom_screen: RefCell::new(BottomScreen), - _service_handler: handler, - }) + 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. @@ -371,11 +357,20 @@ impl Gfx { 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, + vram_buffer: bool, ) -> Result { let handler = ServiceReference::new( &GFX_ACTIVE, || unsafe { - ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), true); + ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), vram_buffer); Ok(()) }, From ccc9dbf57164310342b5d5cb3250ce3af82a3bb0 Mon Sep 17 00:00:00 2001 From: Meziu <55318903+Meziu@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:36:03 +0100 Subject: [PATCH 27/29] Update ctru-rs/examples/movement.rs Co-authored-by: Ian Chamberlain --- ctru-rs/examples/movement.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/examples/movement.rs b/ctru-rs/examples/movement.rs index 1cbefc1..fe5e764 100644 --- a/ctru-rs/examples/movement.rs +++ b/ctru-rs/examples/movement.rs @@ -36,7 +36,7 @@ fn main() { // Be careful: reading without activating the sensors (as done before this loop) will result in a panic. println!( "\x1b[3;0HAcceleration: {:?} ", - Into::<(i16, i16, i16)>::into( + <(i16, i16, i16)>::from( hid.accelerometer_vector() .expect("could not retrieve acceleration vector") ) From 39bd6c27178214eb4369ebf4656e7ab5ddffbbc2 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Wed, 22 Nov 2023 20:29:30 +0100 Subject: [PATCH 28/29] Remove panic handler --- ctru-rs/examples/audio-filters.rs | 2 -- ctru-rs/examples/buttons.rs | 2 -- ctru-rs/examples/camera-image.rs | 2 -- ctru-rs/examples/file-explorer.rs | 2 -- ctru-rs/examples/futures-basic.rs | 2 -- ctru-rs/examples/futures-tokio.rs | 2 -- ctru-rs/examples/gfx-3d-mode.rs | 2 -- ctru-rs/examples/gfx-bitmap.rs | 2 -- ctru-rs/examples/gfx-wide-mode.rs | 2 -- ctru-rs/examples/hashmaps.rs | 2 -- ctru-rs/examples/hello-both-screens.rs | 2 -- ctru-rs/examples/hello-world.rs | 1 - ctru-rs/examples/linear-memory.rs | 2 -- ctru-rs/examples/mii-selector.rs | 2 -- ctru-rs/examples/movement.rs | 2 -- ctru-rs/examples/network-sockets.rs | 2 -- ctru-rs/examples/output-3dslink.rs | 2 -- ctru-rs/examples/romfs.rs | 2 -- ctru-rs/examples/software-keyboard.rs | 2 -- ctru-rs/examples/system-configuration.rs | 2 -- ctru-rs/examples/thread-basic.rs | 2 -- ctru-rs/examples/thread-info.rs | 2 -- ctru-rs/examples/thread-locals.rs | 2 -- ctru-rs/examples/time-rtc.rs | 2 -- ctru-rs/examples/title-info.rs | 2 -- ctru-rs/examples/touch-screen.rs | 2 -- ctru-rs/src/console.rs | 3 -- ctru-rs/src/lib.rs | 39 ------------------------ 28 files changed, 93 deletions(-) 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 90862bf..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(); diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 0c73f92..4084f21 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -14,8 +14,6 @@ use std::time::Duration; 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."); 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..cab15f3 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -8,7 +8,6 @@ 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. 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 2987970..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"); diff --git a/ctru-rs/examples/movement.rs b/ctru-rs/examples/movement.rs index fe5e764..af83488 100644 --- a/ctru-rs/examples/movement.rs +++ b/ctru-rs/examples/movement.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/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 4332750..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"); 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/console.rs b/ctru-rs/src/console.rs index 5c9fba1..5a8c772 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -117,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 /// /// ``` diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index f1569bb..58537ee 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -56,45 +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 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!("\nThe software will exit in 5 seconds"); - - std::thread::sleep(std::time::Duration::from_secs(5)); - } - }); - std::panic::set_hook(new_hook); -} - pub mod applets; pub mod console; pub mod error; From 1f72bfd9d8b97291fd1d4489934895803eaf1930 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Wed, 22 Nov 2023 20:32:15 +0100 Subject: [PATCH 29/29] remove reference to panic hook --- ctru-rs/examples/hello-world.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/ctru-rs/examples/hello-world.rs b/ctru-rs/examples/hello-world.rs index cab15f3..348f96d 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -6,9 +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. - // Setup Graphics, Controller Inputs, Application runtime. // These is standard setup any app would need. let gfx = Gfx::new().expect("Couldn't obtain GFX controller");