From 0c16296b96df292d52ce64b90e279d02d4863c86 Mon Sep 17 00:00:00 2001 From: nick black Date: Tue, 14 Sep 2021 20:17:52 -0400 Subject: [PATCH 1/3] [input] walk specials automaton --- src/lib/in.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/lib/in.c b/src/lib/in.c index 1100ba76c..166c96057 100644 --- a/src/lib/in.c +++ b/src/lib/in.c @@ -21,11 +21,12 @@ // FIXME still need to: // read specials from terminfo -// integrate main specials trie with automaton, or match it alongside -// the main automaton +// integrate main specials trie with automaton (maybe) // wake up input thread when space becomes available // restore stats // modifiers for non-kitty, non-mouse input +// adjust mouse locations for margins +// handle timeouts static sig_atomic_t resize_seen; @@ -131,6 +132,7 @@ typedef struct inputctx { char runstring[BUFSIZ]; // running string (when stringstate != STATE_NULL) int stridx; // length of runstring int p2, p3, p4; // holders for numeric params + struct esctrie* triepos;// position in escapes automaton // ringbuffers for processed, structured input cursorloc* csrs; // cursor reports are dumped here @@ -389,6 +391,7 @@ prep_all_keys(inputctx* ictx){ input_free_esctrie(&ictx->inputescapes); return -1; } + ictx->triepos = ictx->inputescapes; return 0; } @@ -635,6 +638,26 @@ mouse_click(inputctx* ictx){ pthread_cond_broadcast(&ictx->icond); } +static void +special_key(inputctx* ictx){ + assert(ictx->triepos); + assert(NCKEY_INVALID != ictx->triepos->special); + pthread_mutex_lock(&ictx->ilock); + if(ictx->ivalid == ictx->isize){ + pthread_mutex_unlock(&ictx->ilock); + logerror("dropping input 0x%08xx\n", ictx->triepos->special); + return; + } + ncinput* ni = ictx->inputs + ictx->iwrite; + ni->id = ictx->triepos->special; + ni->alt = ictx->triepos->alt; + ni->ctrl = ictx->triepos->ctrl; + ni->shift = ictx->triepos->shift; + ni->x = ni->y = 0; + pthread_mutex_unlock(&ictx->ilock); + pthread_cond_broadcast(&ictx->icond); +} + // ictx->numeric and ictx->p2 have the two parameters static void kitty_kbd(inputctx* ictx){ @@ -1211,6 +1234,12 @@ process_escape(inputctx* ictx, const unsigned char* buf, int buflen){ if(r == 1){ handoff_initial_responses(ictx); } + logtrace("triepos: %p in: %u special: 0x%08x\n", ictx->triepos, buf[used], ictx->triepos->special); + if((ictx->triepos = ictx->triepos->trie[buf[used]]) == NULL){ + ictx->triepos = ictx->inputescapes; + }else if(ictx->triepos->special != NCKEY_INVALID){ + special_key(ictx); + } ++used; } return used; From 37fbd385bb671a9890f7b8875d3e2b9e5312db30 Mon Sep 17 00:00:00 2001 From: nick black Date: Wed, 15 Sep 2021 16:03:33 -0400 Subject: [PATCH 2/3] [input] proper escape trie traversal --- src/lib/in.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/in.c b/src/lib/in.c index 166c96057..fd32a2afc 100644 --- a/src/lib/in.c +++ b/src/lib/in.c @@ -1235,10 +1235,11 @@ process_escape(inputctx* ictx, const unsigned char* buf, int buflen){ handoff_initial_responses(ictx); } logtrace("triepos: %p in: %u special: 0x%08x\n", ictx->triepos, buf[used], ictx->triepos->special); - if((ictx->triepos = ictx->triepos->trie[buf[used]]) == NULL){ - ictx->triepos = ictx->inputescapes; - }else if(ictx->triepos->special != NCKEY_INVALID){ + if(ictx->triepos->special != NCKEY_INVALID){ special_key(ictx); + ictx->triepos = ictx->inputescapes; + }else if((ictx->triepos = ictx->triepos->trie[buf[used]]) == NULL){ + ictx->triepos = ictx->inputescapes; } ++used; } From 5015577e26b154d06d1dfda50c16829c022fc002 Mon Sep 17 00:00:00 2001 From: nick black Date: Wed, 15 Sep 2021 18:46:22 -0400 Subject: [PATCH 3/3] [input] correct for margins --- src/input/input.cpp | 10 +- src/lib/direct.c | 54 +++++---- src/lib/in.c | 269 +++++++++++++++++++++++++++++--------------- src/lib/in.h | 6 +- src/lib/internal.h | 43 +++++-- src/lib/notcurses.c | 3 +- src/lib/render.c | 21 +--- src/lib/termdesc.c | 5 +- src/lib/termdesc.h | 3 +- 9 files changed, 266 insertions(+), 148 deletions(-) diff --git a/src/input/input.cpp b/src/input/input.cpp index b53faf522..1f8214882 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -216,13 +216,13 @@ int input_demo(ncpp::NotCurses* nc) { } struct ncplane_options nopts = { .y = dimy - PLOTHEIGHT - 1, - .x = 0, + .x = NCALIGN_CENTER, .rows = PLOTHEIGHT, - .cols = cols, + .cols = cols / 2, .userptr = nullptr, .name = "plot", .resizecb = nullptr, // FIXME - .flags = 0, + .flags = NCPLANE_OPTION_HORALIGNED, .margin_b = 0, .margin_r = 0, }; @@ -354,6 +354,10 @@ int main(int argc, char** argv){ return EXIT_FAILURE; } notcurses_options nopts{}; + nopts.margin_t = 2; + nopts.margin_l = 2; + nopts.margin_r = 2; + nopts.margin_b = 2; nopts.loglevel = NCLOGLEVEL_ERROR; if(argc > 2){ usage(argv[0], stderr); diff --git a/src/lib/direct.c b/src/lib/direct.c index 6beb739e9..4efd1f1dc 100644 --- a/src/lib/direct.c +++ b/src/lib/direct.c @@ -179,7 +179,11 @@ int ncdirect_cursor_disable(ncdirect* nc){ } static int -cursor_yx_get(struct inputctx* ictx, int ttyfd, const char* u7, int* y, int* x){ +cursor_yx_get(ncdirect* n, int ttyfd, 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; } @@ -201,7 +205,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->tcache.ictx, n->tcache.ttyfd, u7, &y, NULL)){ + if(cursor_yx_get(n, n->tcache.ttyfd, u7, &y, NULL)){ return -1; } }else{ @@ -211,7 +215,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->tcache.ictx, n->tcache.ttyfd, u7, NULL, &x)){ + if(cursor_yx_get(n, n->tcache.ttyfd, u7, NULL, &x)){ return -1; } }else{ @@ -381,7 +385,7 @@ int ncdirect_cursor_yx(ncdirect* n, int* y, int* x){ if(!x){ x = &xval; } - ret = cursor_yx_get(n->tcache.ictx, n->tcache.ttyfd, u7, y, x); + ret = cursor_yx_get(n, n->tcache.ttyfd, 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 @@ -430,7 +434,7 @@ ncdirect_dump_plane(ncdirect* n, const ncplane* np, int xoff){ if(np->sprite){ int y; const char* u7 = get_escape(&n->tcache, ESCAPE_U7); - if(cursor_yx_get(n->tcache.ictx, n->tcache.ttyfd, u7, &y, NULL)){ + if(cursor_yx_get(n, n->tcache.ttyfd, u7, &y, NULL)){ return -1; } if(ncdirect_cursor_move_yx(n, y, xoff)){ @@ -442,10 +446,10 @@ ncdirect_dump_plane(ncdirect* n, const ncplane* np, int xoff){ return -1; } fbuf f = {}; - if(fbuf_init(&f)){ + if(cursor_yx_get(n, n->tcache.ttyfd, u7, &y, NULL)){ return -1; } - if(cursor_yx_get(n->tcache.ictx, n->tcache.ttyfd, u7, &y, NULL)){ + if(fbuf_init(&f)){ return -1; } if(toty - dimy < y){ @@ -458,21 +462,16 @@ ncdirect_dump_plane(ncdirect* n, const ncplane* np, int xoff){ // perform our scrolling outside of the fbuf framework, as we need it // to happen immediately for fbdcon if(ncdirect_cursor_move_yx(n, y, xoff)){ + fbuf_free(&f); return -1; } - const char* indn = get_escape(&n->tcache, ESCAPE_IND); - if(scrolls > 1 && indn){ - if(term_emit(tiparm(indn, scrolls), stdout, true) < 0){ - return -1; - } - } - while(scrolls--){ - if(ncfputc('\v', stdout) < 0){ - return -1; - } + if(emit_scrolls(&n->tcache, scrolls, &f) < 0){ + fbuf_free(&f); + return -1; } } if(sprite_draw(&n->tcache, NULL, np->sprite, &f, y, xoff) < 0){ + fbuf_free(&f); return -1; } if(sprite_commit(&n->tcache, &f, np->sprite, true)){ @@ -790,10 +789,8 @@ ncdirect_stop_minimal(void* vnc){ ret = -1; } ret |= tcsetattr(nc->tcache.ttyfd, TCSANOW, nc->tcache.tpreserved); - ret |= close(nc->tcache.ttyfd); } ret |= ncdirect_flush(nc); - free_terminfo_cache(&nc->tcache); return ret; } @@ -809,6 +806,10 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){ return ret; } memset(ret, 0, sizeof(*ret)); + if(pthread_mutex_init(&ret->stats.lock, NULL)){ + free(ret); + return NULL; + } ret->flags = flags; ret->ttyfp = outfp; if(!(flags & NCDIRECT_OPTION_INHIBIT_SETLOCALE)){ @@ -821,6 +822,7 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){ } if(setup_signals(ret, (flags & NCDIRECT_OPTION_NO_QUIT_SIGHANDLERS), true, ncdirect_stop_minimal)){ + pthread_mutex_destroy(&ret->stats.lock); free(ret); return NULL; } @@ -835,9 +837,10 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){ } int cursor_y = -1; int cursor_x = -1; - if(interrogate_terminfo(&ret->tcache, termtype, ret->ttyfp, utf8, - 1, flags & NCDIRECT_OPTION_INHIBIT_CBREAK, - TERMINAL_UNKNOWN, &cursor_y, &cursor_x, NULL)){ + if(interrogate_terminfo(&ret->tcache, termtype, ret->ttyfp, utf8, 1, + flags & NCDIRECT_OPTION_INHIBIT_CBREAK, + TERMINAL_UNKNOWN, &cursor_y, &cursor_x, + &ret->stats, 0, 0)){ goto err; } if(cursor_y >= 0){ @@ -845,6 +848,7 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){ // unaffected by any query spill (unconsumed control sequences). move // us back to that location, in case there was any such spillage. if(ncdirect_cursor_move_yx(ret, cursor_y, cursor_x)){ + free_terminfo_cache(&ret->tcache); goto err; } } @@ -860,6 +864,7 @@ err: (void)tcsetattr(ret->tcache.ttyfd, TCSANOW, ret->tcache.tpreserved); } drop_signals(ret); + pthread_mutex_destroy(&ret->stats.lock); free(ret); return NULL; } @@ -868,6 +873,11 @@ int ncdirect_stop(ncdirect* nc){ int ret = 0; if(nc){ ret |= ncdirect_stop_minimal(nc); + free_terminfo_cache(&nc->tcache); + if(nc->tcache.ttyfd){ + ret |= close(nc->tcache.ttyfd); + } + pthread_mutex_destroy(&nc->stats.lock); free(nc); } return ret; diff --git a/src/lib/in.c b/src/lib/in.c index fd32a2afc..4a3978329 100644 --- a/src/lib/in.c +++ b/src/lib/in.c @@ -21,16 +21,14 @@ // FIXME still need to: // read specials from terminfo -// integrate main specials trie with automaton (maybe) +// integrate main specials trie with automaton, enable input_errors // wake up input thread when space becomes available -// restore stats -// modifiers for non-kitty, non-mouse input -// adjust mouse locations for margins +// (needs pipes/eventfds) // handle timeouts static sig_atomic_t resize_seen; -// called for SIGWINCH and SIGCONT +// called for SIGWINCH and SIGCONT, and causes block_on_input to return void sigwinch_handler(int signo){ resize_seen = signo; } @@ -105,6 +103,19 @@ typedef struct esctrie { bool shift, ctrl, alt; } esctrie; +static esctrie* +create_esctrie_node(int special){ + esctrie* e = malloc(sizeof(*e)); + if(e){ + e->trie = NULL; + e->special = special; + e->shift = 0; + e->ctrl = 0; + e->alt = 0; + } + return e; +} + // local state for the input thread. don't put this large struct on the stack. typedef struct inputctx { int stdinfd; // bulk in fd. always >= 0 (almost always 0). we do not @@ -115,6 +126,9 @@ typedef struct inputctx { HANDLE stdinhandle; // handle to input terminal for MSFT Terminal #endif + uint64_t seqnum; // process-scope sequence number + int lmargin, tmargin; // margins in use at left and top + struct esctrie* inputescapes; // these two are not ringbuffers; we always move any leftover materia to the @@ -150,23 +164,12 @@ typedef struct inputctx { tinfo* ti; // link back to tinfo pthread_t tid; // tid for input thread + ncsharedstats *stats; // stats shared with notcurses context + struct initial_responses* initdata; struct initial_responses* initdata_complete; } inputctx; -static esctrie* -create_esctrie_node(int special){ - esctrie* e = malloc(sizeof(*e)); - if(e){ - e->special = special; - e->trie = NULL; - e->shift = 0; - e->ctrl = 0; - e->alt = 0; - } - return e; -} - static void input_free_esctrie(esctrie** eptr){ esctrie* e; @@ -185,7 +188,8 @@ input_free_esctrie(esctrie** eptr){ } static inline inputctx* -create_inputctx(tinfo* ti, FILE* infp){ +create_inputctx(tinfo* ti, FILE* infp, int lmargin, int tmargin, + ncsharedstats* stats){ inputctx* i = malloc(sizeof(*i)); if(i){ i->csize = 64; @@ -199,19 +203,23 @@ create_inputctx(tinfo* ti, FILE* infp){ if((i->stdinfd = fileno(infp)) >= 0){ if( (i->initdata = malloc(sizeof(*i->initdata))) ){ if(set_fd_nonblocking(i->stdinfd, 1, &ti->stdio_blocking_save) == 0){ - memset(i->initdata, 0, sizeof(*i->initdata)); i->termfd = tty_check(i->stdinfd) ? -1 : get_tty_fd(infp); + memset(i->initdata, 0, sizeof(*i->initdata)); + i->state = i->stringstate = STATE_NULL; + i->iread = i->iwrite = i->ivalid = 0; + i->cread = i->cwrite = i->cvalid = 0; + i->initdata_complete = NULL; + i->inputescapes = NULL; + i->stats = stats; i->ti = ti; - i->cvalid = i->ivalid = 0; - i->cwrite = i->iwrite = 0; - i->cread = i->iread = 0; i->ibufvalid = 0; i->tbufvalid = 0; - i->state = i->stringstate = STATE_NULL; i->numeric = 0; i->stridx = 0; - i->initdata_complete = NULL; i->runstring[i->stridx] = '\0'; + i->lmargin = lmargin; + i->tmargin = tmargin; + i->seqnum = 0; logdebug("input descriptors: %d/%d\n", i->stdinfd, i->termfd); return i; } @@ -270,41 +278,45 @@ inputctx_add_input_escape(inputctx* ictx, const char* esc, uint32_t special, logerror("not an escape (0x%x)\n", special); return -1; } - esctrie** cur = &ictx->inputescapes; - do{ -//fprintf(stderr, "ADDING: %s (%zu) for %d\n", esc, strlen(esc), special); - ++esc; - int validate = *esc; - if(validate < 0 || validate >= 0x80){ + if(ictx->inputescapes == NULL){ + if((ictx->inputescapes = create_esctrie_node(NCKEY_INVALID)) == NULL){ return -1; } - if(*cur == NULL){ - if((*cur = create_esctrie_node(NCKEY_INVALID)) == NULL){ + } + esctrie* cur = ictx->inputescapes; + ++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 == NULL){ + const size_t tsize = sizeof(cur->trie) * 0x80; + if((cur->trie = malloc(tsize)) == NULL){ + return -1; + } + memset(cur->trie, 0, tsize); + } + if(cur->trie[valid] == NULL){ + if((cur->trie[valid] = create_esctrie_node(NCKEY_INVALID)) == NULL){ return -1; } } - if(validate){ - if((*cur)->trie == NULL){ - const size_t tsize = sizeof((*cur)->trie) * 0x80; - if(((*cur)->trie = malloc(tsize)) == NULL){ - return -1; - } - memset((*cur)->trie, 0, tsize); - } - cur = &(*cur)->trie[validate]; - } + 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)->special != NCKEY_INVALID){ // already had one here! - if((*cur)->special != special){ - logwarn("already added escape (got 0x%x, wanted 0x%x)\n", (*cur)->special, special); + if(cur->special != NCKEY_INVALID){ // already had one here! + if(cur->special != special){ + logwarn("already added escape (got 0x%x, wanted 0x%x)\n", cur->special, special); } }else{ - (*cur)->special = special; - (*cur)->shift = shift; - (*cur)->ctrl = ctrl; - (*cur)->alt = alt; + cur->special = special; + cur->shift = shift; + cur->ctrl = ctrl; + cur->alt = alt; } return 0; } @@ -312,6 +324,7 @@ inputctx_add_input_escape(inputctx* ictx, const char* esc, uint32_t special, // https://sw.kovidgoyal.net/kitty/keyboard-protocol/#functional-key-definitions static int prep_kitty_special_keys(inputctx* nc){ + // we do not list here those already handled by prep_windows_special_keys() static const struct { const char* esc; uint32_t key; @@ -628,8 +641,9 @@ mouse_click(inputctx* ictx){ ni->ctrl = ictx->p2 & 0x10; ni->alt = ictx->p2 & 0x08; ni->shift = ictx->p2 & 0x04; - ni->x = ictx->p3; - ni->y = ictx->numeric; + // convert from 1- to 0-indexing and account for margins + ni->x = ictx->p3 - 1 - ictx->lmargin; + ni->y = ictx->numeric - 1 - ictx->tmargin; if(++ictx->iwrite == ictx->isize){ ictx->iwrite = 0; } @@ -638,6 +652,61 @@ mouse_click(inputctx* ictx){ pthread_cond_broadcast(&ictx->icond); } +static inline void +inc_input_events(inputctx* ictx){ + pthread_mutex_lock(&ictx->stats->lock); + ++ictx->stats->s.input_events; + pthread_mutex_unlock(&ictx->stats->lock); +} + +// add a decoded, valid Unicode to the bulk output buffer, or drop it if no +// space is available. +static void +add_unicode(inputctx* ictx, uint32_t id){ + pthread_mutex_lock(&ictx->ilock); + if(ictx->ivalid == ictx->isize){ + pthread_mutex_unlock(&ictx->ilock); + logerror("dropping input 0x%08xx\n", id); + return; + } + ncinput* ni = ictx->inputs + ictx->iwrite; + ni->id = id; + ni->alt = false; + ni->ctrl = false; + ni->shift = false; + ni->x = ni->y = 0; + if(++ictx->iwrite == ictx->isize){ + ictx->iwrite = 0; + } + ++ictx->ivalid; + inc_input_events(ictx); + pthread_mutex_unlock(&ictx->ilock); + pthread_cond_broadcast(&ictx->icond); +} + +static void +alt_key(inputctx* ictx, unsigned id){ + pthread_mutex_lock(&ictx->ilock); + if(ictx->ivalid == ictx->isize){ + pthread_mutex_unlock(&ictx->ilock); + logerror("dropping input 0x%08xx\n", ictx->triepos->special); + return; + } + ncinput* ni = ictx->inputs + ictx->iwrite; + ni->id = id; + ni->alt = true; + ni->ctrl = false; + ni->shift = false; + ni->x = ni->y = 0; + if(++ictx->iwrite == ictx->isize){ + ictx->iwrite = 0; + } + ++ictx->ivalid; + inc_input_events(ictx); + pthread_mutex_unlock(&ictx->ilock); + pthread_cond_broadcast(&ictx->icond); +} + static void special_key(inputctx* ictx){ assert(ictx->triepos); @@ -654,6 +723,10 @@ special_key(inputctx* ictx){ ni->ctrl = ictx->triepos->ctrl; ni->shift = ictx->triepos->shift; ni->x = ni->y = 0; + if(++ictx->iwrite == ictx->isize){ + ictx->iwrite = 0; + } + ++ictx->ivalid; pthread_mutex_unlock(&ictx->ilock); pthread_cond_broadcast(&ictx->icond); } @@ -683,7 +756,7 @@ kitty_kbd(inputctx* ictx){ ni->ctrl = !!((ictx->numeric - 1) & 0x4); // FIXME decode remaining modifiers through 128 // standard keyboard protocol reports ctrl+ascii as the capital form, - // so (for now) conform with kitty protocol... + // so (for now) conform when using kitty protocol... if(ni->id < 128 && islower(ni->id) && ni->ctrl){ ni->id = toupper(ni->id); } @@ -767,7 +840,7 @@ pump_control_read(inputctx* ictx, unsigned char c){ if(c == '1'){ ictx->state = STATE_BG2; }else{ - // FIXME + ictx->state = STATE_NULL; } break; case STATE_BG2: @@ -776,7 +849,7 @@ pump_control_read(inputctx* ictx, unsigned char c){ ictx->stridx = 0; ictx->runstring[0] = '\0'; }else{ - // FIXME + ictx->state = STATE_NULL; } break; case STATE_BGSEMI: // drain string @@ -867,8 +940,9 @@ pump_control_read(inputctx* ictx, unsigned char c){ }else if(c == 'R'){ //fprintf(stderr, "CURSOR X: %d\n", ictx->numeric); if(ictx->initdata){ - ictx->initdata->cursorx = ictx->numeric - 1; - ictx->initdata->cursory = ictx->p2 - 1; + // convert from 1- to 0-indexing, and account for margins + ictx->initdata->cursorx = ictx->numeric - 1 - ictx->lmargin; + ictx->initdata->cursory = ictx->p2 - 1 - ictx->tmargin; }else{ pthread_mutex_lock(&ictx->clock); if(ictx->cvalid == ictx->csize){ @@ -974,7 +1048,7 @@ pump_control_read(inputctx* ictx, unsigned char c){ ictx->stridx = 0; ictx->runstring[0] = '\0'; }else{ - // FIXME error? + ictx->state = STATE_NULL; } break; case STATE_XTVERSION2: @@ -987,14 +1061,14 @@ pump_control_read(inputctx* ictx, unsigned char c){ if(c == '+'){ ictx->state = STATE_XTGETTCAP2; }else{ - // FIXME malformed + ictx->state = STATE_NULL; } break; case STATE_XTGETTCAP2: if(c == 'r'){ ictx->state = STATE_XTGETTCAP3; }else{ - // FIXME malformed + ictx->state = STATE_NULL; } break; case STATE_XTGETTCAP3: @@ -1033,7 +1107,7 @@ pump_control_read(inputctx* ictx, unsigned char c){ ictx->stridx = 0; ictx->runstring[0] = '\0'; }else{ - // FIXME + ictx->state = STATE_NULL; } break; case STATE_TDA2: @@ -1234,12 +1308,29 @@ process_escape(inputctx* ictx, const unsigned char* buf, int buflen){ if(r == 1){ handoff_initial_responses(ictx); } - logtrace("triepos: %p in: %u special: 0x%08x\n", ictx->triepos, buf[used], ictx->triepos->special); - if(ictx->triepos->special != NCKEY_INVALID){ - special_key(ictx); - ictx->triepos = ictx->inputescapes; - }else if((ictx->triepos = ictx->triepos->trie[buf[used]]) == NULL){ - ictx->triepos = ictx->inputescapes; + // FIXME this drops alt+ characters intermingled with responses + if(ictx->initdata == NULL){ + // an escape always resets the trie, as does a NULL transition + unsigned char candidate = buf[used]; + if(candidate == NCKEY_ESC){ + ictx->triepos = ictx->inputescapes; + }else if(ictx->triepos->trie[candidate] == NULL){ + if(ictx->state == STATE_ESC){ + if(candidate && candidate <= 0x80){ // FIXME what about supraASCII utf8? + alt_key(ictx, candidate); + } + } + ictx->triepos = ictx->inputescapes; + }else{ + ictx->triepos = ictx->triepos->trie[candidate]; + logtrace("triepos: %p in: %u special: 0x%08x\n", ictx->triepos, candidate, ictx->triepos->special); + if(ictx->triepos->special != NCKEY_INVALID){ // match! mark and reset + special_key(ictx); + ictx->triepos = ictx->inputescapes; + }else if(ictx->triepos->trie == NULL){ + ictx->triepos = ictx->inputescapes; + } + } } ++used; } @@ -1275,12 +1366,20 @@ process_input(const unsigned char* buf, int buflen, ncinput* ni){ return 1; } if(buf[0] < 0x80){ // pure ascii can't show up mid-utf8 - ni->id = buf[0]; + if(buf[0] == '\n' || buf[0] == '\r'){ + ni->id = NCKEY_ENTER; + }else if(buf[0] > 0 && buf[0] <= 26 && buf[0] != '\t'){ + ni->id = buf[0] + 'A' - 1; + ni->ctrl = true; + }else{ + ni->id = buf[0]; + } return 1; } int cpointlen = 0; wchar_t w; mbstate_t mbstate; + // FIXME verify that we have enough length based off first char while(++cpointlen <= (int)MB_CUR_MAX && cpointlen < buflen){ //fprintf(stderr, "CANDIDATE: %d cpointlen: %zu cpoint: %d\n", candidate, cpointlen, cpoint[cpointlen]); // FIXME how the hell does this work with 16-bit wchar_t? @@ -1293,7 +1392,6 @@ process_input(const unsigned char* buf, int buflen, ncinput* ni){ } } // FIXME input error stat - // FIXME extract modifiers return 0; } @@ -1310,6 +1408,7 @@ process_ncinput(inputctx* ictx, const unsigned char* buf, int buflen){ ncinput* ni = ictx->inputs + ictx->iwrite; int r = process_input(buf, buflen, ni); if(r > 0){ + inc_input_events(ictx); if(++ictx->iwrite == ictx->isize){ ictx->iwrite = 0; } @@ -1365,6 +1464,10 @@ process_melange(inputctx* ictx, const unsigned char* buf, int* bufused){ // walk the matching automaton from wherever we were. static void process_ibuf(inputctx* ictx){ + if(resize_seen){ + add_unicode(ictx, NCKEY_RESIZE); + resize_seen = 0; + } if(ictx->tbufvalid){ // we could theoretically do this in parallel with process_bulk, but it // hardly seems worthwhile without breaking apart the fetches of input. @@ -1436,10 +1539,12 @@ block_on_input(inputctx* ictx){ sigdelset(&smask, SIGWINCH); while((events = ppoll(pfds, pfdcount, ts, &smask)) < 0){ #endif +fprintf(stderr, "GOT EVENTS: %d!\n", events); if(errno != EINTR && errno != EAGAIN && errno != EBUSY && errno != EWOULDBLOCK){ return -1; } if(resize_seen){ +fprintf(stderr, "SAW A RESIZE!\n"); return 1; } } @@ -1475,8 +1580,9 @@ input_thread(void* vmarshall){ return NULL; } -int init_inputlayer(tinfo* ti, FILE* infp){ - inputctx* ictx = create_inputctx(ti, infp); +int init_inputlayer(tinfo* ti, FILE* infp, int lmargin, int tmargin, + ncsharedstats* stats){ + inputctx* ictx = create_inputctx(ti, infp, lmargin, tmargin, stats); if(ictx == NULL){ return -1; } @@ -1508,8 +1614,7 @@ int inputready_fd(const inputctx* ictx){ } static inline uint32_t -internal_get(inputctx* ictx, const struct timespec* ts, ncinput* ni, - int lmargin, int tmargin){ +internal_get(inputctx* ictx, const struct timespec* ts, ncinput* ni){ pthread_mutex_lock(&ictx->ilock); while(!ictx->ivalid){ pthread_cond_wait(&ictx->icond, &ictx->ilock); @@ -1519,20 +1624,9 @@ internal_get(inputctx* ictx, const struct timespec* ts, ncinput* ni, ictx->iread = 0; } --ictx->ivalid; + ni->seqnum = ++ictx->seqnum; pthread_mutex_unlock(&ictx->ilock); - // FIXME adjust mouse coordinates for margins return ni->id; - /* - uint32_t r = inputctx_prestamp(&nc->tcache, ts, ni, - nc->margin_l, nc->margin_t); - if(r != (uint32_t)-1){ - uint64_t stamp = nc->tcache.input.input_events++; // need increment even if !ni - if(ni){ - ni->seqnum = stamp; - } - } - return r; - */ } struct initial_responses* inputlayer_get_responses(inputctx* ictx){ @@ -1549,8 +1643,7 @@ struct initial_responses* inputlayer_get_responses(inputctx* ictx){ // infp has already been set non-blocking uint32_t notcurses_get(notcurses* nc, const struct timespec* ts, ncinput* ni){ - uint32_t r = internal_get(nc->tcache.ictx, ts, ni, - nc->margin_l, nc->margin_t); + uint32_t r = internal_get(nc->tcache.ictx, ts, ni); if(r != (uint32_t)-1){ ++nc->stats.s.input_events; } @@ -1576,7 +1669,7 @@ int notcurses_getvec(notcurses* n, const struct timespec* ts, } uint32_t ncdirect_get(ncdirect* n, const struct timespec* ts, ncinput* ni){ - return internal_get(n->tcache.ictx, ts, ni, 0, 0); + return internal_get(n->tcache.ictx, ts, ni); } uint32_t notcurses_getc(notcurses* nc, const struct timespec* ts, diff --git a/src/lib/in.h b/src/lib/in.h index 12a37bfa2..f9eb63aed 100644 --- a/src/lib/in.h +++ b/src/lib/in.h @@ -11,9 +11,11 @@ extern "C" { struct tinfo; struct inputctx; +struct ncsharedstats; -int init_inputlayer(struct tinfo* ti, FILE* infp) - __attribute__ ((nonnull (1, 2))); +int init_inputlayer(struct tinfo* ti, FILE* infp, int lmargin, int tmargin, + struct ncsharedstats* stats) + __attribute__ ((nonnull (1, 2, 5))); int stop_inputlayer(struct tinfo* ti); diff --git a/src/lib/internal.h b/src/lib/internal.h index b02b8babc..cf04c3f6e 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -272,6 +272,15 @@ typedef struct nctabbed { nctabbed_options opts; // copied in nctabbed_create() } nctabbed; +// various moving parts within a notcurses context (and the user) might need to +// access the stats object, so throw a lock on it. we don't want the lock in +// the actual structure since (a) it's usually unnecessary and (b) it breaks +// memset() and memcpy(). +typedef struct ncsharedstats { + pthread_mutex_t lock; + ncstats s; +} ncsharedstats; + typedef struct ncdirect { ncpalette palette; // 256-indexed palette can be used instead of/with RGB FILE* ttyfp; // FILE* for output tty @@ -280,6 +289,7 @@ typedef struct ncdirect { uint16_t stylemask; // current styles bool initialized_readline; // have we initialized Readline? uint64_t flags; // copied in ncdirect_init() from param + ncsharedstats stats; // stats! not as broadly used as in notcurses } ncdirect; // Extracellular state for a cell during the render process. There is one @@ -330,15 +340,6 @@ typedef struct ncpile { sprixel* sprixelcache; // list of sprixels } ncpile; -// various moving parts within a notcurses context (and the user) might need to -// access the stats object, so throw a lock on it. we don't want the lock in -// the actual structure since (a) it's usually unnecessary and (b) it breaks -// memset() and memcpy(). -typedef struct ncsharedstats { - pthread_mutex_t lock; - ncstats s; -} ncsharedstats; - // the standard pile can be reached through ->stdplane. typedef struct notcurses { ncplane* stdplane; // standard plane, covers screen @@ -1759,6 +1760,30 @@ cancel_and_join(const char* name, pthread_t tid, void** res){ return 0; } +static inline int +emit_scrolls(const tinfo* ti, int count, fbuf* f){ + if(count > 1){ + const char* indn = get_escape(ti, ESCAPE_INDN); + if(indn){ + if(fbuf_emit(f, tiparm(indn, count)) < 0){ + return -1; + } + return 0; + } + } + const char* ind = get_escape(ti, ESCAPE_IND); + if(ind == NULL){ + ind = "\v"; + } + while(count > 0){ + if(fbuf_emit(f, ind) < 0){ + return -1; + } + --count; + } + return 0; +} + #undef ALLOC #undef API diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 35c75df06..ccff9cb75 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1089,7 +1089,8 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){ if(interrogate_terminfo(&ret->tcache, opts->termtype, ret->ttyfp, utf8, opts->flags & NCOPTION_NO_ALTERNATE_SCREEN, 0, opts->flags & NCOPTION_NO_FONT_CHANGES, - cursory, cursorx, &ret->stats)){ + cursory, cursorx, &ret->stats, + ret->margin_l, ret->margin_t)){ fbuf_free(&ret->rstate.f); pthread_mutex_destroy(&ret->pilelock); pthread_mutex_destroy(&ret->stats.lock); diff --git a/src/lib/render.c b/src/lib/render.c index 34d275776..bf619cd27 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -983,26 +983,7 @@ rasterize_scrolls(const ncpile* p, fbuf* f){ return -1; } } - if(scrolls > 1){ - const char* indn = get_escape(&p->nc->tcache, ESCAPE_INDN); - if(indn){ - if(fbuf_emit(f, tiparm(indn, scrolls)) < 0){ - return -1; - } - return 0; - } - } - const char* ind = get_escape(&p->nc->tcache, ESCAPE_IND); - if(ind == NULL){ - ind = "\v"; - } - while(scrolls > 0){ - if(fbuf_emit(f, ind) < 0){ - return -1; - } - --scrolls; - } - return 0; + return emit_scrolls(&p->nc->tcache, scrolls, f); } // second sprixel pass in rasterization. by this time, all sixels are handled diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c index 14f1d395e..db25b43dc 100644 --- a/src/lib/termdesc.c +++ b/src/lib/termdesc.c @@ -719,7 +719,8 @@ macos_early_matches(void){ // full round trip before getting the reply, which is likely to pace init. int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned utf8, unsigned noaltscreen, unsigned nocbreak, unsigned nonewfonts, - int* cursor_y, int* cursor_x, ncsharedstats* stats){ + int* cursor_y, int* cursor_x, ncsharedstats* stats, + int lmargin, int tmargin){ int foolcursor_x, foolcursor_y; if(!cursor_x){ cursor_x = &foolcursor_x; @@ -779,7 +780,7 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut goto err; } } - if(init_inputlayer(ti, stdin)){ + if(init_inputlayer(ti, stdin, lmargin, tmargin, stats)){ goto err; } #ifndef __MINGW64__ diff --git a/src/lib/termdesc.h b/src/lib/termdesc.h index e3547bb4c..3409830b6 100644 --- a/src/lib/termdesc.h +++ b/src/lib/termdesc.h @@ -210,7 +210,8 @@ term_supported_styles(const tinfo* ti){ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned utf8, unsigned noaltscreen, unsigned nocbreak, unsigned nonewfonts, int* cursor_y, int* cursor_x, - struct ncsharedstats* stats); + struct ncsharedstats* stats, int lmargin, int tmargin) + __attribute__ ((nonnull (1, 2, 3, 10))); void free_terminfo_cache(tinfo* ti);