From d42b3df7083072b9291af070c26ff93b668249c1 Mon Sep 17 00:00:00 2001 From: nick black Date: Wed, 6 Oct 2021 19:56:12 -0400 Subject: [PATCH] Initial XTMODKEYS support #2135 Add prep_xtmodkeys() to handle modified function keys. Unify several paths into load_ncinput(), eliminating several paths that missed statistics and drain checks. Process mouse clicks outside of critical section. Handle XTMODKEY sequences with parameter 5. Interpret 0x8 as NCKEY_BACKSPACE on all paths. Send XTMODKEYS of 2;1 and 4;1 #2135. --- src/lib/in.c | 244 +++++++++++++++++++++++---------------------- src/lib/termdesc.c | 8 +- 2 files changed, 133 insertions(+), 119 deletions(-) diff --git a/src/lib/in.c b/src/lib/in.c index 2bd24d891..1a28580b7 100644 --- a/src/lib/in.c +++ b/src/lib/in.c @@ -119,6 +119,42 @@ inc_input_errors(inputctx* ictx){ pthread_mutex_unlock(&ictx->stats->lock); } +// load representations used by XTMODKEYS +static int +prep_xtmodkeys(inputctx* ictx){ + // XTMODKEYS enables unambiguous representations of certain inputs. We + // enable XTMODKEYS where supported. + static const struct { + const char* esc; + uint32_t key; + bool shift, ctrl, alt; + } keys[] = { + { .esc = "\x1b\x8", .key = NCKEY_BACKSPACE, .alt = 1, }, + { .esc = "\x1b[2P", .key = NCKEY_F01, .shift = 1, }, + { .esc = "\x1b[5P", .key = NCKEY_F01, .ctrl = 1, }, + { .esc = "\x1b[6P", .key = NCKEY_F01, .ctrl = 1, .shift = 1, }, + { .esc = "\x1b[2Q", .key = NCKEY_F02, .shift = 1, }, + { .esc = "\x1b[5Q", .key = NCKEY_F02, .ctrl = 1, }, + { .esc = "\x1b[6Q", .key = NCKEY_F02, .ctrl = 1, .shift = 1, }, + { .esc = "\x1b[2R", .key = NCKEY_F03, .shift = 1, }, + { .esc = "\x1b[5R", .key = NCKEY_F03, .ctrl = 1, }, + { .esc = "\x1b[6R", .key = NCKEY_F03, .ctrl = 1, .shift = 1, }, + { .esc = "\x1b[2S", .key = NCKEY_F04, .shift = 1, }, + { .esc = "\x1b[5S", .key = NCKEY_F04, .ctrl = 1, }, + { .esc = "\x1b[6S", .key = NCKEY_F04, .ctrl = 1, .shift = 1, }, + { .esc = NULL, .key = 0, }, + }, *k; + for(k = keys ; k->esc ; ++k){ + if(inputctx_add_input_escape(&ictx->amata, k->esc, k->key, + k->shift, k->ctrl, k->alt)){ + return -1; + } + logdebug("added %u\n", k->key); + } + loginfo("added all xtmodkeys\n"); + return 0; +} + // load all known special keys from terminfo, and build the input sequence trie static int prep_special_keys(inputctx* ictx){ @@ -373,6 +409,52 @@ amata_next_string(automaton* amata, const char* prefix){ return ret; } +static inline void +send_synth_signal(int sig){ + if(sig){ +#ifndef __MINGW64__ + raise(sig); +#endif + } +} + +static void +mark_pipe_ready(int pipes[static 2]){ + char sig = 1; + if(write(pipes[1], &sig, sizeof(sig)) != 1){ + logwarn("error writing to readypipe (%d) (%s)\n", pipes[1], strerror(errno)); + } +} + +static void +load_ncinput(inputctx* ictx, const ncinput *tni, int synthsig){ + inc_input_events(ictx); + if(ictx->drain){ + return; + } + pthread_mutex_lock(&ictx->ilock); + if(ictx->ivalid == ictx->isize){ + pthread_mutex_unlock(&ictx->ilock); + logerror("dropping input 0x%08x\n", tni->id); + inc_input_errors(ictx); + send_synth_signal(synthsig); + return; + } + ncinput* ni = ictx->inputs + ictx->iwrite; + memcpy(ni, tni, sizeof(*tni)); + if(ni->id == 0x7f || ni->id == 0x8){ + ni->id = NCKEY_BACKSPACE; + } + if(++ictx->iwrite == ictx->isize){ + ictx->iwrite = 0; + } + ++ictx->ivalid; + mark_pipe_ready(ictx->readypipes); + pthread_mutex_unlock(&ictx->ilock); + pthread_cond_broadcast(&ictx->icond); + send_synth_signal(synthsig); +} + // ictx->numeric, ictx->p3, and ictx->p2 have the two parameters. we're using // SGR (1006) mouse encoding, so use the final character to determine release // ('M' for click, 'm' for release). @@ -396,38 +478,26 @@ mouse_click(inputctx* ictx, unsigned release, char follow){ logwarn("dropping click in margins %ld/%ld\n", y, x); return; } - pthread_mutex_lock(&ictx->ilock); - if(ictx->ivalid == ictx->isize){ - pthread_mutex_unlock(&ictx->ilock); - logerror("dropping mouse click 0x%02x %ld %ld\n", mods, y, x); - inc_input_errors(ictx); - return; - } - ncinput* ni = ictx->inputs + ictx->iwrite; + ncinput tni = { + .ctrl = mods & 0x10, + .alt = mods & 0x08, + .shift = mods & 0x04, + }; if(mods < 64){ - ni->id = NCKEY_BUTTON1 + (mods % 4); + tni.id = NCKEY_BUTTON1 + (mods % 4); }else if(mods >= 64 && mods < 128){ - ni->id = NCKEY_BUTTON4 + (mods % 4); + tni.id = NCKEY_BUTTON4 + (mods % 4); }else if(mods >= 128 && mods < 192){ - ni->id = NCKEY_BUTTON8 + (mods % 4); + tni.id = NCKEY_BUTTON8 + (mods % 4); } - ni->ctrl = mods & 0x10; - ni->alt = mods & 0x08; - ni->shift = mods & 0x04; - // mice don't send repeat events, so we know it's either release or press if(release){ - ni->evtype = NCTYPE_RELEASE; + tni.evtype = NCTYPE_RELEASE; }else{ - ni->evtype = NCTYPE_PRESS; - } - ni->x = x; - ni->y = y; - if(++ictx->iwrite == ictx->isize){ - ictx->iwrite = 0; + tni.evtype = NCTYPE_PRESS; } - ++ictx->ivalid; - pthread_mutex_unlock(&ictx->ilock); - pthread_cond_broadcast(&ictx->icond); + tni.x = x; + tni.y = y; + load_ncinput(ictx, &tni, 0); } static int @@ -497,14 +567,18 @@ geom_cb(inputctx* ictx){ } static void -send_synth_signal(int sig){ -#ifndef __MINGW64__ - if(sig){ - raise(sig); +xtmodkey(inputctx* ictx, int val, int mods){ + assert(mods >= 0); + assert(val > 0); + logdebug("v/m %d %d\n", val, mods); + ncinput tni = { + .id = val, + .evtype = NCTYPE_UNKNOWN, + }; + if(mods == 5){ + tni.ctrl = 1; } -#else - (void)sig; // FIXME -#endif + load_ncinput(ictx, &tni, 0); } static void @@ -515,7 +589,7 @@ kitty_kbd(inputctx* ictx, int val, int mods, int evtype){ assert(val > 0); logdebug("v/m/e %d %d %d\n", val, mods, evtype); ncinput tni = { - .id = val == 0x7f ? NCKEY_BACKSPACE : val, + .id = val, .shift = mods && !!((mods - 1) & 0x1), .alt = mods && !!((mods - 1) & 0x2), .ctrl = mods && !!((mods - 1) & 0x4), @@ -535,8 +609,6 @@ kitty_kbd(inputctx* ictx, int val, int mods, int evtype){ } } } - tni.x = 0; - tni.y = 0; switch(evtype){ case 1: tni.evtype = NCTYPE_PRESS; @@ -551,23 +623,7 @@ kitty_kbd(inputctx* ictx, int val, int mods, int evtype){ tni.evtype = NCTYPE_UNKNOWN; break; } - pthread_mutex_lock(&ictx->ilock); - if(ictx->ivalid == ictx->isize){ - pthread_mutex_unlock(&ictx->ilock); - logerror("dropping input 0x%08x 0x%02x\n", val, mods); - inc_input_errors(ictx); - send_synth_signal(synth); - return; - } - ncinput* ni = ictx->inputs + ictx->iwrite; - memcpy(ni, &tni, sizeof(tni)); - if(++ictx->iwrite == ictx->isize){ - ictx->iwrite = 0; - } - ++ictx->ivalid; - pthread_mutex_unlock(&ictx->ilock); - pthread_cond_broadcast(&ictx->icond); - send_synth_signal(synth); + load_ncinput(ictx, &tni, synth); } static int @@ -604,6 +660,14 @@ kitty_keyboard_cb(inputctx* ictx){ return 2; } +static int +xtmodkey_cb(inputctx* ictx){ + unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[27;", ';'); + unsigned val = amata_next_numeric(&ictx->amata, "", '~'); + xtmodkey(ictx, val, mods); + return 2; +} + // the only xtsmgraphics reply with a single Pv arg is color registers static int xtsmgraphics_cregs_cb(inputctx* ictx){ @@ -882,6 +946,7 @@ build_cflow_automaton(inputctx* ictx){ { "[\\Nu", kitty_cb_simple, }, { "[\\N;\\Nu", kitty_cb, }, { "[\\N;\\N:\\Nu", kitty_cb_complex, }, + { "[\\N;\\N;\\N~", xtmodkey_cb, }, { "[?\\Nu", kitty_keyboard_cb, }, { "[?1;2c", da1_cb, }, // CSI ? 1 ; 2 c ("VT100 with Advanced Video Option") { "[?1;0c", da1_cb, }, // CSI ? 1 ; 0 c ("VT101 with No Options") @@ -936,14 +1001,6 @@ endpipes(int pipes[static 2]){ } } -static void -mark_pipe_ready(int pipes[static 2]){ - char sig = 1; - if(write(pipes[1], &sig, sizeof(sig)) != 1){ - logwarn("error writing to readypipe (%d) (%s)\n", pipes[1], strerror(errno)); - } -} - // only linux and freebsd13+ have eventfd(), so we'll fall back to pipes sigh. static int getpipes(int pipes[static 2]){ @@ -1158,6 +1215,9 @@ prep_all_keys(inputctx* ictx){ if(prep_kitty_special_keys(ictx)){ return -1; } + if(prep_xtmodkeys(ictx)){ + return -1; + } return 0; } @@ -1195,61 +1255,6 @@ ictx_independent_p(const inputctx* ictx){ return ictx->termfd >= 0; // FIXME does this hold on MSFT Terminal? } -// add a decoded, valid Unicode to the bulk output buffer, or drop it if no -// space is available. -static void -add_unicode(inputctx* ictx, uint32_t id){ - inc_input_events(ictx); - if(ictx->drain){ - return; - } - pthread_mutex_lock(&ictx->ilock); - if(ictx->ivalid == ictx->isize){ - pthread_mutex_unlock(&ictx->ilock); - logerror("dropping input 0x%08xx\n", id); - inc_input_errors(ictx); - return; - } - ncinput* ni = ictx->inputs + ictx->iwrite; - ni->id = id; - ni->alt = false; - ni->ctrl = false; - ni->shift = false; - ni->x = ni->y = 0; - ni->evtype = NCTYPE_UNKNOWN; - if(++ictx->iwrite == ictx->isize){ - ictx->iwrite = 0; - } - ++ictx->ivalid; - mark_pipe_ready(ictx->readypipes); - pthread_mutex_unlock(&ictx->ilock); - pthread_cond_broadcast(&ictx->icond); -} - -static void -special_key(inputctx* ictx, const ncinput* inni){ - inc_input_events(ictx); - if(ictx->drain){ - return; - } - pthread_mutex_lock(&ictx->ilock); - if(ictx->ivalid == ictx->isize){ - pthread_mutex_unlock(&ictx->ilock); - logerror("dropping input 0x%08xx\n", inni->id); - inc_input_errors(ictx); - return; - } - ncinput* ni = ictx->inputs + ictx->iwrite; - memcpy(ni, inni, sizeof(*ni)); - if(++ictx->iwrite == ictx->isize){ - ictx->iwrite = 0; - } - ++ictx->ivalid; - mark_pipe_ready(ictx->readypipes); - pthread_mutex_unlock(&ictx->ilock); - pthread_cond_broadcast(&ictx->icond); -} - // try to lex a single control sequence off of buf. return the number of bytes // consumed if we do so, and -1 otherwise. buf is almost certainly *not* // NUL-terminated. if we are definitely *not* an escape, or we're unsure when @@ -1295,7 +1300,7 @@ process_escape(inputctx* ictx, const unsigned char* buf, int buflen){ isprint(candidate) ? candidate : ' ', w, ictx->amata.state); if(w > 0){ if(ni.id){ - special_key(ictx, &ni); + load_ncinput(ictx, &ni, 0); } ictx->amata.used = 0; return used; @@ -1373,7 +1378,7 @@ process_input(inputctx* ictx, const unsigned char* buf, int buflen, ncinput* ni) logwarn("invalid UTF8 initiator on input (0x%02x)\n", *buf); return -1; }else if(cpointlen == 1){ // pure ascii can't show up mid-utf8-character - if(buf[0] == 0x7f){ // ASCII del, treated as backspace + if(buf[0] == 0x7f || buf[0] == 0x8){ // ASCII del, treated as backspace ni->id = NCKEY_BACKSPACE; }else if(buf[0] == '\n' || buf[0] == '\r'){ ni->id = NCKEY_ENTER; @@ -1497,7 +1502,10 @@ process_melange(inputctx* ictx, const unsigned char* buf, int* bufused){ static void process_ibuf(inputctx* ictx){ if(resize_seen){ - add_unicode(ictx, NCKEY_RESIZE); + ncinput tni = { + .id = NCKEY_RESIZE, + }; + load_ncinput(ictx, &tni, 0); resize_seen = 0; } if(ictx->tbufvalid){ diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c index 58c08386f..80b459b66 100644 --- a/src/lib/termdesc.c +++ b/src/lib/termdesc.c @@ -320,7 +320,7 @@ init_terminfo_esc(tinfo* ti, const char* name, escape_e idx, // request kitty keyboard protocol through level 1, first pushing current. // see https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement -// FIXME go to level 11, but need handle all functional keys +// FIXME go to level 11, but first need handle all functional keys #define KBDSUPPORT "\x1b[>u\x1b[=1u" // the kitty keyboard protocol allows unambiguous, complete identification of @@ -328,6 +328,11 @@ init_terminfo_esc(tinfo* ti, const char* name, escape_e idx, // because the "keyboard pop" control code is mishandled by kitty < 0.20.0. #define KBDQUERY "\x1b[?u" +// Set modifyFunctionKeys (2) if supported, allowing us to disambiguate +// function keys when used with modifiers. Set modifyOtherKeys (4) if +// supported. +#define XTMODKEYS "\x1b[>2;1m\x1b[>4;1m" + // these queries (terminated with a Primary Device Attributes, to which // all known terminals reply) hopefully can uniquely and unquestionably // identify the terminal to which we are talking. @@ -368,6 +373,7 @@ init_terminfo_esc(tinfo* ti, const char* name, escape_e idx, #define DIRECTIVES CSI_BGQ \ SUMQUERY \ + XTMODKEYS \ "\x1b[?1;3;256S" /* try to set 256 cregs */ \ CREGSXTSM \ GEOMXTSM \