Unify control sequence automata #2183 (#2208)

Unify the dynamic, dataflow special keys automaton and the static, codeflow terminal response automaton, yielding a single automaton. Add kitty keyboard support information to `notcurses-info`. Closes #2183.
pull/2220/head
nick black 3 years ago committed by GitHub
parent 05635aa60d
commit b5c161a07c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -42,7 +42,7 @@ typedef struct ncstats {
uint64_t sprixelelisions; // sprixel elision count
uint64_t sprixelbytes; // sprixel bytes emitted
uint64_t appsync_updates; // application-synchronized updates
uint64_t input_events; // EGC inputs received or synthesized
uint64_t input_events; // inputs received or synthesized
uint64_t input_errors; // errors processing input
// current state -- these can decrease

@ -414,6 +414,7 @@ tinfo_debug_styles(const notcurses* nc, struct ncplane* n, const char* indent){
tinfo_debug_cap(n, "vid", notcurses_canopen_videos(nc));
tinfo_debug_cap(n, "indn", get_escape(ti, ESCAPE_INDN));
tinfo_debug_cap(n, "gpm", ti->gpmfd >= 0);
tinfo_debug_cap(n, "kbd", ti->kbdlevel > 0);
finish_line(n);
}

@ -0,0 +1,530 @@
#include "automaton.h"
#include "internal.h"
// the input automaton, walked for all escape sequences. an escape sequence is
// everything from an escape through recognized termination of that escape, or
// abort of the sequence via another escape, save the case of DCS sequences
// (those beginning with Escape-P), which are terminated by the ST sequence
// Escape-\. in the case of an aborted sequence, the sequence in its entirety
// is replayed as regular input. regular input is not driven through this
// automaton.
//
// one complication is that the user can just press escape themselves, followed
// by arbitrary other keypresses. when input is redirected from some source
// other than the connected terminal, this is no problem: we know control
// sequences to be coming in from the connected terminal, and everything else
// is bulk input.
// we assumed escapes can only be composed of 7-bit chars
typedef struct esctrie {
// if non-NULL, this is the next level of radix-128 trie. it is NULL on
// accepting nodes, since no valid control sequence is a prefix of another
// valid control sequence.
struct esctrie** trie;
enum {
NODE_SPECIAL, // an accepting node, or pure transit (if ni.id == 0)
NODE_NUMERIC, // accumulates a number
NODE_STRING, // accumulates a string
NODE_FUNCTION, // invokes a function
} ntype;
ncinput ni; // composed key terminating here
int number; // accumulated number; reset to 0 on entry
char* str; // accumulated string; reset to NULL on entry
triefunc fxn; // function to call on match
struct esctrie* kleene; // kleene match
} esctrie;
uint32_t esctrie_id(const esctrie* e){
return e->ni.id;
}
const char* esctrie_string(const esctrie* e){
return e->str;
}
esctrie** esctrie_trie(esctrie* e){
return e->trie;
}
int esctrie_numeric(const esctrie* e){
if(e->ntype != NODE_NUMERIC){
return -1;
}
return e->number;
}
static inline esctrie*
create_esctrie_node(int special){
esctrie* e = malloc(sizeof(*e));
if(e){
memset(e, 0, sizeof(*e));
e->ntype = NODE_SPECIAL;
if((e->ni.id = special) == 0){
const size_t tsize = sizeof(*e->trie) * 0x80;
if( (e->trie = malloc(tsize)) ){
memset(e->trie, 0, tsize);
return e;
}
free(e);
return NULL;
}
return e;
}
return e;
}
static void
free_trienode(esctrie** eptr){
esctrie* e;
if( (e = *eptr) ){
if(e->trie){
int z;
for(z = 0 ; z < 0x80 ; ++z){
// don't recurse down a link to ourselves
if(e->trie[z] && e->trie[z] != e){
free_trienode(&e->trie[z]);
}
// if it's a numeric path, only recurse once
if(z == '0'){
if(e->trie['1'] == e->trie[z]){
z = '9';
}
}
// if it's an all-strings path, only recurse once
if(z == ' '){
if(e->trie['!'] == e->trie[z]){
z = 0x80;
}
}
}
free(e->str);
free(e->trie);
}
free(e);
}
}
void input_free_esctrie(automaton* a){
free_trienode(&a->escapes);
}
static int
esctrie_make_numeric(esctrie* e){
if(e->ntype == NODE_NUMERIC){
return 0;
}
if(e->ntype != NODE_SPECIAL){
logerror("can't make node type %d numeric\n", e->ntype);
return -1;
}
for(int i = '0' ; i < '9' ; ++i){
if(e->trie[i]){
logerror("can't make %c-followed numeric\n", i);
return -1;
}
}
e->ntype = NODE_NUMERIC;
for(int i = '0' ; i < '9' ; ++i){
e->trie[i] = e;
}
logdebug("made numeric: %p\n", e);
return 0;
}
static int
esctrie_make_kleene(esctrie* e, unsigned follow, esctrie* term){
if(e->ntype != NODE_SPECIAL){
logerror("can't make node type %d string\n", e->ntype);
return -1;
}
for(unsigned i = 0 ; i < 0x80 ; ++i){
if(i == follow){
e->trie[i] = term;
}else if(e->trie[i] == NULL){
e->trie[i] = e;
}
}
logdebug("made kleene: %p\n", e);
return 0;
}
static int
esctrie_make_function(esctrie* e, triefunc fxn){
if(e->ntype != NODE_SPECIAL){
logerror("can't make node type %d function\n", e->ntype);
return -1;
}
if(e->trie){
logerror("can't make followed function\n");
return -1;
}
e->ntype = NODE_FUNCTION;
e->fxn = fxn;
logdebug("made function %p: %p\n", fxn, e);
return 0;
}
static int
esctrie_make_string(esctrie* e, triefunc fxn){
if(e->ntype == NODE_STRING){
return 0;
}
if(e->ntype != NODE_SPECIAL){
logerror("can't make node type %d string\n", e->ntype);
return -1;
}
for(int i = 0 ; i < 0x80 ; ++i){
if(!isprint(i)){
continue;
}
if(e->trie[i]){
logerror("can't make %c-followed string\n", i);
return -1;
}
}
esctrie* newe = create_esctrie_node(0);
if(newe == NULL){
return -1;
}
for(int i = 0 ; i < 0x80 ; ++i){
if(!isprint(i)){
continue;
}
e->trie[i] = newe;
}
e = newe;
e->ntype = NODE_STRING;
for(int i = 0 ; i < 0x80 ; ++i){
if(!isprint(i)){
continue;
}
e->trie[i] = newe;
}
if((e->trie[0x1b] = create_esctrie_node(0)) == NULL){
return -1;
}
e = e->trie[0x1b];
if((e->trie['\\'] = create_esctrie_node(NCKEY_INVALID)) == NULL){
return -1;
}
e = e->trie['\\'];
e->ni.id = 0;
e->ntype = NODE_SPECIAL;
if(esctrie_make_function(e, fxn)){
return -1;
}
logdebug("made string: %p\n", e);
return 0;
}
static esctrie*
link_kleene(esctrie* e, unsigned follow){
if(e->kleene){
return e->kleene;
}
esctrie* term = create_esctrie_node(0);
if(term == NULL){
return NULL;
}
esctrie* targ = NULL;
if( (targ = create_esctrie_node(0)) ){
if(esctrie_make_kleene(targ, follow, term)){
free_trienode(&targ);
free_trienode(&term);
return NULL;
}
}
// fill in all NULL numeric links with the new target
for(unsigned int i = 0 ; i < 0x80 ; ++i){
if(i == follow){
if(e->trie[i]){
logerror("drain terminator already registered\n");
free_trienode(&targ);
free_trienode(&term);
}
e->trie[follow] = term;
}else if(e->trie[i] == NULL){
e->trie[i] = targ;
// FIXME travel to the ends and link targ there
}
}
targ->kleene = targ;
return e->trie[follow];
}
static void
fill_in_numerics(esctrie* e, esctrie* targ, unsigned follow, esctrie* efollow){
// fill in all NULL numeric links with the new target
for(int i = '0' ; i <= '9' ; ++i){
if(e->trie[i] == NULL){
e->trie[i] = targ;
}else if(e->trie[i] != e){
fill_in_numerics(e->trie[i], targ, follow, efollow);
}
}
e->trie[follow] = efollow;
}
// accept any digit and transition to a numeric node.
static esctrie*
link_numeric(esctrie* e, unsigned follow){
esctrie* targ = NULL;
// find a linked NODE_NUMERIC, if one exists. we'll want to reuse it.
for(int i = '0' ; i <= '9' ; ++i){
targ = e->trie[i];
if(targ && targ->ntype == NODE_NUMERIC){
break;
}
targ = NULL;
}
// we either have a numeric target, or will make one now
if(targ == NULL){
if( (targ = create_esctrie_node(0)) ){
if(esctrie_make_numeric(targ)){
free_trienode(&targ);
return NULL;
}
}
}
// targ is the numeric node we're either creating or coopting
esctrie* efollow = targ->trie[follow];
if(efollow == NULL){
if((efollow = create_esctrie_node(0)) == NULL){
return NULL;
}
}
for(int i = '0' ; i <= '9' ; ++i){
if(e->trie[i] == NULL){
e->trie[i] = targ;
}
fill_in_numerics(e->trie[i], targ, follow, efollow);
}
return efollow;
}
// add a cflow path to the automaton
int inputctx_add_cflow(automaton* a, const char* csi, triefunc fxn){
if(a->escapes == NULL){
if((a->escapes = create_esctrie_node(0)) == NULL){
return -1;
}
}
esctrie* eptr = a->escapes;
bool inescape = false;
unsigned char c;
while( (c = *csi++) ){
if(c == '\\'){
if(inescape){
logerror("illegal escape: \\\n");
return -1;
}
inescape = true;
}else if(inescape){
if(c == 'N'){
// a numeric must be followed by some terminator
if(!*csi){
logerror("illegal numeric terminator\n");
return -1;
}
c = *csi++;
eptr = link_numeric(eptr, c);
if(eptr == NULL){
return -1;
}
}else if(c == 'S'){
if(esctrie_make_string(eptr, fxn)){
return -1;
}
return 0;
}else if(c == 'D'){ // drain (kleene closure)
// a kleene must be followed by some terminator
if(!*csi){
logerror("illegal kleene terminator\n");
return -1;
}
c = *csi++;
eptr = link_kleene(eptr, c);
if(eptr == NULL){
return -1;
}
}else{
logerror("illegal escape: %u\n", c);
return -1;
}
inescape = false;
}else{
if(eptr->trie[c] == NULL){
if((eptr->trie[c] = create_esctrie_node(0)) == NULL){
return -1;
}
if(isdigit(c)){
eptr->trie[c]->number = c - '0';
}
}else if(eptr->trie[c] == eptr->kleene){
if((eptr->trie[c] = create_esctrie_node(0)) == NULL){
return -1;
}
}else if(eptr->trie[c]->ntype == NODE_NUMERIC){
// punch a hole through the numeric loop. create a new one, and fill
// it in with the existing target.
struct esctrie* newe;
if((newe = create_esctrie_node(0)) == NULL){
return -1;
}
for(int i = 0 ; i < 0x80 ; ++i){
newe->trie[i] = eptr->trie[c]->trie[i];
}
eptr->trie[c] = newe;
}
eptr = eptr->trie[c];
}
logdebug("added %c, now at %p (%d) %d (%u)\n", c, eptr, eptr->ntype, eptr->number, *csi);
}
if(inescape){
logerror("illegal escape at end of line\n");
return -1;
}
free(eptr->trie);
eptr->trie = NULL;
return esctrie_make_function(eptr, fxn);
}
// multiple input escapes might map to the same input
int inputctx_add_input_escape(automaton* a, const char* esc, uint32_t special,
unsigned shift, unsigned ctrl, unsigned alt){
if(esc[0] != NCKEY_ESC || strlen(esc) < 2){ // assume ESC prefix + content
logerror("not an escape (0x%x)\n", special);
return -1;
}
esctrie** eptr = &a->escapes;
if(*eptr == NULL){
if((*eptr = create_esctrie_node(0)) == NULL){
return -1;
}
}
esctrie* cur = *eptr;
++esc; // don't encode initial escape as a transition
do{
int valid = *esc;
if(valid <= 0 || valid >= 0x80 || valid == NCKEY_ESC){
logerror("invalid character %d in escape\n", valid);
return -1;
}
if(cur->trie[valid] == NULL){
if((cur->trie[valid] = create_esctrie_node(0)) == NULL){
return -1;
}
}
cur = cur->trie[valid];
++esc;
}while(*esc);
// it appears that multiple keys can be mapped to the same escape string. as
// an example, see "kend" and "kc1" in st ("simple term" from suckless) :/.
if(cur->ni.id){ // already had one here!
if(cur->ni.id != special){
logwarn("already added escape (got 0x%x, wanted 0x%x)\n", cur->ni.id, special);
}
}else{
cur->ni.id = special;
cur->ni.shift = shift;
cur->ni.ctrl = ctrl;
cur->ni.alt = alt;
}
return 0;
}
static int
growstring(automaton* a, esctrie* e, unsigned candidate){
if(!isprint(candidate)){
logerror("unexpected char %u in string\n", candidate);
return -1;
}
char* tmp = realloc(e->str, a->stridx + 1);
if(tmp == NULL){
return -1;
}
e->str = tmp;
e->str[a->stridx - 1] = candidate;
e->str[a->stridx] = '\0';
++a->stridx;
return 0;
}
// returns -1 for non-match, 0 for match, 1 for acceptance. if we are in the
// middle of a sequence, and receive an escape, *do not call this*, but
// instead call reset_automaton() after replaying the used characters to the
// bulk input buffer, and *then* call this with the escape.
int walk_automaton(automaton* a, struct inputctx* ictx, unsigned candidate,
ncinput* ni){
if(candidate >= 0x80){
logerror("eight-bit char %u in control sequence\n", candidate);
return -1;
}
esctrie* e = a->state;
logdebug("state: %p candidate: %c %u type: %d\n", e, candidate, candidate, e->ntype);
// we ought not have been called for an escape with any state!
if(candidate == 0x1b && !a->instring){
assert(NULL == e);
a->state = a->escapes;
return 0;
}
if(e->ntype == NODE_NUMERIC){
if(isdigit(candidate)){
int digit = candidate - '0';
if((INT_MAX - digit) / 10 < e->number){
logwarn("digit %c will overflow %d\n", candidate, e->number);
}
e->number = e->number * 10 + digit;
return 0;
}
}else if(e->ntype == NODE_STRING){
if(candidate == 0x1b){
a->state = e->trie[candidate];
a->instring = 0;
}else if(growstring(a, e, candidate)){
return -1;
}
return 0;
}
if((a->state = e->trie[candidate]) == NULL){
if(isprint(candidate)){
if(e == a->escapes){
memset(ni, 0, sizeof(*ni));
ni->id = candidate;
ni->alt = true;
return 1;
}
}
loginfo("unexpected transition %u\n", candidate);
return -1;
}
e = a->state;
// initialize any node we've just stepped into
switch(e->ntype){
case NODE_NUMERIC:
e->number = candidate - '0';
break;
case NODE_STRING:
a->stridx = 1;
a->instring = 1;
if(growstring(a, e, candidate)){
return -1;
}
break;
case NODE_SPECIAL:
if(e->ni.id){
memcpy(ni, &e->ni, sizeof(*ni));
a->state = NULL; // FIXME?
return 1;
}
break;
case NODE_FUNCTION:
a->state = NULL; // FIXME?
if(e->fxn == NULL){
return 2;
}
return e->fxn(ictx);
break;
}
return 0;
}

@ -0,0 +1,50 @@
#ifndef NOTCURSES_AUTOMATON
#define NOTCURSES_AUTOMATON
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct ncinput;
struct esctrie;
struct inputctx;
typedef int (*triefunc)(struct inputctx*);
// the state necessary for matching input against our automaton of control
// sequences. we *do not* match the bulk UTF-8 input. we match online (i.e.
// we can be passed a byte at a time).
typedef struct automaton {
struct esctrie* escapes; // head Esc node of trie
int used; // bytes consumed thus far
int instring; // are we in an ST-terminated string?
struct esctrie* state;
unsigned stridx; // bytes of accumulating string (includes NUL)
} automaton;
void input_free_esctrie(automaton *a);
int inputctx_add_input_escape(automaton* a, const char* esc,
uint32_t special, unsigned shift,
unsigned ctrl, unsigned alt);
int inputctx_add_cflow(automaton* a, const char* csi, triefunc fxn)
__attribute__ ((nonnull (1, 2, 3)));
int walk_automaton(automaton* a, struct inputctx* ictx, unsigned candidate,
struct ncinput* ni)
__attribute__ ((nonnull (1, 2, 4)));
uint32_t esctrie_id(const struct esctrie* e);
const char* esctrie_string(const struct esctrie* e);
// returns 128-way array of esctrie pointers
struct esctrie** esctrie_trie(struct esctrie* e);
int esctrie_numeric(const struct esctrie* e);
#ifdef __cplusplus
}
#endif
#endif

