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: diff --git a/tmt.c b/tmt.c index 7ace141..83490cd 100644 --- a/tmt.c +++ b/tmt.c @@ -33,11 +33,14 @@ #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)) #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) @@ -49,12 +52,41 @@ 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; 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; @@ -63,19 +95,36 @@ 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 + + bool decode_unicode; // Try to decode characters to ACS equivalents? + mbstate_t ms; size_t nmb; char mb[BUF_MAX + 1]; - size_t pars[PAR_MAX]; + 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; + bool q; + 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}; 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) { @@ -104,7 +153,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' '; } } @@ -117,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); } } @@ -160,7 +211,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; } @@ -183,10 +234,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. @@ -205,13 +260,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; @@ -239,7 +294,7 @@ HANDLER(dsr) HANDLER(resetparser) memset(vt->pars, 0, sizeof(vt->pars)); - 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) @@ -253,6 +308,116 @@ 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: + for (int i = vt->npar; i < PAR_MAX; ++i) + vt->pars[i] = (size_t)-1; + CB(vt, TMT_MSG_SETMODE, &vt->pars[0]); + break; + } +} + +HANDLER(rm) + switch (P0(0)){ + case 25: + 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; + } +} + +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 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) +{ + 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) +{ + 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) { @@ -266,20 +431,28 @@ 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) 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) - 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)) + 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) 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) 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)) @@ -288,6 +461,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)) @@ -295,21 +469,41 @@ 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", 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", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "t")) + 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", 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) + 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)) + 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) + { + if ( (i >= 32) && (vt->ntitle < TITLE_MAX) ) + { + vt->title[vt->ntitle] = i; + vt->ntitle += 1; + return true; + } + } return resetparser(vt), false; } @@ -390,6 +584,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'*'; @@ -402,11 +601,86 @@ 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->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: + // 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); + #ifdef TMT_HAS_WCWIDTH extern int wcwidth(wchar_t c); if (wcwidth(w) > 1) w = TMT_INVALID_CHAR; @@ -420,13 +694,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; } } @@ -454,7 +730,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) @@ -495,7 +771,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)); diff --git a/tmt.h b/tmt.h index ae0ddbb..d76deef 100644 --- a/tmt.h +++ b/tmt.h @@ -42,24 +42,27 @@ #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_DELETE "\033[3~" #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~" +#define TMT_KEY_F11 "\033[23~" +#define TMT_KEY_F12 "\033[24~" /**** BASIC DATA STRUCTURES */ typedef struct TMT TMT; @@ -120,8 +123,11 @@ typedef enum{ TMT_MSG_MOVED, TMT_MSG_UPDATE, 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); @@ -129,6 +135,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);