@@ -3,7 +3,9 @@ use std::fmt::Display;
3
3
use std:: fs;
4
4
use std:: io;
5
5
use std:: io:: { BufRead , BufReader } ;
6
+ use std:: mem;
6
7
use std:: os:: unix:: io:: AsRawFd ;
8
+ use std:: ptr;
7
9
use std:: str;
8
10
9
11
use crate :: kb:: Key ;
@@ -69,7 +71,10 @@ pub fn read_secure() -> io::Result<String> {
69
71
f_tty = None ;
70
72
libc:: STDIN_FILENO
71
73
} 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" ) ?;
73
78
let fd = f. as_raw_fd ( ) ;
74
79
f_tty = Some ( BufReader :: new ( f) ) ;
75
80
fd
@@ -99,20 +104,69 @@ pub fn read_secure() -> io::Result<String> {
99
104
} )
100
105
}
101
106
102
- fn read_single_char ( fd : i32 ) -> io:: Result < Option < char > > {
107
+ fn poll_fd ( fd : i32 , timeout : i32 ) -> io:: Result < bool > {
103
108
let mut pollfd = libc:: pollfd {
104
109
fd,
105
110
events : libc:: POLLIN ,
106
111
revents : 0 ,
107
112
} ;
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) } ;
111
114
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
+ }
113
163
}
164
+ poll_fd ( fd, timeout)
165
+ }
114
166
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 ) ?;
116
170
117
171
if is_ready {
118
172
// if there is something to be read, take 1 byte from it
@@ -154,7 +208,10 @@ pub fn read_single_key() -> io::Result<Key> {
154
208
if libc:: isatty ( libc:: STDIN_FILENO ) == 1 {
155
209
libc:: STDIN_FILENO
156
210
} 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" ) ?;
158
215
tty_f. as_raw_fd ( )
159
216
}
160
217
} ;
@@ -165,103 +222,97 @@ pub fn read_single_key() -> io::Result<Key> {
165
222
unsafe { libc:: cfmakeraw ( & mut termios) } ;
166
223
c_result ( || unsafe { libc:: tcsetattr ( fd, libc:: TCSADRAIN , & termios) } ) ?;
167
224
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] ) )
196
257
}
197
258
} else {
198
- Ok ( Key :: UnknownEscSeq ( vec ! [ c1, c2, c3] ) )
259
+ // \x1b[ and 1 more char
260
+ Ok ( Key :: UnknownEscSeq ( vec ! [ c1, c2] ) )
199
261
}
200
- } else {
201
- // \x1b[ and 1 more char
202
- Ok ( Key :: UnknownEscSeq ( vec ! [ c1, c2] ) )
203
262
}
204
263
}
264
+ } else {
265
+ // \x1b[ and no more input
266
+ Ok ( Key :: UnknownEscSeq ( vec ! [ c1] ) )
205
267
}
206
268
} else {
207
- // \x1b[ and no more input
269
+ // char after escape is not [
208
270
Ok ( Key :: UnknownEscSeq ( vec ! [ c1] ) )
209
271
}
210
272
} 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
+ } ;
217
276
}
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
+ } ;
245
304
}
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
+ }
260
312
}
261
-
262
- read_single_key ( )
263
313
}
264
314
} ;
315
+
265
316
c_result ( || unsafe { libc:: tcsetattr ( fd, libc:: TCSADRAIN , & original) } ) ?;
266
317
267
318
// if the user hit ^C we want to signal SIGINT to outselves.
0 commit comments