@ -181,14 +181,11 @@ int ncdirect_cursor_disable(ncdirect* nc){
}
static int
cursor_yx_get(ncdirect* n, int ttyfd, const char* u7, int* y, int* x){
cursor_yx_get(ncdirect* n, const char* u7, int* y, int* x){
struct inputctx* ictx = n->tcache.ictx;
if(ncdirect_flush(n)){
return -1;
}
if(tty_emit(u7, ttyfd)){
return -1;
}
int fakey, fakex;
if(y == NULL){
y = &fakey;
@ -196,7 +193,7 @@ cursor_yx_get(ncdirect* n, int ttyfd, const char* u7, int* y, int* x){
if(x == NULL){
x = &fakex;
}
get_cursor_location(ictx, y, x);
get_cursor_location(ictx, u7, y, x);
loginfo("cursor at y=%d x=%d\n", *y, *x);
return 0;
}
@ -214,7 +211,7 @@ int ncdirect_cursor_move_yx(ncdirect* n, int y, int x){
if(hpa){
return term_emit(tiparm(hpa, x), n->ttyfp, false);
}else if(n->tcache.ttyfd >= 0 && u7){
if(cursor_yx_get(n, n->tcache.ttyfd, u7, &y, NULL)){
if(cursor_yx_get(n, u7, &y, NULL)){
return -1;
}
}else{
@ -224,7 +221,7 @@ int ncdirect_cursor_move_yx(ncdirect* n, int y, int x){
if(!vpa){
return term_emit(tiparm(vpa, y), n->ttyfp, false);
}else if(n->tcache.ttyfd >= 0 && u7){
if(cursor_yx_get(n, n->tcache.ttyfd, u7, NULL, &x)){
if(cursor_yx_get(n, u7, NULL, &x)){
return -1;
}
}else{
@ -394,7 +391,7 @@ int ncdirect_cursor_yx(ncdirect* n, int* y, int* x){
if(!x){
x = &xval;
}
ret = cursor_yx_get(n, n->tcache.ttyfd, u7, y, x);
ret = cursor_yx_get(n, u7, y, x);
if(tcsetattr(n->tcache.ttyfd, TCSANOW, &oldtermios)){
fprintf(stderr, "Couldn't restore terminal mode on %d (%s)\n",
n->tcache.ttyfd, strerror(errno)); // don't return error for this
@ -844,7 +841,11 @@ ncdirect_stop_minimal(void* vnc){
ret |= fbuf_finalize(&f, stdout);
}
if(nc->tcache.ttyfd >= 0){
ret |= tty_emit("\x1b[<u", nc->tcache.ttyfd);
if(nc->tcache.kbdlevel){
if(tty_emit(KKEYBOARD_POP, nc->tcache.ttyfd)){
ret = -1;
}
}
const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM);
if(cnorm && tty_emit(cnorm, nc->tcache.ttyfd)){
ret = -1;
@ -862,6 +863,11 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
if(flags > (NCDIRECT_OPTION_DRAIN_INPUT << 1)){ // allow them through with warning
logwarn("Passed unsupported flags 0x%016jx\n", (uintmax_t)flags);
}
if(termtype){
if(putenv_term(termtype)){
return NULL;
}
}
ncdirect* ret = malloc(sizeof(ncdirect));
if(ret == NULL){
return ret;
@ -967,7 +973,7 @@ char* ncdirect_readline(ncdirect* n, const char* prompt){
}
// FIXME what if we're reading from redirected input, not a terminal?
int y, xstart;
if(cursor_yx_get(n, n->tcache.ttyfd, u7, &y, &xstart)){
if(cursor_yx_get(n, u7, &y, &xstart)){
return NULL;
}
int tline = y;
@ -1016,7 +1022,7 @@ char* ncdirect_readline(ncdirect* n, const char* prompt){
str[wused - 1] = L'\0';
// FIXME check modifiers
int x;
if(cursor_yx_get(n, n->tcache.ttyfd, u7, &y, &x)){
if(cursor_yx_get(n, u7, &y, &x)){
break;
}
if(x < oldx){

@ -64,7 +64,8 @@ egcpool_grow(egcpool* pool, size_t len){
// get the expected length of the encoded codepoint from the first byte of a
// utf-8 character. if the byte is illegal as a first byte, 1 is returned.
// Table 3.1B, Legal UTF8 Byte Sequences, Corrigendum #1: UTF-8 Shortest Form
// Table 3.1B, Legal UTF8 Byte Sequences, Corrigendum #1: UTF-8 Shortest Form.
// subsequent ("continuation") bytes must start with the bit pattern 10.
static inline size_t
utf8_codepoint_length(unsigned char c){
if(c <= 0x7f){ // 0x000000...0x00007f

File diff suppressed because it is too large Load Diff

@ -68,6 +68,7 @@ struct initial_responses {
int sixely; // maximum sixel height
int sixelx; // maximum sixel width
char* version; // version string, heap-allocated
unsigned kbdlevel; // kitty keyboard protocol level
};
// Blocking call. Waits until the input thread has processed all responses to
@ -75,8 +76,8 @@ struct initial_responses {
struct initial_responses* inputlayer_get_responses(struct inputctx* ictx)
__attribute__ ((nonnull (1)));
int get_cursor_location(struct inputctx* ictx, int* y, int* x)
__attribute__ ((nonnull (1)));
int get_cursor_location(struct inputctx* ictx, const char* u7, int* y, int* x)
__attribute__ ((nonnull (1, 2)));
#ifdef __cplusplus
}

@ -1783,6 +1783,9 @@ emit_scrolls(const tinfo* ti, int count, fbuf* f){
return 0;
}
// replace or populate the TERM environment variable with 'termname'
int putenv_term(const char* termname) __attribute__ ((nonnull (1)));
#undef ALLOC
#undef API

@ -5,7 +5,7 @@
extern "C" {
#endif
void nclog(const char* fmt, ...);
void nclog(const char* fmt, ...) __attribute__ ((format (printf, 1, 2)));
// logging
extern int loglevel;

@ -100,8 +100,10 @@ notcurses_stop_minimal(void* vnc){
if(nc->tcache.tpreserved){
ret |= tcsetattr(nc->tcache.ttyfd, TCSAFLUSH, nc->tcache.tpreserved);
}
if(tty_emit("\x1b[<u", nc->tcache.ttyfd)){
ret = -1;
if(nc->tcache.kbdlevel){
if(tty_emit(KKEYBOARD_POP, nc->tcache.ttyfd)){
ret = -1;
}
}
if((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){
if(sprite_clear_all(&nc->tcache, f)){ // send this to f
@ -731,7 +733,7 @@ int ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
logerror("Can't keep %d@%d cols from %d\n", keeplenx, keepx, cols);
return -1;
}
loginfo("%dx%d @ %d/%d → %d/%d @ %d/%d (keeping %dx%d from %d/%d)\n", rows, cols, n->absy, n->absx, ylen, xlen, n->absy + keepy + yoff, n->absx + keepx + xoff, keepleny, keeplenx, keepy, keepx);
loginfo("%dx%d @ %d/%d → %d/%d @ %d/%d (want %dx%d from %d/%d)\n", rows, cols, n->absy, n->absx, ylen, xlen, n->absy + keepy + yoff, n->absx + keepx + xoff, keepleny, keeplenx, keepy, keepx);
if(n->absy == n->absy + keepy && n->absx == n->absx + keepx &&
rows == ylen && cols == xlen){
return 0;
@ -985,6 +987,11 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
if(opts->flags >= (NCOPTION_DRAIN_INPUT << 1u)){
fprintf(stderr, "Warning: unknown Notcurses options %016" PRIu64 "\n", opts->flags);
}
if(opts->termtype){
if(putenv_term(opts->termtype)){
return NULL;
}
}
notcurses* ret = malloc(sizeof(*ret));
if(ret == NULL){
return ret;
@ -1116,12 +1123,11 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
goto err;
}
init_banner(ret, &ret->rstate.f);
fwrite(ret->rstate.f.buf, ret->rstate.f.used, 1, ret->ttyfp);
fbuf_reset(&ret->rstate.f);
if(ncflush(ret->ttyfp)){
if(blocking_write(fileno(ret->ttyfp), ret->rstate.f.buf, ret->rstate.f.used)){
free_plane(ret->stdplane);
goto err;
}
fbuf_reset(&ret->rstate.f);
if(ret->rstate.logendy >= 0){ // if either is set, both are
if(!ret->suppress_banner && ret->tcache.ttyfd >= 0){
if(locate_cursor(&ret->tcache, &ret->rstate.logendy, &ret->rstate.logendx)){

@ -152,18 +152,6 @@ query_rgb(void){
return rgb;
}
// we couldn't get a terminal from interrogation, so let's see if the TERM
// matches any of our known terminals. this can only be as accurate as the
// TERM setting is (and as up-to-date and complete as we are).
static int
match_termname(const char* termname, queried_terminals_e* qterm){
// https://github.com/alacritty/alacritty/pull/5274 le sigh
if(strstr(termname, "alacritty")){
*qterm = TERMINAL_ALACRITTY;
}
return 0;
}
void free_terminfo_cache(tinfo* ti){
stop_inputlayer(ti);
free(ti->termversion);
@ -328,12 +316,13 @@ 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 1, and push current.
// request kitty keyboard protocol through level 1, first pushing 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.
// input events. this queries for the level of support. we want to do this
// because the "keyboard pop" control code is mishandled by kitty < 0.20.0.
#define KBDQUERY "\x1b[?u"
// these queries (terminated with a Primary Device Attributes, to which
@ -387,6 +376,7 @@ init_terminfo_esc(tinfo* ti, const char* name, escape_e idx,
// 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"
#define RMCUP "\x1b[?1049l"
// 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).
@ -506,15 +496,6 @@ apply_term_heuristics(tinfo* ti, const char* termname, queried_terminals_e qterm
// setupterm interprets a missing/empty TERM variable as the special value “unknown”.
termname = ti->termname ? ti->termname : "unknown";
}
if(qterm == TERMINAL_UNKNOWN){
match_termname(termname, &qterm);
// we pick up alacritty's version via a weird hack involving Secondary
// Device Attributes. if we're not alacritty, don't trust that version.
if(qterm != TERMINAL_ALACRITTY){
free(ti->termversion);
ti->termversion = NULL;
}
}
// st had neither caps.sextants nor caps.quadrants last i checked (0.8.4)
ti->caps.braille = true; // most everyone has working caps.braille, even from fonts
ti->caps.halfblocks = true; // most everyone has working halfblocks
@ -948,6 +929,7 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
goto err;
}
}
ti->kbdlevel = iresp->kbdlevel;
if(iresp->qterm != TERMINAL_UNKNOWN){
ti->qterm = iresp->qterm;
}
@ -998,7 +980,7 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
build_supported_styles(ti);
if(ti->pixel_draw == NULL && ti->pixel_draw_late == NULL){
if(kitty_graphics){
setup_kitty_bitmaps(ti, ti->ttyfd, NCPIXEL_KITTY_ANIMATED);
setup_kitty_bitmaps(ti, ti->ttyfd, NCPIXEL_KITTY_STATIC);
}
// our current sixel quantization algorithm requires at least 64 color
// registers. we make use of no more than 256. this needs to happen
@ -1010,7 +992,10 @@ 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->ttyfd >= 0){
tty_emit(KKEYBOARD_POP, ti->ttyfd);
tty_emit(RMCUP, ti->ttyfd);
}
if(ti->tpreserved){
(void)tcsetattr(ti->ttyfd, TCSANOW, ti->tpreserved);
free(ti->tpreserved);
@ -1071,10 +1056,9 @@ int locate_cursor(tinfo* ti, int* cursor_y, int* cursor_x){
return -1;
}
int fd = ti->ttyfd;
if(tty_emit(u7, fd)){
if(get_cursor_location(ti->ictx, u7, cursor_y, cursor_x)){
return -1;
}
get_cursor_location(ti->ictx, cursor_y, cursor_x);
loginfo("got a report from %d %d/%d\n", fd, *cursor_y, *cursor_x);
return 0;
}
@ -1136,3 +1120,27 @@ int cbreak_mode(tinfo* ti){
#endif
return 0;
}
// replace or populate the TERM environment variable with 'termname'
int putenv_term(const char* termname){
#define ENVVAR "TERM"
const char* oldterm = getenv(ENVVAR);
if(oldterm){
logdebug("replacing %s value %s with %s\n", ENVVAR, oldterm, termname);
}else{
loginfo("provided %s value %s\n", ENVVAR, termname);
}
if(strcmp(oldterm, termname) == 0){
return 0;
}
char* buf = malloc(strlen(termname) + strlen(ENVVAR) + 1);
if(buf == NULL){
return -1;
}
int c = putenv(buf);
if(c){
logerror("couldn't export %s\n", buf);
}
free(buf);
return c;
}

@ -16,6 +16,8 @@ extern "C" {
#include "fbuf.h"
#include "in.h"
#define KKEYBOARD_POP "\x1b[<u"
struct ncpile;
struct sprixel;
struct notcurses;
@ -181,6 +183,7 @@ typedef struct tinfo {
HANDLE outhandle;
#endif
unsigned kbdlevel; // kitty keyboard support level
bool bce; // is the bce property advertised?
bool in_alt_screen; // are we in the alternate screen?
} tinfo;

@ -59,7 +59,7 @@ git pull
TARBALL=v$VERSION.tar.gz
wget https://github.com/dankamongmen/notcurses/archive/$TARBALL
gpg --sign --armor --detach-sign $TARBALL
rm v$VERSION.tar.gz
rm $TARBALL
echo "Cut $VERSION, signed to $TARBALL.asc"
echo "Now uploadling the sig to https://github.com/dankamongmen/notcurses/releases"

Loading…
Cancel
Save