diff options
Diffstat (limited to 'osdep/terminal-unix.c')
-rw-r--r-- | osdep/terminal-unix.c | 219 |
1 files changed, 157 insertions, 62 deletions
diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c index 78eb4c4618..94d3e6085e 100644 --- a/osdep/terminal-unix.c +++ b/osdep/terminal-unix.c @@ -17,15 +17,12 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ -#include "config.h" - #include <stdlib.h> #include <stdint.h> #include <string.h> #include <signal.h> #include <errno.h> #include <sys/ioctl.h> -#include <pthread.h> #include <assert.h> #include <termios.h> @@ -33,7 +30,7 @@ #include "osdep/io.h" #include "osdep/threads.h" -#include "osdep/polldev.h" +#include "osdep/poll_wrapper.h" #include "common/common.h" #include "misc/bstr.h" @@ -45,11 +42,24 @@ // Timeout in ms after which the (normally ambiguous) ESC key is detected. #define ESC_TIMEOUT 100 -static volatile struct termios tio_orig; -static volatile int tio_orig_set; +// Timeout in ms after which the poll for input is aborted. The FG/BG state is +// tested before every wait, and a positive value allows reactivating input +// processing when mpv is brought to the foreground while it was running in the +// background. In such a situation, an infinite timeout (-1) will keep mpv +// waiting for input without realizing the terminal state changed - and thus +// buffer all keypresses until ENTER is pressed. +#define INPUT_TIMEOUT 1000 + +static struct termios tio_orig; static int tty_in = -1, tty_out = -1; +enum entry_type { + ENTRY_TYPE_KEY = 0, + ENTRY_TYPE_MOUSE_BUTTON, + ENTRY_TYPE_MOUSE_MOVE, +}; + struct key_entry { const char *seq; int mpkey; @@ -57,6 +67,10 @@ struct key_entry { // existing sequence is replaced by the following string. Matching // continues normally, and mpkey is or-ed into the final result. const char *replace; + // Extend the match length by a certain length, so the contents + // after the match can be processed with custom logic. + int skip; + enum entry_type type; }; static const struct key_entry keys[] = { @@ -156,6 +170,18 @@ static const struct key_entry keys[] = { {"\033[29~", MP_KEY_MENU}, {"\033[Z", MP_KEY_TAB | MP_KEY_MODIFIER_SHIFT}, + // Mouse button inputs. 2 bytes of position information requires special processing. + {"\033[M ", MP_MBTN_LEFT | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, + {"\033[M!", MP_MBTN_MID | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, + {"\033[M\"", MP_MBTN_RIGHT | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, + {"\033[M#", MP_INPUT_RELEASE_ALL, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, + {"\033[M`", MP_WHEEL_UP, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, + {"\033[Ma", MP_WHEEL_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_BUTTON}, + // Mouse move inputs. No key events should be generated for them. + {"\033[M@", MP_MBTN_LEFT | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_MOVE}, + {"\033[MA", MP_MBTN_MID | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_MOVE}, + {"\033[MB", MP_MBTN_RIGHT | MP_KEY_STATE_DOWN, .skip = 2, .type = ENTRY_TYPE_MOUSE_MOVE}, + {"\033[MC", MP_INPUT_RELEASE_ALL, .skip = 2, .type = ENTRY_TYPE_MOUSE_MOVE}, {0} }; @@ -213,6 +239,24 @@ static void process_input(struct input_ctx *input_ctx, bool timeout) if (!match) { // normal or unknown key int mods = 0; if (buf.b[0] == '\033') { + if (buf.len > 1 && buf.b[1] == '[') { + // Throw away unrecognized mouse CSI sequences. + // Cannot be handled by the loop below since the bytes + // afterwards can be out of that range. + if (buf.len > 2 && buf.b[2] == 'M') { + skip_buf(&buf, buf.len); + continue; + } + // unknown CSI sequence. wait till it completes + for (int i = 2; i < buf.len; i++) { + if (buf.b[i] >= 0x40 && buf.b[i] <= 0x7E) { + skip_buf(&buf, i+1); + continue; // complete - throw it away + } + } + goto read_more; // not yet complete + } + // non-CSI esc sequence skip_buf(&buf, 1); if (buf.len > 0 && buf.b[0] > 0 && buf.b[0] < 127) { // meta+normal key @@ -227,14 +271,15 @@ static void process_input(struct input_ctx *input_ctx, bool timeout) unsigned char c = buf.b[0]; skip_buf(&buf, 1); if (c < 32) { - c = c <= 25 ? (c + 'a' - 1) : (c - 25 + '2' - 1); + // 1..26 is ^A..^Z, and 27..31 is ^3..^7 + c = c <= 26 ? (c + 'a' - 1) : (c + '3' - 27); mods |= MP_KEY_MODIFIER_CTRL; } mp_input_put_key(input_ctx, c | mods); continue; } - int seq_len = strlen(match->seq); + int seq_len = strlen(match->seq) + match->skip; if (seq_len > buf.len) goto read_more; /* partial match */ @@ -248,15 +293,31 @@ static void process_input(struct input_ctx *input_ctx, bool timeout) continue; } - mp_input_put_key(input_ctx, buf.mods | match->mpkey); + // Parse the initially skipped mouse position information. + // The positions are 1-based character cell positions plus 32. + // Treat mouse position as the pixel values at the center of the cell. + if ((match->type == ENTRY_TYPE_MOUSE_BUTTON || + match->type == ENTRY_TYPE_MOUSE_MOVE) && seq_len >= 6) + { + int num_rows = 80; + int num_cols = 25; + int total_px_width = 0; + int total_px_height = 0; + terminal_get_size2(&num_rows, &num_cols, &total_px_width, &total_px_height); + mp_input_set_mouse_pos(input_ctx, + (buf.b[4] - 32.5) * (total_px_width / num_cols), + (buf.b[5] - 32.5) * (total_px_height / num_rows)); + } + if (match->type != ENTRY_TYPE_MOUSE_MOVE) + mp_input_put_key(input_ctx, buf.mods | match->mpkey); skip_buf(&buf, seq_len); } read_more: ; /* need more bytes */ } -static volatile int getch2_active = 0; -static volatile int getch2_enabled = 0; +static int getch2_active = 0; +static int getch2_enabled = 0; static bool read_terminal; static void enable_kx(bool enable) @@ -280,17 +341,12 @@ static void do_activate_getch2(void) enable_kx(true); struct termios tio_new; - tcgetattr(tty_in,&tio_new); - - if (!tio_orig_set) { - tio_orig = tio_new; - tio_orig_set = 1; - } + tcgetattr(tty_in, &tio_new); tio_new.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */ tio_new.c_cc[VMIN] = 1; tio_new.c_cc[VTIME] = 0; - tcsetattr(tty_in,TCSANOW,&tio_new); + tcsetattr(tty_in, TCSANOW, &tio_new); getch2_active = 1; } @@ -301,12 +357,7 @@ static void do_deactivate_getch2(void) return; enable_kx(false); - - if (tio_orig_set) { - // once set, it will never be set again - // so we can cast away volatile here - tcsetattr(tty_in, TCSANOW, (const struct termios *) &tio_orig); - } + tcsetattr(tty_in, TCSANOW, &tio_orig); getch2_active = 0; } @@ -318,7 +369,7 @@ static int setsigaction(int signo, void (*handler) (int), struct sigaction sa; sa.sa_handler = handler; - if(do_mask) + if (do_mask) sigfillset(&sa.sa_mask); else sigemptyset(&sa.sa_mask); @@ -342,33 +393,32 @@ static void getch2_poll(void) do_deactivate_getch2(); } -static void stop_sighandler(int signum) -{ - do_deactivate_getch2(); +static mp_thread input_thread; +static struct input_ctx *input_ctx; +static int death_pipe[2] = {-1, -1}; +enum { PIPE_STOP, PIPE_CONT }; +static int stop_cont_pipe[2] = {-1, -1}; - // note: for this signal, we use SA_RESETHAND but do NOT mask signals - // so this will invoke the default handler - raise(SIGTSTP); +static void stop_cont_sighandler(int signum) +{ + int saved_errno = errno; + char sig = signum == SIGCONT ? PIPE_CONT : PIPE_STOP; + (void)write(stop_cont_pipe[1], &sig, 1); + errno = saved_errno; } -static void continue_sighandler(int signum) +static void safe_close(int *p) { - // SA_RESETHAND has reset SIGTSTP, so we need to restore it here - setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false); - - getch2_poll(); + if (*p >= 0) + close(*p); + *p = -1; } -static pthread_t input_thread; -static struct input_ctx *input_ctx; -static int death_pipe[2] = {-1, -1}; - -static void close_death_pipe(void) +static void close_sig_pipes(void) { for (int n = 0; n < 2; n++) { - if (death_pipe[n] >= 0) - close(death_pipe[n]); - death_pipe[n] = -1; + safe_close(&death_pipe[n]); + safe_close(&stop_cont_pipe[n]); } } @@ -382,27 +432,59 @@ static void close_tty(void) static void quit_request_sighandler(int signum) { - do_deactivate_getch2(); - + int saved_errno = errno; (void)write(death_pipe[1], &(char){1}, 1); + errno = saved_errno; } -static void *terminal_thread(void *ptr) +static MP_THREAD_VOID terminal_thread(void *ptr) { - mpthread_set_name("terminal"); + mp_thread_set_name("terminal/input"); bool stdin_ok = read_terminal; // if false, we still wait for SIGTERM while (1) { getch2_poll(); - struct pollfd fds[2] = { + struct pollfd fds[3] = { { .events = POLLIN, .fd = death_pipe[0] }, + { .events = POLLIN, .fd = stop_cont_pipe[0] }, { .events = POLLIN, .fd = tty_in } }; - int r = polldev(fds, stdin_ok ? 2 : 1, buf.len ? ESC_TIMEOUT : -1); - if (fds[0].revents) + /* + * if the process isn't in foreground process group, then on macos + * polldev() doesn't rest and gets into 100% cpu usage (see issue #11795) + * with read() returning EIO. but we shouldn't quit on EIO either since + * the process might be foregrounded later. + * + * so just avoid poll-ing tty_in when we know the process is not in the + * foreground. there's a small race window, but the timeout will take + * care of it so it's fine. + */ + bool is_fg = tcgetpgrp(tty_in) == getpgrp(); + int r = polldev(fds, stdin_ok && is_fg ? 3 : 2, buf.len ? ESC_TIMEOUT : INPUT_TIMEOUT); + if (fds[0].revents) { + do_deactivate_getch2(); break; - if (fds[1].revents) { + } + if (fds[1].revents & POLLIN) { + int8_t c = -1; + (void)read(stop_cont_pipe[0], &c, 1); + if (c == PIPE_STOP) { + do_deactivate_getch2(); + if (isatty(STDERR_FILENO)) { + (void)write(STDERR_FILENO, TERM_ESC_RESTORE_CURSOR, + sizeof(TERM_ESC_RESTORE_CURSOR) - 1); + } + // trying to reset SIGTSTP handler to default and raise it will + // result in a race and there's no other way to invoke the + // default handler. so just invoke SIGSTOP since it's + // effectively the same thing. + raise(SIGSTOP); + } else if (c == PIPE_CONT) { + getch2_poll(); + } + } + if (fds[2].revents) { int retval = read(tty_in, &buf.b[buf.len], BUF_LEN - buf.len); - if (!retval || (retval == -1 && (errno == EBADF || errno == EINVAL))) + if (!retval || (retval == -1 && errno != EINTR && errno != EAGAIN && errno != EIO)) break; // EOF/closed if (retval > 0) { buf.len += retval; @@ -420,7 +502,7 @@ static void *terminal_thread(void *ptr) if (cmd) mp_input_queue_cmd(input_ctx, cmd); } - return NULL; + MP_THREAD_RETURN(); } void terminal_setup_getch(struct input_ctx *ictx) @@ -438,16 +520,16 @@ void terminal_setup_getch(struct input_ctx *ictx) input_ctx = ictx; - if (pthread_create(&input_thread, NULL, terminal_thread, NULL)) { + if (mp_thread_create(&input_thread, terminal_thread, NULL)) { input_ctx = NULL; - close_death_pipe(); + close_sig_pipes(); close_tty(); return; } setsigaction(SIGINT, quit_request_sighandler, SA_RESETHAND, false); - setsigaction(SIGQUIT, quit_request_sighandler, SA_RESETHAND, false); - setsigaction(SIGTERM, quit_request_sighandler, SA_RESETHAND, false); + setsigaction(SIGQUIT, quit_request_sighandler, 0, true); + setsigaction(SIGTERM, quit_request_sighandler, 0, true); } void terminal_uninit(void) @@ -466,8 +548,8 @@ void terminal_uninit(void) if (input_ctx) { (void)write(death_pipe[1], &(char){0}, 1); - pthread_join(input_thread, NULL); - close_death_pipe(); + mp_thread_join(input_thread); + close_sig_pipes(); input_ctx = NULL; } @@ -506,20 +588,33 @@ void terminal_get_size2(int *rows, int *cols, int *px_width, int *px_height) *px_height = ws.ws_ypixel; } +void terminal_set_mouse_input(bool enable) +{ + printf(enable ? TERM_ESC_ENABLE_MOUSE : TERM_ESC_DISABLE_MOUSE); + fflush(stdout); +} + void terminal_init(void) { assert(!getch2_enabled); getch2_enabled = 1; + if (mp_make_wakeup_pipe(stop_cont_pipe) < 0) { + getch2_enabled = 0; + return; + } + tty_in = tty_out = open("/dev/tty", O_RDWR | O_CLOEXEC); if (tty_in < 0) { tty_in = STDIN_FILENO; tty_out = STDOUT_FILENO; } + tcgetattr(tty_in, &tio_orig); + // handlers to fix terminal settings - setsigaction(SIGCONT, continue_sighandler, 0, true); - setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false); + setsigaction(SIGCONT, stop_cont_sighandler, 0, true); + setsigaction(SIGTSTP, stop_cont_sighandler, 0, true); setsigaction(SIGTTIN, SIG_IGN, 0, true); setsigaction(SIGTTOU, SIG_IGN, 0, true); |