From 76ee6c510acec733a37812c33d77418e68822ec8 Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Sat, 7 Sep 2019 19:57:58 -0700 Subject: [PATCH 01/19] Add callback for xterm-style window title --- tmt.c | 33 +++++++++++++++++++++++++++++++-- tmt.h | 1 + 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/tmt.c b/tmt.c index 7ace141..e8c902e 100644 --- a/tmt.c +++ b/tmt.c @@ -33,6 +33,7 @@ #define BUF_MAX 100 #define PAR_MAX 8 +#define TITLE_MAX 128 #define TAB 8 #define MAX(x, y) (((size_t)(x) > (size_t)(y)) ? (size_t)(x) : (size_t)(y)) #define MIN(x, y) (((size_t)(x) < (size_t)(y)) ? (size_t)(x) : (size_t)(y)) @@ -67,10 +68,13 @@ struct TMT{ size_t nmb; char mb[BUF_MAX + 1]; + char title[TITLE_MAX + 1]; + size_t ntitle; + size_t pars[PAR_MAX]; size_t npar; size_t arg; - enum {S_NUL, S_ESC, S_ARG} state; + enum {S_NUL, S_ESC, S_ARG, S_TITLE, S_TITLE_ARG} state; }; static TMTATTRS defattrs = {.fg = TMT_COLOR_DEFAULT, .bg = TMT_COLOR_DEFAULT}; @@ -239,7 +243,7 @@ HANDLER(dsr) HANDLER(resetparser) memset(vt->pars, 0, sizeof(vt->pars)); - vt->state = vt->npar = vt->arg = vt->ignored = (bool)0; + vt->ntitle = vt->state = vt->npar = vt->arg = vt->ignored = (bool)0; } HANDLER(consumearg) @@ -253,6 +257,17 @@ HANDLER(fixcursor) c->c = MIN(c->c, s->ncol - 1); } +HANDLER(title) + vt->title[vt->ntitle] = 0; + if (vt->npar >= 1) + { + if (vt->pars[0] == 0 || vt->pars[0] == 2) + { + CB(vt, TMT_MSG_TITLE, vt->title); + } + } +} + static bool handlechar(TMT *vt, char i) { @@ -276,10 +291,13 @@ handlechar(TMT *vt, char i) ON(S_ESC, "+*()", vt->ignored = true; vt->state = S_ARG) DO(S_ESC, "c", tmt_reset(vt)) ON(S_ESC, "[", vt->state = S_ARG) + ON(S_ESC, "]", vt->state = S_TITLE_ARG) ON(S_ARG, "\x1b", vt->state = S_ESC) ON(S_ARG, ";", consumearg(vt)) ON(S_ARG, "?", (void)0) ON(S_ARG, "0123456789", vt->arg = vt->arg * 10 + atoi(cs)) + ON(S_TITLE_ARG, "012", vt->arg = vt->arg * 10 + atoi(cs)) + ON(S_TITLE_ARG, ";", consumearg(vt); vt->state = S_TITLE) DO(S_ARG, "A", c->r = MAX(c->r - P1(0), 0)) DO(S_ARG, "B", c->r = MIN(c->r + P1(0), s->nline - 1)) DO(S_ARG, "C", c->c = MIN(c->c + P1(0), s->ncol - 1)) @@ -309,8 +327,19 @@ handlechar(TMT *vt, char i) DO(S_ARG, "l", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "f")) DO(S_ARG, "s", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs) DO(S_ARG, "u", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs) + DO(S_TITLE, "\a", title(vt)) DO(S_ARG, "@", ich(vt)) + if (vt->state == S_TITLE) + { + if ( (i >= 32) && (vt->ntitle < TITLE_MAX) ) + { + vt->title[vt->ntitle] = i; + vt->ntitle += 1; + return true; + } + } + return resetparser(vt), false; } diff --git a/tmt.h b/tmt.h index ae0ddbb..b032a3e 100644 --- a/tmt.h +++ b/tmt.h @@ -120,6 +120,7 @@ typedef enum{ TMT_MSG_MOVED, TMT_MSG_UPDATE, TMT_MSG_ANSWER, + TMT_MSG_TITLE, TMT_MSG_BELL, TMT_MSG_CURSOR } tmt_msg_t; From 50e10c0c2821a07ca8ec0251125c9696b1b05aad Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Sat, 7 Sep 2019 19:58:14 -0700 Subject: [PATCH 02/19] Generic private mode get/set events --- tmt.c | 26 ++++++++++++++++++++++++-- tmt.h | 4 +++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/tmt.c b/tmt.c index e8c902e..b8e7b34 100644 --- a/tmt.c +++ b/tmt.c @@ -257,6 +257,28 @@ HANDLER(fixcursor) c->c = MIN(c->c, s->ncol - 1); } +HANDLER(sm) + switch (P0(0)){ + case 25: + CB(vt, TMT_MSG_CURSOR, "t"); + break; + default: + CB(vt, TMT_MSG_SETMODE, &vt->pars[0]); + break; + } +} + +HANDLER(rm) + switch (P0(0)){ + case 25: + CB(vt, TMT_MSG_CURSOR, "f"); + break; + default: + CB(vt, TMT_MSG_UNSETMODE, &vt->pars[0]); + break; + } +} + HANDLER(title) vt->title[vt->ntitle] = 0; if (vt->npar >= 1) @@ -322,9 +344,9 @@ handlechar(TMT *vt, char i) DO(S_ARG, "g", if (P0(0) == 3) clearline(vt, vt->tabs, 0, s->ncol)) DO(S_ARG, "m", sgr(vt)) DO(S_ARG, "n", if (P0(0) == 6) dsr(vt)) - DO(S_ARG, "h", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "t")) + DO(S_ARG, "h", sm(vt)) DO(S_ARG, "i", (void)0) - DO(S_ARG, "l", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "f")) + DO(S_ARG, "l", rm(vt)) DO(S_ARG, "s", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs) DO(S_ARG, "u", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs) DO(S_TITLE, "\a", title(vt)) diff --git a/tmt.h b/tmt.h index b032a3e..65faba6 100644 --- a/tmt.h +++ b/tmt.h @@ -122,7 +122,9 @@ typedef enum{ TMT_MSG_ANSWER, TMT_MSG_TITLE, TMT_MSG_BELL, - TMT_MSG_CURSOR + TMT_MSG_CURSOR, + TMT_MSG_SETMODE, + TMT_MSG_UNSETMODE, } tmt_msg_t; typedef void (*TMTCALLBACK)(tmt_msg_t m, struct TMT *v, const void *r, void *p); From a0b53c5bc833df53b7d45ced749447a2d2574243 Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:01:57 -0400 Subject: [PATCH 03/19] Improve TMT_MSG_SETMODE Previously, there was no good way to tell which arguments were really set when doing a mode set. We now set all the unused ones to -1, which is probably not valid. The application can look through 8 arguments and see the unset ones. It's still a bit of a hack, but it's better. --- tmt.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tmt.c b/tmt.c index b8e7b34..9d854f5 100644 --- a/tmt.c +++ b/tmt.c @@ -263,6 +263,8 @@ HANDLER(sm) CB(vt, TMT_MSG_CURSOR, "t"); break; default: + for (int i = vt->npar; i < PAR_MAX; ++i) + vt->pars[i] = (size_t)-1; CB(vt, TMT_MSG_SETMODE, &vt->pars[0]); break; } @@ -274,6 +276,8 @@ HANDLER(rm) CB(vt, TMT_MSG_CURSOR, "f"); break; default: + for (int i = vt->npar; i < PAR_MAX; ++i) + vt->pars[i] = (size_t)-1; CB(vt, TMT_MSG_UNSETMODE, &vt->pars[0]); break; } From cd1c2019d7e877c8bd73bdb016e55a208ae0a083 Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:01:57 -0400 Subject: [PATCH 04/19] Implement Send Device Attributes DA2 and XTVERSION --- tmt.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tmt.c b/tmt.c index 9d854f5..438f116 100644 --- a/tmt.c +++ b/tmt.c @@ -56,6 +56,9 @@ struct TMT{ TMTPOINT curs, oldcurs; TMTATTRS attrs, oldattrs; + // Name of the terminal for XTVERSION (if null, use default). + char * terminal_name; + bool dirty, acs, ignored; TMTSCREEN screen; TMTLINE *tabs; @@ -74,7 +77,7 @@ struct TMT{ size_t pars[PAR_MAX]; size_t npar; size_t arg; - enum {S_NUL, S_ESC, S_ARG, S_TITLE, S_TITLE_ARG} state; + enum {S_NUL, S_ESC, S_ARG, S_TITLE, S_TITLE_ARG, S_GT_ARG} state; }; static TMTATTRS defattrs = {.fg = TMT_COLOR_DEFAULT, .bg = TMT_COLOR_DEFAULT}; @@ -294,6 +297,25 @@ HANDLER(title) } } +static void +xtversion(TMT *vt) +{ + char * name = "tmt(0.0.0)"; + char * pre = "\033P>|"; + char * post = "\033\\"; + char buf[255] = {0}; + if (vt->terminal_name) + { + size_t tot_len = strlen(pre)+strlen(post)+strlen(vt->terminal_name)+1; + if (tot_len <= sizeof(buf)) + name = vt->terminal_name; + } + strcpy(buf, pre); + strcat(buf, name); + strcat(buf, post); + CB(vt, TMT_MSG_ANSWER, buf); +} + static bool handlechar(TMT *vt, char i) { @@ -353,6 +375,9 @@ handlechar(TMT *vt, char i) DO(S_ARG, "l", rm(vt)) DO(S_ARG, "s", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs) DO(S_ARG, "u", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs) + ON(S_ARG, ">", vt->state = S_GT_ARG) + DO(S_GT_ARG, "c", CB(vt, TMT_MSG_ANSWER, "\033[>0;95c")) // Send Secondary DA (0=VT100, 95=old xterm) + DO(S_GT_ARG, "q", xtversion(vt)) DO(S_TITLE, "\a", title(vt)) DO(S_ARG, "@", ich(vt)) From 58278f3e2ffbce1e3f089173d030faa60116e67d Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:01:57 -0400 Subject: [PATCH 05/19] Add DEC Special Graphics support Supporting this means setting G0/G1, supporting Shift In/Out, etc. --- tmt.c | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/tmt.c b/tmt.c index 438f116..5332c80 100644 --- a/tmt.c +++ b/tmt.c @@ -67,6 +67,9 @@ struct TMT{ void *p; const wchar_t *acschars; + int charset; // Are we in G0 or G1? + int xlate[2]; // What's in the charset? 0=ASCII, 1=DEC Special Graphics + mbstate_t ms; size_t nmb; char mb[BUF_MAX + 1]; @@ -77,7 +80,7 @@ struct TMT{ size_t pars[PAR_MAX]; size_t npar; size_t arg; - enum {S_NUL, S_ESC, S_ARG, S_TITLE, S_TITLE_ARG, S_GT_ARG} state; + enum {S_NUL, S_ESC, S_ARG, S_TITLE, S_TITLE_ARG, S_GT_ARG, S_LPAREN, S_RPAREN} state; }; static TMTATTRS defattrs = {.fg = TMT_COLOR_DEFAULT, .bg = TMT_COLOR_DEFAULT}; @@ -331,12 +334,14 @@ handlechar(TMT *vt, char i) DO(S_NUL, "\x09", while (++c->c < s->ncol - 1 && t[c->c].c != L'*')) DO(S_NUL, "\x0a", c->r < s->nline - 1? (void)c->r++ : scrup(vt, 0, 1)) DO(S_NUL, "\x0d", c->c = 0) + DO(S_NUL, "\x0e", vt->charset = 1) // Shift Out (Switch to G1) + DO(S_NUL, "\x0f", vt->charset = 0) // Shift In (Switch to G0) ON(S_NUL, "\x1b", vt->state = S_ESC) ON(S_ESC, "\x1b", vt->state = S_ESC) DO(S_ESC, "H", t[c->c].c = L'*') DO(S_ESC, "7", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs) DO(S_ESC, "8", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs) - ON(S_ESC, "+*()", vt->ignored = true; vt->state = S_ARG) + ON(S_ESC, "+*", vt->ignored = true; vt->state = S_ARG) DO(S_ESC, "c", tmt_reset(vt)) ON(S_ESC, "[", vt->state = S_ARG) ON(S_ESC, "]", vt->state = S_TITLE_ARG) @@ -380,6 +385,12 @@ handlechar(TMT *vt, char i) DO(S_GT_ARG, "q", xtversion(vt)) DO(S_TITLE, "\a", title(vt)) DO(S_ARG, "@", ich(vt)) + ON(S_ESC, "(", vt->state = S_LPAREN) + ON(S_ESC, ")", vt->state = S_RPAREN) + DO(S_LPAREN, "AB12", vt->xlate[0] = 0) + DO(S_LPAREN, "0", vt->xlate[0] = 1) + DO(S_RPAREN, "AB12", vt->xlate[1] = 0) + DO(S_RPAREN, "0", vt->xlate[1] = 1) if (vt->state == S_TITLE) { @@ -482,11 +493,33 @@ tmt_resize(TMT *vt, size_t nline, size_t ncol) return true; } +static wchar_t +dec_to_acs(TMT *vt, wchar_t w) +{ + // Translates from DEC Special Graphics to our ACS characters. + + // The capital letters are supposed to be symbols for control chars. + // Specifically: Tab FormFeed CR LF NL VTab + // 0xfa is hopefully an interpunct. + + /**/ if (w == '_' ) w = ' '; // NBSP + else if (w >= '`' && w <= 'a') w = vt->acschars[w - '`' + 5]; + else if (w >= 'b' && w <= 'e') w = "TFCL"[w - 'b']; + else if (w >= 'f' && w <= 'g') w = vt->acschars[w - 'f' + 7]; + else if (w >= 'h' && w <= 'i') w = "NV"[w - 'h']; + else if (w >= 'j' && w <= '~') w = vt->acschars[w - 'j' + 10]; + + return w; +} + static void writecharatcurs(TMT *vt, wchar_t w) { COMMON_VARS; + if (vt->xlate[vt->charset]) + w = dec_to_acs(vt, w); + #ifdef TMT_HAS_WCWIDTH extern int wcwidth(wchar_t c); if (wcwidth(w) > 1) w = TMT_INVALID_CHAR; From c86ec7eb1ec38151a5daad6d85f77aaff1596c00 Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:01:58 -0400 Subject: [PATCH 06/19] Add Unicode mapping / set_unicode_decode() When Unicode decoding is turned on, if we display the Unicode equivalent of an ACS character, we actually map it to the ACS character. --- tmt.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tmt.h | 1 + 2 files changed, 60 insertions(+) diff --git a/tmt.c b/tmt.c index 5332c80..2658ec2 100644 --- a/tmt.c +++ b/tmt.c @@ -70,6 +70,8 @@ struct TMT{ int charset; // Are we in G0 or G1? int xlate[2]; // What's in the charset? 0=ASCII, 1=DEC Special Graphics + bool decode_unicode; // Try to decode characters to ACS equivalents? + mbstate_t ms; size_t nmb; char mb[BUF_MAX + 1]; @@ -86,6 +88,14 @@ struct TMT{ static TMTATTRS defattrs = {.fg = TMT_COLOR_DEFAULT, .bg = TMT_COLOR_DEFAULT}; static void writecharatcurs(TMT *vt, wchar_t w); +bool +tmt_set_unicode_decode(TMT *vt, bool v) +{ + bool r = vt->decode_unicode; + vt->decode_unicode = v; + return r; +} + static wchar_t tacs(const TMT *vt, unsigned char c) { @@ -517,6 +527,55 @@ writecharatcurs(TMT *vt, wchar_t w) { COMMON_VARS; + if (vt->decode_unicode) + { + // We can add more mappings here, but the initial set here comes from: + // justsolve.archiveteam.org/wiki/DEC_Special_Graphics_Character_Set + // See also codepage.c from qodem. + switch (w) + { + case 0x2192: w = vt->acschars[0]; break; // RIGHT ARROW + case 0x2190: w = vt->acschars[1]; break; // LEFT ARROW + case 0x2191: w = vt->acschars[2]; break; // UP ARROW + case 0x2193: w = vt->acschars[3]; break; // DOWN ARROW + case 0x2588: w = vt->acschars[4]; break; // BLOCK + case 0x25A6: w = vt->acschars[9]; break; // BOARD + case 0x00A0: w = dec_to_acs(vt, 0x5f); break; // NBSP + case 0x2666: // BLACK DIAMOND SUIT + case 0x25C6: w = dec_to_acs(vt, 0x60); break; // BLACK DIAMOND + case 0x2592: w = dec_to_acs(vt, 0x61); break; // MEDIUM SHADE + case 0x2409: w = dec_to_acs(vt, 0x62); break; // SYMBOL FOR HORIZONTAL TABULATION + case 0x240C: w = dec_to_acs(vt, 0x63); break; // SYMBOL FOR FORM FEED + case 0x240D: w = dec_to_acs(vt, 0x64); break; // SYMBOL FOR CARRIAGE RETURN + case 0x240A: w = dec_to_acs(vt, 0x65); break; // SYMBOL FOR LINE FEED + case 0x00B0: w = dec_to_acs(vt, 0x66); break; // DEGREE SIGN + case 0x00B1: w = dec_to_acs(vt, 0x67); break; // PLUS-MINUS SIGN + case 0x2424: w = dec_to_acs(vt, 0x68); break; // SYMBOL FOR NEWLINE + case 0x240B: w = dec_to_acs(vt, 0x69); break; // SYMBOL FOR VERTICAL TABULATION + case 0x2518: w = dec_to_acs(vt, 0x6a); break; // BOX DRAWINGS LIGHT UP AND LEFT + case 0x2510: w = dec_to_acs(vt, 0x6b); break; // BOX DRAWINGS LIGHT DOWN AND LEFT + case 0x250C: w = dec_to_acs(vt, 0x6c); break; // BOX DRAWINGS LIGHT DOWN AND RIGHT + case 0x2514: w = dec_to_acs(vt, 0x6d); break; // BOX DRAWINGS LIGHT UP AND RIGHT + case 0x253C: w = dec_to_acs(vt, 0x6e); break; // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + case 0x23BA: w = dec_to_acs(vt, 0x6f); break; // HORIZONTAL SCAN LINE-1 + case 0x23BB: w = dec_to_acs(vt, 0x70); break; // HORIZONTAL SCAN LINE-3 + case 0x2500: w = dec_to_acs(vt, 0x71); break; // BOX DRAWINGS LIGHT HORIZONTAL + case 0x23BC: w = dec_to_acs(vt, 0x72); break; // HORIZONTAL SCAN LINE-7 + case 0x23BD: w = dec_to_acs(vt, 0x73); break; // HORIZONTAL SCAN LINE-9 + case 0x251C: w = dec_to_acs(vt, 0x74); break; // BOX DRAWINGS LIGHT VERTICAL AND RIGHT + case 0x2524: w = dec_to_acs(vt, 0x75); break; // BOX DRAWINGS LIGHT VERTICAL AND LEFT + case 0x2534: w = dec_to_acs(vt, 0x76); break; // BOX DRAWINGS LIGHT UP AND HORIZONTAL + case 0x252C: w = dec_to_acs(vt, 0x77); break; // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + case 0x2502: w = dec_to_acs(vt, 0x78); break; // BOX DRAWINGS LIGHT VERTICAL + case 0x2264: w = dec_to_acs(vt, 0x79); break; // LESS-THAN OR EQUAL TO + case 0x2265: w = dec_to_acs(vt, 0x7a); break; // GREATER-THAN OR EQUAL TO + case 0x03C0: w = dec_to_acs(vt, 0x7b); break; // GREEK SMALL LETTER PI + case 0x2260: w = dec_to_acs(vt, 0x7c); break; // NOT EQUAL TO + case 0x00A3: w = dec_to_acs(vt, 0x7d); break; // POUND SIGN + case 0x00B7: w = dec_to_acs(vt, 0x7e); break; // MIDDLE DOT + } + } + if (vt->xlate[vt->charset]) w = dec_to_acs(vt, w); diff --git a/tmt.h b/tmt.h index 65faba6..ba68ace 100644 --- a/tmt.h +++ b/tmt.h @@ -132,6 +132,7 @@ typedef void (*TMTCALLBACK)(tmt_msg_t m, struct TMT *v, const void *r, void *p); /**** PUBLIC FUNCTIONS */ TMT *tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p, const wchar_t *acs); +bool tmt_set_unicode_decode(TMT *vt, bool v); void tmt_close(TMT *vt); bool tmt_resize(TMT *vt, size_t nline, size_t ncol); void tmt_write(TMT *vt, const char *s, size_t n); From c4b51e9572143550ae4b44068b19a500073fdb5f Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:01:58 -0400 Subject: [PATCH 07/19] Fix indentation --- tmt.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tmt.c b/tmt.c index 2658ec2..6fc67ce 100644 --- a/tmt.c +++ b/tmt.c @@ -303,10 +303,10 @@ HANDLER(title) vt->title[vt->ntitle] = 0; if (vt->npar >= 1) { - if (vt->pars[0] == 0 || vt->pars[0] == 2) - { - CB(vt, TMT_MSG_TITLE, vt->title); - } + if (vt->pars[0] == 0 || vt->pars[0] == 2) + { + CB(vt, TMT_MSG_TITLE, vt->title); + } } } @@ -404,12 +404,12 @@ handlechar(TMT *vt, char i) if (vt->state == S_TITLE) { - if ( (i >= 32) && (vt->ntitle < TITLE_MAX) ) - { - vt->title[vt->ntitle] = i; - vt->ntitle += 1; - return true; - } + if ( (i >= 32) && (vt->ntitle < TITLE_MAX) ) + { + vt->title[vt->ntitle] = i; + vt->ntitle += 1; + return true; + } } return resetparser(vt), false; From fee47b20cdd904efba53c8c91beee9a82bcb2a7c Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:01:58 -0400 Subject: [PATCH 08/19] Remove trailing whitespace --- tmt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tmt.c b/tmt.c index 6fc67ce..405164f 100644 --- a/tmt.c +++ b/tmt.c @@ -50,7 +50,7 @@ TMTLINE *l = CLINE(vt); \ TMTCHAR *t = vt->tabs->chars -#define HANDLER(name) static void name (TMT *vt) { COMMON_VARS; +#define HANDLER(name) static void name (TMT *vt) { COMMON_VARS; struct TMT{ TMTPOINT curs, oldcurs; @@ -79,7 +79,7 @@ struct TMT{ char title[TITLE_MAX + 1]; size_t ntitle; - size_t pars[PAR_MAX]; + size_t pars[PAR_MAX]; size_t npar; size_t arg; enum {S_NUL, S_ESC, S_ARG, S_TITLE, S_TITLE_ARG, S_GT_ARG, S_LPAREN, S_RPAREN} state; @@ -168,7 +168,7 @@ scrdn(TMT *vt, size_t r, size_t n) memmove(vt->screen.lines + r + n, vt->screen.lines + r, (vt->screen.nline - n - r) * sizeof(TMTLINE *)); memcpy(vt->screen.lines + r, buf, n * sizeof(TMTLINE *)); - + clearlines(vt, r, n); dirtylines(vt, r, vt->screen.nline); } From 6bfeeb1872e70b0896f1cf803454a661aa3b4732 Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:01:58 -0400 Subject: [PATCH 09/19] Add ability to disambiguate "[..." and "[?..." --- tmt.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tmt.c b/tmt.c index 405164f..45f473b 100644 --- a/tmt.c +++ b/tmt.c @@ -82,6 +82,7 @@ struct TMT{ size_t pars[PAR_MAX]; size_t npar; size_t arg; + bool q; enum {S_NUL, S_ESC, S_ARG, S_TITLE, S_TITLE_ARG, S_GT_ARG, S_LPAREN, S_RPAREN} state; }; @@ -259,7 +260,7 @@ HANDLER(dsr) HANDLER(resetparser) memset(vt->pars, 0, sizeof(vt->pars)); - vt->ntitle = vt->state = vt->npar = vt->arg = vt->ignored = (bool)0; + vt->q = vt->ntitle = vt->state = vt->npar = vt->arg = vt->ignored = (bool)0; } HANDLER(consumearg) @@ -357,7 +358,7 @@ handlechar(TMT *vt, char i) ON(S_ESC, "]", vt->state = S_TITLE_ARG) ON(S_ARG, "\x1b", vt->state = S_ESC) ON(S_ARG, ";", consumearg(vt)) - ON(S_ARG, "?", (void)0) + ON(S_ARG, "?", vt->q = 1) ON(S_ARG, "0123456789", vt->arg = vt->arg * 10 + atoi(cs)) ON(S_TITLE_ARG, "012", vt->arg = vt->arg * 10 + atoi(cs)) ON(S_TITLE_ARG, ";", consumearg(vt); vt->state = S_TITLE) @@ -381,13 +382,13 @@ handlechar(TMT *vt, char i) DO(S_ARG, "X", clearline(vt, l, c->c, P1(0))) DO(S_ARG, "Z", while (c->c && t[--c->c].c != L'*')) DO(S_ARG, "b", rep(vt)); - DO(S_ARG, "c", CB(vt, TMT_MSG_ANSWER, "\033[?6c")) + DO(S_ARG, "c", if (!vt->q) CB(vt, TMT_MSG_ANSWER, "\033[?6c")) DO(S_ARG, "g", if (P0(0) == 3) clearline(vt, vt->tabs, 0, s->ncol)) DO(S_ARG, "m", sgr(vt)) DO(S_ARG, "n", if (P0(0) == 6) dsr(vt)) - DO(S_ARG, "h", sm(vt)) + DO(S_ARG, "h", sm(vt)) // Handles both ?h and plain h + DO(S_ARG, "l", rm(vt)) // Handles both ?l and plain l DO(S_ARG, "i", (void)0) - DO(S_ARG, "l", rm(vt)) DO(S_ARG, "s", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs) DO(S_ARG, "u", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs) ON(S_ARG, ">", vt->state = S_GT_ARG) From 25d9a04ab6df9e947c8e893c5ccae58825057cee Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:01:59 -0400 Subject: [PATCH 10/19] Fix SGR parsing with multiple arguments Previously, if we were doing SGR with multiple arguments, the first argument ends up determining the enabled state of all the others! --- tmt.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tmt.c b/tmt.c index 45f473b..dc190af 100644 --- a/tmt.c +++ b/tmt.c @@ -226,13 +226,13 @@ HANDLER(sgr) #define FGBG(c) *(P0(i) < 40? &vt->attrs.fg : &vt->attrs.bg) = c for (size_t i = 0; i < vt->npar; i++) switch (P0(i)){ case 0: vt->attrs = defattrs; break; - case 1: case 22: vt->attrs.bold = P0(0) < 20; break; - case 2: case 23: vt->attrs.dim = P0(0) < 20; break; - case 4: case 24: vt->attrs.underline = P0(0) < 20; break; - case 5: case 25: vt->attrs.blink = P0(0) < 20; break; - case 7: case 27: vt->attrs.reverse = P0(0) < 20; break; - case 8: case 28: vt->attrs.invisible = P0(0) < 20; break; - case 10: case 11: vt->acs = P0(0) > 10; break; + case 1: case 22: vt->attrs.bold = P0(i) < 20; break; + case 2: case 23: vt->attrs.dim = P0(i) < 20; break; + case 4: case 24: vt->attrs.underline = P0(i) < 20; break; + case 5: case 25: vt->attrs.blink = P0(i) < 20; break; + case 7: case 27: vt->attrs.reverse = P0(i) < 20; break; + case 8: case 28: vt->attrs.invisible = P0(i) < 20; break; + case 10: case 11: vt->acs = P0(i) > 10; break; case 30: case 40: FGBG(TMT_COLOR_BLACK); break; case 31: case 41: FGBG(TMT_COLOR_RED); break; case 32: case 42: FGBG(TMT_COLOR_GREEN); break; From 56b8518185cec3327cc8850cfdf6be6ce9aa4d5c Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:02:06 -0400 Subject: [PATCH 11/19] clearline fills with current attribute, not default --- tmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tmt.c b/tmt.c index dc190af..29844bf 100644 --- a/tmt.c +++ b/tmt.c @@ -125,7 +125,7 @@ clearline(TMT *vt, TMTLINE *l, size_t s, size_t e) { vt->dirty = l->dirty = true; for (size_t i = s; i < e && i < vt->screen.ncol; i++){ - l->chars[i].a = defattrs; + l->chars[i].a = vt->attrs; l->chars[i].c = L' '; } } From 6e2b56210bf6b6b3278e74a6914657a45831294f Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:02:08 -0400 Subject: [PATCH 12/19] ed: Fix line clearing type 1 misses bottom line Previously, in form 1, this missed clearing the bottom line. --- tmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tmt.c b/tmt.c index 29844bf..bffaa90 100644 --- a/tmt.c +++ b/tmt.c @@ -181,7 +181,7 @@ HANDLER(ed) switch (P0(0)){ case 0: b = c->r + 1; clearline(vt, l, c->c, vt->screen.ncol); break; - case 1: e = c->r - 1; clearline(vt, l, 0, c->c); break; + case 1: e = c->r ; clearline(vt, l, 0, c->c); break; case 2: /* use defaults */ break; default: /* do nothing */ return; } From 7c02930b146ce20aa71d42f96cd211f7608bd3ce Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:02:08 -0400 Subject: [PATCH 13/19] Ignore DECKPAM/DECKPNM --- tmt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tmt.c b/tmt.c index bffaa90..d3af422 100644 --- a/tmt.c +++ b/tmt.c @@ -349,6 +349,8 @@ handlechar(TMT *vt, char i) DO(S_NUL, "\x0f", vt->charset = 0) // Shift In (Switch to G0) ON(S_NUL, "\x1b", vt->state = S_ESC) ON(S_ESC, "\x1b", vt->state = S_ESC) + DO(S_ESC, "=", (void)0) // DECKPAM (application keypad) + DO(S_ESC, ">", (void)0) // DECKPNM (normal keypad) DO(S_ESC, "H", t[c->c].c = L'*') DO(S_ESC, "7", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs) DO(S_ESC, "8", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs) From 1eda3ee9922a0b23876dcfd7ce403789291d7e86 Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:02:09 -0400 Subject: [PATCH 14/19] dch: Clear with rightmost (not current) attribute --- tmt.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tmt.c b/tmt.c index d3af422..eb9742b 100644 --- a/tmt.c +++ b/tmt.c @@ -204,10 +204,14 @@ HANDLER(dch) if (n > s->ncol - c->c) n = s->ncol - c->c; else if (n == 0) return; + TMTATTRS oldattr = vt->attrs; + vt->attrs = (l->chars + s->ncol - n)->a; + memmove(l->chars + c->c, l->chars + c->c + n, (s->ncol - c->c - n) * sizeof(TMTCHAR)); clearline(vt, l, s->ncol - n, s->ncol); + vt->attrs = oldattr; /* VT102 manual says the attribute for the newly empty characters * should be the same as the last character moved left, which isn't * what clearline() currently does. From 931bed623c1418a039433dc6f7a8566fd259fb77 Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:02:09 -0400 Subject: [PATCH 15/19] Add scrolling region and VT100-like line end behavior This implements CSI;r, which lets a program specify a range of lines to scroll, leaving others alone. It also implements code *similar* to how VT terminals deal with it when a character is written to the last column in a line. It essentially defers actually advancing the line, because there are cases where we don't want to (at least not yet), e.g., when the cursor wraps onto the next line and immediately does a CR. The behavior is not 100% the same as a VT, but it is close, and I haven't yet noticed bad behavior. --- tmt.c | 129 +++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 106 insertions(+), 23 deletions(-) diff --git a/tmt.c b/tmt.c index eb9742b..f55b141 100644 --- a/tmt.c +++ b/tmt.c @@ -39,6 +39,8 @@ #define MIN(x, y) (((size_t)(x) < (size_t)(y)) ? (size_t)(x) : (size_t)(y)) #define CLINE(vt) (vt)->screen.lines[MIN((vt)->curs.r, (vt)->screen.nline - 1)] +#define SCR_DEF ((size_t)-1) + #define P0(x) (vt->pars[x]) #define P1(x) (vt->pars[x]? vt->pars[x] : 1) #define CB(vt, m, a) ((vt)->cb? (vt)->cb(m, vt, a, (vt)->p) : (void)0) @@ -56,9 +58,35 @@ struct TMT{ TMTPOINT curs, oldcurs; TMTATTRS attrs, oldattrs; + // VT100-derived terminals have a wrap behavior where the cursor "sticks" + // at the end of a line instead of immediately wrapping. This allows you + // to use the last column without getting extra blank lines or + // unintentionally scrolling the screen. The logic we implement for it + // is not exactly like that of a real VT100, but it seems to be + // sufficient for things to work as expected in the use cases and with + // the terminfo files I've tested with. Specifically, I call the case + // where the cursor has advanced exactly one position past the rightmost + // column "hanging". A rough description of the current algorithm is + // that there are two cases which each have two sub-cases: + // 1. You're hanging onto the next line below. That is, you're not at + // the bottom of the screen/scrolling region. + // 1a. If you receive a newline, hanging mode is canceled and nothing + // else happens. In particular, you do *not* advanced to the next + // line. You're already *at* the start of the "next" line. + // 2b. If you receive a printable character, just cancel hanging mode. + // 2. You're hanging past the bottom of the screen/scrolling region. + // 2a. If you receive a newline or printable character, scroll the + // screen up one line and cancel hanging. + // 2b. If you receive a cursor reposition or whatever, cancel hanging. + // Below, hang is 0 if not hanging, or 1 or 2 as described above. + int hang; + // Name of the terminal for XTVERSION (if null, use default). char * terminal_name; + size_t minline; + size_t maxline; + bool dirty, acs, ignored; TMTSCREEN screen; TMTLINE *tabs; @@ -138,40 +166,42 @@ clearlines(TMT *vt, size_t r, size_t n) } static void -scrup(TMT *vt, size_t r, size_t n) +scrup(TMT *vt, size_t r, ssize_t n) { - n = MIN(n, vt->screen.nline - 1 - r); + if (r == SCR_DEF) r = vt->minline; + n = MIN(n, vt->maxline - r); - if (n){ + if (n>0){ TMTLINE *buf[n]; memcpy(buf, vt->screen.lines + r, n * sizeof(TMTLINE *)); memmove(vt->screen.lines + r, vt->screen.lines + r + n, - (vt->screen.nline - n - r) * sizeof(TMTLINE *)); - memcpy(vt->screen.lines + (vt->screen.nline - n), + (vt->maxline - n - r + 1) * sizeof(TMTLINE *)); + memcpy(vt->screen.lines + (vt->maxline - n + 1), buf, n * sizeof(TMTLINE *)); - clearlines(vt, vt->screen.nline - n, n); - dirtylines(vt, r, vt->screen.nline); + clearlines(vt, vt->maxline - n + 1, n); + dirtylines(vt, r, vt->maxline+1); } } static void -scrdn(TMT *vt, size_t r, size_t n) +scrdn(TMT *vt, size_t r, ssize_t n) { - n = MIN(n, vt->screen.nline - 1 - r); + if (r == SCR_DEF) r = vt->minline; + n = MIN(n, vt->maxline - r); - if (n){ + if (n>0){ TMTLINE *buf[n]; - memcpy(buf, vt->screen.lines + (vt->screen.nline - n), + memcpy(buf, vt->screen.lines + (vt->maxline - n + 1), n * sizeof(TMTLINE *)); memmove(vt->screen.lines + r + n, vt->screen.lines + r, - (vt->screen.nline - n - r) * sizeof(TMTLINE *)); + (vt->maxline - n - r + 1) * sizeof(TMTLINE *)); memcpy(vt->screen.lines + r, buf, n * sizeof(TMTLINE *)); clearlines(vt, r, n); - dirtylines(vt, r, vt->screen.nline); + dirtylines(vt, r, vt->maxline+1); } } @@ -315,6 +345,47 @@ HANDLER(title) } } +static void +nl(TMT *vt) +{ + COMMON_VARS; + + if (vt->hang) + { + if (vt->hang == 2) + scrup(vt, SCR_DEF, 1); + vt->hang = 0; + return; + } + + if (c->r == vt->maxline) + scrup(vt, SCR_DEF, 1); + else if (c->r < (s->nline-1)) + c->r++; +} + +static void +cr(TMT *vt) +{ + COMMON_VARS; + c->c = 0; + if (vt->hang==1) + { + vt->hang = 0; + if (c->r > vt->minline && c->r <= vt->maxline) + c->r--; + } +} + +static void +margin(TMT *vt, size_t top, size_t bot) +{ + if (top >= bot) return; + if (bot >= vt->screen.nline) return; + vt->minline = top; + vt->maxline = bot; +} + static void xtversion(TMT *vt) { @@ -347,8 +418,8 @@ handlechar(TMT *vt, char i) DO(S_NUL, "\x07", CB(vt, TMT_MSG_BELL, NULL)) DO(S_NUL, "\x08", if (c->c) c->c--) DO(S_NUL, "\x09", while (++c->c < s->ncol - 1 && t[c->c].c != L'*')) - DO(S_NUL, "\x0a", c->r < s->nline - 1? (void)c->r++ : scrup(vt, 0, 1)) - DO(S_NUL, "\x0d", c->c = 0) + DO(S_NUL, "\x0a", nl(vt)) + DO(S_NUL, "\x0d", cr(vt)) DO(S_NUL, "\x0e", vt->charset = 1) // Shift Out (Switch to G1) DO(S_NUL, "\x0f", vt->charset = 0) // Shift In (Switch to G0) ON(S_NUL, "\x1b", vt->state = S_ESC) @@ -376,6 +447,7 @@ handlechar(TMT *vt, char i) DO(S_ARG, "F", c->c = 0; c->r = MAX(c->r - P1(0), 0)) DO(S_ARG, "G", c->c = MIN(P1(0) - 1, s->ncol - 1)) DO(S_ARG, "d", c->r = MIN(P1(0) - 1, s->nline - 1)) + DO(S_ARG, "r", margin(vt, P1(0)-1, P1(1)-1)) DO(S_ARG, "Hf", c->r = P1(0) - 1; c->c = P1(1) - 1) DO(S_ARG, "I", while (++c->c < s->ncol - 1 && t[c->c].c != L'*')) DO(S_ARG, "J", ed(vt)) @@ -383,9 +455,9 @@ handlechar(TMT *vt, char i) DO(S_ARG, "L", scrdn(vt, c->r, P1(0))) DO(S_ARG, "M", scrup(vt, c->r, P1(0))) DO(S_ARG, "P", dch(vt)) - DO(S_ARG, "S", scrup(vt, 0, P1(0))) - DO(S_ARG, "T", scrdn(vt, 0, P1(0))) - DO(S_ARG, "X", clearline(vt, l, c->c, P1(0))) + DO(S_ARG, "S", scrup(vt, SCR_DEF, P1(0))) + DO(S_ARG, "T", scrdn(vt, SCR_DEF, P1(0))) + DO(S_ARG, "X", clearline(vt, l, c->c, c->c+P1(0))) DO(S_ARG, "Z", while (c->c && t[--c->c].c != L'*')) DO(S_ARG, "b", rep(vt)); DO(S_ARG, "c", if (!vt->q) CB(vt, TMT_MSG_ANSWER, "\033[?6c")) @@ -498,6 +570,11 @@ tmt_resize(TMT *vt, size_t nline, size_t ncol) } vt->screen.nline = nline; + // We reset this. Maybe we're supposed to maintain it? Hopefully + // anything that needs it will reset it in response to SIGWNCH? + vt->minline = 0; + vt->maxline = nline-1; + vt->tabs = allocline(vt, vt->tabs, ncol, 0); if (!vt->tabs) return free(l), false; vt->tabs->chars[0].c = vt->tabs->chars[ncol - 1].c = L'*'; @@ -534,6 +611,10 @@ writecharatcurs(TMT *vt, wchar_t w) { COMMON_VARS; + if (vt->hang == 2) + scrup(vt, SCR_DEF, 1); + vt->hang = 0; + if (vt->decode_unicode) { // We can add more mappings here, but the initial set here comes from: @@ -599,13 +680,15 @@ writecharatcurs(TMT *vt, wchar_t w) if (c->c < s->ncol - 1) c->c++; else{ + vt->hang = 1; c->c = 0; c->r++; } - if (c->r >= s->nline){ - c->r = s->nline - 1; - scrup(vt, 0, 1); + if (vt->hang && c->r > vt->maxline){ + c->r = vt->maxline; + if (vt->hang) + vt->hang = 2; } } @@ -633,7 +716,7 @@ tmt_write(TMT *vt, const char *s, size_t n) for (size_t p = 0; p < n; p++){ if (handlechar(vt, s[p])) - continue; + vt->hang = 0; else if (vt->acs) writecharatcurs(vt, tacs(vt, (unsigned char)s[p])); else if (vt->nmb >= BUF_MAX) @@ -674,7 +757,7 @@ tmt_clean(TMT *vt) void tmt_reset(TMT *vt) { - vt->curs.r = vt->curs.c = vt->oldcurs.r = vt->oldcurs.c = vt->acs = (bool)0; + memset(vt, 0, sizeof(vt)); resetparser(vt); vt->attrs = vt->oldattrs = defattrs; memset(&vt->ms, 0, sizeof(vt->ms)); From f89b0f2a0f07517e1b7738b64fd434e4df6f6ad9 Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:02:10 -0400 Subject: [PATCH 16/19] Add reverse linefeed This is used by the linux terminfo to scroll up. --- tmt.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tmt.c b/tmt.c index f55b141..83490cd 100644 --- a/tmt.c +++ b/tmt.c @@ -345,6 +345,19 @@ HANDLER(title) } } +static void +reverse_nl(TMT *vt) +{ + COMMON_VARS; + + vt->hang = 0; + + if (c->r == vt->minline) + scrdn(vt, SCR_DEF, 1); + else if (c->r > 0) + c->r--; +} + static void nl(TMT *vt) { @@ -431,6 +444,7 @@ handlechar(TMT *vt, char i) DO(S_ESC, "8", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs) ON(S_ESC, "+*", vt->ignored = true; vt->state = S_ARG) DO(S_ESC, "c", tmt_reset(vt)) + DO(S_ESC, "M", reverse_nl(vt)) ON(S_ESC, "[", vt->state = S_ARG) ON(S_ESC, "]", vt->state = S_TITLE_ARG) ON(S_ARG, "\x1b", vt->state = S_ESC) From f4e8d18118ce7b8ada987b529a46a80f8a793dc1 Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:02:10 -0400 Subject: [PATCH 17/19] Update TMT_KEY constants for linux terminal Since we're currently aiming at the "linux" terminfo entry, the key constants need to be updated. --- tmt.h | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tmt.h b/tmt.h index ba68ace..c72f0da 100644 --- a/tmt.h +++ b/tmt.h @@ -42,24 +42,24 @@ #define TMT_KEY_DOWN "\033[B" #define TMT_KEY_RIGHT "\033[C" #define TMT_KEY_LEFT "\033[D" -#define TMT_KEY_HOME "\033[H" -#define TMT_KEY_END "\033[Y" +#define TMT_KEY_HOME "\033[1~" +#define TMT_KEY_END "\033[4~" #define TMT_KEY_INSERT "\033[L" -#define TMT_KEY_BACKSPACE "\x08" +#define TMT_KEY_BACKSPACE "\x7f" #define TMT_KEY_ESCAPE "\x1b" -#define TMT_KEY_BACK_TAB "\033[Z" -#define TMT_KEY_PAGE_UP "\033[V" -#define TMT_KEY_PAGE_DOWN "\033[U" -#define TMT_KEY_F1 "\033OP" -#define TMT_KEY_F2 "\033OQ" -#define TMT_KEY_F3 "\033OR" -#define TMT_KEY_F4 "\033OS" -#define TMT_KEY_F5 "\033OT" -#define TMT_KEY_F6 "\033OU" -#define TMT_KEY_F7 "\033OV" -#define TMT_KEY_F8 "\033OW" -#define TMT_KEY_F9 "\033OX" -#define TMT_KEY_F10 "\033OY" +#define TMT_KEY_BACK_TAB "\033\x09" +#define TMT_KEY_PAGE_UP "\033[5~" +#define TMT_KEY_PAGE_DOWN "\033[6~" +#define TMT_KEY_F1 "\033[[A" +#define TMT_KEY_F2 "\033[[B" +#define TMT_KEY_F3 "\033[[C" +#define TMT_KEY_F4 "\033[[D" +#define TMT_KEY_F5 "\033[[E" +#define TMT_KEY_F6 "\033[17~" +#define TMT_KEY_F7 "\033[18~" +#define TMT_KEY_F8 "\033[19~" +#define TMT_KEY_F9 "\033[20~" +#define TMT_KEY_F10 "\033[21~" /**** BASIC DATA STRUCTURES */ typedef struct TMT TMT; From 472670f3ff5529ae2fbc506199549e98b79be72a Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:02:11 -0400 Subject: [PATCH 18/19] Add DELETE, F11, and F12 key constants --- tmt.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tmt.h b/tmt.h index c72f0da..d76deef 100644 --- a/tmt.h +++ b/tmt.h @@ -46,6 +46,7 @@ #define TMT_KEY_END "\033[4~" #define TMT_KEY_INSERT "\033[L" #define TMT_KEY_BACKSPACE "\x7f" +#define TMT_KEY_DELETE "\033[3~" #define TMT_KEY_ESCAPE "\x1b" #define TMT_KEY_BACK_TAB "\033\x09" #define TMT_KEY_PAGE_UP "\033[5~" @@ -60,6 +61,8 @@ #define TMT_KEY_F8 "\033[19~" #define TMT_KEY_F9 "\033[20~" #define TMT_KEY_F10 "\033[21~" +#define TMT_KEY_F11 "\033[23~" +#define TMT_KEY_F12 "\033[24~" /**** BASIC DATA STRUCTURES */ typedef struct TMT TMT; From 4b5550202825e4e6ff0443d0bff2dc9bede9bd6d Mon Sep 17 00:00:00 2001 From: Murphy McCauley Date: Wed, 24 May 2023 12:02:11 -0400 Subject: [PATCH 19/19] README: Update --- README.rst | 110 ++++++++++++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 57 deletions(-) diff --git a/README.rst b/README.rst index f3819df..3ce3e4f 100644 --- a/README.rst +++ b/README.rst @@ -3,33 +3,32 @@ libtmt - a simple terminal emulation library ============================================ -libtmt is the Tiny Mock Terminal Library. It provides emulation of a classic -smart text terminal, by maintaining an in-memory screen image. Sending text +libtmt is the Tiny Mock Terminal Library. It provides emulation of a +text terminal by maintaining an in-memory screen image. Sending text and command sequences to libtmt causes it to update this in-memory image, which can then be examined and rendered however the user sees fit. -The imagined primary goal for libtmt is to for terminal emulators and -multiplexers; it provides the terminal emulation layer for the `mtm`_ -terminal multiplexer, for example. Other uses include screen-scraping and -automated test harnesses. +The imagined primary use cases for libtmt are terminal emulators and +multiplexers. Other uses include screen-scraping and automated test +harnesses. -libtmt is similar in purpose to `libtsm`_, but considerably smaller (500 -lines versus 6500 lines). libtmt is also, in this author's humble opinion, -considerably easier to use. +Note! `libtmt`_ was originally written by Rob King. This is an updated +fork. -.. _`mtm`: https://github.com/deadpixi/mtm -.. _`libtsm`: https://www.freedesktop.org/wiki/Software/kmscon/libtsm/ +.. _`libtmt`: https://github.com/deadpixi/libtmt Major Features and Advantages ============================= Works Out-of-the-Box - libtmt emulates a well-known terminal type (`ansi`), the definition of - which has been in the terminfo database since at least 1995. There's no - need to install a custom terminfo entry. There's no claiming to be an - xterm but only emulating a small subset of its features. Any program - using terminfo works automatically: this includes vim, emacs, mc, - cmus, nano, nethack, ... + While libtmt does not currently emulate any other terminal with 100% + fidelity, it does a pretty decent job of emulating the well-known `linux` + terminal type. A definition of this terminal has been in the terminfo + database since 2000 or before, so there is no need to install a custom + terminfo entry. In addition, libtmt supports a number of terminal + control sequences from other popular terminals (e.g., `xterm`, `vt102`). + The emulation is good enough to work with vim, nano, dialog, and tmux, + for example. Portable Written in pure C99. @@ -37,7 +36,7 @@ Portable provides minimal support for combining characters. Small - Less than 500 lines of C, including comments and whitespace. + Less than 1000 lines of C, including comments and whitespace. Free Released under a BSD-style license, free for commercial and @@ -45,7 +44,7 @@ Free redistribution. Simple - Only 8 functions to learn, and really you can get by with 6! + Only 9 functions to learn, and really you can get by with 6! International libtmt internally uses wide characters exclusively, and uses your C @@ -66,9 +65,9 @@ Example Code ------------ Below is a simple program fragment giving the flavor of libtmt. -Note that another good example is the `mtm`_ terminal multiplexer: +Note that another good example is the `lilt`_ terminal emulator: -.. _`mtm`: https://github.com/deadpixi/mtm +.. _`lilt`: https://github.com/MurphyMc/lilt .. code:: c @@ -165,10 +164,13 @@ Data Types and Enumerations /* possible messages sent to the callback */ typedef enum{ - TMT_MSG_MOVED, /* the cursor changed position */ - TMT_MSG_UPDATE, /* the screen image changed */ - TMT_MSG_ANSWER, /* the terminal responded to a query */ - TMT_MSG_BELL /* the terminal bell was rung */ + TMT_MSG_MOVED, /* the cursor changed position */ + TMT_MSG_UPDATE, /* the screen image changed */ + TMT_MSG_ANSWER, /* the terminal responded to a query */ + TMT_MSG_BELL /* the terminal bell was rung */ + TMT_MSG_CURSOR, /* cursor visibility changed */ + TMT_MSG_SETMODE, /* a terminal mode is being enabled */ + TMT_MSG_UNSETMODE, /* a terminal mode is being disabled */ } tmt_msg_T; /* a callback for the library @@ -178,6 +180,8 @@ Data Types and Enumerations * is a pointer to the cursor's TMTPOINT for TMT_MSG_MOVED * is a pointer to the terminal's TMTSCREEN for TMT_MSG_UPDATE * is a pointer to a string for TMT_MSG_ANSWER + * is a pointer to "t" or "f" for TMT_MSG_CURSOR + * is a pointer to args of the mode for TMT_MSG_UNSETMODE * p is whatever was passed to tmt_open (see below). */ typedef void (*TMTCALLBACK)(tmt_msg_t m, struct TMT *vt, @@ -228,8 +232,8 @@ Data Types and Enumerations */ typedef struct TMTLINE TMTLINE; struct TMTLINE{ - bool dirty; /* line has changed since it was last drawn */ - TMTCHAR chars; /* the contents of the line */ + bool dirty; /* line has changed since it was last drawn */ + TMTCHAR chars[]; /* the contents of the line */ }; /* a virtual terminal screen image */ @@ -252,7 +256,9 @@ Functions Terminals must have a size of at least two rows and two columns. `acs` specifies the characters to use when in Alternate Character Set - (ACS) mode. The default string (used if `NULL` is specified) is:: + (ACS) mode and for printing DEC Special Graphics characters (there is a + large amount of crossover). The default string (used if `NULL` is + specified) is:: L"><^v#+:o##+++++~---_++++|<>*!fo" @@ -305,6 +311,10 @@ Functions Resets the virtual terminal to its default state (colors, multibyte decoding state, rendition, etc). +`bool tmt_set_unicode_decode(TMT *vt, bool v);` + Enables to disables Unicode mapping. If true, recognized Unicode + characters will be remapped to the equivalent ACS characters. + Special Keys ------------ @@ -441,7 +451,8 @@ for all characters, so you should be able to use whatever characters you want when writing to the virtual terminal (but see `Alternate Character Set`_). The following escape sequences are recognized and will be processed -specially. +specially, though note that this list is not authoritative or exhaustive. +At present, the real documentation is the source code. In the descriptions below, "ESC" means a literal escape character and "Ps" means zero or more decimal numeric arguments separated by semicolons. @@ -464,6 +475,7 @@ Sequence Action 0x09 (Tab) Cursor to next tab stop or end of line 0x0a (Carriage Return) Cursor to first cell on this line 0x0d (Linefeed) Cursor to same column one line down, scroll if needed +ESC M Reverse linefeed, scroll if needed ESC H Set a tabstop in this column ESC 7 Save cursor position and current graphical state ESC 8 Restore saved cursor position and current graphical state @@ -554,9 +566,6 @@ as equivalent in libtmt), and the various "Media Copy" escape sequences used to print output on paper (officially, there is no printer attached to libtmt). -Additionally, "?" characters are stripped out of escape sequence parameter -lists for compatibility purposes. - Known Issues ============ @@ -578,39 +587,26 @@ that uses the terminfo, termcap, or (pd|n)?curses libraries. Any program that assumes it's running under some specific terminal might fail if its assumption is wrong, and not just under libtmt. -I've tested quite a few applications in libtmt and they've worked flawlessly: -vim, GNU emacs, nano, cmus, mc (Midnight Commander), and others just work -with no changes. +Historically, quite a few applications have been tested and found to work +work, including vim, GNU emacs, nano, cmus, mc (Midnight Commander), and +others. More recently, things that actually get tested decently are: +vim, nano, dialog, readline, and tmux (and all of the above inside tmux). What programs don't work with libtmt? ------------------------------------- -Breakage with libtmt is of two kinds: breakage due to assuming a terminal -type, and reduced functionality. - -In all my testing, I only found one program that didn't work correctly by -default with libtmt: recent versions of Debian's `apt`_ assume a terminal -with definable scrolling regions to draw a fancy progress bar during -package installation. Using apt in its default configuration in libtmt will -result in a corrupted display (that can be fixed by clearing the screen). - -.. _`apt`: https://wiki.debian.org/Apt - -In my honest opinion, this is a bug in apt: it shouldn't assume the type -of terminal it's running in. - -The second kind of breakage is when not all of a program's features are -available. The biggest missing feature here is mouse support: libtmt -doesn't, and probably never will, support mouse tracking. I know of many -programs that *can* use mouse tracking in a terminal, but I don't know -of any that *require* it. Most (if not all?) programs of this kind would -still be completely usable in libtmt. +As of this writing, there are no programs known to not work with libtmt. +This almost certainly just means nobody has noticed or reported the +issue -- not that such issues don't exist. Note that the correct +operation of libtmt depends also on the terminfo entry being used. +Changes to the terminfo can certainly break things. License ------- -Copyright (c) 2017 Rob King -All rights reserved. +| Copyright (c) 2023 Murphy McCauley +| Copyright (c) 2017 Rob King +| All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: