devela/sys/os/linux/
terminal.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// devela::sys::os::linux::terminal
//
//! Linux terminal related items.
//

use super::{LinuxTerminalSize, LinuxTermios};

#[cfg(all(feature = "dep_atomic", feature = "dep_bytemuck"))]
use crate::_dep::atomic::{Atomic, Ordering as AtomicOrdering};

/// State of the terminal saved globally, that can be restored from anywhere.
///
/// This allows to restore the initial terminal state from a panic handler. E.g.:
///
/// ```ignore
/// # use devela::LinuxTerminal;
/// #[panic_handler]
/// fn panic(_info: &core::panic::PanicInfo) -> ! {
///     LinuxTerminal::restore_saved_state().unwrap();
/// }
/// ```
///
/// # Features
/// Makes use of [`atomic`] and [`bytemuck`] dependencies to save the
/// terminal state in an [`Atomic`].
///
/// [`atomic`]: crate::_dep::atomic
/// [`bytemuck`]: crate::_dep::bytemuck
#[cfg_attr(
    feature = "nightly_doc",
    doc(cfg(all(feature = "dep_atomic", feature = "dep_bytemuck")))
)]
#[cfg(all(feature = "dep_atomic", feature = "dep_bytemuck"))]
pub static LINUX_TERMINAL_STATE: Atomic<LinuxTermios> = Atomic::new(LinuxTermios::new());

/// Linux terminal manager.
///
/// # Features
/// With `atomic` and `bytemuck` enabled,
/// the terminal state is saved in [`LINUX_TERMINAL_STATE`] and restored on drop.
#[allow(rustdoc::broken_intra_doc_links, reason = "LINUX_TERMINAL_STATE")]
#[derive(Debug, Default)]
pub struct LinuxTerminal;

#[cfg_attr(
    feature = "nightly_doc",
    doc(cfg(all(feature = "dep_atomic", feature = "dep_bytemuck")))
)]
#[cfg(all(feature = "dep_atomic", feature = "dep_bytemuck"))]
impl Drop for LinuxTerminal {
    fn drop(&mut self) {
        // If we are here, this should work
        Self::restore_saved_state().unwrap();
    }
}

#[allow(rustdoc::broken_intra_doc_links, reason = "LINUX_TERMINAL_STATE")]
impl LinuxTerminal {
    /// Returns a new linux terminal configured in canonical (cooked) mode.
    ///
    /// # Features
    /// With `atomic` and `bytemuck` enabled,
    /// it saves the initial terminal state in [`LINUX_TERMINAL_STATE`].
    pub fn new() -> Result<Self, isize> {
        #[cfg(all(feature = "dep_atomic", feature = "dep_bytemuck"))]
        Self::save_state()?;
        Ok(Self)
    }

    /// Returns a new linux terminal configured in raw mode.
    ///
    /// *Raw* mode is a mode where the terminal's input is processed character
    /// by character, rather than line by line.
    ///
    /// # Features
    /// With `atomic` and `bytemuck` enabled,
    /// it saves the initial terminal state in [`LINUX_TERMINAL_STATE`].
    pub fn new_raw() -> Result<Self, isize> {
        #[cfg(all(feature = "dep_atomic", feature = "dep_bytemuck"))]
        Self::save_state()?;

        let new = Self::new()?;
        new.enable_raw_mode()?;
        Ok(new)
    }

    /// Saves the current terminal state into [`LINUX_TERMINAL_STATE`].
    #[cfg_attr(
        feature = "nightly_doc",
        doc(cfg(all(feature = "dep_bytemuck", feature = "dep_atomic")))
    )]
    #[cfg(all(feature = "dep_atomic", feature = "dep_bytemuck"))]
    pub fn save_state() -> Result<(), isize> {
        LINUX_TERMINAL_STATE.store(LinuxTermios::get_state()?, AtomicOrdering::Relaxed);
        Ok(())
    }

    /// Restores the current terminal state into [`LINUX_TERMINAL_STATE`].
    #[cfg_attr(
        feature = "nightly_doc",
        doc(cfg(all(feature = "dep_bytemuck", feature = "dep_atomic")))
    )]
    #[cfg(all(feature = "dep_atomic", feature = "dep_bytemuck"))]
    pub fn restore_saved_state() -> Result<(), isize> {
        LinuxTermios::set_state(LINUX_TERMINAL_STATE.load(AtomicOrdering::Relaxed))
    }

    /// Returns `true` if we are in a terminal context.
    #[must_use]
    pub fn is_terminal(&self) -> bool {
        LinuxTermios::is_terminal()
    }

    /// Returns the terminal dimensions.
    pub fn size(&self) -> Result<LinuxTerminalSize, isize> {
        LinuxTermios::get_winsize()
    }

    /// Enables raw mode.
    ///
    /// Raw mode is a way to configure the terminal so that it does not process or
    /// interpret any of the input but instead passes it directly to the program.
    pub fn enable_raw_mode(&self) -> Result<(), isize> {
        LinuxTermios::enable_raw_mode()
    }

    /// Disables raw mode.
    pub fn disable_raw_mode(&self) -> Result<(), isize> {
        LinuxTermios::disable_raw_mode()
    }
}