[kitty] handle level 1 of keyboard protocol #2131

This commit is contained in:
nick black 2021-08-31 15:47:21 -04:00 committed by nick black
parent e1a1ac3497
commit 25afdd8ab6
7 changed files with 107 additions and 28 deletions

View File

@ -8,7 +8,7 @@ notcurses-input - Read and display input events
# SYNOPSIS # SYNOPSIS
**notcurses-input** **notcurses-input** [**-v**]
# DESCRIPTION # DESCRIPTION
@ -17,6 +17,8 @@ synthesized events and mouse events. To exit, generate EOF (usually Ctrl+'d').
# OPTIONS # OPTIONS
**-v**: Increase verbosity.
# NOTES # NOTES
Mouse events are only generated for button presses, and for movement while a Mouse events are only generated for button presses, and for movement while a

View File

@ -150,6 +150,9 @@ descriptor returned by **notcurses_inputready_fd** to ensure compatibility with
future versions of Notcurses (it is possible that future versions will process future versions of Notcurses (it is possible that future versions will process
input in their own contexts). input in their own contexts).
When support is detected, the Kitty keyboard disambiguation protocol will be
requested. This eliminates most of the **BUGS** mentioned below.
# BUGS # BUGS
Failed escape sequences are not yet played back in their entirety; only an Failed escape sequences are not yet played back in their entirety; only an
@ -167,6 +170,9 @@ in the future.
Ctrl pressed along with 'J' or 'M', whether Shift is pressed or not, currently Ctrl pressed along with 'J' or 'M', whether Shift is pressed or not, currently
registers as **NCKEY_ENTER**. This will likely change in the future. registers as **NCKEY_ENTER**. This will likely change in the future.
When the Kitty keyboard disambiguation protocol is used, most of these issues
are resolved.
# SEE ALSO # SEE ALSO
**poll(2)**, **poll(2)**,

View File

