crossterm/terminal/sys/
unix.rs1use crate::terminal::{
4 sys::file_descriptor::{tty_fd, FileDesc},
5 WindowSize,
6};
7use libc::{
8 cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
9 TIOCGWINSZ,
10};
11use parking_lot::Mutex;
12use std::fs::File;
13
14use std::os::unix::io::{IntoRawFd, RawFd};
15
16use std::{io, mem, process};
17
18static TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<Option<Termios>> = parking_lot::const_mutex(None);
21
22pub(crate) fn is_raw_mode_enabled() -> bool {
23 TERMINAL_MODE_PRIOR_RAW_MODE.lock().is_some()
24}
25
26impl From<winsize> for WindowSize {
27 fn from(size: winsize) -> WindowSize {
28 WindowSize {
29 columns: size.ws_col,
30 rows: size.ws_row,
31 width: size.ws_xpixel,
32 height: size.ws_ypixel,
33 }
34 }
35}
36
37#[allow(clippy::useless_conversion)]
38pub(crate) fn window_size() -> io::Result<WindowSize> {
39 let mut size = winsize {
41 ws_row: 0,
42 ws_col: 0,
43 ws_xpixel: 0,
44 ws_ypixel: 0,
45 };
46
47 let file = File::open("/dev/tty").map(|file| (FileDesc::new(file.into_raw_fd(), true)));
48 let fd = if let Ok(file) = &file {
49 file.raw_fd()
50 } else {
51 STDOUT_FILENO
53 };
54
55 if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok() {
56 return Ok(size.into());
57 }
58
59 Err(std::io::Error::last_os_error().into())
60}
61
62#[allow(clippy::useless_conversion)]
63pub(crate) fn size() -> io::Result<(u16, u16)> {
64 if let Ok(window_size) = window_size() {
65 return Ok((window_size.columns, window_size.rows));
66 }
67
68 tput_size().ok_or_else(|| std::io::Error::last_os_error().into())
69}
70
71pub(crate) fn enable_raw_mode() -> io::Result<()> {
72 let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock();
73
74 if original_mode.is_some() {
75 return Ok(());
76 }
77
78 let tty = tty_fd()?;
79 let fd = tty.raw_fd();
80 let mut ios = get_terminal_attr(fd)?;
81 let original_mode_ios = ios;
82
83 raw_terminal_attr(&mut ios);
84 set_terminal_attr(fd, &ios)?;
85
86 *original_mode = Some(original_mode_ios);
88
89 Ok(())
90}
91
92pub(crate) fn disable_raw_mode() -> io::Result<()> {
98 let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock();
99
100 if let Some(original_mode_ios) = original_mode.as_ref() {
101 let tty = tty_fd()?;
102 set_terminal_attr(tty.raw_fd(), original_mode_ios)?;
103 *original_mode = None;
105 }
106
107 Ok(())
108}
109
110#[cfg(feature = "events")]
115pub fn supports_keyboard_enhancement() -> io::Result<bool> {
116 if is_raw_mode_enabled() {
117 read_supports_keyboard_enhancement_raw()
118 } else {
119 read_supports_keyboard_enhancement_flags()
120 }
121}
122
123#[cfg(feature = "events")]
124fn read_supports_keyboard_enhancement_flags() -> io::Result<bool> {
125 enable_raw_mode()?;
126 let flags = read_supports_keyboard_enhancement_raw();
127 disable_raw_mode()?;
128 flags
129}
130
131#[cfg(feature = "events")]
132fn read_supports_keyboard_enhancement_raw() -> io::Result<bool> {
133 use crate::event::{
134 filter::{KeyboardEnhancementFlagsFilter, PrimaryDeviceAttributesFilter},
135 poll_internal, read_internal, InternalEvent,
136 };
137 use std::io::Write;
138 use std::time::Duration;
139
140 const QUERY: &[u8] = b"\x1B[?u\x1B[c";
150
151 let result = File::open("/dev/tty").and_then(|mut file| {
152 file.write_all(QUERY)?;
153 file.flush()
154 });
155 if result.is_err() {
156 let mut stdout = io::stdout();
157 stdout.write_all(QUERY)?;
158 stdout.flush()?;
159 }
160
161 loop {
162 match poll_internal(
163 Some(Duration::from_millis(2000)),
164 &KeyboardEnhancementFlagsFilter,
165 ) {
166 Ok(true) => {
167 match read_internal(&KeyboardEnhancementFlagsFilter) {
168 Ok(InternalEvent::KeyboardEnhancementFlags(_current_flags)) => {
169 read_internal(&PrimaryDeviceAttributesFilter).ok();
171 return Ok(true);
172 }
173 _ => return Ok(false),
174 }
175 }
176 Ok(false) => {
177 return Err(io::Error::new(
178 io::ErrorKind::Other,
179 "The keyboard enhancement status could not be read within a normal duration",
180 ));
181 }
182 Err(_) => {}
183 }
184 }
185}
186
187fn tput_value(arg: &str) -> Option<u16> {
192 let output = process::Command::new("tput").arg(arg).output().ok()?;
193 let value = output
194 .stdout
195 .into_iter()
196 .filter_map(|b| char::from(b).to_digit(10))
197 .fold(0, |v, n| v * 10 + n as u16);
198
199 if value > 0 {
200 Some(value)
201 } else {
202 None
203 }
204}
205
206fn tput_size() -> Option<(u16, u16)> {
211 match (tput_value("cols"), tput_value("lines")) {
212 (Some(w), Some(h)) => Some((w, h)),
213 _ => None,
214 }
215}
216
217fn raw_terminal_attr(termios: &mut Termios) {
219 unsafe { cfmakeraw(termios) }
220}
221
222fn get_terminal_attr(fd: RawFd) -> io::Result<Termios> {
223 unsafe {
224 let mut termios = mem::zeroed();
225 wrap_with_result(tcgetattr(fd, &mut termios))?;
226 Ok(termios)
227 }
228}
229
230fn set_terminal_attr(fd: RawFd, termios: &Termios) -> io::Result<()> {
231 wrap_with_result(unsafe { tcsetattr(fd, TCSANOW, termios) })
232}
233
234fn wrap_with_result(result: i32) -> io::Result<()> {
235 if result == -1 {
236 Err(io::Error::last_os_error())
237 } else {
238 Ok(())
239 }
240}