Skip to content

Commit 66cb2aa

Browse files
authored
Prefer select() over poll() on macos for ttys (#169)
1 parent ab9cd9d commit 66cb2aa

File tree

1 file changed

+141
-90
lines changed

1 file changed

+141
-90
lines changed

src/unix_term.rs

Lines changed: 141 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::fmt::Display;
33
use std::fs;
44
use std::io;
55
use std::io::{BufRead, BufReader};
6+
use std::mem;
67
use std::os::unix::io::AsRawFd;
8+
use std::ptr;
79
use std::str;
810

911
use crate::kb::Key;
@@ -69,7 +71,10 @@ pub fn read_secure() -> io::Result<String> {
6971
f_tty = None;
7072
libc::STDIN_FILENO
7173
} else {
72-
let f = fs::File::open("/dev/tty")?;
74+
let f = fs::OpenOptions::new()
75+
.read(true)
76+
.write(true)
77+
.open("/dev/tty")?;
7378
let fd = f.as_raw_fd();
7479
f_tty = Some(BufReader::new(f));
7580
fd
@@ -99,20 +104,69 @@ pub fn read_secure() -> io::Result<String> {
99104
})
100105
}
101106

102-
fn read_single_char(fd: i32) -> io::Result<Option<char>> {
107+
fn poll_fd(fd: i32, timeout: i32) -> io::Result<bool> {
103108
let mut pollfd = libc::pollfd {
104109
fd,
105110
events: libc::POLLIN,
106111
revents: 0,
107112
};
108-
109-
// timeout of zero means that it will not block
110-
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, 0) };
113+
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, timeout) };
111114
if ret < 0 {
112-
return Err(io::Error::last_os_error());
115+
Err(io::Error::last_os_error())
116+
} else {
117+
Ok(pollfd.revents & libc::POLLIN != 0)
118+
}
119+
}
120+
121+
#[cfg(target_os = "macos")]
122+
fn select_fd(fd: i32, timeout: i32) -> io::Result<bool> {
123+
unsafe {
124+
let mut read_fd_set: libc::fd_set = mem::zeroed();
125+
126+
let mut timeout_val;
127+
let timeout = if timeout < 0 {
128+
ptr::null_mut()
129+
} else {
130+
timeout_val = libc::timeval {
131+
tv_sec: (timeout / 1000) as _,
132+
tv_usec: (timeout * 1000) as _,
133+
};
134+
&mut timeout_val
135+
};
136+
137+
libc::FD_ZERO(&mut read_fd_set);
138+
libc::FD_SET(fd, &mut read_fd_set);
139+
let ret = libc::select(
140+
fd + 1,
141+
&mut read_fd_set,
142+
ptr::null_mut(),
143+
ptr::null_mut(),
144+
timeout,
145+
);
146+
if ret < 0 {
147+
Err(io::Error::last_os_error())
148+
} else {
149+
Ok(libc::FD_ISSET(fd, &read_fd_set))
150+
}
151+
}
152+
}
153+
154+
fn select_or_poll_term_fd(fd: i32, timeout: i32) -> io::Result<bool> {
155+
// There is a bug on macos that ttys cannot be polled, only select()
156+
// works. However given how problematic select is in general, we
157+
// normally want to use poll there too.
158+
#[cfg(target_os = "macos")]
159+
{
160+
if unsafe { libc::isatty(fd) == 1 } {
161+
return select_fd(fd, timeout);
162+
}
113163
}
164+
poll_fd(fd, timeout)
165+
}
114166

