@@ -3,9 +3,13 @@ use std::fmt::Display;
33use std:: fs;
44use std:: io;
55use std:: io:: { BufRead , BufReader } ;
6+ use std:: mem;
67use std:: os:: unix:: io:: AsRawFd ;
8+ use std:: ptr;
79use std:: str;
810
11+ use libc:: poll;
12+
913use crate :: kb:: Key ;
1014use 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