crossterm/terminal/sys/
unix.rs1#[cfg(feature = "events")]
4use crate::event::KeyboardEnhancementFlags;
5use crate::terminal::{
6 sys::file_descriptor::{tty_fd, FileDesc},
7 WindowSize,
8};
9#[cfg(feature = "libc")]
10use libc::{
11 cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
12 TIOCGWINSZ,
13};
14use parking_lot::Mutex;
15#[cfg(not(feature = "libc"))]
16use rustix::{
17 fd::AsFd,
18 termios::{Termios, Winsize},
19};
20
21use std::{fs::File, io, process};
22#[cfg(feature = "libc")]
23use std::{
24 mem,
25 os::unix::io::{IntoRawFd, RawFd},
26};
27
28static TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<Option<Termios>> = parking_lot::const_mutex(None);
31
32pub(crate) fn is_raw_mode_enabled() -> bool {
33 TERMINAL_MODE_PRIOR_RAW_MODE.lock().is_some()
34}
35
36#[cfg(feature = "libc")]
37impl From<winsize> for WindowSize {
38 fn from(size: winsize) -> WindowSize {
39 WindowSize {
40 columns: size.ws_col,
41 rows: size.ws_row,
42 width: size.ws_xpixel,
43 height: size.ws_ypixel,
44 }
45 }
46}
47#[cfg(not(feature = "libc"))]
48impl From<Winsize> for WindowSize {
49 fn from(size: Winsize) -> WindowSize {
50 WindowSize {
51 columns: size.ws_col,
52 rows: size.ws_row,
53 width: size.ws_xpixel,
54 height: size.ws_ypixel,
55 }
56 }
57}
58
59#[allow(clippy::useless_conversion)]
60#[cfg(feature = "libc")]
61pub(crate) fn window_size() -> io::Result<WindowSize> {
62 let mut size = winsize {
64 ws_row: 0,
65 ws_col: 0,
66 ws_xpixel: 0,
67 ws_ypixel: 0,
68 };
69
70 let file = File::open("/dev/tty").map(|file| (FileDesc::new(file.into_raw_fd(), true)));
71 let fd = if let Ok(file) = &file {
72 file.raw_fd()
73 } else {
74 STDOUT_FILENO
76 };
77
78 if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok() {
79 return Ok(size.into());
80 }
81
82 Err(std::io::Error::last_os_error().into())
83}
84
85#[cfg(not(feature = "libc"))]
86pub(crate) fn window_size() -> io::Result<WindowSize> {
87 let file = File::open("/dev/tty").map(|file| (FileDesc::Owned(file.into())));
88 let fd = if let Ok(file) = &file {
89 file.as_fd()
90 } else {
91 rustix::stdio::stdout()
93 };
94 let size = rustix::termios::tcgetwinsize(fd)?;
95 Ok(size.into())
96}
97
98#[allow(clippy::useless_conversion)]
99pub(crate) fn size() -> io::Result<(u16, u16)> {
100 if let Ok(window_size) = window_size() {
101 return Ok((window_size.columns, window_size.rows));
102 }
103
104 tput_size().ok_or_else(|| std::io::Error::last_os_error().into())
105}
106
107#[cfg(feature = "libc")]
108pub(crate) fn enable_raw_mode() -> io::Result<()> {
109 let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock();
110 if original_mode.is_some() {
111 return Ok(());
112 }
113
114 let tty = tty_fd()?;
115 let fd = tty.raw_fd();
116 let mut ios = get_terminal_attr(fd)?;
117 let original_mode_ios = ios;
118 raw_terminal_attr(&mut ios);
119 set_terminal_attr(fd, &ios)?;
120 *original_mode = Some(original_mode_ios);
122 Ok(())
123}
124
125#[cfg(not(feature = "libc"))]
126pub(crate) fn enable_raw_mode() -> io::Result<()> {
127 let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock();
128 if original_mode.is_some() {
129 return Ok(());
130 }
131
132 let tty = tty_fd()?;
133 let mut ios = get_terminal_attr(&tty)?;
134 let original_mode_ios = ios.clone();
135 ios.make_raw();
136 set_terminal_attr(&tty, &ios)?;
137 *original_mode = Some(original_mode_ios);
139 Ok(())
140}
141
142#[cfg(feature = "libc")]
148pub(crate) fn disable_raw_mode() -> io::Result<()> {
149 let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock();
150 if let Some(original_mode_ios) = original_mode.as_ref() {
151 let tty = tty_fd()?;
152 set_terminal_attr(tty.raw_fd(), original_mode_ios)?;
153 *original_mode = None;
155 }
156 Ok(())
157}
158
159#[cfg(not(feature = "libc"))]
160pub(crate) fn disable_raw_mode() -> io::Result<()> {
161 let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock();
162 if let Some(original_mode_ios) = original_mode.as_ref() {
163 let tty = tty_fd()?;
164 set_terminal_attr(&tty, original_mode_ios)?;
165 *original_mode = None;
167 }
168 Ok(())
169}
170
171#[cfg(not(feature = "libc"))]
172fn get_terminal_attr(fd: impl AsFd) -> io::Result<Termios> {
173 let result = rustix::termios::tcgetattr(fd)?;
174 Ok(result)
175}
176
177#[cfg(not(feature = "libc"))]
178fn set_terminal_attr(fd: impl AsFd, termios: &Termios) -> io::Result<()> {
179 rustix::termios::tcsetattr(fd, rustix::termios::OptionalActions::Now, termios)?;
180 Ok(())
181}
182
183#[cfg(feature = "events")]
188pub fn supports_keyboard_enhancement() -> io::Result<bool> {
189 query_keyboard_enhancement_flags().map(|flags| flags.is_some())
190}
191
192#[cfg(feature = "events")]
197pub fn query_keyboard_enhancement_flags() -> io::Result<Option<KeyboardEnhancementFlags>> {
198 if is_raw_mode_enabled() {
199 query_keyboard_enhancement_flags_raw()
200 } else {
201 query_keyboard_enhancement_flags_nonraw()
202 }
203}
204
205#[cfg(feature = "events")]
206fn query_keyboard_enhancement_flags_nonraw() -> io::Result<Option<KeyboardEnhancementFlags>> {
207 enable_raw_mode()?;
208 let flags = query_keyboard_enhancement_flags_raw();
209 disable_raw_mode()?;
210 flags
211}
212
213#[cfg(feature = "events")]
214fn query_keyboard_enhancement_flags_raw() -> io::Result<Option<KeyboardEnhancementFlags>> {
215 use crate::event::{
216 filter::{KeyboardEnhancementFlagsFilter, PrimaryDeviceAttributesFilter},
217 poll_internal, read_internal, InternalEvent,
218 };
219 use std::io::Write;
220 use std::time::Duration;
221
222 const QUERY: &[u8] = b"\x1B[?u\x1B[c";
232
233 let result = File::open("/dev/tty").and_then(|mut file| {
234 file.write_all(QUERY)?;
235 file.flush()
236 });
237 if result.is_err() {
238 let mut stdout = io::stdout();
239 stdout.write_all(QUERY)?;
240 stdout.flush()?;
241 }
242
243 loop {
244 match poll_internal(
245 Some(Duration::from_millis(2000)),
246 &KeyboardEnhancementFlagsFilter,
247 ) {
248 Ok(true) => {
249 match read_internal(&KeyboardEnhancementFlagsFilter) {
250 Ok(InternalEvent::KeyboardEnhancementFlags(current_flags)) => {
251 read_internal(&PrimaryDeviceAttributesFilter).ok();
253 return Ok(Some(current_flags));
254 }
255 _ => return Ok(None),
256 }
257 }
258 Ok(false) => {
259 return Err(io::Error::new(
260 io::ErrorKind::Other,
261 "The keyboard enhancement status could not be read within a normal duration",
262 ));
263 }
264 Err(_) => {}
265 }
266 }
267}
268
269fn tput_value(arg: &str) -> Option<u16> {
274 let output = process::Command::new("tput").arg(arg).output().ok()?;
275 let value = output
276 .stdout
277 .into_iter()
278 .filter_map(|b| char::from(b).to_digit(10))
279 .fold(0, |v, n| v * 10 + n as u16);
280
281 if value > 0 {
282 Some(value)
283 } else {
284 None
285 }
286}
287
288fn tput_size() -> Option<(u16, u16)> {
293 match (tput_value("cols"), tput_value("lines")) {
294 (Some(w), Some(h)) => Some((w, h)),
295 _ => None,
296 }
297}
298
299#[cfg(feature = "libc")]
300fn raw_terminal_attr(termios: &mut Termios) {
302 unsafe { cfmakeraw(termios) }
303}
304
305#[cfg(feature = "libc")]
306fn get_terminal_attr(fd: RawFd) -> io::Result<Termios> {
307 unsafe {
308 let mut termios = mem::zeroed();
309 wrap_with_result(tcgetattr(fd, &mut termios))?;
310 Ok(termios)
311 }
312}
313
314#[cfg(feature = "libc")]
315fn set_terminal_attr(fd: RawFd, termios: &Termios) -> io::Result<()> {
316 wrap_with_result(unsafe { tcsetattr(fd, TCSANOW, termios) })
317}
318
319#[cfg(feature = "libc")]
320fn wrap_with_result(result: i32) -> io::Result<()> {
321 if result == -1 {
322 Err(io::Error::last_os_error())
323 } else {
324 Ok(())
325 }
326}