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.
pull/2243/head
nick black 3 years ago committed by nick black
parent b9d774f746
commit d42b3df708

@ -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){

@ -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 \

Loading…
Cancel
Save