mirror of
https://github.com/git/git.git
synced 2024-11-24 02:17:02 +08:00
Merge branch 'pw/add-p-single-key'
Finishing touches to C rewrite of "git add -i" in single-key interactive mode. * pw/add-p-single-key: terminal: restore settings on SIGTSTP terminal: work around macos poll() bug terminal: don't assume stdin is /dev/tty terminal: use flags for save_term()
This commit is contained in:
commit
d723492127
@ -1,4 +1,4 @@
|
||||
#include "git-compat-util.h"
|
||||
#include "cache.h"
|
||||
#include "compat/terminal.h"
|
||||
#include "sigchain.h"
|
||||
#include "strbuf.h"
|
||||
@ -20,39 +20,171 @@ static void restore_term_on_signal(int sig)
|
||||
#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);
|
||||
term_fd = -1;
|
||||
close_term_fd();
|
||||
sigchain_pop_common();
|
||||
reset_job_signals();
|
||||
}
|
||||
|
||||
int save_term(int full_duplex)
|
||||
int save_term(enum save_term_flags flags)
|
||||
{
|
||||
struct sigaction sa;
|
||||
|
||||
if (term_fd < 0)
|
||||
term_fd = open("/dev/tty", O_RDWR);
|
||||
term_fd = ((flags & SAVE_TERM_STDIN)
|
||||
? 0
|
||||
: open("/dev/tty", O_RDWR));
|
||||
if (term_fd < 0)
|
||||
return -1;
|
||||
if (tcgetattr(term_fd, &old_term) < 0)
|
||||
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(tcflag_t bits)
|
||||
static int disable_bits(enum save_term_flags flags, tcflag_t bits)
|
||||
{
|
||||
struct termios t;
|
||||
|
||||
if (save_term(0) < 0)
|
||||
goto error;
|
||||
if (save_term(flags) < 0)
|
||||
return -1;
|
||||
|
||||
t = old_term;
|
||||
|
||||
@ -65,20 +197,50 @@ static int disable_bits(tcflag_t bits)
|
||||
return 0;
|
||||
|
||||
sigchain_pop_common();
|
||||
error:
|
||||
close(term_fd);
|
||||
term_fd = -1;
|
||||
reset_job_signals();
|
||||
close_term_fd();
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int disable_echo(void)
|
||||
static int disable_echo(enum save_term_flags flags)
|
||||
{
|
||||
return disable_bits(ECHO);
|
||||
return disable_bits(flags, ECHO);
|
||||
}
|
||||
|
||||
static int enable_non_canonical(void)
|
||||
static int enable_non_canonical(enum save_term_flags flags)
|
||||
{
|
||||
return disable_bits(ICANON | ECHO);
|
||||
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)
|
||||
@ -126,7 +288,7 @@ void restore_term(void)
|
||||
hconin = hconout = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
int save_term(int full_duplex)
|
||||
int save_term(enum save_term_flags flags)
|
||||
{
|
||||
hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||
@ -134,7 +296,7 @@ int save_term(int full_duplex)
|
||||
if (hconin == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
if (full_duplex) {
|
||||
if (flags & SAVE_TERM_DUPLEX) {
|
||||
hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
@ -154,7 +316,7 @@ error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int disable_bits(DWORD bits)
|
||||
static int disable_bits(enum save_term_flags flags, DWORD bits)
|
||||
{
|
||||
if (use_stty) {
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
@ -191,7 +353,7 @@ static int disable_bits(DWORD bits)
|
||||
use_stty = 0;
|
||||
}
|
||||
|
||||
if (save_term(0) < 0)
|
||||
if (save_term(flags) < 0)
|
||||
return -1;
|
||||
|
||||
if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
|
||||
@ -204,14 +366,15 @@ static int disable_bits(DWORD bits)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int disable_echo(void)
|
||||
static int disable_echo(enum save_term_flags flags)
|
||||
{
|
||||
return disable_bits(ENABLE_ECHO_INPUT);
|
||||
return disable_bits(flags, ENABLE_ECHO_INPUT);
|
||||
}
|
||||
|
||||
static int enable_non_canonical(void)
|
||||
static int enable_non_canonical(enum save_term_flags flags)
|
||||
{
|
||||
return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
|
||||
return disable_bits(flags,
|
||||
ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -245,6 +408,16 @@ static int mingw_getchar(void)
|
||||
}
|
||||
#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
|
||||
@ -267,7 +440,7 @@ char *git_terminal_prompt(const char *prompt, int echo)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!echo && disable_echo()) {
|
||||
if (!echo && disable_echo(0)) {
|
||||
fclose(input_fh);
|
||||
fclose(output_fh);
|
||||
return NULL;
|
||||
@ -361,7 +534,7 @@ int read_key_without_echo(struct strbuf *buf)
|
||||
static int warning_displayed;
|
||||
int ch;
|
||||
|
||||
if (warning_displayed || enable_non_canonical() < 0) {
|
||||
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");
|
||||
@ -395,12 +568,7 @@ int read_key_without_echo(struct strbuf *buf)
|
||||
* 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();
|
||||
ch = getchar_with_timeout(500);
|
||||
if (ch == EOF)
|
||||
break;
|
||||
strbuf_addch(buf, ch);
|
||||
@ -413,10 +581,10 @@ int read_key_without_echo(struct strbuf *buf)
|
||||
|
||||
#else
|
||||
|
||||
int save_term(int full_duplex)
|
||||
int save_term(enum save_term_flags flags)
|
||||
{
|
||||
/* full_duplex == 1, but no support available */
|
||||
return -full_duplex;
|
||||
/* no duplex support available */
|
||||
return -!!(flags & SAVE_TERM_DUPLEX);
|
||||
}
|
||||
|
||||
void restore_term(void)
|
||||
|
@ -1,6 +1,13 @@
|
||||
#ifndef COMPAT_TERMINAL_H
|
||||
#define COMPAT_TERMINAL_H
|
||||
|
||||
enum save_term_flags {
|
||||
/* Save input and output settings */
|
||||
SAVE_TERM_DUPLEX = 1 << 0,
|
||||
/* Save stdin rather than /dev/tty (fails if stdin is not a terminal) */
|
||||
SAVE_TERM_STDIN = 1 << 1,
|
||||
};
|
||||
|
||||
/*
|
||||
* Save the terminal attributes so they can be restored later by a
|
||||
* call to restore_term(). Note that every successful call to
|
||||
@ -8,7 +15,7 @@
|
||||
* attributes have not been changed. Returns 0 on success, -1 on
|
||||
* failure.
|
||||
*/
|
||||
int save_term(int full_duplex);
|
||||
int save_term(enum save_term_flags flags);
|
||||
/* Restore the terminal attributes that were saved with save_term() */
|
||||
void restore_term(void);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user