Fix some terminal encoding issues

- fix `buf_put_key()` for control keys
- add command line argument -mk=X  for xterm modifyOtherKeys setting (0,1,2)
- improve argument parsing to avoid seg fault on bogus arg spec
- report command line argument errors in popup window
- improve escape sequence handling in tty.c
- support bogus iTerm2 encoding of option/command/control key combinations
This commit is contained in:
Charlie Gordon 2024-09-16 20:11:50 +02:00
commit 7a94d82d6c
5 changed files with 67 additions and 53 deletions

View file

@ -954,7 +954,6 @@ void eb_trace_bytes(const void *buf, int size, int state)
endp = p0 + size;
#define MAX_TRACE_WIDTH 98
// XXX: should encode into a local buffer by chunks
for (p = p0; p0 < endp; p++) {
while (p >= endp || *p < 32 || *p >= 127 || *p == '\\') {
if (p0 >= endp)

41
qe.c
View file

@ -61,6 +61,7 @@ static int screen_height = 0;
static int no_init_file;
static int single_window;
int force_tty;
int tty_mk = 2;
int disable_crc;
#ifdef CONFIG_SESSION
int use_session_file;
@ -10374,19 +10375,19 @@ static void show_usage(void)
static int parse_command_line(int argc, char **argv)
{
int _optind;
int optind_;
for (_optind = 1; _optind < argc;) {
const char *arg, *r, *_optarg;
for (optind_ = 1; optind_ < argc;) {
const char *arg, *r, *optarg_;
CmdLineOptionDef *p;
bstr_t opt1;
bstr_t opt2;
r = arg = argv[_optind];
r = arg = argv[optind_];
/* stop before first non option */
if (r[0] != '-')
break;
_optind++;
optind_++;
opt1.s = opt2.s = r + 1;
if (r[1] == '-') {
@ -10396,10 +10397,10 @@ static int parse_command_line(int argc, char **argv)
break;
}
/* parse optional argument specified with opt=arg or opt:arg syntax */
_optarg = NULL;
optarg_ = NULL;
while (*r) {
if (*r == ':' || *r == '=') {
_optarg = r + 1;
optarg_ = r + 1;
break;
}
r++;
@ -10414,30 +10415,30 @@ static int parse_command_line(int argc, char **argv)
bstr_t name = bstr_token(s, '|', &s);
bstr_t argname = bstr_token(s, '|', &s);
if (bstr_equal(opt1, shortname) || bstr_equal(opt2, name)) {
if (argname.len && _optarg == NULL) {
if (_optind >= argc) {
put_status(NULL,
"cmdline argument %.*s expected for --%.*s",
argname.len, argname.s, name.len, name.s);
if (p->need_arg && optarg_ == NULL) {
if (optind_ >= argc) {
put_error(NULL,
"cmdline argument %.*s expected for --%.*s",
argname.len, argname.s, name.len, name.s);
goto next_cmd;
}
_optarg = argv[_optind++];
optarg_ = argv[optind_++];
}
switch (p->type) {
case CMD_LINE_TYPE_BOOL:
*p->u.int_ptr = qe_strtobool(_optarg, 1);
*p->u.int_ptr = qe_strtobool(optarg_, 1);
break;
case CMD_LINE_TYPE_INT:
*p->u.int_ptr = strtol(_optarg, NULL, 0);
*p->u.int_ptr = strtol(optarg_, NULL, 0);
break;
case CMD_LINE_TYPE_STRING:
*p->u.string_ptr = _optarg;
*p->u.string_ptr = optarg_;
break;
case CMD_LINE_TYPE_FVOID:
p->u.func_noarg();
break;
case CMD_LINE_TYPE_FARG:
p->u.func_arg(_optarg);
p->u.func_arg(optarg_);
break;
case CMD_LINE_TYPE_NONE:
case CMD_LINE_TYPE_NEXT:
@ -10448,11 +10449,11 @@ static int parse_command_line(int argc, char **argv)
p++;
}
}
put_status(NULL, "unknown cmdline option '%s'", arg);
put_error(NULL, "unknown cmdline option '%s'", arg);
next_cmd: ;
}
return _optind;
return optind_;
}
void do_add_resource_path(EditState *s, const char *path)
@ -10541,6 +10542,8 @@ static CmdLineOptionDef cmd_options[] = {
CMD_LINE_BOOL("", "free-all", &free_everything,
"free all structures upon exit"),
#endif
CMD_LINE_INT("mk", "modify-other-keys", "VAL", &tty_mk,
"set the modifyOtherKeys tty configuration (0,1,2)"),
CMD_LINE_LINK()
};

16
qe.h
View file

@ -152,6 +152,7 @@ enum CmdLineOptionType {
typedef struct CmdLineOptionDef {
const char *desc;
enum CmdLineOptionType type;
BOOL need_arg;
union {
int *int_ptr;
const char **string_ptr;
@ -161,13 +162,13 @@ typedef struct CmdLineOptionDef {
} u;
} CmdLineOptionDef;
#define CMD_LINE_NONE() { NULL, CMD_LINE_TYPE_NONE, { NULL }}
#define CMD_LINE_BOOL(s,n,p,h) { s "|" n "||" h, CMD_LINE_TYPE_BOOL, { .int_ptr = p }}
#define CMD_LINE_INT(s,n,a,p,h) { s "|" n "|" a "|" h, CMD_LINE_TYPE_INT, { .int_ptr = p }}
#define CMD_LINE_STRING(s,n,a,p,h) { s "|" n "|" a "|" h, CMD_LINE_TYPE_STRING, { .string_ptr = p }}
#define CMD_LINE_FVOID(s,n,p,h) { s "|" n "||" h, CMD_LINE_TYPE_FVOID, { .func_noarg = p }}
#define CMD_LINE_FARG(s,n,a,p,h) { s "|" n "|" a "|" h, CMD_LINE_TYPE_FARG, { .func_arg = p }}
#define CMD_LINE_LINK() { NULL, CMD_LINE_TYPE_NEXT, { NULL }}
#define CMD_LINE_NONE() { NULL, CMD_LINE_TYPE_NONE, FALSE, { NULL }}
#define CMD_LINE_BOOL(s,n,p,h) { s "|" n "||" h, CMD_LINE_TYPE_BOOL, TRUE, { .int_ptr = p }}
#define CMD_LINE_INT(s,n,a,p,h) { s "|" n "|" a "|" h, CMD_LINE_TYPE_INT, TRUE, { .int_ptr = p }}
#define CMD_LINE_STRING(s,n,a,p,h) { s "|" n "|" a "|" h, CMD_LINE_TYPE_STRING, TRUE, { .string_ptr = p }}
#define CMD_LINE_FVOID(s,n,p,h) { s "|" n "||" h, CMD_LINE_TYPE_FVOID, FALSE, { .func_noarg = p }}
#define CMD_LINE_FARG(s,n,a,p,h) { s "|" n "|" a "|" h, CMD_LINE_TYPE_FARG, TRUE, { .func_arg = p }}
#define CMD_LINE_LINK() { NULL, CMD_LINE_TYPE_NEXT, FALSE, { NULL }}
void qe_register_cmd_line_options(CmdLineOptionDef *table);
@ -890,6 +891,7 @@ int qe_free_mode_data(QEModeData *md);
/* from tty.c */
/* set from command line option to prevent GUI such as X11 */
extern int force_tty;
extern int tty_mk;
enum QEStyle {
#define STYLE_DEF(constant, name, fg_color, bg_color, \

52
tty.c
View file

@ -322,6 +322,10 @@ static int tty_dpy_init(QEditScreen *s,
"\033[39;49m" /* orig_pair */
"\033[?1h\033=" /* keypad_xmit */
);
if (tty_mk) {
/* modifyOtherKeys: report shift states */
TTY_FPRINTF(s->STDOUT, "\033[>4;%dm", tty_mk);
}
#endif
/* Get charset from command line option */
@ -417,6 +421,7 @@ static int tty_dpy_init(QEditScreen *s,
sigemptyset(&sig.sa_mask);
sig.sa_flags = 0;
sigaction(SIGWINCH, &sig, NULL);
fcntl(fileno(s->STDIN), F_SETFL, O_NONBLOCK);
/* If stdout is to a pty, make sure we aren't in nonblocking mode.
* Otherwise, the printf()s in term_flush() can fail with EAGAIN,
@ -454,7 +459,11 @@ static void tty_dpy_close(QEditScreen *s)
"\033[?1l\033>" /* keypad_local */
"\033[?25h" /* show cursor */
"\r\033[m\033[K" /* return erase eol */
);
);
if (tty_mk) {
/* modifyOtherKeys: report shift states */
TTY_FPRINTF(s->STDOUT, "\033[>4m");
}
#endif
fflush(s->STDOUT);
tcsetattr(fileno(s->STDIN), TCSANOW, &ts->oldtty);
@ -657,7 +666,7 @@ static void tty_read_handler(void *opaque)
* keys transmitting escape sequences starting with \033
* but followed immediately by more characters.
*/
goto the_end;
goto the_end_meta;
}
ts->input_state = IS_ESC;
break;
@ -675,11 +684,10 @@ static void tty_read_handler(void *opaque)
if (!pending) {
/* Distinguish alt-ESC from ESC prefix applied to other keys */
ch = KEY_META(KEY_ESC);
ts->input_state = IS_NORM;
goto the_end;
}
/* cygwin A-right transmit ESC ESC[C ... */
ts->has_meta = 8;
ts->has_meta = KEY_STATE_META;
break;
}
if (ch == '[' && pending) { // CSI
@ -708,7 +716,6 @@ static void tty_read_handler(void *opaque)
}
/* FIXME: handle terminal answers */
ch = KEY_META(ch);
ts->input_state = IS_NORM;
goto the_end;
case IS_CSI:
@ -731,6 +738,7 @@ static void tty_read_handler(void *opaque)
if (ts->interm) {
/* syntax error: ignore CSI sequence */
ts->input_state = IS_NORM;
ts->has_meta = 0;
break;
}
if (ts->nb_params < countof(ts->params)) {
@ -754,7 +762,6 @@ static void tty_read_handler(void *opaque)
/* FIXME: Should only accept final bytes in range 40..7E,
* and ignore other bytes.
*/
ts->input_state = IS_NORM;
n1 = ts->params[0] >= 0 ? ts->params[0] : 0;
n2 = ts->params[1] >= 0 ? ts->params[1] : 1;
switch ((ts->leader << 16) | (ts->interm << 8) | ch) {
@ -774,8 +781,13 @@ static void tty_read_handler(void *opaque)
ch = ts->params[2];
if (ch == 8 && (ts->term_flags |= KBS_CONTROL_H))
ch = KEY_DEL;
// XXX: iTerm2 3.14.19 encoding bug for M-C-a to M-C-z
if (n2 == 4 && ch >= 'A' && ch <= 'Z') {
n2 |= 2;
ch += 'a' - 'A';
}
ch = get_modified_key(ch, n2);
goto the_end;
goto the_end_meta;
}
if (n1 < countof(csi_lookup)) {
ch = csi_lookup[n1];
@ -789,7 +801,7 @@ static void tty_read_handler(void *opaque)
* supported by xterm if formatOtherKeys resource is selected
*/
ch = get_modified_key(n1, n2 - 1);
goto the_end;
goto the_end_meta;
/* All these for ansi|cygwin */
default:
/* n2 contains the shift status + 1:
@ -834,19 +846,17 @@ static void tty_read_handler(void *opaque)
case 'Z': ch = KEY_SHIFT_TAB; goto the_end_modified; // kcbt
}
ch = KEY_UNKNOWN;
ts->has_meta = 0;
goto the_end;
}
break;
case IS_CSI2:
/* cygwin/linux terminal */
ts->input_state = IS_NORM;
switch (ch) {
case 'A': ch = KEY_F1; goto the_end;
case 'B': ch = KEY_F2; goto the_end;
case 'C': ch = KEY_F3; goto the_end;
case 'D': ch = KEY_F4; goto the_end;
case 'E': ch = KEY_F5; goto the_end;
case 'A': ch = KEY_F1; goto the_end_meta;
case 'B': ch = KEY_F2; goto the_end_meta;
case 'C': ch = KEY_F3; goto the_end_meta;
case 'D': ch = KEY_F4; goto the_end_meta;
case 'E': ch = KEY_F5; goto the_end_meta;
}
ch = KEY_UNKNOWN;
goto the_end;
@ -859,7 +869,6 @@ static void tty_read_handler(void *opaque)
break;
}
n2 = ts->params[0] > 0 ? ts->params[0] - 1 : 0;
ts->input_state = IS_NORM;
switch (ch) {
case 'A': ch = KEY_UP; goto the_end_modified;
case 'B': ch = KEY_DOWN; goto the_end_modified;
@ -888,14 +897,14 @@ static void tty_read_handler(void *opaque)
ch = KEY_META(ch);
if (n2 & 4)
ch = KEY_CONTROL(ch);
goto the_end;
the_end_meta:
if (ts->has_meta) {
ts->has_meta = 0;
if (ts->has_meta)
ch = KEY_META(ch);
}
the_end:
ts->input_state = IS_NORM;
ts->has_meta = 0;
// XXX: should add custom key sequences, especially for KEY_UNKNOWN
ev->key_event.type = QE_KEY_EVENT;
ev->key_event.key = ch;
@ -905,7 +914,7 @@ static void tty_read_handler(void *opaque)
case IS_OSC:
/* OSC syntax is: OSC string 07 or OSC string ST (ESC \) */
if (ch == 27) {
ts->has_meta = 1;
ts->has_meta = KEY_STATE_META;
break;
}
if (ts->has_meta && ch == '\\') {
@ -915,6 +924,7 @@ static void tty_read_handler(void *opaque)
break;
// FIXME: handle terminal response
ts->input_state = IS_NORM;
ts->has_meta = 0;
break;
}
}

10
util.c
View file

@ -1946,8 +1946,8 @@ int buf_put_key(buf_t *out, int key) {
buf_put_key(out, key & 0xff);
} else
// FIXME: handle @-_ @@@
if (key >= KEY_CTRL('a') && key <= KEY_CTRL('z')) {
buf_printf(out, "C-%c", key + 'a' - 1);
if (key >= KEY_CTRL('@') && key <= KEY_CTRL('_')) {
buf_printf(out, "C-%c", qe_tolower(key + 'A' - 1));
} else {
buf_putc_utf8(out, key);
}
@ -2410,9 +2410,9 @@ bstr_t bstr_token(const char *s, int sep, const char **pp) {
continue;
bs.len = s - bs.s;
if (pp) {
*pp = *s ? s + 1 : NULL;
}
}
if (pp) {
*pp = (s && *s) ? s + 1 : NULL;
}
return bs;
}