1 #include "git-compat-util.h"
2 #include "compat/terminal.h"
6 #include "run-command.h"
7 #include "string-list.h"
10 #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
12 static void restore_term_on_signal(int sig
)
15 /* restore_term calls sigchain_pop_common */
21 #define INPUT_PATH "/dev/tty"
22 #define OUTPUT_PATH "/dev/tty"
24 static volatile sig_atomic_t term_fd_needs_closing
;
25 static int term_fd
= -1;
26 static struct termios old_term
;
28 static const char *background_resume_msg
;
29 static const char *restore_error_msg
;
30 static volatile sig_atomic_t ttou_received
;
32 /* async safe error function for use by signal handlers. */
33 static void write_err(const char *msg
)
35 write_in_full(2, "error: ", strlen("error: "));
36 write_in_full(2, msg
, strlen(msg
));
37 write_in_full(2, "\n", 1);
40 static void print_background_resume_msg(int signo
)
42 int saved_errno
= errno
;
44 struct sigaction old_sa
;
45 struct sigaction sa
= { .sa_handler
= SIG_DFL
};
48 write_err(background_resume_msg
);
49 sigaction(signo
, &sa
, &old_sa
);
52 sigaddset(&mask
, signo
);
53 sigprocmask(SIG_UNBLOCK
, &mask
, NULL
);
55 sigprocmask(SIG_BLOCK
, &mask
, NULL
);
56 sigaction(signo
, &old_sa
, NULL
);
60 static void restore_terminal_on_suspend(int signo
)
62 int saved_errno
= errno
;
66 struct sigaction old_sa
;
67 struct sigaction sa
= { .sa_handler
= SIG_DFL
};
70 if (tcgetattr(term_fd
, &t
) < 0)
73 if (tcsetattr(term_fd
, TCSAFLUSH
, &old_term
) < 0)
74 write_err(restore_error_msg
);
76 sigaction(signo
, &sa
, &old_sa
);
79 sigaddset(&mask
, signo
);
80 sigprocmask(SIG_UNBLOCK
, &mask
, NULL
);
82 sigprocmask(SIG_BLOCK
, &mask
, NULL
);
83 sigaction(signo
, &old_sa
, NULL
);
85 write_err(restore_error_msg
);
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
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
);
101 sigprocmask(SIG_UNBLOCK
, &mask
, NULL
);
102 res
= tcsetattr(term_fd
, TCSAFLUSH
, &t
);
103 sigprocmask(SIG_BLOCK
, &mask
, NULL
);
107 write_err(restore_error_msg
);
108 sigaction(SIGTTOU
, &old_sa
, NULL
);
113 static void reset_job_signals(void)
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
;
124 static void close_term_fd(void)
126 if (term_fd_needs_closing
)
128 term_fd_needs_closing
= 0;
132 void restore_term(void)
137 tcsetattr(term_fd
, TCSAFLUSH
, &old_term
);
139 sigchain_pop_common();
143 int save_term(enum save_term_flags flags
)
148 term_fd
= ((flags
& SAVE_TERM_STDIN
)
150 : open("/dev/tty", O_RDWR
));
153 term_fd_needs_closing
= !(flags
& SAVE_TERM_STDIN
);
154 if (tcgetattr(term_fd
, &old_term
) < 0) {
158 sigchain_push_common(restore_term_on_signal
);
160 * If job control is disabled then the shell will have set the
161 * disposition of SIGTSTP to SIG_IGN.
163 sigaction(SIGTSTP
, NULL
, &sa
);
164 if (sa
.sa_handler
== SIG_IGN
)
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
);
183 static int disable_bits(enum save_term_flags flags
, tcflag_t bits
)
187 if (save_term(flags
) < 0)
197 if (!tcsetattr(term_fd
, TCSAFLUSH
, &t
))
200 sigchain_pop_common();
206 static int disable_echo(enum save_term_flags flags
)
208 return disable_bits(flags
, ECHO
);
211 static int enable_non_canonical(enum save_term_flags flags
)
213 return disable_bits(flags
, ICANON
| ECHO
);
217 * On macos it is not possible to use poll() with a terminal so use select
220 static int getchar_with_timeout(int timeout
)
222 struct timeval tv
, *tvp
= NULL
;
228 tv
.tv_sec
= timeout
/ 1000;
229 tv
.tv_usec
= (timeout
% 1000) * 1000;
235 res
= select(1, &readfds
, NULL
, NULL
, tvp
);
247 #elif defined(GIT_WINDOWS_NATIVE)
249 #define INPUT_PATH "CONIN$"
250 #define OUTPUT_PATH "CONOUT$"
251 #define FORCE_TEXT "t"
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
;
259 void restore_term(void)
262 struct child_process cp
= CHILD_PROCESS_INIT
;
264 if (stty_restore
.nr
== 0)
267 strvec_push(&cp
.args
, "stty");
268 for (size_t i
= 0; i
< stty_restore
.nr
; i
++)
269 strvec_push(&cp
.args
, stty_restore
.items
[i
].string
);
271 string_list_clear(&stty_restore
, 0);
275 sigchain_pop_common();
277 if (hconin
== INVALID_HANDLE_VALUE
)
280 SetConsoleMode(hconin
, cmode_in
);
283 assert(hconout
!= INVALID_HANDLE_VALUE
);
284 SetConsoleMode(hconout
, cmode_out
);
285 CloseHandle(hconout
);
288 hconin
= hconout
= INVALID_HANDLE_VALUE
;
291 int save_term(enum save_term_flags flags
)
293 hconin
= CreateFileA("CONIN$", GENERIC_READ
| GENERIC_WRITE
,
294 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
295 FILE_ATTRIBUTE_NORMAL
, NULL
);
296 if (hconin
== INVALID_HANDLE_VALUE
)
299 if (flags
& SAVE_TERM_DUPLEX
) {
300 hconout
= CreateFileA("CONOUT$", GENERIC_READ
| GENERIC_WRITE
,
301 FILE_SHARE_WRITE
, NULL
, OPEN_EXISTING
,
302 FILE_ATTRIBUTE_NORMAL
, NULL
);
303 if (hconout
== INVALID_HANDLE_VALUE
)
306 GetConsoleMode(hconout
, &cmode_out
);
309 GetConsoleMode(hconin
, &cmode_in
);
311 sigchain_push_common(restore_term_on_signal
);
315 hconin
= INVALID_HANDLE_VALUE
;
319 static int disable_bits(enum save_term_flags flags
, DWORD bits
)
322 struct child_process cp
= CHILD_PROCESS_INIT
;
324 strvec_push(&cp
.args
, "stty");
326 if (bits
& ENABLE_LINE_INPUT
) {
327 string_list_append(&stty_restore
, "icanon");
329 * POSIX allows VMIN and VTIME to overlap with VEOF and
330 * VEOL - let's hope that is not the case on windows.
332 strvec_pushl(&cp
.args
, "-icanon", "min", "1", "time", "0", NULL
);
335 if (bits
& ENABLE_ECHO_INPUT
) {
336 string_list_append(&stty_restore
, "echo");
337 strvec_push(&cp
.args
, "-echo");
340 if (bits
& ENABLE_PROCESSED_INPUT
) {
341 string_list_append(&stty_restore
, "-ignbrk");
342 string_list_append(&stty_restore
, "intr");
343 string_list_append(&stty_restore
, "^c");
344 strvec_push(&cp
.args
, "ignbrk");
345 strvec_push(&cp
.args
, "intr");
346 strvec_push(&cp
.args
, "");
349 if (run_command(&cp
) == 0)
352 /* `stty` could not be executed; access the Console directly */
356 if (save_term(flags
) < 0)
359 if (!SetConsoleMode(hconin
, cmode_in
& ~bits
)) {
361 hconin
= INVALID_HANDLE_VALUE
;
362 sigchain_pop_common();
369 static int disable_echo(enum save_term_flags flags
)
371 return disable_bits(flags
, ENABLE_ECHO_INPUT
);
374 static int enable_non_canonical(enum save_term_flags flags
)
376 return disable_bits(flags
,
377 ENABLE_ECHO_INPUT
| ENABLE_LINE_INPUT
| ENABLE_PROCESSED_INPUT
);
381 * Override `getchar()`, as the default implementation does not use
384 * This poses a problem when we want to see whether the standard
385 * input has more characters, as the default of Git for Windows is to start the
386 * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
387 * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
388 * `ReadFile()` to be called first to work properly (it only reports 0
389 * available bytes, otherwise).
391 * So let's just override `getchar()` with a version backed by `ReadFile()` and
392 * go our merry ways from here.
394 static int mingw_getchar(void)
399 if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE
), &ch
, 1, &read
, NULL
))
403 error("Unexpected 0 read");
409 #define getchar mingw_getchar
411 static int getchar_with_timeout(int timeout
)
413 struct pollfd pfd
= { .fd
= 0, .events
= POLLIN
};
415 if (poll(&pfd
, 1, timeout
) < 1)
427 char *git_terminal_prompt(const char *prompt
, int echo
)
429 static struct strbuf buf
= STRBUF_INIT
;
431 FILE *input_fh
, *output_fh
;
433 input_fh
= fopen(INPUT_PATH
, "r" FORCE_TEXT
);
437 output_fh
= fopen(OUTPUT_PATH
, "w" FORCE_TEXT
);
443 if (!echo
&& disable_echo(0)) {
449 fputs(prompt
, output_fh
);
452 r
= strbuf_getline_lf(&buf
, input_fh
);
454 putc('\n', output_fh
);
468 * The `is_known_escape_sequence()` function returns 1 if the passed string
469 * corresponds to an Escape sequence that the terminal capabilities contains.
471 * To avoid depending on ncurses or other platform-specific libraries, we rely
472 * on the presence of the `infocmp` executable to do the job for us (failing
473 * silently if the program is not available or refused to run).
475 struct escape_sequence_entry
{
476 struct hashmap_entry entry
;
477 char sequence
[FLEX_ARRAY
];
480 static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED
,
481 const struct hashmap_entry
*he1
,
482 const struct hashmap_entry
*he2
,
485 const struct escape_sequence_entry
486 *e1
= container_of(he1
, const struct escape_sequence_entry
, entry
),
487 *e2
= container_of(he2
, const struct escape_sequence_entry
, entry
);
488 return strcmp(e1
->sequence
, keydata
? keydata
: e2
->sequence
);
491 static int is_known_escape_sequence(const char *sequence
)
493 static struct hashmap sequences
;
494 static int initialized
;
497 struct child_process cp
= CHILD_PROCESS_INIT
;
498 struct strbuf buf
= STRBUF_INIT
;
501 hashmap_init(&sequences
, sequence_entry_cmp
, NULL
, 0);
503 strvec_pushl(&cp
.args
, "infocmp", "-L", "-1", NULL
);
504 if (pipe_command(&cp
, NULL
, 0, &buf
, 0, NULL
, 0))
505 strbuf_setlen(&buf
, 0);
507 for (eol
= p
= buf
.buf
; *p
; p
= eol
+ 1) {
512 eol
= strchrnul(p
, '\n');
514 if (starts_with(p
, "\\E")) {
515 char *comma
= memchr(p
, ',', eol
- p
);
516 struct escape_sequence_entry
*e
;
520 FLEX_ALLOC_MEM(e
, sequence
, p
, comma
- p
);
521 hashmap_entry_init(&e
->entry
,
522 strhash(e
->sequence
));
523 hashmap_add(&sequences
, &e
->entry
);
531 return !!hashmap_get_from_hash(&sequences
, strhash(sequence
), sequence
);
534 int read_key_without_echo(struct strbuf
*buf
)
536 static int warning_displayed
;
539 if (warning_displayed
|| enable_non_canonical(SAVE_TERM_STDIN
) < 0) {
540 if (!warning_displayed
) {
541 warning("reading single keystrokes not supported on "
542 "this platform; reading line instead");
543 warning_displayed
= 1;
546 return strbuf_getline(buf
, stdin
);
555 strbuf_addch(buf
, ch
);
557 if (ch
== '\033' /* ESC */) {
559 * We are most likely looking at an Escape sequence. Let's try
560 * to read more bytes, waiting at most half a second, assuming
561 * that the sequence is complete if we did not receive any byte
564 * Start by replacing the Escape byte with ^[ */
565 strbuf_splice(buf
, buf
->len
- 1, 1, "^[", 2);
568 * Query the terminal capabilities once about all the Escape
569 * sequences it knows about, so that we can avoid waiting for
570 * half a second when we know that the sequence is complete.
572 while (!is_known_escape_sequence(buf
->buf
)) {
573 ch
= getchar_with_timeout(500);
576 strbuf_addch(buf
, ch
);
586 int save_term(enum save_term_flags flags
)
588 /* no duplex support available */
589 return -!!(flags
& SAVE_TERM_DUPLEX
);
592 void restore_term(void)
596 char *git_terminal_prompt(const char *prompt
, int echo UNUSED
)
598 return getpass(prompt
);
601 int read_key_without_echo(struct strbuf
*buf
)
603 static int warning_displayed
;
606 if (!warning_displayed
) {
607 warning("reading single keystrokes not supported on this "
608 "platform; reading line instead");
609 warning_displayed
= 1;
616 strbuf_addstr(buf
, res
);