Skip to main content

fire_cli_rs/
terminal.rs

1use std::{
2    io::{self, Write},
3    sync::atomic::{AtomicBool, Ordering},
4};
5
6#[cfg(unix)]
7use std::os::unix::io::AsRawFd;
8
9pub struct Terminal {
10    #[cfg(unix)]
11    orig: libc::termios,
12    #[cfg(windows)]
13    orig_mode: u32,
14}
15
16pub static EXIT_REQUESTED: AtomicBool = AtomicBool::new(false);
17
18#[cfg(unix)]
19pub static RESIZE_REQUESTED: AtomicBool = AtomicBool::new(true);
20
21impl Terminal {
22    pub fn new() -> io::Result<Self> {
23        #[cfg(unix)]
24        {
25            let fd = io::stdin().as_raw_fd();
26            let mut orig = unsafe { std::mem::zeroed() };
27            if unsafe { libc::tcgetattr(fd, &mut orig) } != 0 {
28                return Err(io::Error::last_os_error());
29            }
30            let mut raw = orig;
31            unsafe { libc::cfmakeraw(&mut raw) };
32            raw.c_lflag &= !(libc::ECHO | libc::ICANON);
33            if unsafe { libc::tcsetattr(fd, libc::TCSANOW, &raw) } != 0 {
34                return Err(io::Error::last_os_error());
35            }
36            unsafe {
37                libc::signal(libc::SIGINT, handle_sigint as *const () as libc::sighandler_t);
38                libc::signal(libc::SIGTERM, handle_sigint as *const () as libc::sighandler_t);
39                libc::signal(libc::SIGWINCH, handle_sigwinch as *const () as libc::sighandler_t);
40            }
41            Ok(Self { orig })
42        }
43        #[cfg(windows)]
44        {
45            use std::os::windows::io::AsRawHandle;
46            let handle = io::stdin().as_raw_handle();
47            unsafe {
48                let mut mode: u32 = 0;
49                if winapi::um::consoleapi::GetConsoleMode(handle as _, &mut mode) == 0 {
50                    return Err(io::Error::last_os_error());
51                }
52                let new_mode = mode & !(0x0002 | 0x0004 | 0x0010 | 0x0020);
53                if winapi::um::consoleapi::SetConsoleMode(handle as _, new_mode) == 0 {
54                    return Err(io::Error::last_os_error());
55                }
56                unsafe extern "system" fn ctrl_handler(_: u32) -> i32 {
57                    EXIT_REQUESTED.store(true, Ordering::Relaxed);
58                    1
59                }
60                winapi::um::consoleapi::SetConsoleCtrlHandler(Some(ctrl_handler), 1);
61                Ok(Self { orig_mode: mode })
62            }
63        }
64        #[cfg(not(any(unix, windows)))]
65        {
66            Ok(Self {})
67        }
68    }
69}
70
71impl Drop for Terminal {
72    fn drop(&mut self) {
73        restore_terminal();
74
75        #[cfg(unix)]
76        {
77            let fd = io::stdin().as_raw_fd();
78            unsafe { libc::tcsetattr(fd, libc::TCSANOW, &self.orig) };
79        }
80        #[cfg(windows)]
81        {
82            use std::os::windows::io::AsRawHandle;
83            let handle = io::stdin().as_raw_handle();
84            unsafe { winapi::um::consoleapi::SetConsoleMode(handle as _, self.orig_mode) };
85        }
86    }
87}
88
89pub fn get_size() -> (usize, usize) {
90    #[cfg(unix)]
91    {
92        let mut ws: libc::winsize = unsafe { std::mem::zeroed() };
93        if unsafe { libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) } == 0 {
94            return (ws.ws_col as usize, ws.ws_row as usize);
95        }
96    }
97    #[cfg(windows)]
98    {
99        use std::os::windows::io::AsRawHandle;
100        let handle = io::stdout().as_raw_handle();
101        let mut info: winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO = unsafe { std::mem::zeroed() };
102        if unsafe { winapi::um::wincon::GetConsoleScreenBufferInfo(handle as _, &mut info) } != 0 {
103            let w = (info.srWindow.Right - info.srWindow.Left + 1) as usize;
104            let h = (info.srWindow.Bottom - info.srWindow.Top + 1) as usize;
105            return (w, h);
106        }
107    }
108    (80, 24)
109}
110
111pub fn restore_terminal() {
112    let mut stdout = io::stdout();
113    let _ = stdout.write_all(b"\x1b[0m\x1b[?25h\x1b[?1049l");
114    let _ = stdout.flush();
115}
116
117#[cfg(unix)]
118extern "C" fn handle_sigint(_: i32) {
119    EXIT_REQUESTED.store(true, Ordering::Relaxed);
120}
121
122#[cfg(unix)]
123extern "C" fn handle_sigwinch(_: i32) {
124    RESIZE_REQUESTED.store(true, Ordering::Relaxed);
125}