summaryrefslogtreecommitdiffstats
path: root/osdep/terminal-unix.c
diff options
context:
space:
mode:
Diffstat (limited to 'osdep/terminal-unix.c')
-rw-r--r--osdep/terminal-unix.c232
1 files changed, 170 insertions, 62 deletions
diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c
index 8e4d75f78b..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;
}
@@ -493,20 +575,46 @@ void terminal_get_size(int *w, int *h)
*h = ws.ws_row;
}
+void terminal_get_size2(int *rows, int *cols, int *px_width, int *px_height)
+{
+ struct winsize ws;
+ if (ioctl(tty_in, TIOCGWINSZ, &ws) < 0 || !ws.ws_row || !ws.ws_col
+ || !ws.ws_xpixel || !ws.ws_ypixel)
+ return;
+
+ *rows = ws.ws_row;
+ *cols = ws.ws_col;
+ *px_width = ws.ws_xpixel;
+ *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);