ctru/
console.rs

1//! Virtual text console.
2//!
3//! The [`Console`] works as a virtual shell that renders on screen all output of `stdout`. As such, it is useful as a basic interface to show info to the user,
4//! such as in simple "Hello World" applications or more complex software that does not need much user interaction.
5//!
6//! Have a look at [`redirect_stderr`] or [`Soc::redirect_to_3dslink`](crate::services::soc::Soc::redirect_to_3dslink) for better alternatives when debugging applications.
7use std::cell::{RefMut, UnsafeCell};
8
9use ctru_sys::{
10    PrintConsole, consoleClear, consoleDebugInit, consoleInit, consoleSelect, consoleSetWindow,
11};
12
13use crate::services::gfx::{Flush, Screen, Swap};
14
15static mut EMPTY_CONSOLE: PrintConsole = unsafe { std::mem::zeroed::<PrintConsole>() };
16
17/// Error enum for generic errors within [`Console`].
18#[derive(Copy, Clone, Debug, PartialEq, Eq)]
19pub enum Error {
20    /// The coordinate specified on the given axis exceeds the limits imposed by the [`Console`] window.
21    CoordinateOutOfBounds(Axis),
22    /// The size specified for the given dimension exceeds the limits imposed by the [`Console`] window.
23    DimensionOutOfBounds(Dimension),
24}
25
26/// 2D coordinate axes.
27#[allow(missing_docs)]
28#[derive(Copy, Clone, Debug, PartialEq, Eq)]
29pub enum Axis {
30    X,
31    Y,
32}
33
34/// 2D dimensions.
35#[allow(missing_docs)]
36#[derive(Copy, Clone, Debug, PartialEq, Eq)]
37pub enum Dimension {
38    Width,
39    Height,
40}
41
42/// Destination for stderr redirection with [`redirect_stderr`].
43#[doc(alias = "debugDevice")]
44#[repr(u8)]
45pub enum Destination {
46    /// Print stderr to the active [`Console`] window. This is the default behavior.
47    Console = ctru_sys::debugDevice_CONSOLE,
48    /// Print stderr via [`ctru_sys::svcOutputDebugString`]. This allows you to capture error and panic messages with `GDB` or other debuggers.
49    Debugger = ctru_sys::debugDevice_SVC,
50    /// Swallow outputs from stderr.
51    Null = ctru_sys::debugDevice_NULL,
52}
53
54/// A [`Screen`] that can be used as a target for [`Console`].
55pub trait ConsoleScreen: Screen + Swap + Flush {}
56impl<S: Screen + Swap + Flush> ConsoleScreen for S {}
57
58/// Virtual text console.
59///
60/// [`Console`] lets the application redirect `stdout` and `stderr` to a simple text displayer on the 3DS screen.
61/// This means that any text written to `stdout` and `stderr` (e.g. using `println!`, `eprintln!` or `dbg!`) will become visible in the area taken by the console.
62///
63/// # Notes
64///
65/// The [`Console`] will take full possession of the screen handed to it as long as it stays alive. It also supports some ANSI codes, such as text color and cursor positioning.
66/// The [`Console`]'s window size will be:
67/// - 40x30 on the [`BottomScreen`](crate::services::gfx::BottomScreen).
68/// - 50x30 on the normal [`TopScreen`](crate::services::gfx::TopScreen).
69/// - 100x30 on the [`TopScreen`](crate::services::gfx::TopScreen) when wide mode is enabled.
70///
71/// # Alternatives
72///
73/// If you'd like to see live standard output while running the application but cannot or do not want to show the text on the 3DS itself,
74/// you can try using [`Soc::redirect_to_3dslink`](crate::services::soc::Soc::redirect_to_3dslink) while activating the `--server` flag for `3dslink` (also supported by `cargo-3ds`).
75/// More info in the [`cargo-3ds` docs](https://github.com/rust3ds/cargo-3ds#running-executables).
76#[doc(alias = "PrintConsole")]
77pub struct Console<'screen> {
78    context: Box<UnsafeCell<PrintConsole>>,
79    screen: RefMut<'screen, dyn ConsoleScreen>,
80}
81
82/// Send output from stderr to the specified [`Destination`]. This function can be used to capture error and panic messages with `GDB` or other debuggers.
83///
84/// # Notes:
85///
86/// This function is similar in purpose to [`Soc::redirect_to_3dslink`](crate::services::soc::Soc::redirect_to_3dslink), but they each offer slightly different functionality.
87/// `redirect_stderr` enables messages printed via `stderr` to show up in `GDB`, while `redirect_to_3dslink` completely replaces the file descriptor for `stderr` with a network socket instead.
88/// As such, `redirect_to_3dslink` will take priority over this function if both of them are called.
89#[doc(alias = "consoleDebugInit")]
90pub fn redirect_stderr(dest: Destination) {
91    unsafe { consoleDebugInit(dest as _) }
92}
93
94impl<'screen> Console<'screen> {
95    /// Initialize a console on the chosen screen.
96    ///
97    /// # Notes
98    ///
99    /// This operation overwrites whatever was on the screen before the initialization (including other [`Console`]s)
100    /// and changes the [`FramebufferFormat`](crate::services::gspgpu::FramebufferFormat) of the selected screen to better suit the [`Console`].
101    ///
102    /// The new console is automatically selected for printing.
103    ///
104    /// [`Console`] automatically takes care of flushing and swapping buffers for its screen when printing.
105    ///
106    /// # Panics
107    ///
108    /// If the [`Gfx`](crate::services::gfx::Gfx) service was initialised via [`Gfx::with_formats_vram()`](crate::services::gfx::Gfx::with_formats_vram)
109    /// this function will crash the program with an ARM exception.
110    ///
111    /// # Example
112    ///
113    /// ```
114    /// # let _runner = test_runner::GdbRunner::default();
115    /// # use std::error::Error;
116    /// # fn main() -> Result<(), Box<dyn Error>> {
117    /// #
118    /// use ctru::console::Console;
119    /// use ctru::services::gfx::Gfx;
120    ///
121    /// // Initialize graphics (using framebuffers allocated on the HEAP).
122    /// let gfx = Gfx::new()?;
123    ///
124    /// // Create a `Console` that takes control of the upper LCD screen.
125    /// let top_console = Console::new(gfx.top_screen.borrow_mut());
126    ///
127    /// println!("I'm on the top screen!");
128    /// #
129    /// # Ok(())
130    /// # }
131    /// ```
132    #[doc(alias = "consoleInit")]
133    pub fn new<S: ConsoleScreen>(screen: RefMut<'screen, S>) -> Self {
134        let context = Box::<UnsafeCell<PrintConsole>>::default();
135
136        unsafe { consoleInit(screen.as_raw(), context.get()) };
137
138        Console { context, screen }
139    }
140
141    /// Returns `true` if a valid [`Console`] to print on is currently selected.
142    ///
143    /// # Notes
144    ///
145    /// This function is used to check whether one of the two screens has an existing (and selected) [`Console`],
146    /// so that the program can be sure its output will be shown *somewhere*.
147    ///
148    /// # Example
149    ///
150    /// ```
151    /// # let _runner = test_runner::GdbRunner::default();
152    /// # use std::error::Error;
153    /// # fn main() -> Result<(), Box<dyn Error>> {
154    /// #
155    /// # use ctru::services::gfx::Gfx;
156    /// # // Initialize graphics.
157    /// # let gfx = Gfx::new()?;
158    /// #
159    /// use ctru::console::Console;
160    /// let top_console = Console::new(gfx.top_screen.borrow_mut());
161    ///
162    /// // There is at least one selected `Console`.
163    /// assert!(Console::exists());
164    /// #
165    /// # Ok(())
166    /// # }
167    /// ```
168    pub fn exists() -> bool {
169        unsafe {
170            let current_console = ctru_sys::consoleSelect(&raw mut EMPTY_CONSOLE);
171
172            let res = (*current_console).consoleInitialised;
173
174            ctru_sys::consoleSelect(current_console);
175
176            res
177        }
178    }
179
180    /// Select this console as the current target for standard output.
181    ///
182    /// # Notes
183    ///
184    /// Any previously selected console will be unhooked and will not show the `stdout` and `stderr` output.
185    ///
186    /// # Example
187    ///
188    /// ```
189    /// # let _runner = test_runner::GdbRunner::default();
190    /// # use std::error::Error;
191    /// # fn main() -> Result<(), Box<dyn Error>> {
192    /// #
193    /// # use ctru::services::gfx::Gfx;
194    /// # let gfx = Gfx::new()?;
195    /// #
196    /// use ctru::console::Console;
197    ///
198    /// // Create a `Console` that takes control of the upper LCD screen.
199    /// let top_console = Console::new(gfx.top_screen.borrow_mut());
200    ///
201    /// // Create a `Console` that takes control of the lower LCD screen.
202    /// let bottom_console = Console::new(gfx.bottom_screen.borrow_mut());
203    ///
204    /// // Remember that `Console::new` automatically selects the new `Console` for output.
205    /// println!("I'm on the bottom screen!");
206    ///
207    /// top_console.select();
208    ///
209    /// println!("Being on the upper screen is much better!");
210    /// #
211    /// # Ok(())
212    /// # }
213    /// ```
214    #[doc(alias = "consoleSelect")]
215    pub fn select(&self) {
216        unsafe {
217            consoleSelect(self.context.get());
218        }
219    }
220
221    /// Clear all text from the console.
222    #[doc(alias = "consoleClear")]
223    pub fn clear(&self) {
224        unsafe { consoleClear() }
225    }
226
227    /// Resize the console to fit in a smaller portion of the screen.
228    ///
229    /// # Notes
230    ///
231    /// The first two arguments are the desired coordinates of the top-left corner
232    /// of the new window based on the row/column coordinates of a full-screen console.
233    /// The second pair is the new width and height.
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// # use std::error::Error;
239    /// # fn main() -> Result<(), Box<dyn Error>> {
240    /// #
241    /// # use ctru::services::gfx::Gfx;
242    /// # let gfx = Gfx::new()?;
243    /// #
244    /// # use ctru::console::Console;
245    /// #
246    /// let mut top_console = Console::new(gfx.top_screen.borrow_mut());
247    /// top_console.set_window(10, 10, 16, 6);
248    ///
249    /// println!("I'm becoming claustrophobic in here!");
250    /// #
251    /// # Ok(())
252    /// # }
253    /// ```
254    #[doc(alias = "consoleSetWindow")]
255    pub fn set_window(&mut self, x: u8, y: u8, width: u8, height: u8) -> Result<(), Error> {
256        let height_limit = 30;
257        let length_limit = self.max_width();
258
259        if x >= length_limit {
260            return Err(Error::CoordinateOutOfBounds(Axis::X));
261        }
262        if y >= height_limit {
263            return Err(Error::CoordinateOutOfBounds(Axis::Y));
264        }
265
266        if (x + width) > length_limit {
267            return Err(Error::DimensionOutOfBounds(Dimension::Width));
268        }
269        if (y + height) > height_limit {
270            return Err(Error::DimensionOutOfBounds(Dimension::Height));
271        }
272
273        unsafe {
274            consoleSetWindow(
275                self.context.get(),
276                x.into(),
277                y.into(),
278                width.into(),
279                height.into(),
280            )
281        };
282
283        Ok(())
284    }
285
286    /// Reset the window's size to default parameters.
287    ///
288    /// This can be used to undo the changes made by [`set_window()`](Console::set_window()).
289    ///
290    /// # Example
291    ///
292    /// ```
293    /// # use std::error::Error;
294    /// # fn main() -> Result<(), Box<dyn Error>> {
295    /// #
296    /// # use ctru::services::gfx::Gfx;
297    /// # let gfx = Gfx::new()?;
298    /// #
299    /// # use ctru::console::Console;
300    /// #
301    /// let mut top_console = Console::new(gfx.top_screen.borrow_mut());
302    /// top_console.set_window(15, 15, 8, 10);
303    ///
304    /// println!("It's really jammed in here!");
305    ///
306    /// top_console.reset_window();
307    ///
308    /// println!("Phew, finally a breath of fresh air.");
309    /// #
310    /// # Ok(())
311    /// # }
312    /// ```
313    pub fn reset_window(&mut self) {
314        let width = self.max_width();
315
316        self.set_window(0, 0, width, 30).unwrap();
317    }
318
319    /// Returns this [`Console`]'s maximum character width depending on the screen used.
320    ///
321    /// # Example
322    ///
323    /// ```
324    /// # use std::error::Error;
325    /// # fn main() -> Result<(), Box<dyn Error>> {
326    /// #
327    /// # use ctru::services::gfx::Gfx;
328    /// # use ctru::console::Console;
329    /// #
330    /// let gfx = Gfx::new()?;
331    ///
332    /// let top_console = Console::new(gfx.top_screen.borrow_mut());
333    ///
334    /// // The maximum width for the top screen (without any alterations) is 50 characters.
335    /// assert_eq!(top_console.max_width(), 50);
336    /// #
337    /// # Ok(())
338    /// # }
339    /// ```
340    pub fn max_width(&self) -> u8 {
341        match self.screen.as_raw() {
342            ctru_sys::GFX_TOP => {
343                if unsafe { ctru_sys::gfxIsWide() } {
344                    100
345                } else {
346                    50
347                }
348            }
349            ctru_sys::GFX_BOTTOM => 40,
350            _ => unreachable!(),
351        }
352    }
353}
354
355impl Swap for Console<'_> {
356    /// Swaps the video buffers. Note: The console's cursor position is not reset, only the framebuffer is changed.
357    ///
358    /// Even if double buffering is disabled, "swapping" the buffers has the side effect
359    /// of committing any configuration changes to the buffers (e.g. [`TopScreen::set_wide_mode()`],
360    /// [`Screen::set_framebuffer_format()`], [`Swap::set_double_buffering()`]), so it should still be used.
361    ///
362    /// This should be called once per frame at most.
363    fn swap_buffers(&mut self) {
364        self.screen.swap_buffers();
365
366        unsafe {
367            (*self.context.get()).frameBuffer = self.screen.raw_framebuffer().ptr as *mut u16
368        };
369    }
370
371    fn set_double_buffering(&mut self, enabled: bool) {
372        self.screen.set_double_buffering(enabled);
373    }
374}
375
376impl Flush for Console<'_> {
377    fn flush_buffers(&mut self) {
378        self.screen.flush_buffers();
379    }
380}
381
382impl Drop for Console<'_> {
383    fn drop(&mut self) {
384        unsafe {
385            // Safety: We are about to deallocate the PrintConsole data pointed
386            // to by libctru. Without this drop code libctru would have a
387            // dangling pointer that it writes to on every print. To prevent
388            // this we replace the console with an empty one if it was selected.
389            // This is the same state that libctru starts up in, before
390            // initializing a console. Writes to the console will not show up on
391            // the screen, but it won't crash either.
392
393            // Get the current console by replacing it with an empty one.
394            let current_console = ctru_sys::consoleSelect(&raw mut EMPTY_CONSOLE);
395
396            if std::ptr::eq(current_console, self.context.get()) {
397                // Console dropped while selected. We just replaced it with the
398                // empty console so nothing more to do.
399            } else {
400                // Console dropped while a different console was selected. Put back
401                // the console that was selected.
402                ctru_sys::consoleSelect(current_console);
403            }
404        }
405    }
406}
407
408impl std::fmt::Display for Axis {
409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410        match self {
411            Self::X => write!(f, "x"),
412            Self::Y => write!(f, "y"),
413        }
414    }
415}
416
417impl std::fmt::Display for Dimension {
418    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
419        match self {
420            Self::Width => write!(f, "width"),
421            Self::Height => write!(f, "height"),
422        }
423    }
424}
425
426impl std::fmt::Display for Error {
427    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428        match self {
429            Self::CoordinateOutOfBounds(a) => {
430                write!(f, "coordinate specified for the {a} axis is out of bounds")
431            }
432            Self::DimensionOutOfBounds(d) => {
433                write!(f, "size specified for the {d} is out of bounds")
434            }
435        }
436    }
437}
438
439impl std::error::Error for Error {}