devela/sys/os/linux/structs/
termios.rs

1// devela::sys::os::linux::structs::termios
2
3#![cfg_attr(not(feature = "unsafe_syscall"), allow(dead_code))]
4
5use crate::c_uint;
6#[cfg(all(feature = "unsafe_syscall", not(miri)))]
7use crate::{iif, linux_sys_ioctl, LINUX_ERRNO, LINUX_FILENO, LINUX_IOCTL, LINUX_TERMIOS_LFLAG};
8
9/// Represents the [`termios`] structure from libc,
10/// used to control terminal I/O.
11///
12/// It has fields for input, output, control, and local modes,
13/// as well as a line discipline and control characters.
14///
15/// [`termios`]: https://man7.org/linux/man-pages/man3/termios.3.html
16#[derive(Clone, Copy, Debug, Default, PartialEq)]
17#[repr(C)]
18pub struct LinuxTermios {
19    /// Input flags.
20    pub c_iflag: c_uint,
21
22    /// Output flags.
23    pub c_oflag: c_uint,
24
25    /// Control flags.
26    pub c_cflag: c_uint,
27
28    /// Local flags.
29    pub c_lflag: c_uint,
30
31    /// …
32    pub c_line: u8,
33
34    /// …
35    pub c_cc: [u8; 19],
36}
37
38#[cfg_attr(
39    feature = "nightly_doc",
40    doc(cfg(all(feature = "unsafe_syscall", feature = "dep_bytemuck")))
41)]
42#[cfg(all(feature = "unsafe_syscall", feature = "dep_bytemuck"))]
43unsafe impl crate::_dep::bytemuck::NoUninit for LinuxTermios {}
44
45impl LinuxTermios {
46    /// Returns a new empty struct.
47    #[must_use]
48    pub const fn new() -> Self {
49        Self {
50            c_iflag: 0,
51            c_oflag: 0,
52            c_cflag: 0,
53            c_lflag: 0,
54            c_line: 0,
55            c_cc: [0; 19],
56        }
57    }
58
59    /// Returns a raw byte pointer to self.
60    #[must_use]
61    pub const fn as_bytes_ptr(&self) -> *const u8 {
62        self as *const Self as *const u8
63    }
64
65    /// Returns a raw mutable byte pointer to self.
66    #[must_use]
67    pub fn as_mut_bytes_ptr(&mut self) -> *mut u8 {
68        self as *mut Self as *mut u8
69    }
70}
71
72#[cfg(all(
73    any(
74        target_arch = "x86_64",
75        target_arch = "x86",
76        target_arch = "arm",
77        target_arch = "aarch64",
78        target_arch = "riscv32",
79        target_arch = "riscv64"
80    ),
81    feature = "unsafe_syscall",
82    not(miri),
83))]
84impl LinuxTermios {
85    /// Gets the current termios state into `state`.
86    ///
87    /// # Errors
88    /// In case of an error returns the [`LINUX_ERRNO`] value from [`linux_sys_ioctl`].
89    #[cfg(all(feature = "unsafe_syscall", not(miri)))]
90    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "unsafe_syscall")))]
91    pub fn get_state() -> Result<LinuxTermios, isize> {
92        let mut state = LinuxTermios::new();
93        let res = unsafe {
94            linux_sys_ioctl(LINUX_FILENO::STDIN, LINUX_IOCTL::TCGETS, state.as_mut_bytes_ptr())
95        };
96        iif![res >= 0; Ok(state); Err(res)]
97    }
98
99    /// Sets the current termios `state`.
100    ///
101    /// Returns the [`LINUX_ERRNO`] value from [`linux_sys_ioctl`].
102    #[cfg(all(feature = "unsafe_syscall", not(miri)))]
103    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "unsafe_syscall")))]
104    pub fn set_state(mut state: LinuxTermios) -> Result<(), isize> {
105        let res = unsafe {
106            linux_sys_ioctl(LINUX_FILENO::STDIN, LINUX_IOCTL::TCSETS, state.as_mut_bytes_ptr())
107        };
108        iif![res >= 0; Ok(()); Err(res)]
109    }
110
111    /// Returns `true` if we're in a terminal context.
112    #[must_use]
113    pub fn is_terminal() -> bool {
114        match Self::get_state() {
115            Ok(_) => true,
116            Err(e) => e != -LINUX_ERRNO::ENOTTY && e != -LINUX_ERRNO::EINVAL,
117        }
118    }
119
120    /// Disables raw mode.
121    pub fn disable_raw_mode() -> Result<(), isize> {
122        let mut state = LinuxTermios::get_state()?;
123        state.c_lflag |= LINUX_TERMIOS_LFLAG::ICANON;
124        state.c_lflag |= LINUX_TERMIOS_LFLAG::ECHO;
125        LinuxTermios::set_state(state)
126    }
127
128    /// Enables raw mode.
129    pub fn enable_raw_mode() -> Result<(), isize> {
130        let mut state = Self::get_state()?;
131        state.c_lflag &= !LINUX_TERMIOS_LFLAG::ICANON;
132        state.c_lflag &= !LINUX_TERMIOS_LFLAG::ECHO;
133        LinuxTermios::set_state(state)
134    }
135
136    /// Returns the size of the window, in cells and pixels.
137    #[cfg(all(feature = "unsafe_syscall", not(miri)))]
138    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "unsafe_syscall")))]
139    pub fn get_winsize() -> Result<LinuxTerminalSize, isize> {
140        let mut winsize = LinuxTerminalSize::default();
141        let res = unsafe {
142            linux_sys_ioctl(
143                LINUX_FILENO::STDIN,
144                LINUX_IOCTL::TIOCGWINSZ,
145                &mut winsize as *mut LinuxTerminalSize as *mut u8,
146            )
147        };
148        iif![res >= 0; Ok(winsize); Err(res)]
149    }
150}
151
152/// The size of the terminal.
153#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
154#[repr(C)] // field order matters!
155pub struct LinuxTerminalSize {
156    /// Rows of cells.
157    pub rows: u16,
158
159    /// Columns of cells.
160    pub cols: u16,
161
162    /// Horizontal pixels.
163    pub x: u16,
164
165    /// Vertical pixels.
166    pub y: u16,
167}
168
169impl LinuxTerminalSize {
170    /// Returns the number of `[horizontal, vertical]` pixels.
171    #[must_use]
172    pub const fn pixels(self) -> [u16; 2] {
173        [self.x, self.y]
174    }
175    /// Returns the number of `[columns, rows]` of cells.
176    #[must_use]
177    pub const fn cells(self) -> [u16; 2] {
178        [self.cols, self.rows]
179    }
180
181    /// Returns the number of pixels per cell `[horizontal, vertical]`.
182    #[must_use]
183    pub const fn pixels_per_cell(self) -> [u16; 2] {
184        [self.x / self.cols, self.y / self.rows]
185    }
186}