115-
let is_ready = pollfd.revents & libc::POLLIN != 0;
167+
fn read_single_char(fd: i32) -> io::Result<Option<char>> {
168+
// timeout of zero means that it will not block
169+
let is_ready = select_or_poll_term_fd(fd, 0)?;
116170

117171
if is_ready {
118172
// if there is something to be read, take 1 byte from it
@@ -154,7 +208,10 @@ pub fn read_single_key() -> io::Result<Key> {
154208
if libc::isatty(libc::STDIN_FILENO) == 1 {
155209
libc::STDIN_FILENO
156210
} else {
157-
tty_f = fs::File::open("/dev/tty")?;
211+
tty_f = fs::OpenOptions::new()
212+
.read(true)
213+
.write(true)
214+
.open("/dev/tty")?;
158215
tty_f.as_raw_fd()
159216
}
160217
};
@@ -165,103 +222,97 @@ pub fn read_single_key() -> io::Result<Key> {
165222
unsafe { libc::cfmakeraw(&mut termios) };
166223
c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?;
167224

168-
let rv = match read_single_char(fd)? {
169-
Some('\x1b') => {
170-
// Escape was read, keep reading in case we find a familiar key
171-
if let Some(c1) = read_single_char(fd)? {
172-
if c1 == '[' {
173-
if let Some(c2) = read_single_char(fd)? {
174-
match c2 {
175-
'A' => Ok(Key::ArrowUp),
176-
'B' => Ok(Key::ArrowDown),
177-
'C' => Ok(Key::ArrowRight),
178-
'D' => Ok(Key::ArrowLeft),
179-
'H' => Ok(Key::Home),
180-
'F' => Ok(Key::End),
181-
'Z' => Ok(Key::BackTab),
182-
_ => {
183-
let c3 = read_single_char(fd)?;
184-
if let Some(c3) = c3 {
185-
if c3 == '~' {
186-
match c2 {
187-
'1' => Ok(Key::Home), // tmux
188-
'2' => Ok(Key::Insert),
189-
'3' => Ok(Key::Del),
190-
'4' => Ok(Key::End), // tmux
191-
'5' => Ok(Key::PageUp),
192-
'6' => Ok(Key::PageDown),
193-
'7' => Ok(Key::Home), // xrvt
194-
'8' => Ok(Key::End), // xrvt
195-
_ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
225+
let rv: io::Result<Key> = loop {
226+
match read_single_char(fd)? {
227+
Some('\x1b') => {
228+
// Escape was read, keep reading in case we find a familiar key
229+
break if let Some(c1) = read_single_char(fd)? {
230+
if c1 == '[' {
231+
if let Some(c2) = read_single_char(fd)? {
232+
match c2 {
233+
'A' => Ok(Key::ArrowUp),
234+
'B' => Ok(Key::ArrowDown),
235+
'C' => Ok(Key::ArrowRight),
236+
'D' => Ok(Key::ArrowLeft),
237+
'H' => Ok(Key::Home),
238+
'F' => Ok(Key::End),
239+
'Z' => Ok(Key::BackTab),
240+
_ => {
241+
let c3 = read_single_char(fd)?;
242+
if let Some(c3) = c3 {
243+
if c3 == '~' {
244+
match c2 {
245+
'1' => Ok(Key::Home), // tmux
246+
'2' => Ok(Key::Insert),
247+
'3' => Ok(Key::Del),
248+
'4' => Ok(Key::End), // tmux
249+
'5' => Ok(Key::PageUp),
250+
'6' => Ok(Key::PageDown),
251+
'7' => Ok(Key::Home), // xrvt
252+
'8' => Ok(Key::End), // xrvt
253+
_ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
254+
}
255+
} else {
256+
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
196257
}
197258
} else {
198-
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
259+
// \x1b[ and 1 more char
260+
Ok(Key::UnknownEscSeq(vec![c1, c2]))
199261
}
200-
} else {
201-
// \x1b[ and 1 more char
202-
Ok(Key::UnknownEscSeq(vec![c1, c2]))
203262
}
204263
}
264+
} else {
265+
// \x1b[ and no more input
266+
Ok(Key::UnknownEscSeq(vec![c1]))
205267
}
206268
} else {
207-
// \x1b[ and no more input
269+
// char after escape is not [
208270
Ok(Key::UnknownEscSeq(vec![c1]))
209271
}
210272
} else {
211-
// char after escape is not [
212-
Ok(Key::UnknownEscSeq(vec![c1]))
213-
}
214-
} else {
215-
//nothing after escape
216-
Ok(Key::Escape)
273+
//nothing after escape
274+
Ok(Key::Escape)
275+
};
217276
}
218-
}
219-
Some(c) => {
220-
let byte = c as u8;
221-
let mut buf: [u8; 4] = [byte, 0, 0, 0];
222-
223-
if byte & 224u8 == 192u8 {
224-
// a two byte unicode character
225-
read_bytes(fd, &mut buf[1..], 1)?;
226-
Ok(key_from_utf8(&buf[..2]))
227-
} else if byte & 240u8 == 224u8 {
228-
// a three byte unicode character
229-
read_bytes(fd, &mut buf[1..], 2)?;
230-
Ok(key_from_utf8(&buf[..3]))
231-
} else if byte & 248u8 == 240u8 {
232-
// a four byte unicode character
233-
read_bytes(fd, &mut buf[1..], 3)?;
234-
Ok(key_from_utf8(&buf[..4]))
235-
} else {
236-
Ok(match c {
237-
'\n' | '\r' => Key::Enter,
238-
'\x7f' => Key::Backspace,
239-
'\t' => Key::Tab,
240-
'\x01' => Key::Home, // Control-A (home)
241-
'\x05' => Key::End, // Control-E (end)
242-
'\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
243-
_ => Key::Char(c),
244-
})
277+
Some(c) => {
278+
let byte = c as u8;
279+
let mut buf: [u8; 4] = [byte, 0, 0, 0];
280+
281+
break if byte & 224u8 == 192u8 {
282+
// a two byte unicode character
283+
read_bytes(fd, &mut buf[1..], 1)?;
284+
Ok(key_from_utf8(&buf[..2]))
285+
} else if byte & 240u8 == 224u8 {
286+
// a three byte unicode character
287+
read_bytes(fd, &mut buf[1..], 2)?;
288+
Ok(key_from_utf8(&buf[..3]))
289+
} else if byte & 248u8 == 240u8 {
290+
// a four byte unicode character
291+
read_bytes(fd, &mut buf[1..], 3)?;
292+
Ok(key_from_utf8(&buf[..4]))
293+
} else {
294+
Ok(match c {
295+
'\n' | '\r' => Key::Enter,
296+
'\x7f' => Key::Backspace,
297+
'\t' => Key::Tab,
298+
'\x01' => Key::Home, // Control-A (home)
299+
'\x05' => Key::End, // Control-E (end)
300+
'\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
301+
_ => Key::Char(c),
302+
})
303+
};
245304
}
246-
}
247-
None => {
248-
// there is no subsequent byte ready to be read, block and wait for input
249-
250-
let mut pollfd = libc::pollfd {
251-
fd,
252-
events: libc::POLLIN,
253-
revents: 0,
254-
};
255-
256-
// negative timeout means that it will block indefinitely
257-
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, -1) };
258-
if ret < 0 {
259-
return Err(io::Error::last_os_error());
305+
None => {
306+
// there is no subsequent byte ready to be read, block and wait for input
307+
// negative timeout means that it will block indefinitely
308+
match select_or_poll_term_fd(fd, -1) {
309+
Ok(_) => continue,
310+
Err(_) => break Err(io::Error::last_os_error()),
311+
}
260312
}
261-
262-
read_single_key()
263313
}
264314
};
315+
265316
c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &original) })?;
266317

267318
// if the user hit ^C we want to signal SIGINT to outselves.

0 commit comments

Comments
 (0)