]>
Commit | Line | Data |
---|---|---|
a64acf72 | 1 | #include "git-compat-util.h" |
21aeafce | 2 | #include "compat/terminal.h" |
f394e093 | 3 | #include "gettext.h" |
21aeafce JK |
4 | #include "sigchain.h" |
5 | #include "strbuf.h" | |
9ea416cb JS |
6 | #include "run-command.h" |
7 | #include "string-list.h" | |
12acdf57 | 8 | #include "hashmap.h" |
21aeafce | 9 | |
380395d0 | 10 | #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE) |
afb43561 | 11 | |
afb43561 EFL |
12 | static void restore_term_on_signal(int sig) |
13 | { | |
14 | restore_term(); | |
f7da7565 | 15 | /* restore_term calls sigchain_pop_common */ |
afb43561 EFL |
16 | raise(sig); |
17 | } | |
18 | ||
21aeafce JK |
19 | #ifdef HAVE_DEV_TTY |
20 | ||
afb43561 EFL |
21 | #define INPUT_PATH "/dev/tty" |
22 | #define OUTPUT_PATH "/dev/tty" | |
23 | ||
e4938ce3 | 24 | static volatile sig_atomic_t term_fd_needs_closing; |
21aeafce JK |
25 | static int term_fd = -1; |
26 | static struct termios old_term; | |
27 | ||
0f584deb PW |
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 | { | |
35 | write_in_full(2, "error: ", strlen("error: ")); | |
36 | write_in_full(2, msg, strlen(msg)); | |
37 | write_in_full(2, "\n", 1); | |
38 | } | |
39 | ||
40 | static void print_background_resume_msg(int signo) | |
41 | { | |
42 | int saved_errno = errno; | |
43 | sigset_t mask; | |
44 | struct sigaction old_sa; | |
45 | struct sigaction sa = { .sa_handler = SIG_DFL }; | |
46 | ||
47 | ttou_received = 1; | |
48 | write_err(background_resume_msg); | |
49 | sigaction(signo, &sa, &old_sa); | |
50 | raise(signo); | |
51 | sigemptyset(&mask); | |
52 | sigaddset(&mask, signo); | |
53 | sigprocmask(SIG_UNBLOCK, &mask, NULL); | |
54 | /* Stopped here */ | |
55 | sigprocmask(SIG_BLOCK, &mask, NULL); | |
56 | sigaction(signo, &old_sa, NULL); | |
57 | errno = saved_errno; | |
58 | } | |
59 | ||
60 | static void restore_terminal_on_suspend(int signo) | |
61 | { | |
62 | int saved_errno = errno; | |
63 | int res; | |
64 | struct termios t; | |
65 | sigset_t mask; | |
66 | struct sigaction old_sa; | |
67 | struct sigaction sa = { .sa_handler = SIG_DFL }; | |
68 | int can_restore = 1; | |
69 | ||
70 | if (tcgetattr(term_fd, &t) < 0) | |
71 | can_restore = 0; | |
72 | ||
73 | if (tcsetattr(term_fd, TCSAFLUSH, &old_term) < 0) | |
74 | write_err(restore_error_msg); | |
75 | ||
76 | sigaction(signo, &sa, &old_sa); | |
77 | raise(signo); | |
78 | sigemptyset(&mask); | |
79 | sigaddset(&mask, signo); | |
80 | sigprocmask(SIG_UNBLOCK, &mask, NULL); | |
81 | /* Stopped here */ | |
82 | sigprocmask(SIG_BLOCK, &mask, NULL); | |
83 | sigaction(signo, &old_sa, NULL); | |
84 | if (!can_restore) { | |
85 | write_err(restore_error_msg); | |
86 | goto out; | |
87 | } | |
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 | sigemptyset(&mask); | |
94 | sigaddset(&mask, SIGTTOU); | |
95 | sa.sa_mask = old_sa.sa_mask; | |
96 | sa.sa_handler = print_background_resume_msg; | |
97 | sa.sa_flags = SA_RESTART; | |
98 | sigaction(SIGTTOU, &sa, &old_sa); | |
99 | again: | |
100 | ttou_received = 0; | |
101 | sigprocmask(SIG_UNBLOCK, &mask, NULL); | |
102 | res = tcsetattr(term_fd, TCSAFLUSH, &t); | |
103 | sigprocmask(SIG_BLOCK, &mask, NULL); | |
104 | if (ttou_received) | |
105 | goto again; | |
106 | else if (res < 0) | |
107 | write_err(restore_error_msg); | |
108 | sigaction(SIGTTOU, &old_sa, NULL); | |
109 | out: | |
110 | errno = saved_errno; | |
111 | } | |
112 | ||
113 | static void reset_job_signals(void) | |
114 | { | |
115 | if (restore_error_msg) { | |
116 | signal(SIGTTIN, SIG_DFL); | |
117 | signal(SIGTTOU, SIG_DFL); | |
118 | signal(SIGTSTP, SIG_DFL); | |
119 | restore_error_msg = NULL; | |
120 | background_resume_msg = NULL; | |
121 | } | |
122 | } | |
123 | ||
e4938ce3 PW |
124 | static void close_term_fd(void) |
125 | { | |
126 | if (term_fd_needs_closing) | |
127 | close(term_fd); | |
128 | term_fd_needs_closing = 0; | |
129 | term_fd = -1; | |
130 | } | |
131 | ||
e22b245e | 132 | void restore_term(void) |
21aeafce JK |
133 | { |
134 | if (term_fd < 0) | |
135 | return; | |
136 | ||
137 | tcsetattr(term_fd, TCSAFLUSH, &old_term); | |
e4938ce3 | 138 | close_term_fd(); |
f7da7565 | 139 | sigchain_pop_common(); |
0f584deb | 140 | reset_job_signals(); |
21aeafce JK |
141 | } |
142 | ||
02af15de | 143 | int save_term(enum save_term_flags flags) |
e22b245e | 144 | { |
0f584deb PW |
145 | struct sigaction sa; |
146 | ||
e22b245e | 147 | if (term_fd < 0) |
e4938ce3 PW |
148 | term_fd = ((flags & SAVE_TERM_STDIN) |
149 | ? 0 | |
150 | : open("/dev/tty", O_RDWR)); | |
f7da7565 PW |
151 | if (term_fd < 0) |
152 | return -1; | |
e4938ce3 PW |
153 | term_fd_needs_closing = !(flags & SAVE_TERM_STDIN); |
154 | if (tcgetattr(term_fd, &old_term) < 0) { | |
155 | close_term_fd(); | |
f7da7565 | 156 | return -1; |
e4938ce3 | 157 | } |
f7da7565 | 158 | sigchain_push_common(restore_term_on_signal); |
0f584deb PW |
159 | /* |
160 | * If job control is disabled then the shell will have set the | |
161 | * disposition of SIGTSTP to SIG_IGN. | |
162 | */ | |
163 | sigaction(SIGTSTP, NULL, &sa); | |
164 | if (sa.sa_handler == SIG_IGN) | |
165 | return 0; | |
166 | ||
167 | /* avoid calling gettext() from signal handler */ | |
168 | background_resume_msg = _("cannot resume in the background, please use 'fg' to resume"); | |
169 | restore_error_msg = _("cannot restore terminal settings"); | |
170 | sa.sa_handler = restore_terminal_on_suspend; | |
171 | sa.sa_flags = SA_RESTART; | |
172 | sigemptyset(&sa.sa_mask); | |
173 | sigaddset(&sa.sa_mask, SIGTSTP); | |
174 | sigaddset(&sa.sa_mask, SIGTTIN); | |
175 | sigaddset(&sa.sa_mask, SIGTTOU); | |
176 | sigaction(SIGTSTP, &sa, NULL); | |
177 | sigaction(SIGTTIN, &sa, NULL); | |
178 | sigaction(SIGTTOU, &sa, NULL); | |
e22b245e | 179 | |
f7da7565 | 180 | return 0; |
e22b245e CMAB |
181 | } |
182 | ||
02af15de | 183 | static int disable_bits(enum save_term_flags flags, tcflag_t bits) |
9df92e63 EFL |
184 | { |
185 | struct termios t; | |
186 | ||
02af15de | 187 | if (save_term(flags) < 0) |
e4938ce3 | 188 | return -1; |
9df92e63 | 189 | |
e22b245e | 190 | t = old_term; |
9df92e63 | 191 | |
94ac3c31 | 192 | t.c_lflag &= ~bits; |
2c686021 PW |
193 | if (bits & ICANON) { |
194 | t.c_cc[VMIN] = 1; | |
195 | t.c_cc[VTIME] = 0; | |
196 | } | |
9df92e63 EFL |
197 | if (!tcsetattr(term_fd, TCSAFLUSH, &t)) |
198 | return 0; | |
199 | ||
f7da7565 | 200 | sigchain_pop_common(); |
0f584deb | 201 | reset_job_signals(); |
e4938ce3 | 202 | close_term_fd(); |
9df92e63 EFL |
203 | return -1; |
204 | } | |
205 | ||
02af15de | 206 | static int disable_echo(enum save_term_flags flags) |
94ac3c31 | 207 | { |
02af15de | 208 | return disable_bits(flags, ECHO); |
94ac3c31 JS |
209 | } |
210 | ||
02af15de | 211 | static int enable_non_canonical(enum save_term_flags flags) |
a5e46e6b | 212 | { |
02af15de | 213 | return disable_bits(flags, ICANON | ECHO); |
a5e46e6b JS |
214 | } |
215 | ||
6606d99b PW |
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 | { | |
222 | struct timeval tv, *tvp = NULL; | |
223 | fd_set readfds; | |
224 | int res; | |
225 | ||
0f584deb | 226 | again: |
6606d99b PW |
227 | if (timeout >= 0) { |
228 | tv.tv_sec = timeout / 1000; | |
229 | tv.tv_usec = (timeout % 1000) * 1000; | |
230 | tvp = &tv; | |
231 | } | |
232 | ||
233 | FD_ZERO(&readfds); | |
234 | FD_SET(0, &readfds); | |
235 | res = select(1, &readfds, NULL, NULL, tvp); | |
0f584deb | 236 | if (!res) |
6606d99b | 237 | return EOF; |
0f584deb PW |
238 | if (res < 0) { |
239 | if (errno == EINTR) | |
240 | goto again; | |
241 | else | |
242 | return EOF; | |
243 | } | |
6606d99b PW |
244 | return getchar(); |
245 | } | |
246 | ||
380395d0 | 247 | #elif defined(GIT_WINDOWS_NATIVE) |
afb43561 EFL |
248 | |
249 | #define INPUT_PATH "CONIN$" | |
250 | #define OUTPUT_PATH "CONOUT$" | |
251 | #define FORCE_TEXT "t" | |
252 | ||
9ea416cb JS |
253 | static int use_stty = 1; |
254 | static struct string_list stty_restore = STRING_LIST_INIT_DUP; | |
afb43561 | 255 | static HANDLE hconin = INVALID_HANDLE_VALUE; |
e22b245e CMAB |
256 | static HANDLE hconout = INVALID_HANDLE_VALUE; |
257 | static DWORD cmode_in, cmode_out; | |
afb43561 | 258 | |
e22b245e | 259 | void restore_term(void) |
afb43561 | 260 | { |
9ea416cb JS |
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 | ||
ef8d7ac4 | 268 | strvec_push(&cp.args, "stty"); |
9ea416cb | 269 | for (i = 0; i < stty_restore.nr; i++) |
ef8d7ac4 | 270 | strvec_push(&cp.args, stty_restore.items[i].string); |
9ea416cb JS |
271 | run_command(&cp); |
272 | string_list_clear(&stty_restore, 0); | |
273 | return; | |
274 | } | |
275 | ||
f7da7565 PW |
276 | sigchain_pop_common(); |
277 | ||
afb43561 EFL |
278 | if (hconin == INVALID_HANDLE_VALUE) |
279 | return; | |
280 | ||
e22b245e CMAB |
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 | ||
02af15de | 292 | int save_term(enum save_term_flags flags) |
e22b245e CMAB |
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 | ||
02af15de | 300 | if (flags & SAVE_TERM_DUPLEX) { |
e22b245e CMAB |
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; | |
f7da7565 | 312 | sigchain_push_common(restore_term_on_signal); |
e22b245e CMAB |
313 | return 0; |
314 | error: | |
afb43561 EFL |
315 | CloseHandle(hconin); |
316 | hconin = INVALID_HANDLE_VALUE; | |
e22b245e | 317 | return -1; |
afb43561 EFL |
318 | } |
319 | ||
02af15de | 320 | static int disable_bits(enum save_term_flags flags, DWORD bits) |
afb43561 | 321 | { |
9ea416cb JS |
322 | if (use_stty) { |
323 | struct child_process cp = CHILD_PROCESS_INIT; | |
324 | ||
ef8d7ac4 | 325 | strvec_push(&cp.args, "stty"); |
9ea416cb JS |
326 | |
327 | if (bits & ENABLE_LINE_INPUT) { | |
328 | string_list_append(&stty_restore, "icanon"); | |
2c686021 PW |
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); | |
9ea416cb JS |
334 | } |
335 | ||
336 | if (bits & ENABLE_ECHO_INPUT) { | |
337 | string_list_append(&stty_restore, "echo"); | |
ef8d7ac4 | 338 | strvec_push(&cp.args, "-echo"); |
9ea416cb JS |
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"); | |
ef8d7ac4 JK |
345 | strvec_push(&cp.args, "ignbrk"); |
346 | strvec_push(&cp.args, "intr"); | |
347 | strvec_push(&cp.args, ""); | |
9ea416cb JS |
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 | ||
02af15de | 357 | if (save_term(flags) < 0) |
afb43561 EFL |
358 | return -1; |
359 | ||
e22b245e | 360 | if (!SetConsoleMode(hconin, cmode_in & ~bits)) { |
afb43561 EFL |
361 | CloseHandle(hconin); |
362 | hconin = INVALID_HANDLE_VALUE; | |
f7da7565 | 363 | sigchain_pop_common(); |
afb43561 EFL |
364 | return -1; |
365 | } | |
366 | ||
367 | return 0; | |
368 | } | |
369 | ||
02af15de | 370 | static int disable_echo(enum save_term_flags flags) |
94ac3c31 | 371 | { |
02af15de | 372 | return disable_bits(flags, ENABLE_ECHO_INPUT); |
94ac3c31 JS |
373 | } |
374 | ||
02af15de | 375 | static int enable_non_canonical(enum save_term_flags flags) |
a5e46e6b | 376 | { |
02af15de PW |
377 | return disable_bits(flags, |
378 | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); | |
a5e46e6b | 379 | } |
94ac3c31 | 380 | |
e118f063 JS |
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 | ||
6606d99b PW |
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 | ||
afb43561 EFL |
422 | #endif |
423 | ||
424 | #ifndef FORCE_TEXT | |
425 | #define FORCE_TEXT | |
426 | #endif | |
427 | ||
21aeafce JK |
428 | char *git_terminal_prompt(const char *prompt, int echo) |
429 | { | |
430 | static struct strbuf buf = STRBUF_INIT; | |
431 | int r; | |
67fe7356 | 432 | FILE *input_fh, *output_fh; |
21aeafce | 433 | |
afb43561 | 434 | input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT); |
67fe7356 | 435 | if (!input_fh) |
21aeafce JK |
436 | return NULL; |
437 | ||
afb43561 | 438 | output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT); |
67fe7356 EFL |
439 | if (!output_fh) { |
440 | fclose(input_fh); | |
441 | return NULL; | |
442 | } | |
443 | ||
02af15de | 444 | if (!echo && disable_echo(0)) { |
67fe7356 EFL |
445 | fclose(input_fh); |
446 | fclose(output_fh); | |
9df92e63 | 447 | return NULL; |
21aeafce JK |
448 | } |
449 | ||
67fe7356 EFL |
450 | fputs(prompt, output_fh); |
451 | fflush(output_fh); | |
21aeafce | 452 | |
8f309aeb | 453 | r = strbuf_getline_lf(&buf, input_fh); |
21aeafce | 454 | if (!echo) { |
67fe7356 EFL |
455 | putc('\n', output_fh); |
456 | fflush(output_fh); | |
21aeafce JK |
457 | } |
458 | ||
459 | restore_term(); | |
67fe7356 EFL |
460 | fclose(input_fh); |
461 | fclose(output_fh); | |
21aeafce JK |
462 | |
463 | if (r == EOF) | |
464 | return NULL; | |
465 | return buf.buf; | |
466 | } | |
467 | ||
12acdf57 JS |
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 | ||
5cf88fd8 | 481 | static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED, |
beaa1d95 JK |
482 | const struct hashmap_entry *he1, |
483 | const struct hashmap_entry *he2, | |
12acdf57 JS |
484 | const void *keydata) |
485 | { | |
beaa1d95 JK |
486 | const struct escape_sequence_entry |
487 | *e1 = container_of(he1, const struct escape_sequence_entry, entry), | |
488 | *e2 = container_of(he2, const struct escape_sequence_entry, entry); | |
12acdf57 JS |
489 | return strcmp(e1->sequence, keydata ? keydata : e2->sequence); |
490 | } | |
491 | ||
492 | static int is_known_escape_sequence(const char *sequence) | |
493 | { | |
494 | static struct hashmap sequences; | |
495 | static int initialized; | |
496 | ||
497 | if (!initialized) { | |
498 | struct child_process cp = CHILD_PROCESS_INIT; | |
499 | struct strbuf buf = STRBUF_INIT; | |
500 | char *p, *eol; | |
501 | ||
beaa1d95 | 502 | hashmap_init(&sequences, sequence_entry_cmp, NULL, 0); |
12acdf57 | 503 | |
ef8d7ac4 | 504 | strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL); |
12acdf57 JS |
505 | if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0)) |
506 | strbuf_setlen(&buf, 0); | |
507 | ||
508 | for (eol = p = buf.buf; *p; p = eol + 1) { | |
509 | p = strchr(p, '='); | |
510 | if (!p) | |
511 | break; | |
512 | p++; | |
513 | eol = strchrnul(p, '\n'); | |
514 | ||
515 | if (starts_with(p, "\\E")) { | |
516 | char *comma = memchr(p, ',', eol - p); | |
517 | struct escape_sequence_entry *e; | |
518 | ||
519 | p[0] = '^'; | |
520 | p[1] = '['; | |
521 | FLEX_ALLOC_MEM(e, sequence, p, comma - p); | |
522 | hashmap_entry_init(&e->entry, | |
523 | strhash(e->sequence)); | |
524 | hashmap_add(&sequences, &e->entry); | |
525 | } | |
526 | if (!*eol) | |
527 | break; | |
528 | } | |
529 | initialized = 1; | |
530 | } | |
531 | ||
532 | return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence); | |
533 | } | |
534 | ||
a5e46e6b JS |
535 | int read_key_without_echo(struct strbuf *buf) |
536 | { | |
537 | static int warning_displayed; | |
538 | int ch; | |
539 | ||
e4938ce3 | 540 | if (warning_displayed || enable_non_canonical(SAVE_TERM_STDIN) < 0) { |
a5e46e6b JS |
541 | if (!warning_displayed) { |
542 | warning("reading single keystrokes not supported on " | |
543 | "this platform; reading line instead"); | |
544 | warning_displayed = 1; | |
545 | } | |
546 | ||
547 | return strbuf_getline(buf, stdin); | |
548 | } | |
549 | ||
550 | strbuf_reset(buf); | |
551 | ch = getchar(); | |
552 | if (ch == EOF) { | |
553 | restore_term(); | |
554 | return EOF; | |
555 | } | |
a5e46e6b | 556 | strbuf_addch(buf, ch); |
e118f063 JS |
557 | |
558 | 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 | strbuf_splice(buf, buf->len - 1, 1, "^[", 2); | |
567 | ||
12acdf57 JS |
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 | while (!is_known_escape_sequence(buf->buf)) { | |
6606d99b | 574 | ch = getchar_with_timeout(500); |
e118f063 | 575 | if (ch == EOF) |
24d7ce38 | 576 | break; |
e118f063 JS |
577 | strbuf_addch(buf, ch); |
578 | } | |
579 | } | |
580 | ||
a5e46e6b JS |
581 | restore_term(); |
582 | return 0; | |
583 | } | |
584 | ||
21aeafce JK |
585 | #else |
586 | ||
02af15de | 587 | int save_term(enum save_term_flags flags) |
e22b245e | 588 | { |
02af15de PW |
589 | /* no duplex support available */ |
590 | return -!!(flags & SAVE_TERM_DUPLEX); | |
e22b245e CMAB |
591 | } |
592 | ||
593 | void restore_term(void) | |
594 | { | |
595 | } | |
596 | ||
21aeafce JK |
597 | char *git_terminal_prompt(const char *prompt, int echo) |
598 | { | |
599 | return getpass(prompt); | |
600 | } | |
601 | ||
a5e46e6b JS |
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 | ||
21aeafce | 621 | #endif |