Ian Chamberlain
3 years ago
10 changed files with 322 additions and 24 deletions
@ -0,0 +1,32 @@ |
|||||||
|
use std::path::PathBuf; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
// Open Cargo.toml
|
||||||
|
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); |
||||||
|
let manifest_path = format!("{manifest_dir}/Cargo.toml"); |
||||||
|
let manifest_str = std::fs::read_to_string(&manifest_path) |
||||||
|
.unwrap_or_else(|e| panic!("Could not open {manifest_path}: {e}")); |
||||||
|
let manifest_data: toml::Value = |
||||||
|
toml::de::from_str(&manifest_str).expect("Could not parse Cargo manifest as TOML"); |
||||||
|
|
||||||
|
// Find the romfs setting and compute the path
|
||||||
|
let romfs_dir_setting = manifest_data |
||||||
|
.as_table() |
||||||
|
.and_then(|table| table.get("package")) |
||||||
|
.and_then(toml::Value::as_table) |
||||||
|
.and_then(|table| table.get("metadata")) |
||||||
|
.and_then(toml::Value::as_table) |
||||||
|
.and_then(|table| table.get("cargo-3ds")) |
||||||
|
.and_then(toml::Value::as_table) |
||||||
|
.and_then(|table| table.get("romfs_dir")) |
||||||
|
.and_then(toml::Value::as_str) |
||||||
|
.unwrap_or("romfs"); |
||||||
|
let romfs_path = PathBuf::from(format!("{manifest_dir}/{romfs_dir_setting}")); |
||||||
|
|
||||||
|
// Check if the romfs path exists so we can compile the module
|
||||||
|
if romfs_path.exists() { |
||||||
|
println!("cargo:rustc-cfg=romfs_exists"); |
||||||
|
} |
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed={}", manifest_dir); |
||||||
|
} |
@ -0,0 +1,178 @@ |
|||||||
|
//! A file explorer which shows off using standard library file system APIs to
|
||||||
|
//! read the SD card.
|
||||||
|
|
||||||
|
use ctru::applets::swkbd::{Button, Swkbd}; |
||||||
|
use ctru::console::Console; |
||||||
|
use ctru::services::hid::KeyPad; |
||||||
|
use ctru::services::{Apt, Hid}; |
||||||
|
use ctru::Gfx; |
||||||
|
use std::fs::DirEntry; |
||||||
|
use std::path::{Path, PathBuf}; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
ctru::init(); |
||||||
|
let apt = Apt::init().unwrap(); |
||||||
|
let hid = Hid::init().unwrap(); |
||||||
|
let gfx = Gfx::default(); |
||||||
|
|
||||||
|
#[cfg(all(feature = "romfs", romfs_exists))] |
||||||
|
let _romfs = ctru::romfs::RomFS::new().unwrap(); |
||||||
|
|
||||||
|
FileExplorer::init(&apt, &hid, &gfx).run(); |
||||||
|
} |
||||||
|
|
||||||
|
struct FileExplorer<'a> { |
||||||
|
apt: &'a Apt, |
||||||
|
hid: &'a Hid, |
||||||
|
gfx: &'a Gfx, |
||||||
|
console: Console<'a>, |
||||||
|
path: PathBuf, |
||||||
|
entries: Vec<DirEntry>, |
||||||
|
running: bool, |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> FileExplorer<'a> { |
||||||
|
fn init(apt: &'a Apt, hid: &'a Hid, gfx: &'a Gfx) -> Self { |
||||||
|
gfx.top_screen.borrow_mut().set_wide_mode(true); |
||||||
|
let console = Console::init(gfx.top_screen.borrow_mut()); |
||||||
|
|
||||||
|
FileExplorer { |
||||||
|
apt, |
||||||
|
hid, |
||||||
|
gfx, |
||||||
|
console, |
||||||
|
path: PathBuf::from("/"), |
||||||
|
entries: Vec::new(), |
||||||
|
running: false, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn run(&mut self) { |
||||||
|
self.running = true; |
||||||
|
self.print_menu(); |
||||||
|
|
||||||
|
while self.running && self.apt.main_loop() { |
||||||
|
self.hid.scan_input(); |
||||||
|
let input = self.hid.keys_down(); |
||||||
|
|
||||||
|
if input.contains(KeyPad::KEY_START) { |
||||||
|
break; |
||||||
|
} else if input.contains(KeyPad::KEY_B) { |
||||||
|
self.path.pop(); |
||||||
|
self.console.clear(); |
||||||
|
self.print_menu(); |
||||||
|
} else if input.contains(KeyPad::KEY_A) { |
||||||
|
self.get_input_and_run(Self::set_next_path); |
||||||
|
} else if input.contains(KeyPad::KEY_X) { |
||||||
|
self.get_input_and_run(Self::set_exact_path); |
||||||
|
} |
||||||
|
|
||||||
|
self.gfx.flush_buffers(); |
||||||
|
self.gfx.swap_buffers(); |
||||||
|
self.gfx.wait_for_vblank(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn print_menu(&mut self) { |
||||||
|
println!("Viewing {}", self.path.display()); |
||||||
|
|
||||||
|
let dir_listing = std::fs::read_dir(&self.path).expect("Failed to open path"); |
||||||
|
self.entries = Vec::new(); |
||||||
|
|
||||||
|
for (i, entry) in dir_listing.enumerate() { |
||||||
|
match entry { |
||||||
|
Ok(entry) => { |
||||||
|
println!("{:2} - {}", i, entry.file_name().to_string_lossy()); |
||||||
|
self.entries.push(entry); |
||||||
|
|
||||||
|
// Paginate the output
|
||||||
|
if (i + 1) % 20 == 0 { |
||||||
|
println!("Press A to go to next page, or Start to exit"); |
||||||
|
|
||||||
|
while self.apt.main_loop() { |
||||||
|
self.hid.scan_input(); |
||||||
|
let input = self.hid.keys_down(); |
||||||
|
|
||||||
|
if input.contains(KeyPad::KEY_A) { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if input.contains(KeyPad::KEY_START) { |
||||||
|
self.running = false; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
self.gfx.wait_for_vblank(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Err(e) => { |
||||||
|
println!("{} - Error: {}", i, e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
println!("Start to exit, A to select an entry by number, B to go up a directory, X to set the path."); |
||||||
|
} |
||||||
|
|
||||||
|
fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) { |
||||||
|
let mut keyboard = Swkbd::default(); |
||||||
|
let mut new_path_str = String::new(); |
||||||
|
|
||||||
|
match keyboard.get_utf8(&mut new_path_str) { |
||||||
|
Ok(Button::Right) => { |
||||||
|
// Clicked "OK"
|
||||||
|
action(self, new_path_str); |
||||||
|
} |
||||||
|
Ok(Button::Left) => { |
||||||
|
// Clicked "Cancel"
|
||||||
|
} |
||||||
|
Ok(Button::Middle) => { |
||||||
|
// This button wasn't shown
|
||||||
|
unreachable!() |
||||||
|
} |
||||||
|
Err(e) => { |
||||||
|
panic!("Error: {:?}", e) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn set_next_path(&mut self, next_path_index: String) { |
||||||
|
let next_path_index: usize = match next_path_index.parse() { |
||||||
|
Ok(index) => index, |
||||||
|
Err(e) => { |
||||||
|
println!("Number parsing error: {}", e); |
||||||
|
return; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let next_entry = match self.entries.get(next_path_index) { |
||||||
|
Some(entry) => entry, |
||||||
|
None => { |
||||||
|
println!("Input number of bounds"); |
||||||
|
return; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
if !next_entry.file_type().unwrap().is_dir() { |
||||||
|
println!("Not a directory: {}", next_path_index); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
self.console.clear(); |
||||||
|
self.path = next_entry.path(); |
||||||
|
self.print_menu(); |
||||||
|
} |
||||||
|
|
||||||
|
fn set_exact_path(&mut self, new_path_str: String) { |
||||||
|
let new_path = Path::new(&new_path_str); |
||||||
|
if !new_path.is_dir() { |
||||||
|
println!("Not a directory: {}", new_path_str); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
self.console.clear(); |
||||||
|
self.path = new_path.to_path_buf(); |
||||||
|
self.print_menu(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
use ctru::console::Console; |
||||||
|
use ctru::gfx::Gfx; |
||||||
|
use ctru::services::apt::Apt; |
||||||
|
use ctru::services::hid::{Hid, KeyPad}; |
||||||
|
use ctru::thread; |
||||||
|
|
||||||
|
use std::time::Duration; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
// Initialize services
|
||||||
|
ctru::init(); |
||||||
|
let apt = Apt::init().unwrap(); |
||||||
|
let hid = Hid::init().unwrap(); |
||||||
|
let gfx = Gfx::default(); |
||||||
|
let _console = Console::init(gfx.top_screen.borrow_mut()); |
||||||
|
|
||||||
|
let prio = thread::current().priority(); |
||||||
|
println!("Main thread prio: {}\n", prio); |
||||||
|
|
||||||
|
for ix in 0..3 { |
||||||
|
thread::Builder::new() |
||||||
|
.priority(prio - 1) |
||||||
|
.spawn(move || { |
||||||
|
let sleep_duration: u64 = 1000 + ix * 250; |
||||||
|
let mut i = 0; |
||||||
|
loop { |
||||||
|
println!("Thread{ix} says {i}"); |
||||||
|
i += 1; |
||||||
|
thread::sleep(Duration::from_millis(sleep_duration)); |
||||||
|
} |
||||||
|
}) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
println!("Created thread {ix}"); |
||||||
|
} |
||||||
|
|
||||||
|
while apt.main_loop() { |
||||||
|
gfx.flush_buffers(); |
||||||
|
gfx.swap_buffers(); |
||||||
|
gfx.wait_for_vblank(); |
||||||
|
|
||||||
|
hid.scan_input(); |
||||||
|
if hid.keys_down().contains(KeyPad::KEY_START) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
//! This module only gets compiled if the configured RomFS directory is found and the `romfs`
|
||||||
|
//! feature is enabled.
|
||||||
|
//!
|
||||||
|
//! Configure the path in Cargo.toml (the default path is "romfs"). Paths are relative to the
|
||||||
|
//! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of
|
||||||
|
//! your package.
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [package.metadata.cargo-3ds]
|
||||||
|
//! romfs_dir = "romfs"
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use std::ffi::CStr; |
||||||
|
|
||||||
|
#[non_exhaustive] |
||||||
|
pub struct RomFS; |
||||||
|
|
||||||
|
impl RomFS { |
||||||
|
pub fn new() -> crate::Result<Self> { |
||||||
|
let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); |
||||||
|
let result = unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) }; |
||||||
|
|
||||||
|
if result < 0 { |
||||||
|
Err(result.into()) |
||||||
|
} else { |
||||||
|
Ok(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Drop for RomFS { |
||||||
|
fn drop(&mut self) { |
||||||
|
let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); |
||||||
|
unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) }; |
||||||
|
} |
||||||
|
} |
@ -1,20 +0,0 @@ |
|||||||
pub struct Sdmc(()); |
|
||||||
|
|
||||||
impl Sdmc { |
|
||||||
pub fn init() -> crate::Result<Sdmc> { |
|
||||||
unsafe { |
|
||||||
let r = ctru_sys::archiveMountSdmc(); |
|
||||||
if r < 0 { |
|
||||||
Err(r.into()) |
|
||||||
} else { |
|
||||||
Ok(Sdmc(())) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl Drop for Sdmc { |
|
||||||
fn drop(&mut self) { |
|
||||||
unsafe { ctru_sys::archiveUnmountAll() }; |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue