123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- #include "git-compat-util.h"
- #include "compat/terminal.h"
- #include "sigchain.h"
- #include "strbuf.h"
- #include "run-command.h"
- #include "string-list.h"
- #include "hashmap.h"
- #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
- static void restore_term(void);
- static void restore_term_on_signal(int sig)
- {
- restore_term();
- sigchain_pop(sig);
- raise(sig);
- }
- #ifdef HAVE_DEV_TTY
- #define INPUT_PATH "/dev/tty"
- #define OUTPUT_PATH "/dev/tty"
- static int term_fd = -1;
- static struct termios old_term;
- static void restore_term(void)
- {
- if (term_fd < 0)
- return;
- tcsetattr(term_fd, TCSAFLUSH, &old_term);
- close(term_fd);
- term_fd = -1;
- }
- static int disable_bits(tcflag_t bits)
- {
- struct termios t;
- term_fd = open("/dev/tty", O_RDWR);
- if (tcgetattr(term_fd, &t) < 0)
- goto error;
- old_term = t;
- sigchain_push_common(restore_term_on_signal);
- t.c_lflag &= ~bits;
- if (!tcsetattr(term_fd, TCSAFLUSH, &t))
- return 0;
- error:
- close(term_fd);
- term_fd = -1;
- return -1;
- }
- static int disable_echo(void)
- {
- return disable_bits(ECHO);
- }
- static int enable_non_canonical(void)
- {
- return disable_bits(ICANON | ECHO);
- }
- #elif defined(GIT_WINDOWS_NATIVE)
- #define INPUT_PATH "CONIN$"
- #define OUTPUT_PATH "CONOUT$"
- #define FORCE_TEXT "t"
- static int use_stty = 1;
- static struct string_list stty_restore = STRING_LIST_INIT_DUP;
- static HANDLE hconin = INVALID_HANDLE_VALUE;
- static DWORD cmode;
- static void restore_term(void)
- {
- if (use_stty) {
- int i;
- struct child_process cp = CHILD_PROCESS_INIT;
- if (stty_restore.nr == 0)
- return;
- strvec_push(&cp.args, "stty");
- for (i = 0; i < stty_restore.nr; i++)
- strvec_push(&cp.args, stty_restore.items[i].string);
- run_command(&cp);
- string_list_clear(&stty_restore, 0);
- return;
- }
- if (hconin == INVALID_HANDLE_VALUE)
- return;
- SetConsoleMode(hconin, cmode);
- CloseHandle(hconin);
- hconin = INVALID_HANDLE_VALUE;
- }
- static int disable_bits(DWORD bits)
- {
- if (use_stty) {
- struct child_process cp = CHILD_PROCESS_INIT;
- strvec_push(&cp.args, "stty");
- if (bits & ENABLE_LINE_INPUT) {
- string_list_append(&stty_restore, "icanon");
- strvec_push(&cp.args, "-icanon");
- }
- if (bits & ENABLE_ECHO_INPUT) {
- string_list_append(&stty_restore, "echo");
- strvec_push(&cp.args, "-echo");
- }
- if (bits & ENABLE_PROCESSED_INPUT) {
- string_list_append(&stty_restore, "-ignbrk");
- string_list_append(&stty_restore, "intr");
- string_list_append(&stty_restore, "^c");
- strvec_push(&cp.args, "ignbrk");
- strvec_push(&cp.args, "intr");
- strvec_push(&cp.args, "");
- }
- if (run_command(&cp) == 0)
- return 0;
- /* `stty` could not be executed; access the Console directly */
- use_stty = 0;
- }
- hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ, NULL, OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL, NULL);
- if (hconin == INVALID_HANDLE_VALUE)
- return -1;
- GetConsoleMode(hconin, &cmode);
- sigchain_push_common(restore_term_on_signal);
- if (!SetConsoleMode(hconin, cmode & ~bits)) {
- CloseHandle(hconin);
- hconin = INVALID_HANDLE_VALUE;
- return -1;
- }
- return 0;
- }
- static int disable_echo(void)
- {
- return disable_bits(ENABLE_ECHO_INPUT);
- }
- static int enable_non_canonical(void)
- {
- return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
- }
- /*
- * Override `getchar()`, as the default implementation does not use
- * `ReadFile()`.
- *
- * This poses a problem when we want to see whether the standard
- * input has more characters, as the default of Git for Windows is to start the
- * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
- * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
- * `ReadFile()` to be called first to work properly (it only reports 0
- * available bytes, otherwise).
- *
- * So let's just override `getchar()` with a version backed by `ReadFile()` and
- * go our merry ways from here.
- */
- static int mingw_getchar(void)
- {
- DWORD read = 0;
- unsigned char ch;
- if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
- return EOF;
- if (!read) {
- error("Unexpected 0 read");
- return EOF;
- }
- return ch;
- }
- #define getchar mingw_getchar
- #endif
- #ifndef FORCE_TEXT
- #define FORCE_TEXT
- #endif
- char *git_terminal_prompt(const char *prompt, int echo)
- {
- static struct strbuf buf = STRBUF_INIT;
- int r;
- FILE *input_fh, *output_fh;
- input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
- if (!input_fh)
- return NULL;
- output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT);
- if (!output_fh) {
- fclose(input_fh);
- return NULL;
- }
- if (!echo && disable_echo()) {
- fclose(input_fh);
- fclose(output_fh);
- return NULL;
- }
- fputs(prompt, output_fh);
- fflush(output_fh);
- r = strbuf_getline_lf(&buf, input_fh);
- if (!echo) {
- putc('\n', output_fh);
- fflush(output_fh);
- }
- restore_term();
- fclose(input_fh);
- fclose(output_fh);
- if (r == EOF)
- return NULL;
- return buf.buf;
- }
- /*
- * The `is_known_escape_sequence()` function returns 1 if the passed string
- * corresponds to an Escape sequence that the terminal capabilities contains.
- *
- * To avoid depending on ncurses or other platform-specific libraries, we rely
- * on the presence of the `infocmp` executable to do the job for us (failing
- * silently if the program is not available or refused to run).
- */
- struct escape_sequence_entry {
- struct hashmap_entry entry;
- char sequence[FLEX_ARRAY];
- };
- static int sequence_entry_cmp(const void *hashmap_cmp_fn_data,
- const struct escape_sequence_entry *e1,
- const struct escape_sequence_entry *e2,
- const void *keydata)
- {
- return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
- }
- static int is_known_escape_sequence(const char *sequence)
- {
- static struct hashmap sequences;
- static int initialized;
- if (!initialized) {
- struct child_process cp = CHILD_PROCESS_INIT;
- struct strbuf buf = STRBUF_INIT;
- char *p, *eol;
- hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp,
- NULL, 0);
- strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
- if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
- strbuf_setlen(&buf, 0);
- for (eol = p = buf.buf; *p; p = eol + 1) {
- p = strchr(p, '=');
- if (!p)
- break;
- p++;
- eol = strchrnul(p, '\n');
- if (starts_with(p, "\\E")) {
- char *comma = memchr(p, ',', eol - p);
- struct escape_sequence_entry *e;
- p[0] = '^';
- p[1] = '[';
- FLEX_ALLOC_MEM(e, sequence, p, comma - p);
- hashmap_entry_init(&e->entry,
- strhash(e->sequence));
- hashmap_add(&sequences, &e->entry);
- }
- if (!*eol)
- break;
- }
- initialized = 1;
- }
- return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence);
- }
- int read_key_without_echo(struct strbuf *buf)
- {
- static int warning_displayed;
- int ch;
- if (warning_displayed || enable_non_canonical() < 0) {
- if (!warning_displayed) {
- warning("reading single keystrokes not supported on "
- "this platform; reading line instead");
- warning_displayed = 1;
- }
- return strbuf_getline(buf, stdin);
- }
- strbuf_reset(buf);
- ch = getchar();
- if (ch == EOF) {
- restore_term();
- return EOF;
- }
- strbuf_addch(buf, ch);
- if (ch == '\033' /* ESC */) {
- /*
- * We are most likely looking at an Escape sequence. Let's try
- * to read more bytes, waiting at most half a second, assuming
- * that the sequence is complete if we did not receive any byte
- * within that time.
- *
- * Start by replacing the Escape byte with ^[ */
- strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
- /*
- * Query the terminal capabilities once about all the Escape
- * sequences it knows about, so that we can avoid waiting for
- * half a second when we know that the sequence is complete.
- */
- while (!is_known_escape_sequence(buf->buf)) {
- struct pollfd pfd = { .fd = 0, .events = POLLIN };
- if (poll(&pfd, 1, 500) < 1)
- break;
- ch = getchar();
- if (ch == EOF)
- return 0;
- strbuf_addch(buf, ch);
- }
- }
- restore_term();
- return 0;
- }
- #else
- char *git_terminal_prompt(const char *prompt, int echo)
- {
- return getpass(prompt);
- }
- int read_key_without_echo(struct strbuf *buf)
- {
- static int warning_displayed;
- const char *res;
- if (!warning_displayed) {
- warning("reading single keystrokes not supported on this "
- "platform; reading line instead");
- warning_displayed = 1;
- }
- res = getpass("");
- strbuf_reset(buf);
- if (!res)
- return EOF;
- strbuf_addstr(buf, res);
- return 0;
- }
- #endif
|