diff --git a/USAGE.md b/USAGE.md index e084e26d4..0ad16df26 100644 --- a/USAGE.md +++ b/USAGE.md @@ -106,6 +106,12 @@ typedef enum { // registration of these signal handlers. #define NCOPTION_NO_QUIT_SIGHANDLERS 0x0008 +// Initialize the standard plane's virtual cursor to match the physical cursor +// at context creation time. Together with NCOPTION_NO_ALTERNATE_SCREEN and a +// scrolling standard plane, this facilitates easy scrolling-style programs in +// rendered mode. +#define NCOPTION_PRESERVE_CURSOR 0x0010ull + // Notcurses typically prints version info in notcurses_init() and performance // info in notcurses_stop(). This inhibits that output. #define NCOPTION_SUPPRESS_BANNERS 0x0020 diff --git a/src/info/main.c b/src/info/main.c index ad671fd9d..3b07ceb2a 100644 --- a/src/info/main.c +++ b/src/info/main.c @@ -117,7 +117,7 @@ tinfo_debug_styles(struct ncplane* n, const char* indent){ // notcurses_render() without the alternate screen, no? int main(void){ notcurses_options nopts = { - .flags = NCOPTION_NO_ALTERNATE_SCREEN, + .flags = NCOPTION_NO_ALTERNATE_SCREEN | NCOPTION_PRESERVE_CURSOR, }; struct notcurses* nc = notcurses_init(&nopts, NULL); if(nc == NULL){ diff --git a/src/lib/input.c b/src/lib/input.c index b90524e64..a28ec0045 100644 --- a/src/lib/input.c +++ b/src/lib/input.c @@ -650,6 +650,8 @@ typedef enum { STATE_XTSMGRAPHICS_DRAIN, // drain out XTSMGRAPHICS to 'S' STATE_APPSYNC_REPORT, // got DECRPT ?2026 STATE_APPSYNC_REPORT_DRAIN, // drain out decrpt to 'y' + STATE_CURSOR, // reading row of cursor location to ';' + STATE_CURSOR_COL, // reading col of cursor location to 'R' } initstates_e; typedef struct query_state { @@ -661,12 +663,13 @@ typedef struct query_state { // stringstate is the state at which this string was initialized, and can be // one of STATE_XTVERSION1, STATE_XTGETTCAP_TERMNAME1, STATE_TDA1, and STATE_BG1 initstates_e state, stringstate; - int numeric; // currently-lexed numeric - char runstring[80]; // running string - size_t stridx; // position to write in string - uint32_t bg; // queried default background or 0 - bool xtgettcap_good; // high when we've received DCS 1 - bool appsync; // application-synchronized updates advertised + int numeric; // currently-lexed numeric + char runstring[80]; // running string + size_t stridx; // position to write in string + uint32_t bg; // queried default background or 0 + int cursor_y, cursor_x;// cursor location + bool xtgettcap_good; // high when we've received DCS 1 + bool appsync; // application-synchronized updates advertised } query_state; static int @@ -879,10 +882,38 @@ pump_control_read(query_state* inits, unsigned char c){ inits->state = STATE_DA; // could also be DECRPM/XTSMGRAPHICS }else if(c == '>'){ inits->state = STATE_SDA; + }else if(isdigit(c)){ + inits->numeric = 0; + inits->state = STATE_CURSOR; }else if(c >= 0x40 && c <= 0x7E){ inits->state = STATE_NULL; } break; + case STATE_CURSOR: + if(isdigit(c)){ + if(ruts_hex(&inits->numeric, c)){ + return -1; + } + }else if(c == ';'){ + inits->cursor_y = inits->numeric; + inits->state = STATE_CURSOR_COL; + inits->numeric = 0; + }else{ + inits->state = STATE_NULL; + } + break; + case STATE_CURSOR_COL: + if(isdigit(c)){ + if(ruts_hex(&inits->numeric, c)){ + return -1; + } + }else if(c == 'R'){ + inits->cursor_x = inits->numeric; + inits->state = STATE_NULL; + }else{ + inits->state = STATE_NULL; + } + break; case STATE_DCS: // terminated by ST if(c == '\\'){ //fprintf(stderr, "terminated DCS\n"); @@ -1168,7 +1199,7 @@ err: } int ncinputlayer_init(tinfo* tcache, FILE* infp, queried_terminals_e* detected, - unsigned* appsync){ + unsigned* appsync, int* cursor_y, int* cursor_x){ ncinputlayer* nilayer = &tcache->input; setbuffer(infp, NULL, 0); nilayer->inputescapes = NULL; @@ -1188,6 +1219,8 @@ int ncinputlayer_init(tinfo* tcache, FILE* infp, queried_terminals_e* detected, .tcache = tcache, .state = STATE_NULL, .qterm = TERMINAL_UNKNOWN, + .cursor_x = -1, + .cursor_y = -1, }; if(control_read(csifd, &inits)){ input_free_esctrie(&nilayer->inputescapes); @@ -1198,6 +1231,8 @@ int ncinputlayer_init(tinfo* tcache, FILE* infp, queried_terminals_e* detected, tcache->termversion = inits.version; *detected = inits.qterm; *appsync = inits.appsync; + *cursor_x = inits.cursor_x; + *cursor_y = inits.cursor_y; } } return 0; diff --git a/src/lib/input.h b/src/lib/input.h index 55e3dedac..13e0404f5 100644 --- a/src/lib/input.h +++ b/src/lib/input.h @@ -34,7 +34,8 @@ typedef enum { // advertised support for application-sychronized updates, |appsync| will be // non-zero. int ncinputlayer_init(struct tinfo* tcache, FILE* infp, - queried_terminals_e* detected, unsigned* appsync); + queried_terminals_e* detected, unsigned* appsync, + int* cursor_y, int* cursor_x); void ncinputlayer_stop(struct ncinputlayer* nilayer); diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c index 0af78653a..af362f58a 100644 --- a/src/lib/termdesc.c +++ b/src/lib/termdesc.c @@ -150,6 +150,46 @@ grow_esc_table(tinfo* ti, const char* tstr, escape_e esc, return 0; } +// Device Attributes; replies with (depending on decTerminalID resource): +// ⇒ CSI ? 1 ; 2 c ("VT100 with Advanced Video Option") +// ⇒ CSI ? 1 ; 0 c ("VT101 with No Options") +// ⇒ CSI ? 4 ; 6 c ("VT132 with Advanced Video and Graphics") +// ⇒ CSI ? 6 c ("VT102") +// ⇒ CSI ? 7 c ("VT131") +// ⇒ CSI ? 1 2 ; Ps c ("VT125") +// ⇒ CSI ? 6 2 ; Ps c ("VT220") +// ⇒ CSI ? 6 3 ; Ps c ("VT320") +// ⇒ CSI ? 6 4 ; Ps c ("VT420") + +// query background, replies in X color https://www.x.org/releases/X11R7.7/doc/man/man7/X.7.xhtml#heading11 +#define CSI_BGQ "\e]11;?\e\\" + +// ought be using the u7 terminfo string here, if it exists. the great thing +// is, if we get a response to this, we know we can use it for u7! +#define DSRCPR "\e[6n" + +// 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. +static int +send_initial_queries(int fd){ + const char queries[] = CSI_BGQ + DSRCPR + "\x1b[?2026$p" // query for App-sync updates + "\x1b[=0c" // Tertiary Device Attributes + "\x1b[>0q" // XTVERSION + "\x1bP+q544e\x1b\\" // XTGETTCAP['TN'] + "\x1b[?1;3;256S" // try to set 256 cregs + "\x1b[?2;1;0S" // XTSMGRAPHICS (cregs) + "\x1b[?1;1;0S" // XTSMGRAPHICS (geometry) + "\x1b[c"; // Device Attributes + if(blocking_write(fd, queries, strlen(queries))){ + return -1; + } + return 0; +} + static int init_terminfo_esc(tinfo* ti, const char* name, escape_e idx, size_t* tablelen, size_t* tableused){ @@ -164,14 +204,15 @@ init_terminfo_esc(tinfo* ti, const char* name, escape_e idx, return 0; } -// older kitty terminfo doesn't include u7. remove this when we can FIXME. +// if we get a response to the standard cursor locator escape, we know this +// terminal supports it, hah. static int add_u7_escape(tinfo* ti, size_t* tablelen, size_t* tableused){ const char* u7 = get_escape(ti, ESCAPE_DSRCPR); if(u7){ return 0; // already present } - if(grow_esc_table(ti, "\e[6n", ESCAPE_DSRCPR, tablelen, tableused)){ + if(grow_esc_table(ti, DSRCPR, ESCAPE_DSRCPR, tablelen, tableused)){ return -1; } return 0; @@ -221,9 +262,6 @@ apply_term_heuristics(tinfo* ti, const char* termname, int fd, if(add_smulx_escapes(ti, tablelen, tableused)){ return -1; } - if(add_u7_escape(ti, tablelen, tableused)){ - return -1; - } }else if(qterm == TERMINAL_ALACRITTY){ termname = "Alacritty"; ti->caps.quadrants = true; @@ -286,41 +324,6 @@ apply_term_heuristics(tinfo* ti, const char* termname, int fd, return 0; } -// Device Attributes; replies with (depending on decTerminalID resource): -// ⇒ CSI ? 1 ; 2 c ("VT100 with Advanced Video Option") -// ⇒ CSI ? 1 ; 0 c ("VT101 with No Options") -// ⇒ CSI ? 4 ; 6 c ("VT132 with Advanced Video and Graphics") -// ⇒ CSI ? 6 c ("VT102") -// ⇒ CSI ? 7 c ("VT131") -// ⇒ CSI ? 1 2 ; Ps c ("VT125") -// ⇒ CSI ? 6 2 ; Ps c ("VT220") -// ⇒ CSI ? 6 3 ; Ps c ("VT320") -// ⇒ CSI ? 6 4 ; Ps c ("VT420") - -// query background, replies in X color https://www.x.org/releases/X11R7.7/doc/man/man7/X.7.xhtml#heading11 -#define CSI_BGQ "\e]11;?\e\\" - -// 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. -static int -send_initial_queries(int fd){ - const char queries[] = CSI_BGQ - "\x1b[?2026$p" // query for App-sync updates - "\x1b[=0c" // Tertiary Device Attributes - "\x1b[>0q" // XTVERSION - "\x1bP+q544e\x1b\\" // XTGETTCAP['TN'] - "\x1b[?1;3;256S" // try to set 256 cregs - "\x1b[?2;1;0S" // XTSMGRAPHICS (cregs) - "\x1b[?1;1;0S" // XTSMGRAPHICS (geometry) - "\x1b[c"; // Device Attributes - if(blocking_write(fd, queries, strlen(queries))){ - return -1; - } - return 0; -} - // termname is just the TERM environment variable. some details are not // exposed via terminfo, and we must make heuristic decisions based on // the detected terminal type, yuck :/. @@ -501,7 +504,9 @@ int interrogate_terminfo(tinfo* ti, int fd, const char* termname, unsigned utf8, } } unsigned appsync_advertised; - if(ncinputlayer_init(ti, stdin, &qterm, &appsync_advertised)){ + int cursor_x = -1; + int cursor_y = -1; + if(ncinputlayer_init(ti, stdin, &qterm, &appsync_advertised, &cursor_y, &cursor_x)){ goto err; } if(nocbreak){ @@ -512,6 +517,12 @@ int interrogate_terminfo(tinfo* ti, int fd, const char* termname, unsigned utf8, } } } + if(cursor_x >= 0 && cursor_y >= 0){ + if(add_u7_escape(ti, &tablelen, &tableused)){ + return -1; + } + // FIXME set cursor up + } if(appsync_advertised){ if(add_appsync_escapes(ti, &tablelen, &tableused)){ goto err;