@@ -3,7 +3,9 @@ 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
911use 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