622 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			622 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
| #include "git-compat-util.h"
 | |
| #include "compat/terminal.h"
 | |
| #include "gettext.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_on_signal(int sig)
 | |
| {
 | |
| 	restore_term();
 | |
| 	/* restore_term calls sigchain_pop_common */
 | |
| 	raise(sig);
 | |
| }
 | |
| 
 | |
| #ifdef HAVE_DEV_TTY
 | |
| 
 | |
| #define INPUT_PATH "/dev/tty"
 | |
| #define OUTPUT_PATH "/dev/tty"
 | |
| 
 | |
| static volatile sig_atomic_t term_fd_needs_closing;
 | |
| static int term_fd = -1;
 | |
| static struct termios old_term;
 | |
| 
 | |
| static const char *background_resume_msg;
 | |
| static const char *restore_error_msg;
 | |
| static volatile sig_atomic_t ttou_received;
 | |
| 
 | |
| /* async safe error function for use by signal handlers. */
 | |
| static void write_err(const char *msg)
 | |
| {
 | |
| 	write_in_full(2, "error: ", strlen("error: "));
 | |
| 	write_in_full(2, msg, strlen(msg));
 | |
| 	write_in_full(2, "\n", 1);
 | |
| }
 | |
| 
 | |
| static void print_background_resume_msg(int signo)
 | |
| {
 | |
| 	int saved_errno = errno;
 | |
| 	sigset_t mask;
 | |
| 	struct sigaction old_sa;
 | |
| 	struct sigaction sa = { .sa_handler = SIG_DFL };
 | |
| 
 | |
| 	ttou_received = 1;
 | |
| 	write_err(background_resume_msg);
 | |
| 	sigaction(signo, &sa, &old_sa);
 | |
| 	raise(signo);
 | |
| 	sigemptyset(&mask);
 | |
| 	sigaddset(&mask, signo);
 | |
| 	sigprocmask(SIG_UNBLOCK, &mask, NULL);
 | |
| 	/* Stopped here */
 | |
| 	sigprocmask(SIG_BLOCK, &mask, NULL);
 | |
| 	sigaction(signo, &old_sa, NULL);
 | |
| 	errno = saved_errno;
 | |
| }
 | |
| 
 | |
| static void restore_terminal_on_suspend(int signo)
 | |
| {
 | |
| 	int saved_errno = errno;
 | |
| 	int res;
 | |
| 	struct termios t;
 | |
| 	sigset_t mask;
 | |
| 	struct sigaction old_sa;
 | |
| 	struct sigaction sa = { .sa_handler = SIG_DFL };
 | |
| 	int can_restore = 1;
 | |
| 
 | |
| 	if (tcgetattr(term_fd, &t) < 0)
 | |
| 		can_restore = 0;
 | |
| 
 | |
| 	if (tcsetattr(term_fd, TCSAFLUSH, &old_term) < 0)
 | |
| 		write_err(restore_error_msg);
 | |
| 
 | |
| 	sigaction(signo, &sa, &old_sa);
 | |
| 	raise(signo);
 | |
| 	sigemptyset(&mask);
 | |
| 	sigaddset(&mask, signo);
 | |
| 	sigprocmask(SIG_UNBLOCK, &mask, NULL);
 | |
| 	/* Stopped here */
 | |
| 	sigprocmask(SIG_BLOCK, &mask, NULL);
 | |
| 	sigaction(signo, &old_sa, NULL);
 | |
| 	if (!can_restore) {
 | |
| 		write_err(restore_error_msg);
 | |
| 		goto out;
 | |
| 	}
 | |
| 	/*
 | |
| 	 * If we resume in the background then we receive SIGTTOU when calling
 | |
| 	 * tcsetattr() below. Set up a handler to print an error message in that
 | |
| 	 * case.
 | |
| 	 */
 | |
| 	sigemptyset(&mask);
 | |
| 	sigaddset(&mask, SIGTTOU);
 | |
| 	sa.sa_mask = old_sa.sa_mask;
 | |
| 	sa.sa_handler = print_background_resume_msg;
 | |
| 	sa.sa_flags = SA_RESTART;
 | |
| 	sigaction(SIGTTOU, &sa, &old_sa);
 | |
|  again:
 | |
| 	ttou_received = 0;
 | |
| 	sigprocmask(SIG_UNBLOCK, &mask, NULL);
 | |
| 	res = tcsetattr(term_fd, TCSAFLUSH, &t);
 | |
| 	sigprocmask(SIG_BLOCK, &mask, NULL);
 | |
| 	if (ttou_received)
 | |
| 		goto again;
 | |
| 	else if (res < 0)
 | |
| 		write_err(restore_error_msg);
 | |
| 	sigaction(SIGTTOU, &old_sa, NULL);
 | |
|  out:
 | |
| 	errno = saved_errno;
 | |
| }
 | |
| 
 | |
| static void reset_job_signals(void)
 | |
