Browse Source

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

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

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

@ -40,8 +40,6 @@ fn fill_buffer(audio_data: &mut [u8], frequency: f32) { @@ -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");

10
ctru-rs/examples/buttons.rs

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

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

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

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

@ -11,8 +11,6 @@ use std::os::horizon::fs::MetadataExt; @@ -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();

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

@ -13,8 +13,6 @@ use futures::StreamExt; @@ -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");

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

@ -6,8 +6,6 @@ use std::os::horizon::thread::BuilderExt; @@ -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");

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

@ -17,8 +17,6 @@ const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); @@ -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");

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

@ -18,8 +18,6 @@ use ctru::services::gfx::{Flush, Screen, Swap}; @@ -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");

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

@ -8,8 +8,6 @@ @@ -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();

2
ctru-rs/examples/hashmaps.rs

@ -9,8 +9,6 @@ @@ -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.

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

@ -5,8 +5,6 @@ @@ -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();

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

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

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

@ -10,8 +10,6 @@ use ctru::linear::LinearAllocator; @@ -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");

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

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

52
ctru-rs/examples/movement.rs

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

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

@ -9,8 +9,6 @@ use std::net::{Shutdown, TcpListener}; @@ -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();

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

@ -13,8 +13,6 @@ @@ -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");

2
ctru-rs/examples/romfs.rs

@ -5,8 +5,6 @@ @@ -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");

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

@ -6,8 +6,6 @@ use ctru::applets::swkbd::{Button, SoftwareKeyboard}; @@ -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();

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

@ -7,8 +7,6 @@ use ctru::prelude::*; @@ -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");

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

@ -6,8 +6,6 @@ use std::os::horizon::thread::BuilderExt; @@ -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();

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

@ -7,8 +7,6 @@ use ctru::prelude::*; @@ -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");

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

@ -10,8 +10,6 @@ std::thread_local! { @@ -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");

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

@ -6,8 +6,6 @@ @@ -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");

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

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

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

@ -5,8 +5,6 @@ @@ -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");

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

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
//! Mii Selector applet.
//!
//! This applet opens a window which lets the player/user choose a Mii from the ones present on their console.
//! The selected Mii is readable as a [`Mii`](crate::mii::Mii).
//! The selected Mii is readable as a [`Mii`].
use crate::mii::Mii;
use bitflags::bitflags;
@ -9,7 +9,7 @@ use std::{ffi::CString, fmt}; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -228,12 +228,12 @@ impl MiiSelector {
/// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new();
///
/// // Blacklist all user-created Miis so that they cannot be selected.
/// mii_selector.blacklist_user_mii(Index::All);
/// // Blocklist all user-created Miis so that they cannot be selected.
/// mii_selector.blocklist_user_mii(Index::All);
/// # }
/// ```
#[doc(alias = "miiSelectorBlacklistUserMii")]
pub fn blacklist_user_mii(&mut self, mii_index: Index) {
pub fn blocklist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index {
Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
@ -247,9 +247,7 @@ impl MiiSelector { @@ -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.

199
ctru-rs/src/console.rs

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

49
ctru-rs/src/lib.rs

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

2
ctru-rs/src/linear.rs

@ -16,7 +16,7 @@ use std::ptr::NonNull; @@ -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)]

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

File diff suppressed because it is too large Load Diff

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

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

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

@ -5,19 +5,19 @@ @@ -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,
}

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

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

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

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

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

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

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

@ -1,35 +1,37 @@ @@ -1,35 +1,37 @@
use crate::Error;
use std::sync::Mutex;
use std::sync::{Mutex, MutexGuard, TryLockError};
pub(crate) struct ServiceReference {
counter: &'static Mutex<usize>,
_guard: MutexGuard<'static, ()>,
close: Box<dyn Fn() + Send + Sync>,
}
impl ServiceReference {
pub fn new<S, E>(
counter: &'static Mutex<usize>,
allow_multiple: bool,
start: S,
close: E,
) -> crate::Result<Self>
pub fn new<S, E>(counter: &'static Mutex<()>, start: S, close: E) -> crate::Result<Self>
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 { @@ -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)();
}
}

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

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

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

@ -19,7 +19,7 @@ pub struct Soc { @@ -19,7 +19,7 @@ pub struct Soc {
sock_3dslink: libc::c_int,
}
static SOC_ACTIVE: Mutex<usize> = Mutex::new(0);
static SOC_ACTIVE: Mutex<()> = Mutex::new(());
impl Soc {
/// Initialize a new service handle using a socket buffer size of `0x100000` bytes.
@ -73,7 +73,6 @@ impl Soc { @@ -73,7 +73,6 @@ impl Soc {
pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result<Self> {
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) })?;

Loading…
Cancel
Save