mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
[kitty] handle level 1 of keyboard protocol #2131
This commit is contained in:
parent
e1a1ac3497
commit
25afdd8ab6
@ -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
|
||||
|
@ -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)**,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user