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
|
# 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
|
||||||
|
@ -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)**,
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user