blob: 5b903e7c7e3e32c25e59efcc151217d00d243d65 [file] [log] [blame]
Jeff King21aeafc2011-12-10 05:41:01 -05001#include "git-compat-util.h"
2#include "compat/terminal.h"
3#include "sigchain.h"
4#include "strbuf.h"
Johannes Schindelin9ea416c2020-01-14 18:43:48 +00005#include "run-command.h"
6#include "string-list.h"
Johannes Schindelin12acdf52020-01-14 18:43:52 +00007#include "hashmap.h"
Jeff King21aeafc2011-12-10 05:41:01 -05008
Jonathan Nieder380395d2013-05-02 20:26:08 +01009#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
Erik Faye-Lundafb43562012-12-04 09:10:41 +010010
Erik Faye-Lundafb43562012-12-04 09:10:41 +010011static void restore_term_on_signal(int sig)
12{
13 restore_term();
14 sigchain_pop(sig);
15 raise(sig);
16}
17
Jeff King21aeafc2011-12-10 05:41:01 -050018#ifdef HAVE_DEV_TTY
19
Erik Faye-Lundafb43562012-12-04 09:10:41 +010020#define INPUT_PATH "/dev/tty"
21#define OUTPUT_PATH "/dev/tty"
22
Jeff King21aeafc2011-12-10 05:41:01 -050023static int term_fd = -1;
24static struct termios old_term;
25
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -070026void restore_term(void)
Jeff King21aeafc2011-12-10 05:41:01 -050027{
28 if (term_fd < 0)
29 return;
30
31 tcsetattr(term_fd, TCSAFLUSH, &old_term);
Erik Faye-Lund9df92e62012-12-04 09:10:39 +010032 close(term_fd);
Jeff King21aeafc2011-12-10 05:41:01 -050033 term_fd = -1;
34}
35
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -070036int save_term(int full_duplex)
37{
38 if (term_fd < 0)
39 term_fd = open("/dev/tty", O_RDWR);
40
41 return (term_fd < 0) ? -1 : tcgetattr(term_fd, &old_term);
42}
43
Johannes Schindelin94ac3c32020-01-14 18:43:47 +000044static int disable_bits(tcflag_t bits)
Erik Faye-Lund9df92e62012-12-04 09:10:39 +010045{
46 struct termios t;
47
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -070048 if (save_term(0) < 0)
Erik Faye-Lund9df92e62012-12-04 09:10:39 +010049 goto error;
50
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -070051 t = old_term;
Erik Faye-Lund9df92e62012-12-04 09:10:39 +010052 sigchain_push_common(restore_term_on_signal);
53
Johannes Schindelin94ac3c32020-01-14 18:43:47 +000054 t.c_lflag &= ~bits;
Erik Faye-Lund9df92e62012-12-04 09:10:39 +010055 if (!tcsetattr(term_fd, TCSAFLUSH, &t))
56 return 0;
57
58error:
59 close(term_fd);
60 term_fd = -1;
61 return -1;
62}
63
Johannes Schindelin94ac3c32020-01-14 18:43:47 +000064static int disable_echo(void)
65{
66 return disable_bits(ECHO);
67}
68
Johannes Schindelina5e46e62020-01-14 18:43:49 +000069static int enable_non_canonical(void)
70{
71 return disable_bits(ICANON | ECHO);
72}
73
Jonathan Nieder380395d2013-05-02 20:26:08 +010074#elif defined(GIT_WINDOWS_NATIVE)
Erik Faye-Lundafb43562012-12-04 09:10:41 +010075
76#define INPUT_PATH "CONIN$"
77#define OUTPUT_PATH "CONOUT$"
78#define FORCE_TEXT "t"
79
Johannes Schindelin9ea416c2020-01-14 18:43:48 +000080static int use_stty = 1;
81static struct string_list stty_restore = STRING_LIST_INIT_DUP;
Erik Faye-Lundafb43562012-12-04 09:10:41 +010082static HANDLE hconin = INVALID_HANDLE_VALUE;
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -070083static HANDLE hconout = INVALID_HANDLE_VALUE;
84static DWORD cmode_in, cmode_out;
Erik Faye-Lundafb43562012-12-04 09:10:41 +010085
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -070086void restore_term(void)
Erik Faye-Lundafb43562012-12-04 09:10:41 +010087{
Johannes Schindelin9ea416c2020-01-14 18:43:48 +000088 if (use_stty) {
89 int i;
90 struct child_process cp = CHILD_PROCESS_INIT;
91
92 if (stty_restore.nr == 0)
93 return;
94
Jeff Kingef8d7ac2020-07-28 16:24:53 -040095 strvec_push(&cp.args, "stty");
Johannes Schindelin9ea416c2020-01-14 18:43:48 +000096 for (i = 0; i < stty_restore.nr; i++)
Jeff Kingef8d7ac2020-07-28 16:24:53 -040097 strvec_push(&cp.args, stty_restore.items[i].string);
Johannes Schindelin9ea416c2020-01-14 18:43:48 +000098 run_command(&cp);
99 string_list_clear(&stty_restore, 0);
100 return;
101 }
102
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100103 if (hconin == INVALID_HANDLE_VALUE)
104 return;
105
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -0700106 SetConsoleMode(hconin, cmode_in);
107 CloseHandle(hconin);
108 if (cmode_out) {
109 assert(hconout != INVALID_HANDLE_VALUE);
110 SetConsoleMode(hconout, cmode_out);
111 CloseHandle(hconout);
112 }
113
114 hconin = hconout = INVALID_HANDLE_VALUE;
115}
116
117int save_term(int full_duplex)
118{
119 hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
120 FILE_SHARE_READ, NULL, OPEN_EXISTING,
121 FILE_ATTRIBUTE_NORMAL, NULL);
122 if (hconin == INVALID_HANDLE_VALUE)
123 return -1;
124
125 if (full_duplex) {
126 hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
127 FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
128 FILE_ATTRIBUTE_NORMAL, NULL);
129 if (hconout == INVALID_HANDLE_VALUE)
130 goto error;
131
132 GetConsoleMode(hconout, &cmode_out);
133 }
134
135 GetConsoleMode(hconin, &cmode_in);
136 use_stty = 0;
137 return 0;
138error:
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100139 CloseHandle(hconin);
140 hconin = INVALID_HANDLE_VALUE;
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -0700141 return -1;
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100142}
143
Johannes Schindelin94ac3c32020-01-14 18:43:47 +0000144static int disable_bits(DWORD bits)
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100145{
Johannes Schindelin9ea416c2020-01-14 18:43:48 +0000146 if (use_stty) {
147 struct child_process cp = CHILD_PROCESS_INIT;
148
Jeff Kingef8d7ac2020-07-28 16:24:53 -0400149 strvec_push(&cp.args, "stty");
Johannes Schindelin9ea416c2020-01-14 18:43:48 +0000150
151 if (bits & ENABLE_LINE_INPUT) {
152 string_list_append(&stty_restore, "icanon");
Jeff Kingef8d7ac2020-07-28 16:24:53 -0400153 strvec_push(&cp.args, "-icanon");
Johannes Schindelin9ea416c2020-01-14 18:43:48 +0000154 }
155
156 if (bits & ENABLE_ECHO_INPUT) {
157 string_list_append(&stty_restore, "echo");
Jeff Kingef8d7ac2020-07-28 16:24:53 -0400158 strvec_push(&cp.args, "-echo");
Johannes Schindelin9ea416c2020-01-14 18:43:48 +0000159 }
160
161 if (bits & ENABLE_PROCESSED_INPUT) {
162 string_list_append(&stty_restore, "-ignbrk");
163 string_list_append(&stty_restore, "intr");
164 string_list_append(&stty_restore, "^c");
Jeff Kingef8d7ac2020-07-28 16:24:53 -0400165 strvec_push(&cp.args, "ignbrk");
166 strvec_push(&cp.args, "intr");
167 strvec_push(&cp.args, "");
Johannes Schindelin9ea416c2020-01-14 18:43:48 +0000168 }
169
170 if (run_command(&cp) == 0)
171 return 0;
172
173 /* `stty` could not be executed; access the Console directly */
174 use_stty = 0;
175 }
176
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -0700177 if (save_term(0) < 0)
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100178 return -1;
179
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100180 sigchain_push_common(restore_term_on_signal);
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -0700181 if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100182 CloseHandle(hconin);
183 hconin = INVALID_HANDLE_VALUE;
184 return -1;
185 }
186
187 return 0;
188}
189
Johannes Schindelin94ac3c32020-01-14 18:43:47 +0000190static int disable_echo(void)
191{
192 return disable_bits(ENABLE_ECHO_INPUT);
193}
194
Johannes Schindelina5e46e62020-01-14 18:43:49 +0000195static int enable_non_canonical(void)
196{
197 return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
198}
Johannes Schindelin94ac3c32020-01-14 18:43:47 +0000199
Johannes Schindeline118f062020-01-14 18:43:51 +0000200/*
201 * Override `getchar()`, as the default implementation does not use
202 * `ReadFile()`.
203 *
204 * This poses a problem when we want to see whether the standard
205 * input has more characters, as the default of Git for Windows is to start the
206 * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
207 * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
208 * `ReadFile()` to be called first to work properly (it only reports 0
209 * available bytes, otherwise).
210 *
211 * So let's just override `getchar()` with a version backed by `ReadFile()` and
212 * go our merry ways from here.
213 */
214static int mingw_getchar(void)
215{
216 DWORD read = 0;
217 unsigned char ch;
218
219 if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
220 return EOF;
221
222 if (!read) {
223 error("Unexpected 0 read");
224 return EOF;
225 }
226
227 return ch;
228}
229#define getchar mingw_getchar
230
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100231#endif
232
233#ifndef FORCE_TEXT
234#define FORCE_TEXT
235#endif
236
Jeff King21aeafc2011-12-10 05:41:01 -0500237char *git_terminal_prompt(const char *prompt, int echo)
238{
239 static struct strbuf buf = STRBUF_INIT;
240 int r;
Erik Faye-Lund67fe7352012-12-04 09:10:40 +0100241 FILE *input_fh, *output_fh;
Jeff King21aeafc2011-12-10 05:41:01 -0500242
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100243 input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
Erik Faye-Lund67fe7352012-12-04 09:10:40 +0100244 if (!input_fh)
Jeff King21aeafc2011-12-10 05:41:01 -0500245 return NULL;
246
Erik Faye-Lundafb43562012-12-04 09:10:41 +0100247 output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT);
Erik Faye-Lund67fe7352012-12-04 09:10:40 +0100248 if (!output_fh) {
249 fclose(input_fh);
Erik Faye-Lund9df92e62012-12-04 09:10:39 +0100250 return NULL;
Jeff King21aeafc2011-12-10 05:41:01 -0500251 }
252
Erik Faye-Lund67fe7352012-12-04 09:10:40 +0100253 if (!echo && disable_echo()) {
254 fclose(input_fh);
255 fclose(output_fh);
256 return NULL;
257 }
Jeff King21aeafc2011-12-10 05:41:01 -0500258
Erik Faye-Lund67fe7352012-12-04 09:10:40 +0100259 fputs(prompt, output_fh);
260 fflush(output_fh);
261
Junio C Hamano8f309ae2016-01-13 15:31:17 -0800262 r = strbuf_getline_lf(&buf, input_fh);
Jeff King21aeafc2011-12-10 05:41:01 -0500263 if (!echo) {
Erik Faye-Lund67fe7352012-12-04 09:10:40 +0100264 putc('\n', output_fh);
265 fflush(output_fh);
Jeff King21aeafc2011-12-10 05:41:01 -0500266 }
267
268 restore_term();
Erik Faye-Lund67fe7352012-12-04 09:10:40 +0100269 fclose(input_fh);
270 fclose(output_fh);
Jeff King21aeafc2011-12-10 05:41:01 -0500271
272 if (r == EOF)
273 return NULL;
274 return buf.buf;
275}
276
Johannes Schindelin12acdf52020-01-14 18:43:52 +0000277/*
278 * The `is_known_escape_sequence()` function returns 1 if the passed string
279 * corresponds to an Escape sequence that the terminal capabilities contains.
280 *
281 * To avoid depending on ncurses or other platform-specific libraries, we rely
282 * on the presence of the `infocmp` executable to do the job for us (failing
283 * silently if the program is not available or refused to run).
284 */
285struct escape_sequence_entry {
286 struct hashmap_entry entry;
287 char sequence[FLEX_ARRAY];
288};
289
290static int sequence_entry_cmp(const void *hashmap_cmp_fn_data,
291 const struct escape_sequence_entry *e1,
292 const struct escape_sequence_entry *e2,
293 const void *keydata)
294{
295 return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
296}
297
298static int is_known_escape_sequence(const char *sequence)
299{
300 static struct hashmap sequences;
301 static int initialized;
302
303 if (!initialized) {
304 struct child_process cp = CHILD_PROCESS_INIT;
305 struct strbuf buf = STRBUF_INIT;
306 char *p, *eol;
307
308 hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp,
309 NULL, 0);
310
Jeff Kingef8d7ac2020-07-28 16:24:53 -0400311 strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
Johannes Schindelin12acdf52020-01-14 18:43:52 +0000312 if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
313 strbuf_setlen(&buf, 0);
314
315 for (eol = p = buf.buf; *p; p = eol + 1) {
316 p = strchr(p, '=');
317 if (!p)
318 break;
319 p++;
320 eol = strchrnul(p, '\n');
321
322 if (starts_with(p, "\\E")) {
323 char *comma = memchr(p, ',', eol - p);
324 struct escape_sequence_entry *e;
325
326 p[0] = '^';
327 p[1] = '[';
328 FLEX_ALLOC_MEM(e, sequence, p, comma - p);
329 hashmap_entry_init(&e->entry,
330 strhash(e->sequence));
331 hashmap_add(&sequences, &e->entry);
332 }
333 if (!*eol)
334 break;
335 }
336 initialized = 1;
337 }
338
339 return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence);
340}
341
Johannes Schindelina5e46e62020-01-14 18:43:49 +0000342int read_key_without_echo(struct strbuf *buf)
343{
344 static int warning_displayed;
345 int ch;
346
347 if (warning_displayed || enable_non_canonical() < 0) {
348 if (!warning_displayed) {
349 warning("reading single keystrokes not supported on "
350 "this platform; reading line instead");
351 warning_displayed = 1;
352 }
353
354 return strbuf_getline(buf, stdin);
355 }
356
357 strbuf_reset(buf);
358 ch = getchar();
359 if (ch == EOF) {
360 restore_term();
361 return EOF;
362 }
Johannes Schindelina5e46e62020-01-14 18:43:49 +0000363 strbuf_addch(buf, ch);
Johannes Schindeline118f062020-01-14 18:43:51 +0000364
365 if (ch == '\033' /* ESC */) {
366 /*
367 * We are most likely looking at an Escape sequence. Let's try
368 * to read more bytes, waiting at most half a second, assuming
369 * that the sequence is complete if we did not receive any byte
370 * within that time.
371 *
372 * Start by replacing the Escape byte with ^[ */
373 strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
374
Johannes Schindelin12acdf52020-01-14 18:43:52 +0000375 /*
376 * Query the terminal capabilities once about all the Escape
377 * sequences it knows about, so that we can avoid waiting for
378 * half a second when we know that the sequence is complete.
379 */
380 while (!is_known_escape_sequence(buf->buf)) {
Johannes Schindeline118f062020-01-14 18:43:51 +0000381 struct pollfd pfd = { .fd = 0, .events = POLLIN };
382
383 if (poll(&pfd, 1, 500) < 1)
384 break;
385
386 ch = getchar();
387 if (ch == EOF)
388 return 0;
389 strbuf_addch(buf, ch);
390 }
391 }
392
Johannes Schindelina5e46e62020-01-14 18:43:49 +0000393 restore_term();
394 return 0;
395}
396
Jeff King21aeafc2011-12-10 05:41:01 -0500397#else
398
Carlo Marcelo Arenas Belóne22b2452021-10-05 00:46:47 -0700399int save_term(int full_duplex)
400{
401 /* full_duplex == 1, but no support available */
402 return -full_duplex;
403}
404
405void restore_term(void)
406{
407}
408
Jeff King21aeafc2011-12-10 05:41:01 -0500409char *git_terminal_prompt(const char *prompt, int echo)
410{
411 return getpass(prompt);
412}
413
Johannes Schindelina5e46e62020-01-14 18:43:49 +0000414int read_key_without_echo(struct strbuf *buf)
415{
416 static int warning_displayed;
417 const char *res;
418
419 if (!warning_displayed) {
420 warning("reading single keystrokes not supported on this "
421 "platform; reading line instead");
422 warning_displayed = 1;
423 }
424
425 res = getpass("");
426 strbuf_reset(buf);
427 if (!res)
428 return EOF;
429 strbuf_addstr(buf, res);
430 return 0;
431}
432
Jeff King21aeafc2011-12-10 05:41:01 -0500433#endif