/src/git/compat/terminal.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "git-compat-util.h" |
2 | | #include "compat/terminal.h" |
3 | | #include "gettext.h" |
4 | | #include "sigchain.h" |
5 | | #include "strbuf.h" |
6 | | #include "run-command.h" |
7 | | #include "string-list.h" |
8 | | #include "hashmap.h" |
9 | | |
10 | | #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE) |
11 | | |
12 | | static void restore_term_on_signal(int sig) |
13 | 0 | { |
14 | 0 | restore_term(); |
15 | | /* restore_term calls sigchain_pop_common */ |
16 | 0 | raise(sig); |
17 | 0 | } |
18 | | |
19 | | #ifdef HAVE_DEV_TTY |
20 | | |
21 | | #define INPUT_PATH "/dev/tty" |
22 | | #define OUTPUT_PATH "/dev/tty" |
23 | | |
24 | | static volatile sig_atomic_t term_fd_needs_closing; |
25 | | static int term_fd = -1; |
26 | | static struct termios old_term; |
27 | | |
28 | | static const char *background_resume_msg; |
29 | | static const char *restore_error_msg; |
30 | | static volatile sig_atomic_t ttou_received; |
31 | | |
32 | | /* async safe error function for use by signal handlers. */ |
33 | | static void write_err(const char *msg) |
34 | 0 | { |
35 | 0 | write_in_full(2, "error: ", strlen("error: ")); |
36 | 0 | write_in_full(2, msg, strlen(msg)); |
37 | 0 | write_in_full(2, "\n", 1); |
38 | 0 | } |
39 | | |
40 | | static void print_background_resume_msg(int signo) |
41 | 0 | { |
42 | 0 | int saved_errno = errno; |
43 | 0 | sigset_t mask; |
44 | 0 | struct sigaction old_sa; |
45 | 0 | struct sigaction sa = { .sa_handler = SIG_DFL }; |
46 | |
|
47 | 0 | ttou_received = 1; |
48 | 0 | write_err(background_resume_msg); |
49 | 0 | sigaction(signo, &sa, &old_sa); |
50 | 0 | raise(signo); |
51 | 0 | sigemptyset(&mask); |
52 | 0 | sigaddset(&mask, signo); |
53 | 0 | sigprocmask(SIG_UNBLOCK, &mask, NULL); |
54 | | /* Stopped here */ |
55 | 0 | sigprocmask(SIG_BLOCK, &mask, NULL); |
56 | 0 | sigaction(signo, &old_sa, NULL); |
57 | 0 | errno = saved_errno; |
58 | 0 | } |
59 | | |
60 | | static void restore_terminal_on_suspend(int signo) |
61 | 0 | { |
62 | 0 | int saved_errno = errno; |
63 | 0 | int res; |
64 | 0 | struct termios t; |
65 | 0 | sigset_t mask; |
66 | 0 | struct sigaction old_sa; |
67 | 0 | struct sigaction sa = { .sa_handler = SIG_DFL }; |
68 | 0 | int can_restore = 1; |
69 | |
|
70 | 0 | if (tcgetattr(term_fd, &t) < 0) |
71 | 0 | can_restore = 0; |
72 | |
|
73 | 0 | if (tcsetattr(term_fd, TCSAFLUSH, &old_term) < 0) |
74 | 0 | write_err(restore_error_msg); |
75 | |
|
76 | 0 | sigaction(signo, &sa, &old_sa); |
77 | 0 | raise(signo); |
78 | 0 | sigemptyset(&mask); |
79 | 0 | sigaddset(&mask, signo); |
80 | 0 | sigprocmask(SIG_UNBLOCK, &mask, NULL); |
81 | | /* Stopped here */ |
82 | 0 | sigprocmask(SIG_BLOCK, &mask, NULL); |
83 | 0 | sigaction(signo, &old_sa, NULL); |
84 | 0 | if (!can_restore) { |
85 | 0 | write_err(restore_error_msg); |
86 | 0 | goto out; |
87 | 0 | } |
88 | | /* |
89 | | * If we resume in the background then we receive SIGTTOU when calling |
90 | | * tcsetattr() below. Set up a handler to print an error message in that |
91 | | * case. |
92 | | */ |
93 | 0 | sigemptyset(&mask); |
94 | 0 | sigaddset(&mask, SIGTTOU); |
95 | 0 | sa.sa_mask = old_sa.sa_mask; |
96 | 0 | sa.sa_handler = print_background_resume_msg; |
97 | 0 | sa.sa_flags = SA_RESTART; |
98 | 0 | sigaction(SIGTTOU, &sa, &old_sa); |
99 | 0 | again: |
100 | 0 | ttou_received = 0; |
101 | 0 | sigprocmask(SIG_UNBLOCK, &mask, NULL); |
102 | 0 | res = tcsetattr(term_fd, TCSAFLUSH, &t); |
103 | 0 | sigprocmask(SIG_BLOCK, &mask, NULL); |
104 | 0 | if (ttou_received) |
105 | 0 | goto again; |
106 | 0 | else if (res < 0) |
107 | 0 | write_err(restore_error_msg); |
108 | 0 | sigaction(SIGTTOU, &old_sa, NULL); |
109 | 0 | out: |
110 | 0 | errno = saved_errno; |
111 | 0 | } |
112 | | |
113 | | static void reset_job_signals(void) |
114 | 0 | { |
115 | 0 | if (restore_error_msg) { |
116 | 0 | signal(SIGTTIN, SIG_DFL); |
117 | 0 | signal(SIGTTOU, SIG_DFL); |
118 | 0 | signal(SIGTSTP, SIG_DFL); |
119 | 0 | restore_error_msg = NULL; |
120 | 0 | background_resume_msg = NULL; |
121 | 0 | } |
122 | 0 | } |
123 | | |
124 | | static void close_term_fd(void) |
125 | 0 | { |
126 | 0 | if (term_fd_needs_closing) |
127 | 0 | close(term_fd); |
128 | 0 | term_fd_needs_closing = 0; |
129 | 0 | term_fd = -1; |
130 | 0 | } |
131 | | |
132 | | void restore_term(void) |
133 | 0 | { |
134 | 0 | if (term_fd < 0) |
135 | 0 | return; |
136 | | |
137 | 0 | tcsetattr(term_fd, TCSAFLUSH, &old_term); |
138 | 0 | close_term_fd(); |
139 | 0 | sigchain_pop_common(); |
140 | 0 | reset_job_signals(); |
141 | 0 | } |
142 | | |
143 | | int save_term(enum save_term_flags flags) |
144 | 0 | { |
145 | 0 | struct sigaction sa; |
146 | |
|
147 | 0 | if (term_fd < 0) |
148 | 0 | term_fd = ((flags & SAVE_TERM_STDIN) |
149 | 0 | ? 0 |
150 | 0 | : open("/dev/tty", O_RDWR)); |
151 | 0 | if (term_fd < 0) |
152 | 0 | return -1; |
153 | 0 | term_fd_needs_closing = !(flags & SAVE_TERM_STDIN); |
154 | 0 | if (tcgetattr(term_fd, &old_term) < 0) { |
155 | 0 | close_term_fd(); |
156 | 0 | return -1; |
157 | 0 | } |
158 | 0 | sigchain_push_common(restore_term_on_signal); |
159 | | /* |
160 | | * If job control is disabled then the shell will have set the |
161 | | * disposition of SIGTSTP to SIG_IGN. |
162 | | */ |
163 | 0 | sigaction(SIGTSTP, NULL, &sa); |
164 | 0 | if (sa.sa_handler == SIG_IGN) |
165 | 0 | return 0; |
166 | | |
167 | | /* avoid calling gettext() from signal handler */ |
168 | 0 | background_resume_msg = _("cannot resume in the background, please use 'fg' to resume"); |
169 | 0 | restore_error_msg = _("cannot restore terminal settings"); |
170 | 0 | sa.sa_handler = restore_terminal_on_suspend; |
171 | 0 | sa.sa_flags = SA_RESTART; |
172 | 0 | sigemptyset(&sa.sa_mask); |
173 | 0 | sigaddset(&sa.sa_mask, SIGTSTP); |
174 | 0 | sigaddset(&sa.sa_mask, SIGTTIN); |
175 | 0 | sigaddset(&sa.sa_mask, SIGTTOU); |
176 | 0 | sigaction(SIGTSTP, &sa, NULL); |
177 | 0 | sigaction(SIGTTIN, &sa, NULL); |
178 | 0 | sigaction(SIGTTOU, &sa, NULL); |
179 | |
|
180 | 0 | return 0; |
181 | 0 | } |
182 | | |
183 | | static int disable_bits(enum save_term_flags flags, tcflag_t bits) |
184 | 0 | { |
185 | 0 | struct termios t; |
186 | |
|
187 | 0 | if (save_term(flags) < 0) |
188 | 0 | return -1; |
189 | | |
190 | 0 | t = old_term; |
191 | |
|
192 | 0 | t.c_lflag &= ~bits; |
193 | 0 | if (bits & ICANON) { |
194 | 0 | t.c_cc[VMIN] = 1; |
195 | 0 | t.c_cc[VTIME] = 0; |
196 | 0 | } |
197 | 0 | if (!tcsetattr(term_fd, TCSAFLUSH, &t)) |
198 | 0 | return 0; |
199 | | |
200 | 0 | sigchain_pop_common(); |
201 | 0 | reset_job_signals(); |
202 | 0 | close_term_fd(); |
203 | 0 | return -1; |
204 | 0 | } |
205 | | |
206 | | static int disable_echo(enum save_term_flags flags) |
207 | 0 | { |
208 | 0 | return disable_bits(flags, ECHO); |
209 | 0 | } |
210 | | |
211 | | static int enable_non_canonical(enum save_term_flags flags) |
212 | 0 | { |
213 | 0 | return disable_bits(flags, ICANON | ECHO); |
214 | 0 | } |
215 | | |
216 | | /* |
217 | | * On macos it is not possible to use poll() with a terminal so use select |
218 | | * instead. |
219 | | */ |
220 | | static int getchar_with_timeout(int timeout) |
221 | 0 | { |
222 | 0 | struct timeval tv, *tvp = NULL; |
223 | 0 | fd_set readfds; |
224 | 0 | int res; |
225 | |
|
226 | 0 | again: |
227 | 0 | if (timeout >= 0) { |
228 | 0 | tv.tv_sec = timeout / 1000; |
229 | 0 | tv.tv_usec = (timeout % 1000) * 1000; |
230 | 0 | tvp = &tv; |
231 | 0 | } |
232 | |
|
233 | 0 | FD_ZERO(&readfds); |
234 | 0 | FD_SET(0, &readfds); |
235 | 0 | res = select(1, &readfds, NULL, NULL, tvp); |
236 | 0 | if (!res) |
237 | 0 | return EOF; |
238 | 0 | if (res < 0) { |
239 | 0 | if (errno == EINTR) |
240 | 0 | goto again; |
241 | 0 | else |
242 | 0 | return EOF; |
243 | 0 | } |
244 | 0 | return getchar(); |
245 | 0 | } |
246 | | |
247 | | #elif defined(GIT_WINDOWS_NATIVE) |
248 | | |
249 | | #define INPUT_PATH "CONIN$" |
250 | | #define OUTPUT_PATH "CONOUT$" |
251 | | #define FORCE_TEXT "t" |
252 | | |
253 | | static int use_stty = 1; |
254 | | static struct string_list stty_restore = STRING_LIST_INIT_DUP; |
255 | | static HANDLE hconin = INVALID_HANDLE_VALUE; |
256 | | static HANDLE hconout = INVALID_HANDLE_VALUE; |
257 | | static DWORD cmode_in, cmode_out; |
258 | | |
259 | | void restore_term(void) |
260 | | { |
261 | | if (use_stty) { |
262 | | int i; |
263 | | struct child_process cp = CHILD_PROCESS_INIT; |
264 | | |
265 | | if (stty_restore.nr == 0) |
266 | | return; |
267 | | |
268 | | strvec_push(&cp.args, "stty"); |
269 | | for (i = 0; i < stty_restore.nr; i++) |
270 | | strvec_push(&cp.args, stty_restore.items[i].string); |
271 | | run_command(&cp); |
272 | | string_list_clear(&stty_restore, 0); |
273 | | return; |
274 | | } |
275 | | |
276 | | sigchain_pop_common(); |
277 | | |
278 | | if (hconin == INVALID_HANDLE_VALUE) |
279 | | return; |
280 | | |
281 | | SetConsoleMode(hconin, cmode_in); |
282 | | CloseHandle(hconin); |
283 | | if (cmode_out) { |
284 | | assert(hconout != INVALID_HANDLE_VALUE); |
285 | | SetConsoleMode(hconout, cmode_out); |
286 | | CloseHandle(hconout); |
287 | | } |
288 | | |
289 | | hconin = hconout = INVALID_HANDLE_VALUE; |
290 | | } |
291 | | |
292 | | int save_term(enum save_term_flags flags) |
293 | | { |
294 | | hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE, |
295 | | FILE_SHARE_READ, NULL, OPEN_EXISTING, |
296 | | FILE_ATTRIBUTE_NORMAL, NULL); |
297 | | if (hconin == INVALID_HANDLE_VALUE) |
298 | | return -1; |
299 | | |
300 | | if (flags & SAVE_TERM_DUPLEX) { |
301 | | hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, |
302 | | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, |
303 | | FILE_ATTRIBUTE_NORMAL, NULL); |
304 | | if (hconout == INVALID_HANDLE_VALUE) |
305 | | goto error; |
306 | | |
307 | | GetConsoleMode(hconout, &cmode_out); |
308 | | } |
309 | | |
310 | | GetConsoleMode(hconin, &cmode_in); |
311 | | use_stty = 0; |
312 | | sigchain_push_common(restore_term_on_signal); |
313 | | return 0; |
314 | | error: |
315 | | CloseHandle(hconin); |
316 | | hconin = INVALID_HANDLE_VALUE; |
317 | | return -1; |
318 | | } |
319 | | |
320 | | static int disable_bits(enum save_term_flags flags, DWORD bits) |
321 | | { |
322 | | if (use_stty) { |
323 | | struct child_process cp = CHILD_PROCESS_INIT; |
324 | | |
325 | | strvec_push(&cp.args, "stty"); |
326 | | |
327 | | if (bits & ENABLE_LINE_INPUT) { |
328 | | string_list_append(&stty_restore, "icanon"); |
329 | | /* |
330 | | * POSIX allows VMIN and VTIME to overlap with VEOF and |
331 | | * VEOL - let's hope that is not the case on windows. |
332 | | */ |
333 | | strvec_pushl(&cp.args, "-icanon", "min", "1", "time", "0", NULL); |
334 | | } |
335 | | |
336 | | if (bits & ENABLE_ECHO_INPUT) { |
337 | | string_list_append(&stty_restore, "echo"); |
338 | | strvec_push(&cp.args, "-echo"); |
339 | | } |
340 | | |
341 | | if (bits & ENABLE_PROCESSED_INPUT) { |
342 | | string_list_append(&stty_restore, "-ignbrk"); |
343 | | string_list_append(&stty_restore, "intr"); |
344 | | string_list_append(&stty_restore, "^c"); |
345 | | strvec_push(&cp.args, "ignbrk"); |
346 | | strvec_push(&cp.args, "intr"); |
347 | | strvec_push(&cp.args, ""); |
348 | | } |
349 | | |
350 | | if (run_command(&cp) == 0) |
351 | | return 0; |
352 | | |
353 | | /* `stty` could not be executed; access the Console directly */ |
354 | | use_stty = 0; |
355 | | } |
356 | | |
357 | | if (save_term(flags) < 0) |
358 | | return -1; |
359 | | |
360 | | if (!SetConsoleMode(hconin, cmode_in & ~bits)) { |
361 | | CloseHandle(hconin); |
362 | | hconin = INVALID_HANDLE_VALUE; |
363 | | sigchain_pop_common(); |
364 | | return -1; |
365 | | } |
366 | | |
367 | | return 0; |
368 | | } |
369 | | |
370 | | static int disable_echo(enum save_term_flags flags) |
371 | | { |
372 | | return disable_bits(flags, ENABLE_ECHO_INPUT); |
373 | | } |
374 | | |
375 | | static int enable_non_canonical(enum save_term_flags flags) |
376 | | { |
377 | | return disable_bits(flags, |
378 | | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); |
379 | | } |
380 | | |
381 | | /* |
382 | | * Override `getchar()`, as the default implementation does not use |
383 | | * `ReadFile()`. |
384 | | * |
385 | | * This poses a problem when we want to see whether the standard |
386 | | * input has more characters, as the default of Git for Windows is to start the |
387 | | * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case |
388 | | * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require |
389 | | * `ReadFile()` to be called first to work properly (it only reports 0 |
390 | | * available bytes, otherwise). |
391 | | * |
392 | | * So let's just override `getchar()` with a version backed by `ReadFile()` and |
393 | | * go our merry ways from here. |
394 | | */ |
395 | | static int mingw_getchar(void) |
396 | | { |
397 | | DWORD read = 0; |
398 | | unsigned char ch; |
399 | | |
400 | | if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL)) |
401 | | return EOF; |
402 | | |
403 | | if (!read) { |
404 | | error("Unexpected 0 read"); |
405 | | return EOF; |
406 | | } |
407 | | |
408 | | return ch; |
409 | | } |
410 | | #define getchar mingw_getchar |
411 | | |
412 | | static int getchar_with_timeout(int timeout) |
413 | | { |
414 | | struct pollfd pfd = { .fd = 0, .events = POLLIN }; |
415 | | |
416 | | if (poll(&pfd, 1, timeout) < 1) |
417 | | return EOF; |
418 | | |
419 | | return getchar(); |
420 | | } |
421 | | |
422 | | #endif |
423 | | |
424 | | #ifndef FORCE_TEXT |
425 | | #define FORCE_TEXT |
426 | | #endif |
427 | | |
428 | | char *git_terminal_prompt(const char *prompt, int echo) |
429 | 0 | { |
430 | 0 | static struct strbuf buf = STRBUF_INIT; |
431 | 0 | int r; |
432 | 0 | FILE *input_fh, *output_fh; |
433 | |
|
434 | 0 | input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT); |
435 | 0 | if (!input_fh) |
436 | 0 | return NULL; |
437 | | |
438 | 0 | output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT); |
439 | 0 | if (!output_fh) { |
440 | 0 | fclose(input_fh); |
441 | 0 | return NULL; |
442 | 0 | } |
443 | | |
444 | 0 | if (!echo && disable_echo(0)) { |
445 | 0 | fclose(input_fh); |
446 | 0 | fclose(output_fh); |
447 | 0 | return NULL; |
448 | 0 | } |
449 | | |
450 | 0 | fputs(prompt, output_fh); |
451 | 0 | fflush(output_fh); |
452 | |
|
453 | 0 | r = strbuf_getline_lf(&buf, input_fh); |
454 | 0 | if (!echo) { |
455 | 0 | putc('\n', output_fh); |
456 | 0 | fflush(output_fh); |
457 | 0 | } |
458 | |
|
459 | 0 | restore_term(); |
460 | 0 | fclose(input_fh); |
461 | 0 | fclose(output_fh); |
462 | |
|
463 | 0 | if (r == EOF) |
464 | 0 | return NULL; |
465 | 0 | return buf.buf; |
466 | 0 | } |
467 | | |
468 | | /* |
469 | | * The `is_known_escape_sequence()` function returns 1 if the passed string |
470 | | * corresponds to an Escape sequence that the terminal capabilities contains. |
471 | | * |
472 | | * To avoid depending on ncurses or other platform-specific libraries, we rely |
473 | | * on the presence of the `infocmp` executable to do the job for us (failing |
474 | | * silently if the program is not available or refused to run). |
475 | | */ |
476 | | struct escape_sequence_entry { |
477 | | struct hashmap_entry entry; |
478 | | char sequence[FLEX_ARRAY]; |
479 | | }; |
480 | | |
481 | | static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED, |
482 | | const struct hashmap_entry *he1, |
483 | | const struct hashmap_entry *he2, |
484 | | const void *keydata) |
485 | 0 | { |
486 | 0 | const struct escape_sequence_entry |
487 | 0 | *e1 = container_of(he1, const struct escape_sequence_entry, entry), |
488 | 0 | *e2 = container_of(he2, const struct escape_sequence_entry, entry); |
489 | 0 | return strcmp(e1->sequence, keydata ? keydata : e2->sequence); |
490 | 0 | } |
491 | | |
492 | | static int is_known_escape_sequence(const char *sequence) |
493 | 0 | { |
494 | 0 | static struct hashmap sequences; |
495 | 0 | static int initialized; |
496 | |
|
497 | 0 | if (!initialized) { |
498 | 0 | struct child_process cp = CHILD_PROCESS_INIT; |
499 | 0 | struct strbuf buf = STRBUF_INIT; |
500 | 0 | char *p, *eol; |
501 | |
|
502 | 0 | hashmap_init(&sequences, sequence_entry_cmp, NULL, 0); |
503 | |
|
504 | 0 | strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL); |
505 | 0 | if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0)) |
506 | 0 | strbuf_setlen(&buf, 0); |
507 | |
|
508 | 0 | for (eol = p = buf.buf; *p; p = eol + 1) { |
509 | 0 | p = strchr(p, '='); |
510 | 0 | if (!p) |
511 | 0 | break; |
512 | 0 | p++; |
513 | 0 | eol = strchrnul(p, '\n'); |
514 | |
|
515 | 0 | if (starts_with(p, "\\E")) { |
516 | 0 | char *comma = memchr(p, ',', eol - p); |
517 | 0 | struct escape_sequence_entry *e; |
518 | |
|
519 | 0 | p[0] = '^'; |
520 | 0 | p[1] = '['; |
521 | 0 | FLEX_ALLOC_MEM(e, sequence, p, comma - p); |
522 | 0 | hashmap_entry_init(&e->entry, |
523 | 0 | strhash(e->sequence)); |
524 | 0 | hashmap_add(&sequences, &e->entry); |
525 | 0 | } |
526 | 0 | if (!*eol) |
527 | 0 | break; |
528 | 0 | } |
529 | 0 | initialized = 1; |
530 | 0 | } |
531 | |
|
532 | 0 | return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence); |
533 | 0 | } |
534 | | |
535 | | int read_key_without_echo(struct strbuf *buf) |
536 | 0 | { |
537 | 0 | static int warning_displayed; |
538 | 0 | int ch; |
539 | |
|
540 | 0 | if (warning_displayed || enable_non_canonical(SAVE_TERM_STDIN) < 0) { |
541 | 0 | if (!warning_displayed) { |
542 | 0 | warning("reading single keystrokes not supported on " |
543 | 0 | "this platform; reading line instead"); |
544 | 0 | warning_displayed = 1; |
545 | 0 | } |
546 | |
|
547 | 0 | return strbuf_getline(buf, stdin); |
548 | 0 | } |
549 | | |
550 | 0 | strbuf_reset(buf); |
551 | 0 | ch = getchar(); |
552 | 0 | if (ch == EOF) { |
553 | 0 | restore_term(); |
554 | 0 | return EOF; |
555 | 0 | } |
556 | 0 | strbuf_addch(buf, ch); |
557 | |
|
558 | 0 | if (ch == '\033' /* ESC */) { |
559 | | /* |
560 | | * We are most likely looking at an Escape sequence. Let's try |
561 | | * to read more bytes, waiting at most half a second, assuming |
562 | | * that the sequence is complete if we did not receive any byte |
563 | | * within that time. |
564 | | * |
565 | | * Start by replacing the Escape byte with ^[ */ |
566 | 0 | strbuf_splice(buf, buf->len - 1, 1, "^[", 2); |
567 | | |
568 | | /* |
569 | | * Query the terminal capabilities once about all the Escape |
570 | | * sequences it knows about, so that we can avoid waiting for |
571 | | * half a second when we know that the sequence is complete. |
572 | | */ |
573 | 0 | while (!is_known_escape_sequence(buf->buf)) { |
574 | 0 | ch = getchar_with_timeout(500); |
575 | 0 | if (ch == EOF) |
576 | 0 | break; |
577 | 0 | strbuf_addch(buf, ch); |
578 | 0 | } |
579 | 0 | } |
580 | |
|
581 | 0 | restore_term(); |
582 | 0 | return 0; |
583 | 0 | } |
584 | | |
585 | | #else |
586 | | |
587 | | int save_term(enum save_term_flags flags) |
588 | | { |
589 | | /* no duplex support available */ |
590 | | return -!!(flags & SAVE_TERM_DUPLEX); |
591 | | } |
592 | | |
593 | | void restore_term(void) |
594 | | { |
595 | | } |
596 | | |
597 | | char *git_terminal_prompt(const char *prompt, int echo) |
598 | | { |
599 | | return getpass(prompt); |
600 | | } |
601 | | |
602 | | int read_key_without_echo(struct strbuf *buf) |
603 | | { |
604 | | static int warning_displayed; |
605 | | const char *res; |
606 | | |
607 | | if (!warning_displayed) { |
608 | | warning("reading single keystrokes not supported on this " |
609 | | "platform; reading line instead"); |
610 | | warning_displayed = 1; |
611 | | } |
612 | | |
613 | | res = getpass(""); |
614 | | strbuf_reset(buf); |
615 | | if (!res) |
616 | | return EOF; |
617 | | strbuf_addstr(buf, res); |
618 | | return 0; |
619 | | } |
620 | | |
621 | | #endif |