[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
**notcurses-input**
**notcurses-input** [**-v**]
# DESCRIPTION
@ -17,6 +17,8 @@ synthesized events and mouse events. To exit, generate EOF (usually Ctrl+'d').
# OPTIONS
**-v**: Increase verbosity.
# NOTES
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
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
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
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
**poll(2)**,

View File

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

View File

@ -331,12 +331,30 @@ int input_demo(ncpp::NotCurses* nc) {
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){
return EXIT_FAILURE;
}
notcurses_options nopts{};
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;
NotCurses nc(nopts);
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){
int candidate = pop_input_keypress(nc);
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 !mouse and candidate is '>', set mouse. otherwise it ought be a
// digit or a semicolon.

View File

@ -92,6 +92,11 @@ notcurses_stop_minimal(void* vnc){
}
ret |= mouse_disable(&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((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){
if(sprite_clear_all(&nc->tcache, f)){
@ -101,7 +106,9 @@ notcurses_stop_minimal(void* vnc){
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)){
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_FONT_CHANGES,
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){
// 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)){
goto err;
}
// if not connected to an actual terminal, we're not going to try entering
// the alternate screen; we're not even going to bother clearing the screen.
if(ret->tcache.ttyfd >= 0){
if(!(opts->flags & NCOPTION_NO_ALTERNATE_SCREEN)){
const char* smcup = get_escape(&ret->tcache, ESCAPE_SMCUP);
if(smcup){
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.
if(!(opts->flags & NCOPTION_NO_ALTERNATE_SCREEN)){
// 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.
}
// the sprite clear ought take place within the alternate screen, if it's
// being used.
@ -1261,6 +1262,7 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
err:
logpanic("Alas, you will not be going to space today.\n");
notcurses_stop_minimal(ret);
fbuf_free(&ret->rstate.f);
if(ret->tcache.ttyfd >= 0 && 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.
#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
// all known terminals reply) hopefully can uniquely and unquestionably
// identify the terminal to which we are talking.
#define IDQUERIES KITTYQUERY \
KBDSUPPORT \
KBDQUERY \
TRIDEVATTR \
XTVERSION \
XTGETTCAPTN \
@ -362,18 +372,31 @@ init_terminfo_esc(tinfo* ti, const char* name, escape_e idx,
GEOMCELL \
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
// 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
// and geometry. send XTGETTCAP for terminal name. if 'minimal' is set, don't
// send any identification queries (we've already identified the terminal).
static int
send_initial_queries(int fd, bool minimal){
send_initial_queries(int fd, bool minimal, bool noaltscreen){
const char *queries;
if(minimal){
queries = DSRCPR DIRECTIVES;
if(noaltscreen){
if(minimal){
queries = DSRCPR DIRECTIVES;
}else{
queries = DSRCPR IDQUERIES DIRECTIVES;
}
}else{
queries = DSRCPR IDQUERIES DIRECTIVES;
if(minimal){
queries = SMCUP DSRCPR DIRECTIVES;
}else{
queries = SMCUP DSRCPR IDQUERIES DIRECTIVES;
}
}
size_t len = strlen(queries);
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);
return -1;
}
// FIXME need to enter alternate screen here
// 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.
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)){
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{
ti->escindices[ESCAPE_SMCUP] = 0;
ti->escindices[ESCAPE_RMCUP] = 0;
@ -919,6 +952,7 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
return 0;
err:
// FIXME need to leave alternate screen if we entered it
if(ti->tpreserved){
(void)tcsetattr(ti->ttyfd, TCSANOW, ti->tpreserved);
free(ti->tpreserved);
@ -927,6 +961,8 @@ err:
free(ti->esctable);
free(ti->termversion);
del_curterm(cur_term);
close(ti->ttyfd);
ti->ttyfd = -1;
return -1;
}