| {
 | |
| 	if (restore_error_msg) {
 | |
| 		signal(SIGTTIN, SIG_DFL);
 | |
| 		signal(SIGTTOU, SIG_DFL);
 | |
| 		signal(SIGTSTP, SIG_DFL);
 | |
| 		restore_error_msg = NULL;
 | |
| 		background_resume_msg = NULL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void close_term_fd(void)
 | |
| {
 | |
| 	if (term_fd_needs_closing)
 | |
| 		close(term_fd);
 | |
| 	term_fd_needs_closing = 0;
 | |
| 	term_fd = -1;
 | |
| }
 | |
| 
 | |
| void restore_term(void)
 | |
| {
 | |
| 	if (term_fd < 0)
 | |
| 		return;
 | |
| 
 | |
| 	tcsetattr(term_fd, TCSAFLUSH, &old_term);
 | |
| 	close_term_fd();
 | |
| 	sigchain_pop_common();
 | |
| 	reset_job_signals();
 | |
| }
 | |
| 
 | |
| int save_term(enum save_term_flags flags)
 | |
| {
 | |
| 	struct sigaction sa;
 | |
| 
 | |
| 	if (term_fd < 0)
 | |
| 		term_fd = ((flags & SAVE_TERM_STDIN)
 | |
| 			   ? 0
 | |
| 			   : open("/dev/tty", O_RDWR));
 | |
| 	if (term_fd < 0)
 | |
| 		return -1;
 | |
| 	term_fd_needs_closing = !(flags & SAVE_TERM_STDIN);
 | |
| 	if (tcgetattr(term_fd, &old_term) < 0) {
 | |
| 		close_term_fd();
 | |
| 		return -1;
 | |
| 	}
 | |
| 	sigchain_push_common(restore_term_on_signal);
 | |
| 	/*
 | |
| 	 * If job control is disabled then the shell will have set the
 | |
| 	 * disposition of SIGTSTP to SIG_IGN.
 | |
| 	 */
 | |
| 	sigaction(SIGTSTP, NULL, &sa);
 | |
| 	if (sa.sa_handler == SIG_IGN)
 | |
| 		return 0;
 | |
| 
 | |
| 	/* avoid calling gettext() from signal handler */
 | |
| 	background_resume_msg = _("cannot resume in the background, please use 'fg' to resume");
 | |
| 	restore_error_msg = _("cannot restore terminal settings");
 | |
| 	sa.sa_handler = restore_terminal_on_suspend;
 | |
| 	sa.sa_flags = SA_RESTART;
 | |
| 	sigemptyset(&sa.sa_mask);
 | |
| 	sigaddset(&sa.sa_mask, SIGTSTP);
 | |
| 	sigaddset(&sa.sa_mask, SIGTTIN);
 | |
| 	sigaddset(&sa.sa_mask, SIGTTOU);
 | |
| 	sigaction(SIGTSTP, &sa, NULL);
 | |
| 	sigaction(SIGTTIN, &sa, NULL);
 | |
| 	sigaction(SIGTTOU, &sa, NULL);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int disable_bits(enum save_term_flags flags, tcflag_t bits)
 | |
| {
 | |
| 	struct termios t;
 | |
| 
 | |
| 	if (save_term(flags) < 0)
 | |
| 		return -1;
 | |
| 
 | |
| 	t = old_term;
 | |
| 
 | |
| 	t.c_lflag &= ~bits;
 | |
| 	if (bits & ICANON) {
 | |
| 		t.c_cc[VMIN] = 1;
 | |
| 		t.c_cc[VTIME] = 0;
 | |
| 	}
 | |
| 	if (!tcsetattr(term_fd, TCSAFLUSH, &t))
 | |
| 		return 0;
 | |
| 
 | |
| 	sigchain_pop_common();
 | |
| 	reset_job_signals();
 | |
| 	close_term_fd();
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| static int disable_echo(enum save_term_flags flags)
 | |
| {
 | |
| 	return disable_bits(flags, ECHO);
 | |
| }
 | |
| 
 | |
| static int enable_non_canonical(enum save_term_flags flags)
 | |
| {
 | |
| 	return disable_bits(flags, ICANON | ECHO);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * On macos it is not possible to use poll() with a terminal so use select
 | |
|  * instead.
 | |
|  */
 | |
| static int getchar_with_timeout(int timeout)
 | |
| {
 | |
| 	struct timeval tv, *tvp = NULL;
 | |
| 	fd_set readfds;
 | |
| 	int res;
 | |
| 
 | |
|  again:
 | |
| 	if (timeout >= 0) {
 | |
| 		tv.tv_sec = timeout / 1000;
 | |
| 		tv.tv_usec = (timeout % 1000) * 1000;
 | |
| 		tvp = &tv;
 | |
| 	}
 | |
| 
 | |
| 	FD_ZERO(&readfds);
 | |
| 	FD_SET(0, &readfds);
 | |
| 	res = select(1, &readfds, NULL, NULL, tvp);
 | |
| 	if (!res)
 | |
| 		return EOF;
 | |
| 	if (res < 0) {
 | |
| 		if (errno == EINTR)
 | |
| 			goto again;
 | |
| 		else
 | |
| 			return EOF;
 | |
| 	}
 | |
| 	return getchar();
 | |
| }
 | |
| 
 | |
| #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 HANDLE hconout = INVALID_HANDLE_VALUE;
 | |
| static DWORD cmode_in, cmode_out;
 | |
| 
 | |
| 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;
 | |
| 	}
 | |
| 
 | |
| 	sigchain_pop_common();
 | |
| 
 | |
| 	if (hconin == INVALID_HANDLE_VALUE)
 | |
| 		return;
 | |
| 
 | |
| 	SetConsoleMode(hconin, cmode_in);
 | |
| 	CloseHandle(hconin);
 | |
| 	if (cmode_out) {
 | |
| 		assert(hconout != INVALID_HANDLE_VALUE);
 | |
| 		SetConsoleMode(hconout, cmode_out);
 | |
| 		CloseHandle(hconout);
 | |
| 	}
 | |
| 
 | |
| 	hconin = hconout = INVALID_HANDLE_VALUE;
 | |
| }
 | |
| 
 | |
| int save_term(enum save_term_flags flags)
 | |
| {
 | |
| 	hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
 | |
| 	    FILE_SHARE_READ, NULL, OPEN_EXISTING,
 | |
| 	    FILE_ATTRIBUTE_NORMAL, NULL);
 | |
| 	if (hconin == INVALID_HANDLE_VALUE)
 | |
| 		return -1;
 | |
| 
 | |
| 	if (flags & SAVE_TERM_DUPLEX) {
 | |
| 		hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
 | |
| 			FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
 | |
| 			FILE_ATTRIBUTE_NORMAL, NULL);
 | |
| 		if (hconout == INVALID_HANDLE_VALUE)
 | |
| 			goto error;
 | |
| 
 | |
| 		GetConsoleMode(hconout, &cmode_out);
 | |
| 	}
 | |
| 
 | |
| 	GetConsoleMode(hconin, &cmode_in);
 | |
| 	use_stty = 0;
 | |
| 	sigchain_push_common(restore_term_on_signal);
 | |
| 	return 0;
 | |
| error:
 | |
| 	CloseHandle(hconin);
 | |
| 	hconin = INVALID_HANDLE_VALUE;
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| static int disable_bits(enum save_term_flags flags, 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");
 | |
| 			/*
 | |
| 			 * POSIX allows VMIN and VTIME to overlap with VEOF and
 | |
| 			 * VEOL - let's hope that is not the case on windows.
 | |
| 			 */
 | |
| 			strvec_pushl(&cp.args, "-icanon", "min", "1", "time", "0", NULL);
 | |
| 		}
 | |
| 
 | |
| 		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;
 | |
| 	}
 | |
| 
 | |
| 	if (save_term(flags) < 0)
 | |
| 		return -1;
 | |
| 
 | |
| 	if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
 | |
| 		CloseHandle(hconin);
 | |
| 		hconin = INVALID_HANDLE_VALUE;
 | |
| 		sigchain_pop_common();
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int disable_echo(enum save_term_flags flags)
 | |
| {
 | |
| 	return disable_bits(flags, ENABLE_ECHO_INPUT);
 | |
| }
 | |
| 
 | |
| static int enable_non_canonical(enum save_term_flags flags)
 | |
| {
 | |
| 	return disable_bits(flags,
 | |
| 			    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
 | |
| 
 | |
| static int getchar_with_timeout(int timeout)
 | |
| {
 | |
| 	struct pollfd pfd = { .fd = 0, .events = POLLIN };
 | |
| 
 | |
| 	if (poll(&pfd, 1, timeout) < 1)
 | |
| 		return EOF;
 | |
| 
 | |
| 	return 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(0)) {
 | |
| 		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 UNUSED,
 | |
| 			      const struct hashmap_entry *he1,
 | |
| 			      const struct hashmap_entry *he2,
 | |
| 			      const void *keydata)
 | |
| {
 | |
| 	const struct escape_sequence_entry
 | |
| 		*e1 = container_of(he1, const struct escape_sequence_entry, entry),
 | |
| 		*e2 = container_of(he2, const struct escape_sequence_entry, entry);
 | |
| 	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, 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(SAVE_TERM_STDIN) < 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)) {
 | |
| 			ch = getchar_with_timeout(500);
 | |
| 			if (ch == EOF)
 | |
| 				break;
 | |
| 			strbuf_addch(buf, ch);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	restore_term();
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #else
 | |
| 
 | |
| int save_term(enum save_term_flags flags)
 | |
| {
 | |
| 	/* no duplex support available */
 | |
| 	return -!!(flags & SAVE_TERM_DUPLEX);
 | |
| }
 | |
| 
 | |
| void restore_term(void)
 | |
| {
 | |
| }
 | |
| 
 | |
| 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
 |