mirror of https://github.com/mpv-player/mpv
terminal-unix: new input handling code
This is independent of terminfo/termcap, and supports more keys. Originally, the goal was just extending the set of supported key sequences, but since the terminfo stuff actually makes this much harder, and since it's a big blob of bloated legacy crap, just drop it. Instead, use hardcoded tables. It's pretty easy to get on the same level as the old code (with fewer LOC), and we avoid additional error situations, such as mallocs which could fail (the old code just ignores malloc failures). We also try to support some xterm escape sequences, which are in relatively widespread use. (I'm not sure about the urxvt ones.) Trying to deal with xterm shift/ctrl/alt modifiers is probably a bit overcomplicated, and only deals with prefixes - xterm randomly uses prefix sequences for some keys, and suffixes for others (what the heck). Additionally, try to drop unknown escape codes. This basically relies on a trick: in almost 100% of all situations, a read() call will actually return complete sequences (possibly because of pipe semantics and atomic writes from the terminal emulator?), so it's easy to drop unknown sequences. This prevents that they trigger random key bindings as the code interprets the part after ESC as normal keys. This also drops the use of terminfo for sending smkx/rmkx. It seems even vt100 (to which virtually everything non-legacy is reasonably compatible with) supports the codes we hardcode, so it should be fine. This commit actually changes only the code if terminfo/termcap are not found. The next commit will make this code default.
This commit is contained in:
parent
58a9610acf
commit
4b5c3ea7a7
|
@ -46,6 +46,7 @@
|
|||
#include "bstr/bstr.h"
|
||||
#include "input/input.h"
|
||||
#include "input/keycodes.h"
|
||||
#include "misc/ctype.h"
|
||||
#include "terminal.h"
|
||||
|
||||
#if HAVE_TERMIOS
|
||||
|
@ -53,6 +54,225 @@ static volatile struct termios tio_orig;
|
|||
static volatile int tio_orig_set;
|
||||
#endif
|
||||
|
||||
#if !(HAVE_TERMINFO || HAVE_TERMCAP)
|
||||
|
||||
struct key_entry {
|
||||
const char *seq;
|
||||
int mpkey;
|
||||
// If this is not NULL, then if seq is matched as unique prefix, the
|
||||
// existing sequence is replaced by the following string. Matching
|
||||
// continues normally, and mpkey is or-ed into the final result.
|
||||
const char *replace;
|
||||
};
|
||||
|
||||
static const struct key_entry keys[] = {
|
||||
{"\011", MP_KEY_TAB},
|
||||
{"\012", MP_KEY_ENTER},
|
||||
{"\177", MP_KEY_BS},
|
||||
|
||||
{"\033[1~", MP_KEY_HOME},
|
||||
{"\033[2~", MP_KEY_INS},
|
||||
{"\033[3~", MP_KEY_DEL},
|
||||
{"\033[4~", MP_KEY_END},
|
||||
{"\033[5~", MP_KEY_PGUP},
|
||||
{"\033[6~", MP_KEY_PGDWN},
|
||||
{"\033[7~", MP_KEY_HOME},
|
||||
{"\033[8~", MP_KEY_END},
|
||||
|
||||
{"\033[11~", MP_KEY_F+1},
|
||||
{"\033[12~", MP_KEY_F+2},
|
||||
{"\033[13~", MP_KEY_F+3},
|
||||
{"\033[14~", MP_KEY_F+4},
|
||||
{"\033[15~", MP_KEY_F+5},
|
||||
{"\033[17~", MP_KEY_F+6},
|
||||
{"\033[18~", MP_KEY_F+7},
|
||||
{"\033[19~", MP_KEY_F+8},
|
||||
{"\033[20~", MP_KEY_F+9},
|
||||
{"\033[21~", MP_KEY_F+10},
|
||||
{"\033[23~", MP_KEY_F+11},
|
||||
{"\033[24~", MP_KEY_F+12},
|
||||
|
||||
{"\033[A", MP_KEY_UP},
|
||||
{"\033[B", MP_KEY_DOWN},
|
||||
{"\033[C", MP_KEY_RIGHT},
|
||||
{"\033[D", MP_KEY_LEFT},
|
||||
{"\033[E", MP_KEY_KP5},
|
||||
{"\033[F", MP_KEY_END},
|
||||
{"\033[H", MP_KEY_HOME},
|
||||
|
||||
{"\033[[A", MP_KEY_F+1},
|
||||
{"\033[[B", MP_KEY_F+2},
|
||||
{"\033[[C", MP_KEY_F+3},
|
||||
{"\033[[D", MP_KEY_F+4},
|
||||
{"\033[[E", MP_KEY_F+5},
|
||||
|
||||
{"\033OE", MP_KEY_KP5}, // mintty?
|
||||
{"\033OM", MP_KEY_KPENTER},
|
||||
{"\033OP", MP_KEY_F+1},
|
||||
{"\033OQ", MP_KEY_F+2},
|
||||
{"\033OR", MP_KEY_F+3},
|
||||
{"\033OS", MP_KEY_F+4},
|
||||
|
||||
{"\033Oa", MP_KEY_UP | MP_KEY_MODIFIER_CTRL}, // urxvt
|
||||
{"\033Ob", MP_KEY_DOWN | MP_KEY_MODIFIER_CTRL},
|
||||
{"\033Oc", MP_KEY_RIGHT | MP_KEY_MODIFIER_CTRL},
|
||||
{"\033Od", MP_KEY_LEFT | MP_KEY_MODIFIER_CTRL},
|
||||
{"\033Oj", '*'}, // also keypad, but we don't have separate codes for them
|
||||
{"\033Ok", '+'},
|
||||
{"\033Om", '-'},
|
||||
{"\033On", MP_KEY_KPDEC},
|
||||
{"\033Oo", '/'},
|
||||
{"\033Op", MP_KEY_KP0},
|
||||
{"\033Oq", MP_KEY_KP1},
|
||||
{"\033Or", MP_KEY_KP2},
|
||||
{"\033Os", MP_KEY_KP3},
|
||||
{"\033Ot", MP_KEY_KP4},
|
||||
{"\033Ou", MP_KEY_KP5},
|
||||
{"\033Ov", MP_KEY_KP6},
|
||||
{"\033Ow", MP_KEY_KP7},
|
||||
{"\033Ox", MP_KEY_KP8},
|
||||
{"\033Oy", MP_KEY_KP9},
|
||||
|
||||
{"\033[a", MP_KEY_UP | MP_KEY_MODIFIER_SHIFT}, // urxvt
|
||||
{"\033[b", MP_KEY_DOWN | MP_KEY_MODIFIER_SHIFT},
|
||||
{"\033[c", MP_KEY_RIGHT | MP_KEY_MODIFIER_SHIFT},
|
||||
{"\033[d", MP_KEY_LEFT | MP_KEY_MODIFIER_SHIFT},
|
||||
{"\033[2^", MP_KEY_INS | MP_KEY_MODIFIER_CTRL},
|
||||
{"\033[3^", MP_KEY_DEL | MP_KEY_MODIFIER_CTRL},
|
||||
{"\033[5^", MP_KEY_PGUP | MP_KEY_MODIFIER_CTRL},
|
||||
{"\033[6^", MP_KEY_PGDWN | MP_KEY_MODIFIER_CTRL},
|
||||
{"\033[7^", MP_KEY_HOME | MP_KEY_MODIFIER_CTRL},
|
||||
{"\033[8^", MP_KEY_END | MP_KEY_MODIFIER_CTRL},
|
||||
|
||||
{"\033[1;2", MP_KEY_MODIFIER_SHIFT, .replace = "\033["}, // xterm
|
||||
{"\033[1;3", MP_KEY_MODIFIER_ALT, .replace = "\033["},
|
||||
{"\033[1;5", MP_KEY_MODIFIER_CTRL, .replace = "\033["},
|
||||
{"\033[1;4", MP_KEY_MODIFIER_ALT | MP_KEY_MODIFIER_SHIFT, .replace = "\033["},
|
||||
{"\033[1;6", MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_SHIFT, .replace = "\033["},
|
||||
{"\033[1;7", MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_ALT, .replace = "\033["},
|
||||
{"\033[1;8",
|
||||
MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_ALT | MP_KEY_MODIFIER_SHIFT,
|
||||
.replace = "\033["},
|
||||
|
||||
{"\033[29~", MP_KEY_MENU},
|
||||
{"\033[Z", MP_KEY_TAB | MP_KEY_MODIFIER_SHIFT},
|
||||
|
||||
{0}
|
||||
};
|
||||
|
||||
#define BUF_LEN 256
|
||||
|
||||
struct termbuf {
|
||||
unsigned char b[BUF_LEN];
|
||||
int len;
|
||||
int mods;
|
||||
};
|
||||
|
||||
static void skip_buf(struct termbuf *b, unsigned int count)
|
||||
{
|
||||
assert(count <= b->len);
|
||||
|
||||
memmove(&b->b[0], &b->b[count], b->len - count);
|
||||
b->len -= count;
|
||||
b->mods = 0;
|
||||
}
|
||||
|
||||
static struct termbuf buf;
|
||||
|
||||
static bool getch2(struct input_ctx *input_ctx)
|
||||
{
|
||||
int retval = read(0, &buf.b[buf.len], BUF_LEN - buf.len);
|
||||
/* Return false on EOF to stop running select() on the FD, as it'd
|
||||
* trigger all the time. Note that it's possible to get temporary
|
||||
* EOF on terminal if the user presses ctrl-d, but that shouldn't
|
||||
* happen if the terminal state change done in getch2_enable()
|
||||
* works.
|
||||
*/
|
||||
if (retval == 0)
|
||||
return false;
|
||||
if (retval == -1)
|
||||
return errno != EBADF && errno != EINVAL;
|
||||
buf.len += retval;
|
||||
|
||||
while (buf.len) {
|
||||
int utf8_len = bstr_parse_utf8_code_length(buf.b[0]);
|
||||
if (utf8_len > 1) {
|
||||
if (buf.len < utf8_len)
|
||||
goto read_more;
|
||||
|
||||
mp_input_put_key_utf8(input_ctx, buf.mods, (bstr){buf.b, utf8_len});
|
||||
skip_buf(&buf, utf8_len);
|
||||
continue;
|
||||
}
|
||||
|
||||
const struct key_entry *match = NULL; // may be a partial match
|
||||
for (int n = 0; keys[n].seq; n++) {
|
||||
const struct key_entry *e = &keys[n];
|
||||
if (memcmp(e->seq, buf.b, MPMIN(buf.len, strlen(e->seq))) == 0) {
|
||||
if (match)
|
||||
goto read_more; /* need more bytes to disambiguate */
|
||||
match = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) { // normal or unknown key
|
||||
if (buf.b[0] == '\033') {
|
||||
skip_buf(&buf, 1);
|
||||
if (buf.len > 0 && mp_isalnum(buf.b[0])) { // meta+normal key
|
||||
mp_input_put_key(input_ctx, buf.b[0] | MP_KEY_MODIFIER_ALT);
|
||||
skip_buf(&buf, 1);
|
||||
} else if (buf.len == 1 && buf.b[0] == '\033') {
|
||||
mp_input_put_key(input_ctx, MP_KEY_ESC);
|
||||
skip_buf(&buf, 1);
|
||||
} else {
|
||||
// Throw it away. Typically, this will be a complete,
|
||||
// unsupported sequence, and dropping this will skip it.
|
||||
skip_buf(&buf, buf.len);
|
||||
}
|
||||
} else {
|
||||
mp_input_put_key(input_ctx, buf.b[0]);
|
||||
skip_buf(&buf, 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int seq_len = strlen(match->seq);
|
||||
if (seq_len > buf.len)
|
||||
goto read_more; /* partial match */
|
||||
|
||||
if (match->replace) {
|
||||
int rep = strlen(match->replace);
|
||||
assert(rep <= seq_len);
|
||||
memcpy(buf.b, match->replace, rep);
|
||||
memmove(buf.b + rep, buf.b + seq_len, buf.len - seq_len);
|
||||
buf.len = rep + buf.len - seq_len;
|
||||
buf.mods |= match->mpkey;
|
||||
continue;
|
||||
}
|
||||
|
||||
mp_input_put_key(input_ctx, buf.mods | match->mpkey);
|
||||
skip_buf(&buf, seq_len);
|
||||
}
|
||||
|
||||
read_more: /* need more bytes */
|
||||
return true;
|
||||
}
|
||||
|
||||
static void load_termcap(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void enable_kx(bool enable)
|
||||
{
|
||||
if (isatty(1)) {
|
||||
char *cmd = enable ? "\033=" : "\033>";
|
||||
printf("%s", cmd);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
#else /* terminfo/termcap */
|
||||
|
||||
typedef struct {
|
||||
char *cap;
|
||||
int len;
|
||||
|
@ -68,8 +288,6 @@ typedef struct {
|
|||
|
||||
static keycode_map getch2_keys;
|
||||
|
||||
#if HAVE_TERMINFO || HAVE_TERMCAP
|
||||
|
||||
static char *term_rmkx = NULL;
|
||||
static char *term_smkx = NULL;
|
||||
|
||||
|
@ -78,8 +296,6 @@ static char *term_smkx = NULL;
|
|||
#endif
|
||||
#include <term.h>
|
||||
|
||||
#endif
|
||||
|
||||
static keycode_st *keys_push(char *p, int code) {
|
||||
if (strlen(p) > 8)
|
||||
return NULL;
|
||||
|
@ -138,8 +354,6 @@ static keycode_st* keys_push_once(char *p, int code) {
|
|||
return st;
|
||||
}
|
||||
|
||||
#if HAVE_TERMINFO || HAVE_TERMCAP
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
char *pos;
|
||||
|
@ -225,10 +439,9 @@ static void termcap_add_extra_f_keys(void) {
|
|||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int load_termcap(char *termtype){
|
||||
#if HAVE_TERMINFO || HAVE_TERMCAP
|
||||
static void load_termcap(void)
|
||||
{
|
||||
char *termtype = NULL;
|
||||
|
||||
#if HAVE_TERMINFO
|
||||
use_env(TRUE);
|
||||
|
@ -243,10 +456,10 @@ static int load_termcap(char *termtype){
|
|||
if (setupterm(termtype, 1, &ret) != OK) {
|
||||
if (ret < 0) {
|
||||
printf("Could not access the 'terminfo' data base.\n");
|
||||
return 0;
|
||||
return;
|
||||
} else {
|
||||
printf("Couldn't use terminal `%s' for input.\n", termtype);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -257,10 +470,10 @@ static int load_termcap(char *termtype){
|
|||
int success = tgetent(term_buffer, termtype);
|
||||
if (success < 0) {
|
||||
printf("Could not access the 'termcap' data base.\n");
|
||||
return 0;
|
||||
return;
|
||||
} else if (success == 0) {
|
||||
printf("Terminal type `%s' is not defined.\n", termtype);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
ensure_cap(&termcap_buf, 2048);
|
||||
|
@ -299,7 +512,6 @@ static int load_termcap(char *termtype){
|
|||
termcap_add(keys[i]);
|
||||
}
|
||||
termcap_add_extra_f_keys();
|
||||
#endif
|
||||
|
||||
/* special cases (hardcoded, no need for HAVE_TERMCAP) */
|
||||
|
||||
|
@ -321,18 +533,13 @@ static int load_termcap(char *termtype){
|
|||
|
||||
/* fallback if terminfo and termcap are not available */
|
||||
keys_push_once("\012", MP_KEY_ENTER);
|
||||
|
||||
return getch2_keys.len;
|
||||
}
|
||||
|
||||
void terminal_get_size(int *w, int *h)
|
||||
static void enable_kx(bool enable)
|
||||
{
|
||||
struct winsize ws;
|
||||
if (ioctl(0, TIOCGWINSZ, &ws) < 0 || !ws.ws_row || !ws.ws_col)
|
||||
return;
|
||||
|
||||
*w = ws.ws_col;
|
||||
*h = ws.ws_row;
|
||||
char *cmd = enable ? term_smkx : term_rmkx;
|
||||
if (cmd)
|
||||
tputs(cmd, 1, putchar);
|
||||
}
|
||||
|
||||
#define BUF_LEN 256
|
||||
|
@ -436,6 +643,8 @@ static bool getch2(struct input_ctx *input_ctx)
|
|||
return true;
|
||||
}
|
||||
|
||||
#endif /* terminfo/termcap */
|
||||
|
||||
static int read_keys(void *ctx, int fd)
|
||||
{
|
||||
if (getch2(ctx))
|
||||
|
@ -458,10 +667,7 @@ static void do_activate_getch2(void)
|
|||
if (getch2_active || !isatty(1))
|
||||
return;
|
||||
|
||||
#if HAVE_TERMINFO || HAVE_TERMCAP
|
||||
if (term_smkx)
|
||||
tputs(term_smkx, 1, putchar);
|
||||
#endif
|
||||
enable_kx(true);
|
||||
|
||||
#if HAVE_TERMIOS
|
||||
struct termios tio_new;
|
||||
|
@ -486,10 +692,7 @@ static void do_deactivate_getch2(void)
|
|||
if (!getch2_active)
|
||||
return;
|
||||
|
||||
#if HAVE_TERMINFO || HAVE_TERMCAP
|
||||
if (term_rmkx)
|
||||
tputs(term_rmkx, 1, putchar);
|
||||
#endif
|
||||
enable_kx(false);
|
||||
|
||||
#if HAVE_TERMIOS
|
||||
if (tio_orig_set) {
|
||||
|
@ -598,10 +801,20 @@ bool terminal_in_background(void)
|
|||
return isatty(2) && tcgetpgrp(2) != getpgrp();
|
||||
}
|
||||
|
||||
void terminal_get_size(int *w, int *h)
|
||||
{
|
||||
struct winsize ws;
|
||||
if (ioctl(0, TIOCGWINSZ, &ws) < 0 || !ws.ws_row || !ws.ws_col)
|
||||
return;
|
||||
|
||||
*w = ws.ws_col;
|
||||
*h = ws.ws_row;
|
||||
}
|
||||
|
||||
int terminal_init(void)
|
||||
{
|
||||
if (isatty(1))
|
||||
load_termcap(NULL);
|
||||
load_termcap();
|
||||
getch2_enable();
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue