1 use std::env;
2 use std::fmt::Display;
3 use std::fs;
4 use std::io;
5 use std::io::{BufRead, BufReader};
6 use std::os::unix::io::AsRawFd;
7 use std::str;
8 
9 use crate::kb::Key;
10 use crate::term::Term;
11 
12 pub use crate::common_term::*;
13 
14 pub const DEFAULT_WIDTH: u16 = 80;
15 
16 #[inline]
is_a_terminal(out: &Term) -> bool17 pub fn is_a_terminal(out: &Term) -> bool {
18     unsafe { libc::isatty(out.as_raw_fd()) != 0 }
19 }
20 
is_a_color_terminal(out: &Term) -> bool21 pub fn is_a_color_terminal(out: &Term) -> bool {
22     if !is_a_terminal(out) {
23         return false;
24     }
25 
26     if env::var("NO_COLOR").is_ok() {
27         return false;
28     }
29 
30     match env::var("TERM") {
31         Ok(term) => term != "dumb",
32         Err(_) => false,
33     }
34 }
35 
c_result<F: FnOnce() -> libc::c_int>(f: F) -> io::Result<()>36 pub fn c_result<F: FnOnce() -> libc::c_int>(f: F) -> io::Result<()> {
37     let res = f();
38     if res != 0 {
39         Err(io::Error::last_os_error())
40     } else {
41         Ok(())
42     }
43 }
44 
45 #[inline]
terminal_size(out: &Term) -> Option<(u16, u16)>46 pub fn terminal_size(out: &Term) -> Option<(u16, u16)> {
47     terminal_size::terminal_size_using_fd(out.as_raw_fd()).map(|x| ((x.1).0, (x.0).0))
48 }
49 
read_secure() -> io::Result<String>50 pub fn read_secure() -> io::Result<String> {
51     let f_tty;
52     let fd = unsafe {
53         if libc::isatty(libc::STDIN_FILENO) == 1 {
54             f_tty = None;
55             libc::STDIN_FILENO
56         } else {
57             let f = fs::File::open("/dev/tty")?;
58             let fd = f.as_raw_fd();
59             f_tty = Some(BufReader::new(f));
60             fd
61         }
62     };
63 
64     let mut termios = core::mem::MaybeUninit::uninit();
65     c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
66     let mut termios = unsafe { termios.assume_init() };
67     let original = termios;
68     termios.c_lflag &= !libc::ECHO;
69     c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &termios) })?;
70     let mut rv = String::new();
71 
72     let read_rv = if let Some(mut f) = f_tty {
73         f.read_line(&mut rv)
74     } else {
75         io::stdin().read_line(&mut rv)
76     };
77 
78     c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &original) })?;
79 
80     read_rv.map(|_| {
81         let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
82         rv.truncate(len);
83         rv
84     })
85 }
86 
read_single_char(fd: i32) -> io::Result<Option<char>>87 fn read_single_char(fd: i32) -> io::Result<Option<char>> {
88     let mut pollfd = libc::pollfd {
89         fd,
90         events: libc::POLLIN,
91         revents: 0,
92     };
93 
94     // timeout of zero means that it will not block
95     let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, 0) };
96     if ret < 0 {
97         return Err(io::Error::last_os_error());
98     }
99 
100     let is_ready = pollfd.revents & libc::POLLIN != 0;
101 
102     if is_ready {
103         // if there is something to be read, take 1 byte from it
104         let mut buf: [u8; 1] = [0];
105 
106         read_bytes(fd, &mut buf, 1)?;
107         Ok(Some(buf[0] as char))
108     } else {
109         //there is nothing to be read
110         Ok(None)
111     }
112 }
113 
114 // Similar to libc::read. Read count bytes into slice buf from descriptor fd.
115 // If successful, return the number of bytes read.
116 // Will return an error if nothing was read, i.e when called at end of file.
read_bytes(fd: i32, buf: &mut [u8], count: u8) -> io::Result<u8>117 fn read_bytes(fd: i32, buf: &mut [u8], count: u8) -> io::Result<u8> {
118     let read = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, count as usize) };
119     if read < 0 {
120         Err(io::Error::last_os_error())
121     } else if read == 0 {
122         Err(io::Error::new(
123             io::ErrorKind::UnexpectedEof,
124             "Reached end of file",
125         ))
126     } else if buf[0] == b'\x03' {
127         Err(io::Error::new(
128             io::ErrorKind::Interrupted,
129             "read interrupted",
130         ))
131     } else {
132         Ok(read as u8)
133     }
134 }
135 
read_single_key() -> io::Result<Key>136 pub fn read_single_key() -> io::Result<Key> {
137     let tty_f;
138     let fd = unsafe {
139         if libc::isatty(libc::STDIN_FILENO) == 1 {
140             libc::STDIN_FILENO
141         } else {
142             tty_f = fs::File::open("/dev/tty")?;
143             tty_f.as_raw_fd()
144         }
145     };
146     let mut termios = core::mem::MaybeUninit::uninit();
147     c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
148     let mut termios = unsafe { termios.assume_init() };
149     let original = termios;
150     unsafe { libc::cfmakeraw(&mut termios) };
151     c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?;
152 
153     let rv = match read_single_char(fd)? {
154         Some('\x1b') => {
155             // Escape was read, keep reading in case we find a familiar key
156             if let Some(c1) = read_single_char(fd)? {
157                 if c1 == '[' {
158                     if let Some(c2) = read_single_char(fd)? {
159                         match c2 {
160                             'A' => Ok(Key::ArrowUp),
161                             'B' => Ok(Key::ArrowDown),
162                             'C' => Ok(Key::ArrowRight),
163                             'D' => Ok(Key::ArrowLeft),
164                             'H' => Ok(Key::Home),
165                             'F' => Ok(Key::End),
166                             'Z' => Ok(Key::BackTab),
167                             _ => {
168                                 let c3 = read_single_char(fd)?;
169                                 if let Some(c3) = c3 {
170                                     if c3 == '~' {
171                                         match c2 {
172                                             '1' => Ok(Key::Home), // tmux
173                                             '2' => Ok(Key::Insert),
174                                             '3' => Ok(Key::Del),
175                                             '4' => Ok(Key::End), // tmux
176                                             '5' => Ok(Key::PageUp),
177                                             '6' => Ok(Key::PageDown),
178                                             '7' => Ok(Key::Home), // xrvt
179                                             '8' => Ok(Key::End),  // xrvt
180                                             _ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
181                                         }
182                                     } else {
183                                         Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
184                                     }
185                                 } else {
186                                     // \x1b[ and 1 more char
187                                     Ok(Key::UnknownEscSeq(vec![c1, c2]))
188                                 }
189                             }
190                         }
191                     } else {
192                         // \x1b[ and no more input
193                         Ok(Key::UnknownEscSeq(vec![c1]))
194                     }
195                 } else {
196                     // char after escape is not [
197                     Ok(Key::UnknownEscSeq(vec![c1]))
198                 }
199             } else {
200                 //nothing after escape
201                 Ok(Key::Escape)
202             }
203         }
204         Some(c) => {
205             let byte = c as u8;
206             let mut buf: [u8; 4] = [byte, 0, 0, 0];
207 
208             if byte & 224u8 == 192u8 {
209                 // a two byte unicode character
210                 read_bytes(fd, &mut buf[1..], 1)?;
211                 Ok(key_from_utf8(&buf[..2]))
212             } else if byte & 240u8 == 224u8 {
213                 // a three byte unicode character
214                 read_bytes(fd, &mut buf[1..], 2)?;
215                 Ok(key_from_utf8(&buf[..3]))
216             } else if byte & 248u8 == 240u8 {
217                 // a four byte unicode character
218                 read_bytes(fd, &mut buf[1..], 3)?;
219                 Ok(key_from_utf8(&buf[..4]))
220             } else {
221                 Ok(match c {
222                     '\n' | '\r' => Key::Enter,
223                     '\x7f' => Key::Backspace,
224                     '\t' => Key::Tab,
225                     '\x01' => Key::Home,      // Control-A (home)
226                     '\x05' => Key::End,       // Control-E (end)
227                     '\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
228                     _ => Key::Char(c),
229                 })
230             }
231         }
232         None => {
233             // there is no subsequent byte ready to be read, block and wait for input
234 
235             let mut pollfd = libc::pollfd {
236                 fd,
237                 events: libc::POLLIN,
238                 revents: 0,
239             };
240 
241             // negative timeout means that it will block indefinitely
242             let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, -1) };
243             if ret < 0 {
244                 return Err(io::Error::last_os_error());
245             }
246 
247             read_single_key()
248         }
249     };
250     c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &original) })?;
251 
252     // if the user hit ^C we want to signal SIGINT to outselves.
253     if let Err(ref err) = rv {
254         if err.kind() == io::ErrorKind::Interrupted {
255             unsafe {
256                 libc::raise(libc::SIGINT);
257             }
258         }
259     }
260 
261     rv
262 }
263 
key_from_utf8(buf: &[u8]) -> Key264 pub fn key_from_utf8(buf: &[u8]) -> Key {
265     if let Ok(s) = str::from_utf8(buf) {
266         if let Some(c) = s.chars().next() {
267             return Key::Char(c);
268         }
269     }
270     Key::Unknown
271 }
272 
273 #[cfg(not(target_os = "macos"))]
274 static IS_LANG_UTF8: once_cell::sync::Lazy<bool> =
275     once_cell::sync::Lazy::new(|| match std::env::var("LANG") {
276         Ok(lang) => lang.to_uppercase().ends_with("UTF-8"),
277         _ => false,
278     });
279 
280 #[cfg(target_os = "macos")]
wants_emoji() -> bool281 pub fn wants_emoji() -> bool {
282     true
283 }
284 
285 #[cfg(not(target_os = "macos"))]
wants_emoji() -> bool286 pub fn wants_emoji() -> bool {
287     *IS_LANG_UTF8
288 }
289 
set_title<T: Display>(title: T)290 pub fn set_title<T: Display>(title: T) {
291     print!("\x1b]0;{}\x07", title);
292 }
293