Skip to content

Commit d1ed42d

Browse files
committed
Prefer select() over poll() on macos for ttys
1 parent ab9cd9d commit d1ed42d

File tree

1 file changed

+143
-90
lines changed

1 file changed

+143
-90
lines changed

src/unix_term.rs

Lines changed: 143 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ 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

11+
use libc::poll;
12+
913
use crate::kb::Key;
1014
use crate::term::Term;
1115

@@ -69,7 +73,10 @@ pub fn read_secure() -> io::Result<String> {
6973
f_tty = None;
7074
libc::STDIN_FILENO
7175
} else {
72-
let f = fs::File::open("/dev/tty")?;
76+
let f = fs::OpenOptions::new()
77+
.read(true)
78+
.write(true)
79+
.open("/dev/tty")?;
7380
let fd = f.as_raw_fd();
7481
f_tty = Some(BufReader::new(f));
7582
fd
@@ -99,20 +106,69 @@ pub fn read_secure() -> io::Result<String> {
99106
})
100107
}
101108

102-
fn read_single_char(fd: i32) -> io::Result<Option<char>> {
109+
fn poll_fd(fd: i32, timeout: i32) -> io::Result<bool> {
103110
let mut pollfd = libc::pollfd {
104111
fd,
105112
events: libc::POLLIN,
106113
revents: 0,
107114
};
108-
109-
// timeout of zero means that it will not block
110-
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, 0) };
115+
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, timeout) };
111116
if ret < 0 {
112-
return Err(io::Error::last_os_error());
117+
Err(io::Error::last_os_error())
118+
} else {
119+
Ok(pollfd.revents & libc::POLLIN != 0)
113120
}
121+
}
114122

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

117173
if is_ready {
118174
// if there is something to be read, take 1 byte from it
@@ -154,7 +210,10 @@ pub fn read_single_key() -> io::Result<Key> {
154210
if libc::isatty(libc::STDIN_FILENO) == 1 {
155211
libc::STDIN_FILENO
156212
} else {
157-
tty_f = fs::File::open("/dev/tty")?;
213+
tty_f = fs::OpenOptions::new()
214+
.read(true)
215+
.write(true)
216+
.open("/dev/tty")?;
158217
tty_f.as_raw_fd()
159218
}
160219
};
@@ -165,103 +224,97 @@ pub fn read_single_key() -> io::Result<Key> {
165224
unsafe { libc::cfmakeraw(&mut termios) };
166225
c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?;
167226

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])),
227+
let rv: io::Result<Key> = loop {
228+
match read_single_char(fd)? {
229+
Some('\x1b') => {
230+
// Escape was read, keep reading in case we find a familiar key
231+
break if let Some(c1) = read_single_char(fd)? {
232+
if c1 == '[' {
233+
if let Some(c2) = read_single_char(fd)? {
234+
match c2 {
235+
'A' => Ok(Key::ArrowUp),
236+
'B' => Ok(Key::ArrowDown),
237+
'C' => Ok(Key::ArrowRight),
238+
'D' => Ok(Key::ArrowLeft),
239+
'H' => Ok(Key::Home),
240+
'F' => Ok(Key::End),
241+
'Z' => Ok(Key::BackTab),
242+
_ => {
243+
let c3 = read_single_char(fd)?;
244+
if let Some(c3) = c3 {
245+
if c3 == '~' {
246+
match c2 {
247+
'1' => Ok(Key::Home), // tmux
248+
'2' => Ok(Key::Insert),
249+
'3' => Ok(Key::Del),
250+
'4' => Ok(Key::End), // tmux
251+
'5' => Ok(Key::PageUp),
252+
'6' => Ok(Key::PageDown),
253+
'7' => Ok(Key::Home), // xrvt
254+
'8' => Ok(Key::End), // xrvt
255+
_ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
256+
}
257+
} else {
258+
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
196259
}
197260
} else {
198-
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
261+
// \x1b[ and 1 more char
262+
Ok(Key::UnknownEscSeq(vec![c1, c2]))
199263
}
200-
} else {
201-
// \x1b[ and 1 more char
202-
Ok(Key::UnknownEscSeq(vec![c1, c2]))
203264
}
204265
}
266+
} else {
267+
// \x1b[ and no more input
268+
Ok(Key::UnknownEscSeq(vec![c1]))
205269
}
206270
} else {
207-
// \x1b[ and no more input
271+
// char after escape is not [
208272
Ok(Key::UnknownEscSeq(vec![c1]))
209273
}
210274
} else {
211-
// char after escape is not [
212-
Ok(Key::UnknownEscSeq(vec![c1]))
213-
}
214-
} else {
215-
//nothing after escape
216-
Ok(Key::Escape)
275+
//nothing after escape
276+
Ok(Key::Escape)
277+
};
217278
}
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-
})
279+
Some(c) => {
280+
let byte = c as u8;
281+
let mut buf: [u8; 4] = [byte, 0, 0, 0];
282+
283+
break if byte & 224u8 == 192u8 {
284+
// a two byte unicode character
285+
read_bytes(fd, &mut buf[1..], 1)?;
286+
Ok(key_from_utf8(&buf[..2]))
287+
} else if byte & 240u8 == 224u8 {
288+
// a three byte unicode character
289+
read_bytes(fd, &mut buf[1..], 2)?;
290+
Ok(key_from_utf8(&buf[..3]))
291+
} else if byte & 248u8 == 240u8 {
292+
// a four byte unicode character
293+
read_bytes(fd, &mut buf[1..], 3)?;
294+
Ok(key_from_utf8(&buf[..4]))
295+
} else {
296+
Ok(match c {
297+
'\n' | '\r' => Key::Enter,
298+
'\x7f' => Key::Backspace,
299+
'\t' => Key::Tab,
300+
'\x01' => Key::Home, // Control-A (home)
301+
'\x05' => Key::End, // Control-E (end)
302+
'\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
303+
_ => Key::Char(c),
304+
})
305+
};
245306
}
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());
307+
None => {
308+
// there is no subsequent byte ready to be read, block and wait for input
309+
// negative timeout means that it will block indefinitely
310+
match select_or_poll_term_fd(fd, -1) {
311+
Ok(_) => continue,
312+
Err(_) => break Err(io::Error::last_os_error()),
313+
}
260314
}
261-
262-
read_single_key()
263315
}
264316
};
317+
265318
c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &original) })?;
266319

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

0 commit comments

Comments
 (0)