diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index f33e177..29323b4 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -18,3 +18,6 @@ pthread-3ds = { git = "https://github.com/Meziu/pthread-3ds.git" } libc = { git = "https://github.com/Meziu/libc.git" } bitflags = "1.0.0" widestring = "0.2.2" + +[dev-dependencies] +ferris-says = "0.2.1" diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index 36127cd..152ede6 100644 --- a/ctru-rs/examples/buttons.rs +++ b/ctru-rs/examples/buttons.rs @@ -1,5 +1,5 @@ use ctru::console::Console; -use ctru::gfx::{Gfx, Screen}; +use ctru::gfx::Gfx; use ctru::services::apt::Apt; use ctru::services::hid::{Hid, KeyPad}; @@ -9,7 +9,7 @@ fn main() { let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); let gfx = Gfx::default(); - let console = Console::init(&gfx, Screen::Top); + let console = Console::init(gfx.top_screen.borrow_mut()); println!("Hi there! Try pressing a button"); println!("\x1b[29;16HPress Start to exit"); diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 690c6a8..988283b 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -3,7 +3,6 @@ use ctru::applets::swkbd::{Button, Swkbd}; use ctru::console::Console; -use ctru::gfx::Screen; use ctru::services::hid::KeyPad; use ctru::services::{Apt, Hid}; use ctru::Gfx; @@ -31,8 +30,8 @@ struct FileExplorer<'a> { impl<'a> FileExplorer<'a> { fn init(apt: &'a Apt, hid: &'a Hid, gfx: &'a Gfx) -> Self { - gfx.set_wide_mode(true); - let console = Console::init(gfx, Screen::Top); + gfx.top_screen.borrow_mut().set_wide_mode(true); + let console = Console::init(gfx.top_screen.borrow_mut()); FileExplorer { apt, diff --git a/ctru-rs/examples/gfx_wide_mode.rs b/ctru-rs/examples/gfx-wide-mode.rs similarity index 62% rename from ctru-rs/examples/gfx_wide_mode.rs rename to ctru-rs/examples/gfx-wide-mode.rs index f4ba365..fcf3fa9 100644 --- a/ctru-rs/examples/gfx_wide_mode.rs +++ b/ctru-rs/examples/gfx-wide-mode.rs @@ -1,7 +1,4 @@ -extern crate ctru; - use ctru::console::Console; -use ctru::gfx::Screen; use ctru::services::hid::KeyPad; use ctru::services::{Apt, Hid}; use ctru::Gfx; @@ -11,7 +8,7 @@ fn main() { let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); let gfx = Gfx::default(); - let _console = Console::init(&gfx, Screen::Top); + let mut console = Console::init(gfx.top_screen.borrow_mut()); println!("Press A to enable/disable wide screen mode."); @@ -23,7 +20,13 @@ fn main() { } if hid.keys_down().contains(KeyPad::KEY_A) { - gfx.set_wide_mode(!gfx.get_wide_mode()); + drop(console); + + let wide_mode = gfx.top_screen.borrow().get_wide_mode(); + gfx.top_screen.borrow_mut().set_wide_mode(!wide_mode); + + console = Console::init(gfx.top_screen.borrow_mut()); + println!("Press A to enable/disable wide screen mode."); } gfx.flush_buffers(); diff --git a/ctru-rs/examples/hello-both-screens.rs b/ctru-rs/examples/hello-both-screens.rs index 180b1b2..67113a3 100644 --- a/ctru-rs/examples/hello-both-screens.rs +++ b/ctru-rs/examples/hello-both-screens.rs @@ -1,5 +1,5 @@ use ctru::console::Console; -use ctru::gfx::{Gfx, Screen}; +use ctru::gfx::Gfx; use ctru::services::apt::Apt; use ctru::services::hid::{Hid, KeyPad}; @@ -11,11 +11,11 @@ fn main() { let gfx = Gfx::default(); // Start a console on the top screen - let top_screen = Console::init(&gfx, Screen::Top); + let top_screen = Console::init(gfx.top_screen.borrow_mut()); // Start a console on the bottom screen. // The most recently initialized console will be active by default - let bottom_screen = Console::init(&gfx, Screen::Bottom); + let bottom_screen = Console::init(gfx.bottom_screen.borrow_mut()); // Let's print on the top screen first top_screen.select(); diff --git a/ctru-rs/examples/hello-world.rs b/ctru-rs/examples/hello-world.rs index ccebef7..930549f 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -1,56 +1,41 @@ use ctru::console::Console; -use ctru::gfx::{Gfx, Screen}; +use ctru::gfx::Gfx; use ctru::services::apt::Apt; use ctru::services::hid::{Hid, KeyPad}; +use std::io::BufWriter; + fn main() { - // Initialize ctrulib service handles. - // Service handles are internally reference-counted. When all instances of a - // service handle go out of scope, the service will be closed. ctru::init(); - - // The APT service handles application management functions, such as enabling sleep - // mode and jumping to the home menu or to other applications - let apt = Apt::init().unwrap(); - - // The HID service handles button and touch screen inputs. - let hid = Hid::init().unwrap(); - - // The GFX service manages the framebuffers for the top and bottom screens. let gfx = Gfx::default(); + let hid = Hid::init().expect("Couldn't obtain HID controller"); + let apt = Apt::init().expect("Couldn't obtain APT controller"); + let _console = Console::init(gfx.top_screen.borrow_mut()); - // Initialize a ctrulib console and direct standard output to it. - // Consoles can be initialized on both the top and bottom screens. - let _console = Console::init(&gfx, Screen::Top); + let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!"; + let width = 24; - // Now we can print to stdout! - println!("Hello, world!"); + let mut writer = BufWriter::new(Vec::new()); + ferris_says::say(out, width, &mut writer).unwrap(); - // We can use escape sequences to move the cursor around the terminal. - // The following text will be moved down 29 rows and right 16 characters - // before printing begins. - println!("\x1b[29;16HPress Start to exit"); + println!( + "\x1b[0;0H{}", + String::from_utf8_lossy(&writer.into_inner().unwrap()) + ); - // Main application loop. + // Main loop while apt.main_loop() { - // Flushes and swaps the framebuffers when double-buffering - // is enabled - gfx.flush_buffers(); - gfx.swap_buffers(); - - // Wait for the next frame to begin - gfx.wait_for_vblank(); - - // Scan for user input. + //Scan all the inputs. This should be done once for each frame hid.scan_input(); - // Check if the user has pressed the given button on this frame. - // If so, break out of the loop. if hid.keys_down().contains(KeyPad::KEY_START) { break; } - } + // Flush and swap framebuffers + gfx.flush_buffers(); + gfx.swap_buffers(); - // All of our service handles will drop out of scope at this point, - // triggering the end of our application. + //Wait for VBlank + gfx.wait_for_vblank(); + } } diff --git a/ctru-rs/examples/network-sockets.rs b/ctru-rs/examples/network-sockets.rs new file mode 100644 index 0000000..84978e9 --- /dev/null +++ b/ctru-rs/examples/network-sockets.rs @@ -0,0 +1,65 @@ +use ctru::console::Console; +use ctru::gfx::Gfx; +use ctru::services::apt::Apt; +use ctru::services::hid::{Hid, KeyPad}; +use ctru::services::soc::Soc; + +use std::io::{Read, Write}; +use std::net::{Shutdown, TcpListener}; +use std::time::Duration; + +fn main() { + ctru::init(); + let gfx = Gfx::default(); + let _console = Console::init(gfx.top_screen.borrow_mut()); + let hid = Hid::init().unwrap(); + let apt = Apt::init().unwrap(); + + println!("\nlibctru sockets demo\n"); + + let soc = Soc::init().unwrap(); + + let server = TcpListener::bind("0.0.0.0:80").unwrap(); + server.set_nonblocking(true).unwrap(); + + println!("Point your browser to http://{}/\n", soc.host_address()); + + while apt.main_loop() { + gfx.wait_for_vblank(); + + match server.accept() { + Ok((mut stream, socket_addr)) => { + println!("Got connection from {}", socket_addr); + + let mut buf = [0u8; 4096]; + match stream.read(&mut buf) { + Ok(_) => { + let req_str = String::from_utf8_lossy(&buf); + println!("{}", req_str); + } + Err(e) => println!("Unable to read stream: {}", e), + } + + let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nHello world\r\n"; + + if let Err(e) = stream.write(response) { + println!("Error writing http response: {}", e); + } + + stream.shutdown(Shutdown::Both).unwrap(); + } + Err(e) => match e.kind() { + std::io::ErrorKind::WouldBlock => {} + _ => { + println!("Error accepting connection: {}", e); + std::thread::sleep(Duration::from_secs(2)); + } + }, + } + + hid.scan_input(); + if hid.keys_down().contains(KeyPad::KEY_START) { + break; + }; + } +} diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index b3b9347..6be9e81 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -1,6 +1,6 @@ use ctru::applets::swkbd::{Button, Swkbd}; use ctru::console::Console; -use ctru::gfx::{Gfx, Screen}; +use ctru::gfx::Gfx; use ctru::services::apt::Apt; use ctru::services::hid::{Hid, KeyPad}; @@ -9,7 +9,7 @@ fn main() { let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); let gfx = Gfx::default(); - let _console = Console::init(&gfx, Screen::Top); + let _console = Console::init(gfx.top_screen.borrow_mut()); println!("Press A to enter some text or press Start to quit"); diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 0c86129..2df1495 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -1,28 +1,25 @@ +use std::cell::RefMut; use std::default::Default; -use std::marker::PhantomData; use ctru_sys::{consoleClear, consoleInit, consoleSelect, consoleSetWindow, PrintConsole}; -use crate::gfx::{Gfx, Screen}; +use crate::gfx::Screen; -pub struct Console<'gfx> { +pub struct Console<'screen> { context: Box, - _gfx: PhantomData<&'gfx ()>, + screen: RefMut<'screen, dyn Screen>, } -impl<'gfx> Console<'gfx> { +impl<'screen> Console<'screen> { /// Initialize a console on the chosen screen, overwriting whatever was on the screen /// previously (including other consoles). The new console is automatically selected for /// printing. - pub fn init(_gfx: &'gfx Gfx, screen: Screen) -> Self { + pub fn init(screen: RefMut<'screen, dyn Screen>) -> Self { let mut context = Box::new(PrintConsole::default()); - unsafe { consoleInit(screen.into(), context.as_mut()) }; + unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; - Console { - context, - _gfx: PhantomData, - } + Console { context, screen } } /// Select this console as the current target for stdout diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 8bd4694..1092640 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -1,25 +1,56 @@ //! LCD screens manipulation helper +use std::cell::RefCell; use std::default::Default; use std::ops::Drop; use crate::services::gspgpu::{self, FramebufferFormat}; -/// A handle to libctru's gfx module. This module is a wrapper around the GSPGPU service that -/// provides helper functions and utilities for software rendering. -/// -/// The service exits when this struct is dropped. -pub struct Gfx(()); +/// Trait implemented by TopScreen and BottomScreen for common methods +pub trait Screen { + /// Returns the libctru value for the Screen kind + fn as_raw(&self) -> ctru_sys::gfxScreen_t; -/// Available screens on the 3DS -#[derive(Copy, Clone, Debug)] -pub enum Screen { - /// The top screen - Top, - /// The bottom screen - Bottom, + /// Sets whether to use double buffering. Enabled by default. + /// + /// Note that even when double buffering is disabled, one should still use the `swap_buffers` + /// method on each frame to keep the gsp configuration up to date + fn set_double_buffering(&mut self, enabled: bool) { + unsafe { ctru_sys::gfxSetDoubleBuffering(self.as_raw(), enabled) } + } + + /// Gets the framebuffer format + fn get_framebuffer_format(&self) -> FramebufferFormat { + unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()).into() } + } + + /// Change the framebuffer format + fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) { + unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) } + } + + /// Returns a tuple containing a pointer to the specifified framebuffer (as determined by the + /// calling screen and `Side`), the width of the framebuffer in pixels, and the height of + /// the framebuffer in pixels + /// + /// Note that the pointer returned by this function can change after each call to this function + /// if double buffering is enabled + fn get_raw_framebuffer(&self, side: Side) -> (*mut u8, u16, u16) { + let mut width: u16 = 0; + let mut height: u16 = 0; + unsafe { + let buf: *mut u8 = + ctru_sys::gfxGetFramebuffer(self.as_raw(), side.into(), &mut width, &mut height); + (buf, width, height) + } + } } +#[non_exhaustive] +pub struct TopScreen; +#[non_exhaustive] +pub struct BottomScreen; + #[derive(Copy, Clone, Debug)] /// Side of top screen framebuffer /// @@ -31,6 +62,16 @@ pub enum Side { Right, } +/// A handle to libctru's gfx module. This module is a wrapper around the GSPGPU service that +/// provides helper functions and utilities for software rendering. +/// +/// The service exits when this struct is dropped. +#[non_exhaustive] +pub struct Gfx { + pub top_screen: RefCell, + pub bottom_screen: RefCell, +} + impl Gfx { /// Initialize the Gfx module with the chosen framebuffer formats for the top and bottom /// screens @@ -44,32 +85,10 @@ impl Gfx { unsafe { ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); } - Gfx(()) - } - - /// Enable or disable the 3D stereoscopic effect - pub fn set_3d_enabled(&self, enabled: bool) { - unsafe { ctru_sys::gfxSet3D(enabled) } - } - - /// Enable or disable the wide screen mode (top screen). - /// - /// This only works when 3D is disabled. - pub fn set_wide_mode(&self, enabled: bool) { - unsafe { ctru_sys::gfxSetWide(enabled) }; - } - - /// Get the status of wide screen mode. - pub fn get_wide_mode(&self) -> bool { - unsafe { ctru_sys::gfxIsWide() } - } - - /// Sets whether to use double buffering. Enabled by default. - /// - /// Note that even when double buffering is disabled, one should still use the `swap_buffers` - /// method on each frame to keep the gsp configuration up to date - pub fn set_double_buffering(&self, screen: Screen, enabled: bool) { - unsafe { ctru_sys::gfxSetDoubleBuffering(screen.into(), enabled) } + Gfx { + top_screen: RefCell::new(TopScreen), + bottom_screen: RefCell::new(BottomScreen), + } } /// Flushes the current framebuffers @@ -97,41 +116,39 @@ impl Gfx { pub fn wait_for_vblank(&self) { gspgpu::wait_for_event(gspgpu::Event::VBlank0, true); } +} - /// Gets the framebuffer format for a screen - pub fn get_framebuffer_format(&self, screen: Screen) -> FramebufferFormat { - unsafe { ctru_sys::gfxGetScreenFormat(screen.into()).into() } - } - - /// Change the framebuffer format for a screen - pub fn set_framebuffer_format(&self, screen: Screen, fmt: FramebufferFormat) { - unsafe { ctru_sys::gfxSetScreenFormat(screen.into(), fmt.into()) } +impl TopScreen { + /// Enable or disable the 3D stereoscopic effect + pub fn set_3d_enabled(&mut self, enabled: bool) { + unsafe { + ctru_sys::gfxSet3D(enabled); + } } - /// Returns a tuple containing a pointer to the specifified framebuffer (as determined by the - /// provided `Screen` and `Side`), the width of the framebuffer in pixels, and the height of - /// the framebuffer in pixels - /// - /// Note that the pointer returned by this function can change after each call to this function - /// if double buffering is enabled - pub fn get_raw_framebuffer(&self, screen: Screen, side: Side) -> (*mut u8, u16, u16) { + /// Enable or disable the wide screen mode (top screen). + /// This only works when 3D is disabled. + pub fn set_wide_mode(&mut self, enabled: bool) { unsafe { - let mut width: u16 = 0; - let mut height: u16 = 0; - let buf: *mut u8 = - ctru_sys::gfxGetFramebuffer(screen.into(), side.into(), &mut width, &mut height); - (buf, width, height) + ctru_sys::gfxSetWide(enabled); } } + + /// Get the status of wide screen mode. + pub fn get_wide_mode(&self) -> bool { + unsafe { ctru_sys::gfxIsWide() } + } } -impl From for ctru_sys::gfxScreen_t { - fn from(g: Screen) -> ctru_sys::gfxScreen_t { - use self::Screen::*; - match g { - Top => ctru_sys::GFX_TOP, - Bottom => ctru_sys::GFX_BOTTOM, - } +impl Screen for TopScreen { + fn as_raw(&self) -> ctru_sys::gfxScreen_t { + ctru_sys::GFX_TOP + } +} + +impl Screen for BottomScreen { + fn as_raw(&self) -> ctru_sys::gfxScreen_t { + ctru_sys::GFX_BOTTOM } } @@ -148,7 +165,10 @@ impl From for ctru_sys::gfx3dSide_t { impl Default for Gfx { fn default() -> Self { unsafe { ctru_sys::gfxInitDefault() }; - Gfx(()) + Gfx { + top_screen: RefCell::new(TopScreen), + bottom_screen: RefCell::new(BottomScreen), + } } }