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}