@ -681,7 +681,7 @@ int main(int argc, char** argv){
usage(argv[0], stderr); usage(argv[0], stderr);
}else if(argc == 2){ }else if(argc == 2){
if(strcmp(argv[1], "-v") == 0){ if(strcmp(argv[1], "-v") == 0){
opts.loglevel = NCLOGLEVEL_DEBUG; opts.loglevel = NCLOGLEVEL_TRACE;
}else{ }else{
usage(argv[0], stderr); usage(argv[0], stderr);
} }

View File

@ -331,12 +331,30 @@ int input_demo(ncpp::NotCurses* nc) {
return 0; return 0;
} }
int main(void){ static void
usage(const char* arg0, FILE* fp){
fprintf(fp, "usage: %s [ -v ]\n", arg0);
if(fp == stderr){
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
int main(int argc, char** argv){
if(setlocale(LC_ALL, "") == nullptr){ if(setlocale(LC_ALL, "") == nullptr){
return EXIT_FAILURE; return EXIT_FAILURE;
} }
notcurses_options nopts{}; notcurses_options nopts{};
nopts.loglevel = NCLOGLEVEL_ERROR; nopts.loglevel = NCLOGLEVEL_ERROR;
if(argc > 2){
usage(argv[0], stderr);
}else if(argc == 2){
if(strcmp(argv[1], "-v") == 0){
nopts.loglevel = NCLOGLEVEL_TRACE;
}else{
usage(argv[0], stderr);
}
}
nopts.flags = NCOPTION_INHIBIT_SETLOCALE; nopts.flags = NCOPTION_INHIBIT_SETLOCALE;
NotCurses nc(nopts); NotCurses nc(nopts);
nc.mouse_enable(); // might fail if no mouse is available nc.mouse_enable(); // might fail if no mouse is available

View File

@ -243,6 +243,21 @@ handle_csi(ncinputlayer* nc, ncinput* ni, int leftmargin, int topmargin){
while(nc->inputbuf_occupied){ while(nc->inputbuf_occupied){
int candidate = pop_input_keypress(nc); int candidate = pop_input_keypress(nc);
logdebug("candidate: %c (%d)\n", candidate, candidate); logdebug("candidate: %c (%d)\n", candidate, candidate);
if(candidate == 'u'){ // kitty keyboard protocol
if(state == PARAM3){
logwarn("triparam kitty message?\n");
break;
}else if(state == PARAM1){
param1 = param;
param = 1;
}
ni->id = param1;
ni->shift = !!((param - 1) & 0x1);
ni->alt = !!((param - 1) & 0x2);
ni->ctrl = !!((param - 1) & 0x4);
// FIXME decode remaining modifiers through 128
return param1;
}
if(state == PARAM1){ if(state == PARAM1){
// if !mouse and candidate is '>', set mouse. otherwise it ought be a // if !mouse and candidate is '>', set mouse. otherwise it ought be a
// digit or a semicolon. // digit or a semicolon.

View File

@ -92,6 +92,11 @@ notcurses_stop_minimal(void* vnc){
} }
ret |= mouse_disable(&nc->tcache, f); ret |= mouse_disable(&nc->tcache, f);
ret |= reset_term_attributes(&nc->tcache, f); ret |= reset_term_attributes(&nc->tcache, f);
if(nc->tcache.kittykbd){
if(fbuf_emit(f, "\x1b[<u")){
ret = -1;
}
}
if(nc->tcache.ttyfd >= 0){ if(nc->tcache.ttyfd >= 0){
if((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){ if((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){
if(sprite_clear_all(&nc->tcache, f)){ if(sprite_clear_all(&nc->tcache, f)){
@ -101,7 +106,9 @@ notcurses_stop_minimal(void* vnc){
ret = -1; ret = -1;
} }
} }
ret |= tcsetattr(nc->tcache.ttyfd, TCSAFLUSH, nc->tcache.tpreserved); if(nc->tcache.tpreserved){
ret |= tcsetattr(nc->tcache.ttyfd, TCSAFLUSH, nc->tcache.tpreserved);
}
} }
if((esc = get_escape(&nc->tcache, ESCAPE_RMKX)) && fbuf_emit(f, esc)){ if((esc = get_escape(&nc->tcache, ESCAPE_RMKX)) && fbuf_emit(f, esc)){
ret = -1; ret = -1;
@ -1168,7 +1175,12 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
opts->flags & NCOPTION_NO_ALTERNATE_SCREEN, 0, opts->flags & NCOPTION_NO_ALTERNATE_SCREEN, 0,
opts->flags & NCOPTION_NO_FONT_CHANGES, opts->flags & NCOPTION_NO_FONT_CHANGES,
cursory, cursorx, &ret->stats)){ cursory, cursorx, &ret->stats)){
goto err; fbuf_free(&ret->rstate.f);
pthread_mutex_destroy(&ret->pilelock);
pthread_mutex_destroy(&ret->stats.lock);
drop_signals(ret);
free(ret);
return NULL;
} }
if((opts->flags & NCOPTION_PRESERVE_CURSOR) || !ret->suppress_banner){ if((opts->flags & NCOPTION_PRESERVE_CURSOR) || !ret->suppress_banner){
// the u7 led the queries so that we would get a cursor position // the u7 led the queries so that we would get a cursor position
@ -1222,26 +1234,15 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
if(set_fd_nonblocking(ret->tcache.input.infd, 1, &ret->stdio_blocking_save)){ if(set_fd_nonblocking(ret->tcache.input.infd, 1, &ret->stdio_blocking_save)){
goto err; goto err;
} }
// if not connected to an actual terminal, we're not going to try entering if(!(opts->flags & NCOPTION_NO_ALTERNATE_SCREEN)){
// the alternate screen; we're not even going to bother clearing the screen. // perform an explicit clear since the alternate screen was requested
if(ret->tcache.ttyfd >= 0){ // (smcup *might* clear, but who knows? and it might not have been
if(!(opts->flags & NCOPTION_NO_ALTERNATE_SCREEN)){ // available in any case).
const char* smcup = get_escape(&ret->tcache, ESCAPE_SMCUP); if(clear_and_home(ret, &ret->tcache, &ret->rstate.f)){
if(smcup){ goto err;
if(enter_alternate_screen(ret->ttyfp, &ret->tcache, false)){
free_plane(ret->stdplane);
goto err;
}
}
// perform an explicit clear since the alternate screen was requested
// (smcup *might* clear, but who knows? and it might not have been
// available in any case).
if(clear_and_home(ret, &ret->tcache, &ret->rstate.f)){
goto err;
}
// no need to reestablish a preserved cursor -- that only affects the
// standard plane, not the physical cursor that was just disrupted.
} }
// no need to reestablish a preserved cursor -- that only affects the
// standard plane, not the physical cursor that was just disrupted.
} }
// the sprite clear ought take place within the alternate screen, if it's // the sprite clear ought take place within the alternate screen, if it's
// being used. // being used.
@ -1261,6 +1262,7 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
err: err:
logpanic("Alas, you will not be going to space today.\n"); logpanic("Alas, you will not be going to space today.\n");
notcurses_stop_minimal(ret);
fbuf_free(&ret->rstate.f); fbuf_free(&ret->rstate.f);
if(ret->tcache.ttyfd >= 0 && ret->tcache.tpreserved){ if(ret->tcache.ttyfd >= 0 && ret->tcache.tpreserved){
(void)tcsetattr(ret->tcache.ttyfd, TCSAFLUSH, ret->tcache.tpreserved); (void)tcsetattr(ret->tcache.ttyfd, TCSAFLUSH, ret->tcache.tpreserved);

View File

@ -317,10 +317,20 @@ init_terminfo_esc(tinfo* ti, const char* name, escape_e idx,
// which can be identified directly, sans queries. // which can be identified directly, sans queries.
#define KITTYQUERY "\x1b_Gi=1,a=q;\x1b\\" #define KITTYQUERY "\x1b_Gi=1,a=q;\x1b\\"
// request kitty keyboard protocol through level 31, and push current.
// see https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
#define KBDSUPPORT "\x1b[>u\x1b[=1u"
// the kitty keyboard protocol allows unambiguous, complete identification of
// input events. this queries for the level of support.
#define KBDQUERY "\x1b[?u"
// these queries (terminated with a Primary Device Attributes, to which // these queries (terminated with a Primary Device Attributes, to which
// all known terminals reply) hopefully can uniquely and unquestionably // all known terminals reply) hopefully can uniquely and unquestionably
// identify the terminal to which we are talking. // identify the terminal to which we are talking.
#define IDQUERIES KITTYQUERY \ #define IDQUERIES KITTYQUERY \
KBDSUPPORT \
KBDQUERY \
TRIDEVATTR \ TRIDEVATTR \
XTVERSION \ XTVERSION \
XTGETTCAPTN \ XTGETTCAPTN \
@ -362,18 +372,31 @@ init_terminfo_esc(tinfo* ti, const char* name, escape_e idx,
GEOMCELL \ GEOMCELL \
PRIDEVATTR PRIDEVATTR
// enter the alternate screen (smcup). we could technically get this from
// terminfo, but everyone who supports it supports it the same way, and we
// need to send it before our other directives if we're going to use it.
#define SMCUP "\x1b[?1049h"
// we send an XTSMGRAPHICS to set up 256 color registers (the most we can // we send an XTSMGRAPHICS to set up 256 color registers (the most we can
// currently take advantage of; we need at least 64 to use sixel at all). // currently take advantage of; we need at least 64 to use sixel at all).
// maybe that works, maybe it doesn't. then query both color registers // maybe that works, maybe it doesn't. then query both color registers
// and geometry. send XTGETTCAP for terminal name. if 'minimal' is set, don't // and geometry. send XTGETTCAP for terminal name. if 'minimal' is set, don't
// send any identification queries (we've already identified the terminal). // send any identification queries (we've already identified the terminal).
static int static int
send_initial_queries(int fd, bool minimal){ send_initial_queries(int fd, bool minimal, bool noaltscreen){
const char *queries; const char *queries;
if(minimal){ if(noaltscreen){
queries = DSRCPR DIRECTIVES; if(minimal){
queries = DSRCPR DIRECTIVES;
}else{
queries = DSRCPR IDQUERIES DIRECTIVES;
}
}else{ }else{
queries = DSRCPR IDQUERIES DIRECTIVES; if(minimal){
queries = SMCUP DSRCPR DIRECTIVES;
}else{
queries = SMCUP DSRCPR IDQUERIES DIRECTIVES;
}
} }
size_t len = strlen(queries); size_t len = strlen(queries);
loginfo("sending %lluB queries\n", (unsigned long long)len); loginfo("sending %lluB queries\n", (unsigned long long)len);
@ -741,6 +764,7 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
free(ti->tpreserved); free(ti->tpreserved);
return -1; return -1;
} }
// FIXME need to enter alternate screen here
// if we already know our terminal (e.g. on the linux console), there's no // if we already know our terminal (e.g. on the linux console), there's no
// need to send the identification queries. the controls are sufficient. // need to send the identification queries. the controls are sufficient.
bool minimal = (ti->qterm != TERMINAL_UNKNOWN); bool minimal = (ti->qterm != TERMINAL_UNKNOWN);
@ -847,6 +871,15 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
init_terminfo_esc(ti, "rmcup", ESCAPE_RMCUP, &tablelen, &tableused)){ init_terminfo_esc(ti, "rmcup", ESCAPE_RMCUP, &tablelen, &tableused)){
goto err; goto err;
} }
const char* smcup = get_escape(ti, ESCAPE_SMCUP);
if(smcup){
ti->in_alt_screen = 1;
// if we're not using the standard smcup, our initial hardcoded use of it
// presumably had no effect; warn the user.
if(strcmp(smcup, SMCUP)){
logwarn("warning: non-standard smcup!\n");
}
}
}else{ }else{
ti->escindices[ESCAPE_SMCUP] = 0; ti->escindices[ESCAPE_SMCUP] = 0;
ti->escindices[ESCAPE_RMCUP] = 0; ti->escindices[ESCAPE_RMCUP] = 0;
@ -919,6 +952,7 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
return 0; return 0;
err: err:
// FIXME need to leave alternate screen if we entered it
if(ti->tpreserved){ if(ti->tpreserved){
(void)tcsetattr(ti->ttyfd, TCSANOW, ti->tpreserved); (void)tcsetattr(ti->ttyfd, TCSANOW, ti->tpreserved);
free(ti->tpreserved); free(ti->tpreserved);
@ -927,6 +961,8 @@ err:
free(ti->esctable); free(ti->esctable);
free(ti->termversion); free(ti->termversion);
del_curterm(cur_term); del_curterm(cur_term);
close(ti->ttyfd);
ti->ttyfd = -1;
return -1; return -1;
} }