1 #include "git-compat-util.h"
2 #include "compat/terminal.h"
5 #include "run-command.h"
6 #include "string-list.h"
9 #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
11 static void restore_term_on_signal(int sig
)
14 /* restore_term calls sigchain_pop_common */
20 #define INPUT_PATH "/dev/tty"
21 #define OUTPUT_PATH "/dev/tty"
23 static int term_fd
= -1;
24 static struct termios old_term
;
26 void restore_term(void)
31 tcsetattr(term_fd
, TCSAFLUSH
, &old_term
);
34 sigchain_pop_common();
37 int save_term(int full_duplex
)
40 term_fd
= open("/dev/tty", O_RDWR
);
43 if (tcgetattr(term_fd
, &old_term
) < 0)
45 sigchain_push_common(restore_term_on_signal
);
50 static int disable_bits(tcflag_t bits
)
60 if (!tcsetattr(term_fd
, TCSAFLUSH
, &t
))
63 sigchain_pop_common();
70 static int disable_echo(void)
72 return disable_bits(ECHO
);
75 static int enable_non_canonical(void)
77 return disable_bits(ICANON
| ECHO
);
80 #elif defined(GIT_WINDOWS_NATIVE)
82 #define INPUT_PATH "CONIN$"
83 #define OUTPUT_PATH "CONOUT$"
84 #define FORCE_TEXT "t"
86 static int use_stty
= 1;
87 static struct string_list stty_restore
= STRING_LIST_INIT_DUP
;
88 static HANDLE hconin
= INVALID_HANDLE_VALUE
;
89 static HANDLE hconout
= INVALID_HANDLE_VALUE
;
90 static DWORD cmode_in
, cmode_out
;
92 void restore_term(void)
96 struct child_process cp
= CHILD_PROCESS_INIT
;
98 if (stty_restore
.nr
== 0)
101 strvec_push(&cp
.args
, "stty");
102 for (i
= 0; i
< stty_restore
.nr
; i
++)
103 strvec_push(&cp
.args
, stty_restore
.items
[i
].string
);
105 string_list_clear(&stty_restore
, 0);
109 sigchain_pop_common();
111 if (hconin
== INVALID_HANDLE_VALUE
)
114 SetConsoleMode(hconin
, cmode_in
);
117 assert(hconout
!= INVALID_HANDLE_VALUE
);
118 SetConsoleMode(hconout
, cmode_out
);
119 CloseHandle(hconout
);
122 hconin
= hconout
= INVALID_HANDLE_VALUE
;
125 int save_term(int full_duplex
)
127 hconin
= CreateFileA("CONIN$", GENERIC_READ
| GENERIC_WRITE
,
128 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
129 FILE_ATTRIBUTE_NORMAL
, NULL
);
130 if (hconin
== INVALID_HANDLE_VALUE
)
134 hconout
= CreateFileA("CONOUT$", GENERIC_READ
| GENERIC_WRITE
,
135 FILE_SHARE_WRITE
, NULL
, OPEN_EXISTING
,
136 FILE_ATTRIBUTE_NORMAL
, NULL
);
137 if (hconout
== INVALID_HANDLE_VALUE
)
140 GetConsoleMode(hconout
, &cmode_out
);
143 GetConsoleMode(hconin
, &cmode_in
);
145 sigchain_push_common(restore_term_on_signal
);
149 hconin
= INVALID_HANDLE_VALUE
;
153 static int disable_bits(DWORD bits
)
156 struct child_process cp
= CHILD_PROCESS_INIT
;
158 strvec_push(&cp
.args
, "stty");
160 if (bits
& ENABLE_LINE_INPUT
) {
161 string_list_append(&stty_restore
, "icanon");
162 strvec_push(&cp
.args
, "-icanon");
165 if (bits
& ENABLE_ECHO_INPUT
) {
166 string_list_append(&stty_restore
, "echo");
167 strvec_push(&cp
.args
, "-echo");
170 if (bits
& ENABLE_PROCESSED_INPUT
) {
171 string_list_append(&stty_restore
, "-ignbrk");
172 string_list_append(&stty_restore
, "intr");
173 string_list_append(&stty_restore
, "^c");
174 strvec_push(&cp
.args
, "ignbrk");
175 strvec_push(&cp
.args
, "intr");
176 strvec_push(&cp
.args
, "");
179 if (run_command(&cp
) == 0)
182 /* `stty` could not be executed; access the Console directly */
186 if (save_term(0) < 0)
189 if (!SetConsoleMode(hconin
, cmode_in
& ~bits
)) {
191 hconin
= INVALID_HANDLE_VALUE
;
192 sigchain_pop_common();
199 static int disable_echo(void)
201 return disable_bits(ENABLE_ECHO_INPUT
);
204 static int enable_non_canonical(void)
206 return disable_bits(ENABLE_ECHO_INPUT
| ENABLE_LINE_INPUT
| ENABLE_PROCESSED_INPUT
);
210 * Override `getchar()`, as the default implementation does not use
213 * This poses a problem when we want to see whether the standard
214 * input has more characters, as the default of Git for Windows is to start the
215 * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
216 * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
217 * `ReadFile()` to be called first to work properly (it only reports 0
218 * available bytes, otherwise).
220 * So let's just override `getchar()` with a version backed by `ReadFile()` and
221 * go our merry ways from here.
223 static int mingw_getchar(void)
228 if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE
), &ch
, 1, &read
, NULL
))
232 error("Unexpected 0 read");
238 #define getchar mingw_getchar
246 char *git_terminal_prompt(const char *prompt
, int echo
)
248 static struct strbuf buf
= STRBUF_INIT
;
250 FILE *input_fh
, *output_fh
;
252 input_fh
= fopen(INPUT_PATH
, "r" FORCE_TEXT
);
256 output_fh
= fopen(OUTPUT_PATH
, "w" FORCE_TEXT
);
262 if (!echo
&& disable_echo()) {
268 fputs(prompt
, output_fh
);
271 r
= strbuf_getline_lf(&buf
, input_fh
);
273 putc('\n', output_fh
);
287 * The `is_known_escape_sequence()` function returns 1 if the passed string
288 * corresponds to an Escape sequence that the terminal capabilities contains.
290 * To avoid depending on ncurses or other platform-specific libraries, we rely
291 * on the presence of the `infocmp` executable to do the job for us (failing
292 * silently if the program is not available or refused to run).
294 struct escape_sequence_entry
{
295 struct hashmap_entry entry
;
296 char sequence
[FLEX_ARRAY
];
299 static int sequence_entry_cmp(const void *hashmap_cmp_fn_data
,
300 const struct escape_sequence_entry
*e1
,
301 const struct escape_sequence_entry
*e2
,
304 return strcmp(e1
->sequence
, keydata
? keydata
: e2
->sequence
);
307 static int is_known_escape_sequence(const char *sequence
)
309 static struct hashmap sequences
;
310 static int initialized
;
313 struct child_process cp
= CHILD_PROCESS_INIT
;
314 struct strbuf buf
= STRBUF_INIT
;
317 hashmap_init(&sequences
, (hashmap_cmp_fn
)sequence_entry_cmp
,
320 strvec_pushl(&cp
.args
, "infocmp", "-L", "-1", NULL
);
321 if (pipe_command(&cp
, NULL
, 0, &buf
, 0, NULL
, 0))
322 strbuf_setlen(&buf
, 0);
324 for (eol
= p
= buf
.buf
; *p
; p
= eol
+ 1) {
329 eol
= strchrnul(p
, '\n');
331 if (starts_with(p
, "\\E")) {
332 char *comma
= memchr(p
, ',', eol
- p
);
333 struct escape_sequence_entry
*e
;
337 FLEX_ALLOC_MEM(e
, sequence
, p
, comma
- p
);
338 hashmap_entry_init(&e
->entry
,
339 strhash(e
->sequence
));
340 hashmap_add(&sequences
, &e
->entry
);
348 return !!hashmap_get_from_hash(&sequences
, strhash(sequence
), sequence
);
351 int read_key_without_echo(struct strbuf
*buf
)
353 static int warning_displayed
;
356 if (warning_displayed
|| enable_non_canonical() < 0) {
357 if (!warning_displayed
) {
358 warning("reading single keystrokes not supported on "
359 "this platform; reading line instead");
360 warning_displayed
= 1;
363 return strbuf_getline(buf
, stdin
);
372 strbuf_addch(buf
, ch
);
374 if (ch
== '\033' /* ESC */) {
376 * We are most likely looking at an Escape sequence. Let's try
377 * to read more bytes, waiting at most half a second, assuming
378 * that the sequence is complete if we did not receive any byte
381 * Start by replacing the Escape byte with ^[ */
382 strbuf_splice(buf
, buf
->len
- 1, 1, "^[", 2);
385 * Query the terminal capabilities once about all the Escape
386 * sequences it knows about, so that we can avoid waiting for
387 * half a second when we know that the sequence is complete.
389 while (!is_known_escape_sequence(buf
->buf
)) {
390 struct pollfd pfd
= { .fd
= 0, .events
= POLLIN
};
392 if (poll(&pfd
, 1, 500) < 1)
398 strbuf_addch(buf
, ch
);
408 int save_term(int full_duplex
)
410 /* full_duplex == 1, but no support available */
414 void restore_term(void)
418 char *git_terminal_prompt(const char *prompt
, int echo
)
420 return getpass(prompt
);
423 int read_key_without_echo(struct strbuf
*buf
)
425 static int warning_displayed
;
428 if (!warning_displayed
) {
429 warning("reading single keystrokes not supported on this "
430 "platform; reading line instead");
431 warning_displayed
= 1;
438 strbuf_addstr(buf
, res
);