From: Mark Wooding Date: Fri, 25 Apr 2025 01:19:44 +0000 (+0100) Subject: @@@ tty cleanup X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/commitdiff_plain/9a5e5808ff09935b8562d7bf93b3ada040d96669 @@@ tty cleanup --- diff --git a/ui/tty.c b/ui/tty.c index f3f526f..001878c 100644 --- a/ui/tty.c +++ b/ui/tty.c @@ -64,8 +64,64 @@ #include "str.h" #include "tty.h" +/*----- Operations table --------------------------------------------------*/ + +/* Incorporate the published control-block structure into our more elaborate + * object model. + */ +#define TTY_BASEPFX struct tty tty +#define TTY_BASEUSFX struct tty tty + +struct tty_ops { + void (*release)(struct tty */*tty*/); + /* Free any resources held by the backend. */ + + /* The following operations handle the correspondingly named interface + * functions. + */ + int (*setattr)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + const struct tty_attr */*a*/); + int (*setmodes)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + uint32 /*modes_bic*/, uint32 /*modes_xor*/); + int (*move)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + unsigned /*orig*/, int /*y*/, int /*x*/); + int (*repeat)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + int /*ch*/, unsigned /*n*/); + int (*erase)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + unsigned /*f*/); + int (*erch)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + unsigned /*n*/); + int (*ins)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + unsigned /*f*/, unsigned /*n*/); + int (*inch)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + int /*ch*/); + int (*del)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + unsigned /*f*/, unsigned /*n*/); +}; +#define TTY_BASEOPSPFX struct tty_ops tty +#define TTY_BASEOPSUXFX struct tty_ops tty + /*----- Common support machinery ------------------------------------------*/ +/* --- @CHECK@ --- * + * + * Arguments@ @expr@ = expression to evaluate + * + * Use: Evaluate @expr@. If the result is (strictly) negative, then + * set @rc = -1@ and transfer control to the label @end@. + */ + +#define CHECK(expr) do { if ((expr) < 0) { rc = -1; goto end; } } while (0) + /* --- @debug@ --- * * * Arguments: @const char *fmt@ = format control string @@ -91,6 +147,21 @@ static PRINTF_LIKE(1, 2) void debug(const char *fmt, ...) } } +/* --- @common_init@ --- * + * + * Arguments: @struct tty *tty@ = pointer to terminal control block + * @FILE *fp@ = output file stream + * + * Returns: --- + * + * Use: Perform general initialization on the terminal control + * block. + * + * Specifically, this fills in the @fpout@, @baud@, @ht@, and + * @wd@ slots. The width and height come from the kernel, or, + * failing that, the environment. + */ + static void common_init(struct tty *tty, FILE *fp) { static const struct baudtab { speed_t code; unsigned baud; } baudtab[] = { @@ -216,7 +287,13 @@ static void common_init(struct tty *tty, FILE *fp) struct termios c; speed_t code; + /* Save the output stream. */ tty->fpout = fp; + + /* Determine the output baud rate. Unhelpfully, the kernel provides a + * weird code, so we have to convert it into an actual rate in bits per + * second. + */ if (!fp || tcgetattr(fileno(fp), &c)) tty->baud = 0; else { @@ -228,35 +305,87 @@ static void common_init(struct tty *tty, FILE *fp) tty_resized(tty); } + /* If the kernel didn't tell us the terminal dimensions, try to read them + * from the environment. + */ if (!tty->wd) { p = getenv("COLUMNS"); if (p) { n = atoi(p); if (n) tty->wd = n; } } if (!tty->ht) { p = getenv("LINES"); if (p) { n = atoi(p); if (n) tty->ht = n; } } } -static void env_colour_caps(unsigned *caps_inout) +/* --- @env_colour_caps@ --- * + * + * Arguments: @unsigned *caps_inout@ = attribute capabilities to update + * @unsigned f@ = flags + * + * Returns: --- + * + * Use: Check the %|FORCE_COLOR|% environment variable and update the + * capabilities as required. + * + * The %|FORCE_COLOR|% variable originates with the Node + * community, with two objectives: (a) to convey policy + * regarding whether to produce coloured output, and (b) to + * describe the colour capabilities of the terminal, because the + * traditional mechanisms are deemed inadequate. + * + * The following values have defined meanings. + * + * * Unset or empty: no effect. + * + * * %|0|%: monochrome; don't produce colour. + * + * * %|1|%: 16 colours; 1-bit-per-channel colours are + * available, with an additional common brightness bit. + * + * * %|2|%: 256 colours; the `xterm' 256-bit palette is + * available, consisting of the 1-bit-per-channel colours + * with common brightness bit, a 6 × 6 × 6 colour cube, and + * a 24-level greyscale ramp. + * + * * %|3|%: full 24-bit colour. + * + * * Anything else: a request to use colour if available. + * This is ignored here, in the expectation that it will be + * given effect elsewhere, e.g., by @ttycolour_enablep@. + * + * If @ECCF_SET@ is set, then set or clear capabilities as + * required. Otherwise, clear capability bits which are denied + * by the variable setting, but no bits will be set. (This + * latter is necessary for backends which use terminal + * databases, since they can't be expected to make up the + * necessary control sequences for themselves.) + */ + +#define ECCF_SET 1u +static void env_colour_caps(unsigned *caps_inout, unsigned f) { const char *p; - unsigned caps = *caps_inout; + unsigned caps = *caps_inout, mask; - p = getenv("FORCE_COLOR"); - if (p) switch (*p) { + p = getenv("FORCE_COLOR"); if (!p) return; + switch (*p) { case '0': - caps &= TTACF_CSPCMASK | TTACF_FG | TTACF_BG; + mask = 0; break; case '1': - caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG | - TTACF_1BPC | TTACF_1BPCBR; + mask = TTACF_FG | TTACF_BG | TTACF_1BPC | TTACF_1BPCBR; break; case '2': - caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG | - TTACF_1BPC | TTACF_1BPCBR | TTACF_6LPC | TTACF_8LGS; + mask = TTACF_FG | TTACF_BG | + TTACF_1BPC | TTACF_1BPCBR | TTACF_6LPC | TTACF_24LGS; break; case '3': - caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG | + mask = TTACF_FG | TTACF_BG | TTACF_1BPC | TTACF_1BPCBR | TTACF_8BPC; break; + default: + return; } + if (!(f&ECCF_SET)) caps &= mask; + else caps = (caps&~(TTACF_CSPCMASK | TTACF_FG | TTACF_BG)) | mask; + *caps_inout = caps; } @@ -594,7 +723,7 @@ inval: #undef D } -/* --- @tty_clampattr@ --- * +/* --- @clamp_attr@ --- * * * Arguments: @struct tty_attr *a_out@ = selected attributes * @const struct tty_attr *a@ = requested attributes @@ -606,8 +735,8 @@ inval: * which can be accommodated by the terminal. */ -void tty_clampattr(struct tty_attr *a_out, - const struct tty_attr *a, uint32 acaps) +static void clamp_attr(struct tty_attr *a_out, + const struct tty_attr *a, uint32 acaps) { uint32 ff = 0, f = a ? a->f : 0, t; @@ -662,16 +791,47 @@ void tty_clampattr(struct tty_attr *a_out, a_out->f = ff; a_out->_res0 = 0; } -int tty_resized(struct tty *tty) +/* --- @stupid_repeat@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @int ch@ = character to write + * @unsigned n@ = number of copies + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Write @n@ copies of the character @ch@ to the terminal, the + * hard way. This function tries to be reasonably efficient, by + * transmitting buffers rather than exercising the output + * machinery for each individual character. + */ + +static int stupid_repeat(struct tty *tty, + const struct gprintf_ops *gops, void *go, + int ch, unsigned n) { - struct winsize ws; + char buf[4096]; + unsigned nn; + int rc; - if (!tty->fpout) { errno = ENOTTY; return (-1); } - if (ioctl(fileno(tty->fpout), TIOCGWINSZ, &ws)) return (-1); - tty->wd = ws.ws_col; tty->ht = ws.ws_row; return (0); + if (n < sizeof(buf)) + { memset(buf, ch, n); CHECK(gops->putm(go, buf, n)); } + else { + memset(buf, ch, sizeof(buf)); + nn = sizeof(buf); + for (;;) { + CHECK(gops->putm(go, buf, nn)); + n -= nn; if (!n) break; + if (n < nn) nn = n; + } + } + rc = 0; +end: + return (rc); } -/*----- Common machinery for `termcap' and `terminfo' ---------------------*/ +/*----- Common machinery for %|termcap|% and %|terminfo|% -----------------*/ #if defined(HAVE_TERMINFO) || \ defined(HAVE_TERMCAP) || \ @@ -679,27 +839,142 @@ int tty_resized(struct tty *tty) #if defined(HAVE_TERMINFO) || defined(HAVE_TERMCAP) -static const struct gprintf_ops *global_gops; -static void *global_gout; -static struct tty *global_lock = 0; +/* Global state. + * + * The `termcap' and `terminfo' functions call a user-provided function to + * actually send control codes to the terminal. The bad news is that + * `termcap' doesn't provide any way to pass information to the output + * function beyond the character to be sent, and `terminfo' doesn't fix this + * mistake. So we must save the necessary context as global variables. For + * good measure, at least some implementations ignore errors from the output + * function, so we must keep track of them ourselves. More global variables. + * + * It's worse. Both libraries maintain significant global state of their + * own. And, at least with the `ncurses' implementation, the two share the + * same global state. The only thing to do is maintain a big interlock to + * make sure that only one is active at a time. + */ +static const struct gprintf_ops *global_gops; /* output operations ... */ +static void *global_gout; /* and context, for @caps_putch */ +static char global_buf[4096]; /* a big output buffer */ +static size_t global_len; /* length of buffer used */ +static int global_err; /* error latch, zero if all ok */ +static struct tty *global_lock = 0; /* interlock for global state */ + +/* --- @caps_claim@ --- * + * + * Arguments: --- + * + * Returns: Zero on success, %$-1$% if already claimed. + * + * Use: Return %$-1$% if the interlock is already held. This is a + * function to call near the beginning of initializing a new + * control block, before the common global state gets + * clobbered. If initialization is successful, the caller is + * expected to actually store the control block pointer in + * @global_lock@ themselves. + */ + +static int caps_claim(void) +{ + if (global_lock) + { debug("termcap/terminfo terminal already open"); return (-1); } + else + return (0); +} + +/* --- @caps_claim@, @caps_release@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer for current lock + * holder + * + * Returns: --- + * + * Use: Release the lock. + */ static void caps_release(struct tty *tty) { assert(global_lock == tty); global_lock = 0; } +/* --- @caps_putch@ --- * + * + * Arguments: @int ch@ = character to write + * + * Returns: Nonnegative on success, negative on failure. (But @tputs@ + * ignores this.) + * + * Use: Output the character @ch@. + */ + static int caps_putch(int ch) - { return (global_gops->putch(global_gout, ch)); } +{ + if (global_len >= sizeof(global_buf)) { + if (global_gops->putm(global_gout, global_buf, global_len)) + global_err = -1; + global_len = 0; + } + global_buf[global_len++] = ch; + return (0); +} -#endif +/* --- @caps_prepout@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer (ignored) + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * + * Returns: --- + * + * Use: Prepare output to the given destination. + */ + +static void caps_prepout(struct tty *tty, + const struct gprintf_ops *gops, void *go) + { assert(!global_len); global_gops = gops; global_gout = go; } + +/* --- @caps_flush@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer (ignored) + * + * Returns: Zero for success, %$-1$% if error pending. + * + * Use: Flush the output buffer to the backend. If an error is + * pending, clear it and return failure. + */ + +static int caps_flush(struct tty *tty) +{ + int rc = global_err; + + if (global_len) { + if (global_gops->putm(global_gout, global_buf, global_len)) rc = -1; + global_len = 0; + } + global_err = 0; return (rc); +} -#ifdef HAVE_UNIBILIUM -# define UNIBI_(x) unibi##x -#else -# define UNIBI_(x) 0 #endif +/* The list of interesting capabilities. + * + * We never actually need all of these: some are only needed if others are + * unavailable. But the list isn't too huge, so we'll live with it. + * + * The main thing is that each capability has three different names: the + * `full' name (corresponding to a `terminfo' variable name), the `terminfo' + * capability name, as used in terminal descriptions, and the two-character + * `termcap' name. Unibilium uses the long names, but to reduce typing, the + * `unibi_' prefix is omitted here. (Annoyingly, in `ncurses', at least, the + * `variable' names are `secretly' macros referencing a current state, and + * premature expansion causes misery, so I've left the leading underscores in + * place as a countermeasure.) Internally, we use the short `terminfo' + * names, since they generally express the most useful information in the + * smallest space. + */ + #define BASICCAPS(_bool, _int, _str) \ _str(_repeat_char, rep, rp) \ - _int(_padding_baud_rate, pb, pb) _str(_pad_char, pad, pc) \ + _str(_pad_char, pad, pc) _int(_padding_baud_rate, pb, pb) \ _bool(_no_pad_char, npc, NP) _bool(_xon_xoff, xon, xo) \ _bool(_move_insert_mode, mir, mi) _bool(_move_standout_mode, msgr, ms) @@ -759,8 +1034,20 @@ static int caps_putch(int ch) ERASECAPS(_bool, _int, _str) \ INSDELCAPS(_bool, _int, _str) +#ifdef HAVE_UNIBILIUM +# define UNIBI_(x) unibi##x +#else +# define UNIBI_(x) 0 +#endif + #define CAPREF(var, info, cap) UNIBI_(var), #info, #cap + /* Expand a capability triple into a group of three usable C arguments. If + * Unibilium isn't available, then we can use nonsense for its cap index. + */ +/* Some other capabilities which we want to refer to during initialization, + * but don't need to keep around. + */ #define CAP_XMC CAPREF(_magic_cookie_glitch, xmc, sg) #define CAP_BCE CAPREF(_back_color_erase, bce, ut) #define CAP_XHPA CAPREF(_row_addr_glitch, xvpa, YD) @@ -770,12 +1057,12 @@ static int caps_putch(int ch) #define CAP_HT CAPREF(_lines, lines, li) #define CAP_WD CAPREF(_columns, cols, co) -#define TTY_BASEOPSPFX struct tty_ops tty -#define TTY_BASEOPSUXFX struct tty_ops tty -#define TTY_BASEPFX struct tty tty -#define TTY_BASEUSFX struct tty tty - +/* Additional operations required of terminal backends which make use of the + * common capability machinery. + */ struct tty_capopslots { + + /* Retrieving capabilities. */ int (*boolcap)(struct tty */*tty*/, int /*uix*/, const char */*info*/, const char */*cap*/); int (*intcap)(struct tty */*tty*/, @@ -783,14 +1070,17 @@ struct tty_capopslots { const char *(*strcap)(struct tty */*tty*/, int /*uix*/, const char */*info*/, const char */*cap*/); - int (*put0)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - unsigned /*npad*/, const char */*cap*/); + + /* Preparing and completing output. */ + void (*prepout)(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/); + int (*flush)(struct tty */*tty*/); + + /* Writing capabilities with various kinds of arguments. */ + int (*put0)(struct tty */*tty*/, unsigned /*npad*/, const char */*cap*/); int (*put1i)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, unsigned /*npad*/, const char */*cap*/, int /*i0*/); int (*put2i)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, unsigned /*npad*/, const char */*cap*/, int /*i0*/, int /*i1*/); }; @@ -799,6 +1089,7 @@ struct tty_capops { TTY_CAPOPSPFX; }; #define TTY_CAPOPSUSFX struct tty_capops cap; TTY_BASEOPSUXFX union tty_capopsu { TTY_CAPOPSUSFX; }; +/* An extension of the control block to track the above capabilities. */ struct tty_capslots { #define DEF_BOOLCAP(uix, info, cap) unsigned info : 1; #define DEF_INTCAP(uix, info, cap) int info; @@ -817,6 +1108,18 @@ struct tty_caps { TTY_CAPSPFX; }; struct tty tty union tty_capsu { TTY_CAPSUSFX; }; +/* ---- @init_caps@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * + * Returns: --- + * + * Use: Populate the capabilities in the terminal control block, and + * advertise the results to the public part. Set @ht@ and @wd@ + * from the terminal description if they've not been set + * already. + */ + static void init_caps(struct tty_caps *t) { const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops; @@ -873,7 +1176,7 @@ static void init_caps(struct tty_caps *t) else if (t->cap.colors >= 16) t->tty.acaps |= TTACF_1BPCBR; if (ops->cap.boolcap(&t->tty, CAP_BCE)) t->tty.ocaps |= TTCF_BGER; - env_colour_caps(&t->tty.acaps); + env_colour_caps(&t->tty.acaps, 0); } } @@ -924,21 +1227,24 @@ static void init_caps(struct tty_caps *t) { ht = ops->cap.intcap(&t->tty, CAP_HT); if (ht > 0) t->tty.ht = ht; } } -#define CHECK(expr) do { if ((expr) < 0) { rc = -1; goto end; } } while (0) - +/* Macros for formatting capabilities. */ #define PUT0V(npad, cap_) \ - CHECK(ops->cap.put0(&t->tty, gops, go, (npad), (cap_))) + CHECK(ops->cap.put0(&t->tty, (npad), (cap_))) #define PUT1IV(npad, cap_, i0) \ - CHECK(ops->cap.put1i(&t->tty, gops, go, (npad), (cap_), (i0))) + CHECK(ops->cap.put1i(&t->tty, (npad), (cap_), (i0))) #define PUT2IV(npad, cap_, i0, i1) \ - CHECK(ops->cap.put2i(&t->tty, gops, go, (npad), (cap_), (i0), (i1))) + CHECK(ops->cap.put2i(&t->tty, (npad), (cap_), (i0), (i1))) #define PUT0(npad, name) PUT0V(npad, t->cap.name) #define PUT1I(npad, name, i0) PUT1IV(npad, t->cap.name, i0) #define PUT2I(npad, name, i0, i1) PUT2IV(npad, t->cap.name, i0, i1) +/* --- @caps_setcolour@ --- * + * + * Arguments: @struct tty_caps *t@ = extended control block pointer + * + */ static int caps_setcolour(struct tty_caps *t, - const struct gprintf_ops *gops, void *go, const char *cap, uint32 spc, uint32 clr) { const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops; @@ -951,7 +1257,7 @@ static int caps_setcolour(struct tty_caps *t, case TTCSPC_24LGS: PUT1IV(0, cap, clr + 232); break; case TTCSPC_8BPC: - /* There's an unfortunate ambiguity in the `setaf' conventions. The + /* There's an unfortunate ambiguity in the %|setaf|% conventions. The * first eight colours should be dark shades of blue, but in fact * they're interpreted as the one-bit-per-channel basic colours by * common `terminfo' settings. Notice and kludge by adding a little @@ -970,36 +1276,32 @@ end: return (rc); } -static int caps_setattr(struct tty *tty, - const struct gprintf_ops *gops, void *go, - const struct tty_attr *a) +static int caps_setattr_internal(struct tty_caps *t, + const struct tty_attr *a) { - struct tty_caps *t = (struct tty_caps *)tty; const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops; - struct tty_attr aa; uint32 diff; int rc; /* Work out what needs doing. */ - tty_clampattr(&aa, a, t->tty.acaps); - diff = aa.f ^ t->tty.st.attr.f; + diff = a->f ^ t->tty.st.attr.f; /* Some terminals might not be able to clear individual attributes that * they can set, and some capabilities for turning attributes on don't even * have a corresponding attribute for turning them off again individually, - * so we have to use `sgr0' to start from scratch. Of course, if we need + * so we have to use %|sgr0|% to start from scratch. Of course, if we need * to do that, we need to restore the other active attributes, so we must * check up front. */ - if (((diff&TTAF_LNMASK) && !(aa.f&TTAF_LNMASK) && !t->cap.rmul) || - ((diff&TTAF_WTMASK) && !(aa.f&TTAF_WTMASK)) || - ((diff&~aa.f&TTAF_ITAL) && !t->cap.ritm) || - (diff&~aa.f&TTAF_INVV)) - { PUT0(0, sgr0); diff = aa.f; } + if (((diff&TTAF_LNMASK) && !(a->f&TTAF_LNMASK) && !t->cap.rmul) || + ((diff&TTAF_WTMASK) && !(a->f&TTAF_WTMASK)) || + ((diff&~a->f&TTAF_ITAL) && !t->cap.ritm) || + (diff&~a->f&TTAF_INVV)) + { PUT0(0, sgr0); diff = a->f; } /* Line style. */ if (diff&TTAF_LNMASK) - switch ((aa.f&TTAF_LNMASK) >> TTAF_LNSHIFT) { + switch ((a->f&TTAF_LNMASK) >> TTAF_LNSHIFT) { case TTLN_NONE: PUT0(0, rmul); break; case TTLN_ULINE: PUT0(0, smul); break; /* case TTLN_UULINE: */ @@ -1009,7 +1311,7 @@ static int caps_setattr(struct tty *tty, /* Text weight. */ if (diff&TTAF_WTMASK) - switch ((aa.f&TTAF_WTMASK) >> TTAF_WTSHIFT) { + switch ((a->f&TTAF_WTMASK) >> TTAF_WTSHIFT) { /* case TTWT_MED: */ case TTWT_BOLD: PUT0(0, bold); break; case TTWT_DIM: PUT0(0, dim); break; @@ -1018,33 +1320,47 @@ static int caps_setattr(struct tty *tty, /* Other text effects. */ if (diff&TTAF_ITAL) { - if (aa.f&TTAF_ITAL) PUT0(0, sitm); + if (a->f&TTAF_ITAL) PUT0(0, sitm); else PUT0(0, ritm); } - if (diff&aa.f&TTAF_INVV) PUT0(0, rev); + if (diff&a->f&TTAF_INVV) PUT0(0, rev); /* Colours. */ - if (((diff&TTAF_FGSPCMASK) && !(aa.f&TTAF_FGSPCMASK)) || - ((diff&TTAF_BGSPCMASK) && !(aa.f&TTAF_BGSPCMASK))) { + if (((diff&TTAF_FGSPCMASK) && !(a->f&TTAF_FGSPCMASK)) || + ((diff&TTAF_BGSPCMASK) && !(a->f&TTAF_BGSPCMASK))) { /* There's no capability string for resetting just the foreground * or background colours to the defaults, so deal with that here. */ PUT0(0, op); diff = (diff&~(TTAF_FGSPCMASK | TTAF_BGSPCMASK)) | - (aa.f&(TTAF_FGSPCMASK | TTAF_BGSPCMASK)); + (a->f&(TTAF_FGSPCMASK | TTAF_BGSPCMASK)); } - if ((diff&TTAF_FGSPCMASK) || aa.fg != t->tty.st.attr.fg) - CHECK(caps_setcolour(t, gops, go, t->cap.setaf, - (aa.f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, aa.fg)); - if ((diff&TTAF_BGSPCMASK) || aa.bg != t->tty.st.attr.bg) - CHECK(caps_setcolour(t, gops, go, t->cap.setab, - (aa.f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, aa.bg)); + if ((diff&TTAF_FGSPCMASK) || a->fg != t->tty.st.attr.fg) + CHECK(caps_setcolour(t, t->cap.setaf, + (a->f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a->fg)); + if ((diff&TTAF_BGSPCMASK) || a->bg != t->tty.st.attr.bg) + CHECK(caps_setcolour(t, t->cap.setab, + (a->f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a->bg)); /* All done. */ rc = 0; end: - t->tty.st.attr = aa; return (rc); + t->tty.st.attr = *a; return (rc); +} + +static int caps_setattr(struct tty *tty, + const struct gprintf_ops *gops, void *go, + const struct tty_attr *a) +{ + struct tty_caps *t = (struct tty_caps *)tty; + const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops; + int rc; + + ops->cap.prepout(&t->tty, gops, go); + rc = caps_setattr_internal(t, a); + if (ops->cap.flush(&t->tty)) rc = -1; + return (rc); } static int caps_setmodes(struct tty *tty, @@ -1061,6 +1377,9 @@ static int caps_setmodes(struct tty *tty, modes = (t->tty.st.modes&~modes_bic) ^ modes_xor; diff = modes ^ t->tty.st.modes; + /* Prepare output. */ + ops->cap.prepout(&t->tty, gops, go); + /* Automatic margins. */ if (diff&TTMF_AUTOM) { if (modes&TTMF_AUTOM) PUT0(0, smam); @@ -1096,13 +1415,12 @@ static int caps_setmodes(struct tty *tty, /* Done. */ rc = 0; end: + if (ops->cap.flush(&t->tty)) rc = -1; t->tty.st.modes = modes; return (rc); } #define CIF_PADMUL 1u - static int caps_iterate(struct tty_caps *t, - const struct gprintf_ops *gops, void *go, const char *cap1, const char *capn, unsigned f, unsigned npad, unsigned n) { @@ -1126,7 +1444,6 @@ end: } static int caps_move_relative(struct tty_caps *t, - const struct gprintf_ops *gops, void *go, int delta, const char *fw1, const char *fwn, const char *rv1, const char *rvn) @@ -1136,7 +1453,7 @@ static int caps_move_relative(struct tty_caps *t, if (!delta) return (0); else if (delta > 0) { mv1 = fw1; mvn = fwn; } else { mv1 = rv1; mvn = rvn; delta = - delta; } - return (caps_iterate(t, gops, go, mv1, mvn, 0, 0, delta)); + return (caps_iterate(t, mv1, mvn, 0, 0, delta)); } static int caps_move(struct tty *tty, @@ -1148,6 +1465,8 @@ static int caps_move(struct tty *tty, struct tty_attr a; int rc; + ops->cap.prepout(&t->tty, gops, go); + if (!t->cap.mir && (t->tty.st.modes&TTMF_INS)) PUT0(0, rmir); a = t->tty.st.attr; @@ -1165,23 +1484,23 @@ static int caps_move(struct tty *tty, PUT1I(1, hpa, x); else { PUT0(1, cr); - CHECK(caps_move_relative(t, gops, go, x, + CHECK(caps_move_relative(t, x, t->cap.cuf1, t->cap.cuf, t->cap.cub1, t->cap.cub)); } } else if (t->cap.home) { PUT0(1, home); - CHECK(caps_iterate(t, gops, go, t->cap.cud1, t->cap.cud, 0, 1, y)); - CHECK(caps_iterate(t, gops, go, t->cap.cuf1, t->cap.cuf, 0, 1, x)); + CHECK(caps_iterate(t, t->cap.cud1, t->cap.cud, 0, 1, y)); + CHECK(caps_iterate(t, t->cap.cuf1, t->cap.cuf, 0, 1, x)); } else { rc = -1; goto end; } break; case TTORG_CUR: - CHECK(caps_move_relative(t, gops, go, y, + CHECK(caps_move_relative(t, y, t->cap.cud1, t->cap.cud, t->cap.cuu1, t->cap.cuu)); - CHECK(caps_move_relative(t, gops, go, x, + CHECK(caps_move_relative(t, x, t->cap.cuf1, t->cap.cuf, t->cap.cub1, t->cap.cub)); break; @@ -1190,21 +1509,21 @@ static int caps_move(struct tty *tty, if (x == 0 && y == 1) PUT0(1, nel); else { - CHECK(caps_move_relative(t, gops, go, y, + CHECK(caps_move_relative(t, y, t->cap.cud1, t->cap.cud, t->cap.cuu1, t->cap.cuu)); if (t->cap.hpa && x) PUT1I(1, hpa, x); else { PUT0(1, cr); - CHECK(caps_iterate(t, gops, go, t->cap.cuf1, t->cap.cuf, 0, 1, x)); + CHECK(caps_iterate(t, t->cap.cuf1, t->cap.cuf, 0, 1, x)); } } break; case TTOF_XCUR | TTOF_YHOME: PUT1I(1, vpa, y); - CHECK(caps_move_relative(t, gops, go, x, + CHECK(caps_move_relative(t, x, t->cap.cuf1, t->cap.cuf, t->cap.cub1, t->cap.cub)); break; @@ -1216,11 +1535,12 @@ static int caps_move(struct tty *tty, if (!t->cap.mir && (t->tty.st.modes&TTMF_INS)) PUT0(0, smir); - if (!t->cap.msgr && t->tty.st.attr.f) - CHECK(caps_setattr(&t->tty, gops, go, &a)); + if (!t->cap.msgr && a.f) + CHECK(caps_setattr_internal(t, &a)); rc = 0; end: + if (ops->cap.flush(&t->tty)) rc = -1; return (rc); } @@ -1233,8 +1553,9 @@ static int caps_repeat(struct tty *tty, unsigned wd, nn; int rc; + ops->cap.prepout(&t->tty, gops, go); if (!t->cap.rep) - while (n--) CHECK(gops->putch(go, ch)); + CHECK(stupid_repeat(tty, gops, go, ch, n)); else { wd = t->tty.wd; while (n) { @@ -1245,6 +1566,7 @@ static int caps_repeat(struct tty *tty, } rc = 0; end: + if (ops->cap.flush(&t->tty)) rc = -1; return (rc); } @@ -1256,6 +1578,7 @@ static int caps_erase(struct tty *tty, const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops; int rc; + ops->cap.prepout(&t->tty, gops, go); if (f&TTEF_DSP) switch (f&(TTEF_BEGIN | TTEF_END)) { case 0: @@ -1277,6 +1600,7 @@ static int caps_erase(struct tty *tty, } rc = 0; end: + if (ops->cap.flush(&t->tty)) rc = -1; return (rc); } @@ -1288,9 +1612,11 @@ static int caps_erch(struct tty *tty, const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops; int rc; + ops->cap.prepout(&t->tty, gops, go); if (n) PUT1I(1, ech, n); rc = 0; end: + if (ops->cap.flush(&t->tty)) rc = -1; return (rc); } @@ -1299,16 +1625,17 @@ static int caps_ins(struct tty *tty, unsigned f, unsigned n) { struct tty_caps *t = (struct tty_caps *)tty; + const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops; int rc; + ops->cap.prepout(&t->tty, gops, go); if (f&TTIDF_LN) - CHECK(caps_iterate(t, gops, go, - t->cap.il1, t->cap.il, CIF_PADMUL, 1, n)); + CHECK(caps_iterate(t, t->cap.il1, t->cap.il, CIF_PADMUL, 1, n)); else - CHECK(caps_iterate(t, gops, go, - t->cap.ich1, t->cap.ich, 0, 1, n)); + CHECK(caps_iterate(t, t->cap.ich1, t->cap.ich, 0, 1, n)); rc = 0; end: + if (ops->cap.flush(&t->tty)) rc = -1; return (rc); } @@ -1320,6 +1647,7 @@ static int caps_inch(struct tty *tty, const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops; int rc; + ops->cap.prepout(&t->tty, gops, go); if (t->cap.smir ? !(t->tty.st.modes&TTMF_INS) : !t->cap.ich) { rc = -1; goto end; } if (t->cap.ich) PUT0(1, ich); @@ -1327,6 +1655,7 @@ static int caps_inch(struct tty *tty, if (t->cap.ip) PUT0(1, ip); rc = 0; end: + if (ops->cap.flush(&t->tty)) rc = -1; return (rc); } @@ -1335,22 +1664,22 @@ static int caps_del(struct tty *tty, unsigned f, unsigned n) { struct tty_caps *t = (struct tty_caps *)tty; + const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops; int rc; + ops->cap.prepout(&t->tty, gops, go); if (n) { if (f&TTIDF_LN) - CHECK(caps_iterate(t, gops, go, - t->cap.dl1, t->cap.dl, CIF_PADMUL, 1, n)); + CHECK(caps_iterate(t, t->cap.dl1, t->cap.dl, CIF_PADMUL, 1, n)); else - CHECK(caps_iterate(t, gops, go, - t->cap.dch1, t->cap.dch, 0, 1, n)); + CHECK(caps_iterate(t, t->cap.dch1, t->cap.dch, 0, 1, n)); } rc = 0; end: + if (ops->cap.flush(&t->tty)) rc = -1; return (rc); } -#undef CHECK #undef PUT0V #undef PUT1IV #undef PUT2IV @@ -1361,8 +1690,7 @@ end: #define TTY_CAPOPS \ caps_setattr, caps_setmodes, \ caps_move, caps_repeat, \ - caps_erase, caps_erch, caps_ins, caps_inch, caps_del, \ - 0, 0, 0, 0 + caps_erase, caps_erch, caps_ins, caps_inch, caps_del #endif @@ -1406,36 +1734,30 @@ static const char *termcap_strcap(struct tty *tty, } static int termcap_put0(struct tty *tty, - const struct gprintf_ops *gops, void *go, unsigned npad, const char *cap) { if (!cap) return (-1); - global_gops = gops; global_gout = go; return (tputs(cap, npad, caps_putch)); } static int termcap_put1i(struct tty *tty, - const struct gprintf_ops *gops, void *go, unsigned npad, const char *cap, int i0) { if (!cap) return (-1); - global_gops = gops; global_gout = go; return (tputs(tgoto(cap, -1, i0), npad, caps_putch) == OK ? 0 : -1); } static int termcap_put2i(struct tty *tty, - const struct gprintf_ops *gops, void *go, - unsigned npad, - const char *cap, int i0, int i1) + unsigned npad, const char *cap, int i0, int i1) { if (!cap) return (-1); - global_gops = gops; global_gout = go; return (tputs(tgoto(cap, i1, i0), npad, caps_putch) == OK ? 0 : -1); } static const union tty_capopsu termcap_ops = { { { caps_release, TTY_CAPOPS }, { termcap_boolcap, termcap_intcap, termcap_strcap, + caps_prepout, caps_flush, termcap_put0, termcap_put1i, termcap_put2i } } }; @@ -1444,8 +1766,7 @@ static struct tty *termcap_init(FILE *fp) union tty_termcapu *u = 0; struct tty *ret = 0; const char *term; - if (global_lock) - { debug("termcap/terminfo terminal already open"); goto end; } + if (caps_claim()) goto end; term = getenv("TERM"); if (!term) goto end; XNEW(u); if (tgetent(u->tc.tc.termbuf, term) < 1) goto end; @@ -1493,36 +1814,30 @@ static const char *terminfo_strcap(struct tty *tty, } static int terminfo_put0(struct tty *tty, - const struct gprintf_ops *gops, void *go, unsigned npad, const char *cap) { if (!cap) return (-1); - global_gops = gops; global_gout = go; return (tputs(cap, npad, caps_putch)); } static int terminfo_put1i(struct tty *tty, - const struct gprintf_ops *gops, void *go, unsigned npad, const char *cap, int i0) { if (!cap) return (-1); - global_gops = gops; global_gout = go; return (tputs(tparm(cap, i0), npad, caps_putch) == OK ? 0 : -1); } static int terminfo_put2i(struct tty *tty, - const struct gprintf_ops *gops, void *go, - unsigned npad, - const char *cap, int i0, int i1) + unsigned npad, const char *cap, int i0, int i1) { if (!cap) return (-1); - global_gops = gops; global_gout = go; return (tputs(tparm(cap, i0, i1), npad, caps_putch) == OK ? 0 : -1); } static const union tty_capopsu terminfo_ops = { { { caps_release, TTY_CAPOPS }, { terminfo_boolcap, terminfo_intcap, terminfo_strcap, + caps_prepout, caps_flush, terminfo_put0, terminfo_put1i, terminfo_put2i } } }; @@ -1531,8 +1846,7 @@ static struct tty *terminfo_init(FILE *fp) union tty_capsu *u = 0; struct tty *ret = 0; int err; - if (global_lock) - { debug("termcap/terminfo terminal already open"); goto end; } + if (caps_claim()) goto end; if (setupterm(0, fp ? fileno(fp) : -1, &err) != OK || err < 1) goto end; XNEW(u); u->tty.ops = &terminfo_ops.tty; @@ -1552,6 +1866,9 @@ end: struct tty_unibislots { unibi_term *ut; unibi_var_t dy[26], st[26]; + const struct gprintf_ops *gops; void *go; + char buf[4096]; size_t n; + int err; }; struct tty_unibilium { TTY_CAPSPFX; struct tty_unibislots u; }; union tty_unibiliumu { struct tty_unibilium u; TTY_CAPSUSFX; }; @@ -1583,41 +1900,70 @@ static const char *termunibi_strcap(struct tty *tty, struct termunibi_outctx { struct tty_unibilium *t; - const struct gprintf_ops *gops; void *go; - char pad[128]; - int rc; }; -static void termunibi_putch(void *ctx, const char *p, size_t sz) +static void termunibi_putm(void *ctx, const char *p, size_t sz) { - struct termunibi_outctx *out = ctx; + struct tty_unibilium *t = ctx; + size_t n; - if (out->gops->putm(out->go, p, sz)) out->rc = -1; + n = sizeof(t->u.buf) - t->u.n; + if (sz <= n) + { memcpy(t->u.buf + t->u.n, p, sz); t->u.n += sz; } + else { + if (n) { memcpy(t->u.buf + t->u.n, p, n); p += n; sz -= n; } + for (;;) { + if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf))) + t->u.err = -1; + if (sz <= sizeof(t->u.buf)) break; + memcpy(t->u.buf, p, sizeof(t->u.buf)); + p += sizeof(t->u.buf); sz -= sizeof(t->u.buf); + } + memcpy(t->u.buf, p, sz); t->u.n = sz; + } } static void termunibi_pad(void *ctx, size_t ms, int mulp, int forcep) { - char pad[128]; - struct termunibi_outctx *out = ctx; - struct tty_unibilium *t = out->t; + struct tty_unibilium *t = ctx; struct timeval tv; - size_t n, nn; + int pc; + size_t sz, n; /* Based on 7 data bits, 1 stop bit, 1 parity bit. */ #define BITS_PER_KB 9000 - if (forcep || t->tty.baud >= t->cap.pb) { + if (forcep || (t->tty.baud >= t->cap.pb && !t->cap.xon)) { if (t->cap.npc) { tv.tv_sec = ms/1000; tv.tv_usec = 1000*(ms%1000); + if (t->u.n) { + if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf))) + t->u.err = -1; + t->u.n = 0; + } if (t->tty.fpout) fflush(t->tty.fpout); select(0, 0, 0, 0, &tv); } else { - n = (ms*t->tty.baud + BITS_PER_KB - 1)/BITS_PER_KB; - while (n) { - if (n < sizeof(out->pad)) nn = n; - else nn = sizeof(out->pad); - if (out->gops->putm(out->go, pad, nn)) out->rc = -1; - n -= nn; + pc = t->cap.pad ? *t->cap.pad : 0; + sz = (ms*t->tty.baud + BITS_PER_KB - 1)/BITS_PER_KB; + n = sizeof(t->u.buf) - t->u.n; + if (sz <= n) + { memset(t->u.buf + t->u.n, pc, sz); t->u.n += sz; } + else { + if (n) { memset(t->u.buf + t->u.n, pc, sz); sz -= n; } + if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf))) + t->u.err = -1; + if (sz < sizeof(t->u.buf)) + memset(t->u.buf, pc, sz); + else { + memset(t->u.buf, pc, sizeof(t->u.buf)); + do { + if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf))) + t->u.err = -1; + sz -= sizeof(t->u.buf); + } while (sz > sizeof(t->u.buf)); + } + t->u.n = sz; } } } @@ -1625,66 +1971,67 @@ static void termunibi_pad(void *ctx, size_t ms, int mulp, int forcep) #undef BITS_PER_KB } -static void setup_termunibi_outctx(struct tty_unibilium *t, - struct termunibi_outctx *out, - const struct gprintf_ops *gops, void *go) +static void termunibi_prepout(struct tty *tty, + const struct gprintf_ops *gops, void *go) { - out->t = t; out->rc = 0; - out->gops = gops; out->go = go; - if (!t->cap.npc) - memset(out->pad, t->cap.pad ? *t->cap.pad : 0, sizeof(out->pad)); + struct tty_unibilium *t = (struct tty_unibilium *)tty; + + assert(!t->u.n); t->u.gops = gops; t->u.go = go; +} + +static int termunibi_flush(struct tty *tty) +{ + struct tty_unibilium *t = (struct tty_unibilium *)tty; + int rc = t->u.err; + + if (t->u.n) { + if (t->u.gops->putm(t->u.go, t->u.buf, t->u.n)) rc = -1; + t->u.n = 0; + } + t->u.err = 0; return (rc); } static int termunibi_put0(struct tty *tty, - const struct gprintf_ops *gops, void *go, - unsigned npad, const char *cap) + unsigned npad, const char *cap) { struct tty_unibilium *t = (struct tty_unibilium *)tty; - struct termunibi_outctx out; unibi_var_t arg[9]; if (!cap) return (-1); - setup_termunibi_outctx(t, &out, gops, go); unibi_format(t->u.dy, t->u.st, cap, arg, - termunibi_putch, &out, - termunibi_pad, &out); - return (out.rc); + termunibi_putm, t, + termunibi_pad, t); + return (0); } static int termunibi_put1i(struct tty *tty, - const struct gprintf_ops *gops, void *go, unsigned npad, const char *cap, int i0) { struct tty_unibilium *t = (struct tty_unibilium *)tty; - struct termunibi_outctx out; unibi_var_t arg[9]; if (!cap) return (-1); - setup_termunibi_outctx(t, &out, gops, go); arg[0] = unibi_var_from_num(i0); unibi_format(t->u.dy, t->u.st, cap, arg, - termunibi_putch, &out, - termunibi_pad, &out); - return (out.rc); + termunibi_putm, t, + termunibi_pad, t); + return (0); } static int termunibi_put2i(struct tty *tty, - const struct gprintf_ops *gops, void *go, unsigned npad, const char *cap, int i0, int i1) { struct tty_unibilium *t = (struct tty_unibilium *)tty; - struct termunibi_outctx out; unibi_var_t arg[9]; if (!cap) return (-1); - setup_termunibi_outctx(t, &out, gops, go); arg[0] = unibi_var_from_num(i0); arg[1] = unibi_var_from_num(i1); unibi_format(t->u.dy, t->u.st, cap, arg, - termunibi_putch, &out, - termunibi_pad, &out); - return (out.rc); + termunibi_putm, t, + termunibi_pad, t); + return (0); } static void termunibi_release(struct tty *tty) @@ -1697,6 +2044,7 @@ static void termunibi_release(struct tty *tty) static const union tty_capopsu termunibi_ops = { { { termunibi_release, TTY_CAPOPS }, { termunibi_boolcap, termunibi_intcap, termunibi_strcap, + termunibi_prepout, termunibi_flush, termunibi_put0, termunibi_put1i, termunibi_put2i } } }; @@ -1711,6 +2059,7 @@ static struct tty *termunibi_init(FILE *fp) XNEW(u); u->tty.ops = &termunibi_ops.tty; u->u.u.ut = ut; ut = 0; + u->u.u.n = 0; u->u.u.err = 0; common_init(&u->tty, fp); init_caps(&u->cap); ret = &u->tty; u = 0; @@ -1800,8 +2149,6 @@ union tty_ansiu { struct tty_ansi ansi; TTY_BASEUSFX; }; static void ansi_release(struct tty *tty) { ; } -#define CHECK(expr) do { if ((expr) < 0) { rc = -1; goto end; } } while (0) - #define PUTCH(ch) CHECK(gops->putch(go, (ch))) #define PUTLIT(lit) CHECK(gops->putm(go, (lit), sizeof(lit) - 1)) #define SEMI do { \ @@ -1871,38 +2218,36 @@ static int ansi_setattr(struct tty *tty, const struct tty_attr *a) { struct tty_ansi *t = (struct tty_ansi *)tty; - struct tty_attr aa; uint32 diff; int rc = 0; unsigned z, c, f = 0; - tty_clampattr(&aa, a, t->tty.acaps); - diff = aa.f ^ t->tty.st.attr.f; - if (!diff && aa.fg == t->tty.st.attr.fg && aa.bg == t->tty.st.attr.bg) + diff = a->f ^ t->tty.st.attr.f; + if (!diff && a->fg == t->tty.st.attr.fg && a->bg == t->tty.st.attr.bg) return (0); c = 0; -#define CLEARP(mask) ((diff&(mask)) && !(aa.f&(mask))) +#define CLEARP(mask) ((diff&(mask)) && !(a->f&(mask))) if (CLEARP(TTAF_LNMASK)) c += 3; if (CLEARP(TTAF_WTMASK)) c += 3; - if (diff&~aa.f&TTAF_INVV) c += 3; - if (diff&~aa.f&TTAF_STRIKE) c += 3; - if (diff&~aa.f&TTAF_ITAL) c += 3; + if (diff&~a->f&TTAF_INVV) c += 3; + if (diff&~a->f&TTAF_STRIKE) c += 3; + if (diff&~a->f&TTAF_ITAL) c += 3; if (CLEARP(TTAF_FGSPCMASK)) c += 3; if (CLEARP(TTAF_BGSPCMASK)) c += 3; #undef CLEARP z = 0; - switch ((aa.f&TTAF_LNMASK) >> TTAF_LNSHIFT) { + switch ((a->f&TTAF_LNMASK) >> TTAF_LNSHIFT) { case TTLN_ULINE: z += 2; break; case TTLN_UULINE: z += 3; break; } - if (aa.f&TTAF_WTMASK) z += 2; - if (aa.f&TTAF_INVV) z += 2; - if (aa.f&TTAF_STRIKE) z += 2; - if (aa.f&TTAF_ITAL) z += 2; + if (a->f&TTAF_WTMASK) z += 2; + if (a->f&TTAF_INVV) z += 2; + if (a->f&TTAF_STRIKE) z += 2; + if (a->f&TTAF_ITAL) z += 2; #define COLOURCOST(col) do { \ - switch ((aa.f&TTAF_##col##SPCMASK) >> TTAF_##col##SPCSHIFT) { \ + switch ((a->f&TTAF_##col##SPCMASK) >> TTAF_##col##SPCSHIFT) { \ case TTCSPC_1BPC: case TTCSPC_1BPCBR: z += 3; break; \ case TTCSPC_4LPC: case TTCSPC_8LGS: z += 8; break; \ case TTCSPC_6LPC: case TTCSPC_24LGS: z += 9; break; \ @@ -1914,10 +2259,11 @@ static int ansi_setattr(struct tty *tty, PUTLIT("\33["); - if (z <= c) { SEMI; diff = aa.f; } + if (z <= c) + { SEMI; diff = a->f; t->tty.st.attr.fg = t->tty.st.attr.bg = 0; } if (diff&TTAF_LNMASK) - switch ((aa.f&TTAF_LNMASK) >> TTAF_LNSHIFT) { + switch ((a->f&TTAF_LNMASK) >> TTAF_LNSHIFT) { case TTLN_NONE: SEMI; PUTLIT("24"); break; case TTLN_ULINE: SEMI; PUTCH('4'); break; case TTLN_UULINE: SEMI; PUTLIT("21"); break; @@ -1925,7 +2271,7 @@ static int ansi_setattr(struct tty *tty, } if (diff&TTAF_WTMASK) - switch ((aa.f&TTAF_WTMASK) >> TTAF_WTSHIFT) { + switch ((a->f&TTAF_WTMASK) >> TTAF_WTSHIFT) { case TTWT_MED: SEMI; PUTLIT("22"); break; case TTWT_BOLD: SEMI; PUTCH('1'); break; case TTWT_DIM: SEMI; PUTCH('2'); break; @@ -1933,22 +2279,22 @@ static int ansi_setattr(struct tty *tty, } if (diff&TTAF_INVV) - { SEMI; if (aa.f&TTAF_INVV) PUTCH('7'); else PUTLIT("27"); } + { SEMI; if (a->f&TTAF_INVV) PUTCH('7'); else PUTLIT("27"); } if (diff&TTAF_STRIKE) - { SEMI; if (aa.f&TTAF_STRIKE) PUTCH('9'); else PUTLIT("29"); } + { SEMI; if (a->f&TTAF_STRIKE) PUTCH('9'); else PUTLIT("29"); } if (diff&TTAF_ITAL) - { SEMI; if (aa.f&TTAF_ITAL) PUTCH('3'); else PUTLIT("23"); } + { SEMI; if (a->f&TTAF_ITAL) PUTCH('3'); else PUTLIT("23"); } - if (diff&TTAF_FGSPCMASK || aa.fg != tty->st.attr.fg) + if (diff&TTAF_FGSPCMASK || a->fg != tty->st.attr.fg) CHECK(ansi_setcolour(t, &f, gops, go, 30, 90, - (aa.f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, aa.fg)); - if (diff&TTAF_BGSPCMASK || aa.bg != tty->st.attr.bg) + (a->f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a->fg)); + if (diff&TTAF_BGSPCMASK || a->bg != tty->st.attr.bg) CHECK(ansi_setcolour(t, &f, gops, go, 40, 100, - (aa.f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, aa.bg)); + (a->f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a->bg)); PUTCH('m'); rc = 0; end: - t->tty.st.attr = aa; return (rc); + t->tty.st.attr = *a; return (rc); } @@ -2009,7 +2355,7 @@ static int ansi_move(struct tty *tty, if (!(orig&TTOF_YCUR)) CHECK(gprintf(gops, go, "\33[%dd", y + 1)); else if (y == -1) PUTLIT("\33[A"); else if (y < 0) CHECK(gprintf(gops, go, "\33[%dA", -y)); - else if (y == +1) PUTLIT("\33[B"); /* not `^J'! */ + else if (y == +1) PUTLIT("\33[B"); /* not %|^J|%! */ else if (y > 1) CHECK(gprintf(gops, go, "\33[%dB", y)); if (!(orig&TTOF_XCUR)) { if (!x) @@ -2030,18 +2376,6 @@ end: return (rc); } -static int ansi_repeat(struct tty *tty, - const struct gprintf_ops *gops, void *go, - int ch, unsigned n) -{ - int rc; - - while (n--) PUTCH(ch); - rc = 0; -end: - return (rc); -} - static int ansi_erase(struct tty *tty, const struct gprintf_ops *gops, void *go, unsigned f) @@ -2133,9 +2467,8 @@ end: static const struct tty_ops ansi_ops = { ansi_release, ansi_setattr, ansi_setmodes, - ansi_move, ansi_repeat, - ansi_erase, ansi_erch, ansi_ins, ansi_inch, ansi_del, - 0, 0, 0, 0 + ansi_move, stupid_repeat, + ansi_erase, ansi_erch, ansi_ins, ansi_inch, ansi_del }; static struct tty *ansi_init(FILE *fp) @@ -2390,7 +2723,7 @@ static struct tty *ansi_init(FILE *fp) tf |= tm->tf&~tfset; } - env_colour_caps(&acaps); + if (!(acapset&TTACF_CSPCMASK)) env_colour_caps(&acaps, ECCF_SET); if (acaps&TTACF_CSPCMASK) ocaps |= TTCF_BGER; XNEW(u); @@ -2439,7 +2772,7 @@ struct tty *tty_open(FILE *fp, unsigned f, const unsigned *backends) else if (isatty(STDERR_FILENO)) { fp = stderr; f |= TTF_BORROW; } else { fp = fopen("/dev/tty", "r+"); if (!fp) goto end; - f &= ~TTF_BORROW; + fpin = fp; f &= ~TTF_BORROW; } } @@ -2500,20 +2833,56 @@ void tty_close(struct tty *tty) } } + +int tty_resized(struct tty *tty) +{ + struct winsize ws; + + if (!tty || !tty->fpout) { errno = ENOTTY; return (-1); } + else if (ioctl(fileno(tty->fpout), TIOCGWINSZ, &ws)) return (-1); + else if (tty->wd == ws.ws_col && tty->ht == ws.ws_row) return (0); + else { tty->wd = ws.ws_col; tty->ht = ws.ws_row; return (1); } +} + /*----- Terminal operations -----------------------------------------------*/ +int tty_setattr(struct tty *tty, const struct tty_attr *a) +{ + struct tty_attr aa; + + if (!tty || !tty->fpout) + return (-1); + else { + clamp_attr(&aa, a, tty->acaps); + return (tty->ops->setattr(tty, &file_printops, tty->fpout, &aa)); + } +} + int tty_setattrg(struct tty *tty, const struct gprintf_ops *gops, void *go, const struct tty_attr *a) - { return (tty->ops->setattr(tty, gops, go, a)); } +{ + struct tty_attr aa; -int tty_setattr(struct tty *tty, const struct tty_attr *a) - { return (tty->ops->setattr(tty, &file_printops, tty->fpout, a)); } + if (!tty) + return (-1); + else { + clamp_attr(&aa, a, tty->acaps); + return (tty->ops->setattr(tty, gops, go, &aa)); + } +} + +int tty_setattrlist(struct tty *tty, const struct tty_attrlist *aa) +{ + if (!tty || !tty->fpout) return (-1); + else return (tty_setattrlistg(tty, &file_printops, tty->fpout, aa)); +} int tty_setattrlistg(struct tty *tty, const struct gprintf_ops *gops, void *go, const struct tty_attrlist *aa) { + if (!tty) return (-1); for (;; aa++) if ((tty->acaps&aa->cap_mask) == aa->cap_eq) return (tty->ops->setattr(tty, gops, go, &aa->attr)); @@ -2521,74 +2890,138 @@ int tty_setattrlistg(struct tty *tty, return (0); } -int tty_setattrlist(struct tty *tty, const struct tty_attrlist *aa) - { return (tty_setattrlistg(tty, &file_printops, tty->fpout, aa)); } +int tty_setmodes(struct tty *tty, uint32 modes_bic, uint32 modes_xor) +{ + if (!tty || !tty->fpout) return (-1); + else return (tty->ops->setmodes(tty, &file_printops, tty->fpout, + modes_bic, modes_xor)); +} int tty_setmodesg(struct tty *tty, const struct gprintf_ops *gops, void *go, uint32 modes_bic, uint32 modes_xor) - { return (tty->ops->setmodes(tty, gops, go, modes_bic, modes_xor)); } +{ + if (!tty) return (-1); + else return (tty->ops->setmodes(tty, gops, go, modes_bic, modes_xor)); +} -int tty_setmodes(struct tty *tty, uint32 modes_bic, uint32 modes_xor) +int tty_move(struct tty *tty, unsigned orig, int y, int x) { - return (tty->ops->setmodes(tty, &file_printops, tty->fpout, - modes_bic, modes_xor)); + if (!tty || !tty->fpout) return (-1); + else return (tty->ops->move(tty, &file_printops, tty->fpout, orig, y, x)); } int tty_moveg(struct tty *tty, const struct gprintf_ops *gops, void *go, unsigned orig, int y, int x) - { return (tty->ops->move(tty, gops, go, orig, y, x)); } +{ + if (!tty) return (-1); + else return (tty->ops->move(tty, gops, go, orig, y, x)); +} -int tty_move(struct tty *tty, unsigned orig, int y, int x) - { return (tty->ops->move(tty, &file_printops, tty->fpout, orig, y, x)); } +int tty_repeat(struct tty *tty, int ch, unsigned n) +{ + if (!tty || !tty->fpout) return (-1); + else return (tty->ops->repeat(tty, &file_printops, tty->fpout, ch, n)); +} int tty_repeatg(struct tty *tty, const struct gprintf_ops *gops, void *go, int ch, unsigned n) - { return (tty->ops->repeat(tty, gops, go, ch, n)); } +{ + if (!tty) return (-1); + else return (tty->ops->repeat(tty, gops, go, ch, n)); +} -int tty_repeat(struct tty *tty, int ch, unsigned n) - { return (tty->ops->repeat(tty, &file_printops, tty->fpout, ch, n)); } +int tty_erase(struct tty *tty, unsigned f) +{ + if (!tty || !tty->fpout) return (-1); + else return (tty->ops->erase(tty, &file_printops, tty->fpout, f)); +} int tty_eraseg(struct tty *tty, const struct gprintf_ops *gops, void *go, unsigned f) - { return (tty->ops->erase(tty, gops, go, f)); } +{ + if (!tty) return (-1); + else return (tty->ops->erase(tty, gops, go, f)); +} -int tty_erase(struct tty *tty, unsigned f) - { return (tty->ops->erase(tty, &file_printops, tty->fpout, f)); } +int tty_erch(struct tty *tty, unsigned n) +{ + if (!tty || !tty->fpout) return (-1); + else return (tty->ops->erch(tty, &file_printops, tty->fpout, n)); +} int tty_erchg(struct tty *tty, const struct gprintf_ops *gops, void *go, unsigned n) - { return (tty->ops->erch(tty, gops, go, n)); } +{ + if (!tty) return (-1); + else return (tty->ops->erch(tty, gops, go, n)); +} -int tty_erch(struct tty *tty, unsigned n) - { return (tty->ops->erch(tty, &file_printops, tty->fpout, n)); } +int tty_ins(struct tty *tty, unsigned f, unsigned n) +{ + if (!tty || !tty->fpout) return (-1); + else return (tty->ops->ins(tty, &file_printops, tty->fpout, f, n)); +} int tty_insg(struct tty *tty, const struct gprintf_ops *gops, void *go, unsigned f, unsigned n) - { return (tty->ops->ins(tty, gops, go, f, n)); } +{ + if (!tty) return (-1); + else return (tty->ops->ins(tty, gops, go, f, n)); +} -int tty_ins(struct tty *tty, unsigned f, unsigned n) - { return (tty->ops->ins(tty, &file_printops, tty->fpout, f, n)); } +int tty_inch(struct tty *tty, int ch) +{ + if (!tty || !tty->fpout) return (-1); + else return (tty->ops->inch(tty, &file_printops, tty->fpout, ch)); +} int tty_inchg(struct tty *tty, const struct gprintf_ops *gops, void *go, int ch) - { return (tty->ops->inch(tty, gops, go, ch)); } +{ + if (!tty) return (-1); + else return (tty->ops->inch(tty, gops, go, ch)); +} -int tty_inch(struct tty *tty, int ch) - { return (tty->ops->inch(tty, &file_printops, tty->fpout, ch)); } +int tty_del(struct tty *tty, unsigned f, unsigned n) +{ + if (!tty || !tty->fpout) return (-1); + else return (tty->ops->del(tty, &file_printops, tty->fpout, f, n)); +} int tty_delg(struct tty *tty, const struct gprintf_ops *gops, void *go, unsigned f, unsigned n) - { return (tty->ops->del(tty, gops, go, f, n)); } +{ + if (!tty) return (-1); + else return (tty->ops->del(tty, gops, go, f, n)); +} -int tty_del(struct tty *tty, unsigned f, unsigned n) - { return (tty->ops->del(tty, &file_printops, tty->fpout, f, n)); } +int tty_restore(struct tty *tty, const struct tty_state *st) +{ + if (!tty || !tty->fpout) return (-1); + else return (tty_restoreg(tty, &file_printops, tty->fpout, st)); +} + +int tty_restoreg(struct tty *tty, + const struct gprintf_ops *gops, void *go, + const struct tty_state *st) +{ + int rc; + + if (!tty || + tty->ops->setmodes(tty, gops, go, MASK32, st->modes) || + tty->ops->setattr(tty, gops, go, &st->attr)) + { rc = -1; goto end; } + rc = 0; +end: + return (rc); +} /*----- That's all, folks -------------------------------------------------*/ diff --git a/ui/tty.h b/ui/tty.h index e026c15..57ed489 100644 --- a/ui/tty.h +++ b/ui/tty.h @@ -162,7 +162,7 @@ enum { /* Other capabilities. */ #define TTCF_RELMV 0x00000100u /* relative cursor motion */ #define TTCF_ABSMV 0x00000200u /* absolute cursor motion */ -#define TTCF_MIXMV 0x00000400u /* mixed cursor motion */ +#define TTCF_MIXMV 0x00000400u /* mixed (y-abs, x-rel) motion */ #define TTCF_MMARG 0x00000800u /* proper magic margins */ #define TTCF_BGER 0x00001000u /* erasure uses background colour */ #define TTCF_ERCH 0x00002000u /* erase characters */ @@ -231,135 +231,380 @@ struct tty_state { /* Terminal control state. */ struct tty { - const struct tty_ops *ops; - FILE *fpin, *fpout; - unsigned baud, ht, wd, f; -#define TTF_BORROW 1u - uint32 acaps, ocaps; - struct tty_state st; + const struct tty_ops *ops; /* operations table (opaque) */ + FILE *fpin, *fpout; /* input and output streams */ + unsigned baud; /* (output) baud rate (bits/s) */ + unsigned ht, wd; /* terminal dimensions */ + unsigned f; /* flags */ +#define TTF_BORROW 1u /* don't close the streams */ + uint32 acaps, ocaps; /* attribute and other capabilities */ + struct tty_state st; /* current terminal state */ }; -struct tty_ops { - void (*release)(struct tty */*tty*/); - int (*setattr)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - const struct tty_attr */*a*/); - int (*setmodes)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - uint32 /*modes_bic*/, uint32 /*modes_xor*/); - int (*move)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - unsigned /*orig*/, int /*y*/, int /*x*/); - int (*repeat)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - int /*ch*/, unsigned /*n*/); - int (*erase)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - unsigned /*f*/); - int (*erch)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - unsigned /*n*/); - int (*ins)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - unsigned /*f*/, unsigned /*n*/); - int (*inch)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - int /*ch*/); - int (*del)(struct tty */*tty*/, - const struct gprintf_ops */*gops*/, void */*go*/, - unsigned /*f*/, unsigned /*n*/); - int (*_res0)(void), (*_res1)(void), (*_res2)(void), (*_res3)(void); -}; +/*----- Lifecycle and maintenance -----------------------------------------*/ + +/* --- @tty_open@ --- * + * + * Arguments: @FILE *fp@ = stream open on terminal, or null + * @unsigned f@ = flags (@TTF_...@) + * @const unsigned *backends@ = ordered list of backends to try + * + * Returns: Pointer to terminal control block, or null on error. + * + * Use: Open a terminal and return a @struct tty *@ terminal control + * block pointer. + * + * If @fp@ is provided, then it will be used for terminal + * output. If @fp@ is @stdout@, then input (not currently + * supported) will come from @stdin@; otherwise, input comes + * from @fp@. If @fp@ is null and @TTF_OPEN@ is set, then + * @tty_open@ will attempt to open a terminal for itself: if + * @stdin@ is interactive then it used for input; if @stdout@ -- + * or, failing that, @stderr@ -- is interactive, then it is used + * for output; otherwise %|/dev/tty|% is opened and used. If + * @fp@ is null and @TTF_OPEN@ is not set, then a usable + * terminal control block is still returned, but output cannot + * be sent directly to the terminal -- since there isn't one. + * + * If @TTF_BORROW@ is set, then the stream will not be closed by + * @tty_close@. (This flag is ignored if @fp@ is null.) + * + * If @beckends@ is provided, then it points to a vector of + * @TTBK_...@ constants describing the backends to be tried in + * order. The vector is terminated by @TTBK_END@; if this is + * found, then a null pointer is returned. + * + * A null control block pointer is valid for all @tty@ + * functions: most will just immediately report failure, but + * there won't be any crashing. + */ -/*----- Functions provided ------------------------------------------------*/ +#define TTF_OPEN 256u /* open terminal if @fp@ is null */ +extern struct tty *tty_open(FILE */*fp*/, unsigned /*f*/, + const unsigned */*backends*/); -/* --- @tty_clampattr@ --- * +/* --- @tty_close@ --- * * - * Arguments: @struct tty_attr *a_out@ = selected attributes - * @const struct tty_attr *a@ = requested attributes - * @uint32 acaps@ = terminal's attribute capability mask + * Arguments: @struct tty *tty@ = control block pointer * * Returns: --- * - * Use: Select the closest approximation to the requested attributes - * which can be accommodated by the terminal. + * Use: Closes a terminal, releasing the control block and any + * resources it held. In particular, if the terminal was opened + * without @TTF_BORROW@, then the output stream is closed. */ -extern void tty_clampattr(struct tty_attr */*a_out*/, - const struct tty_attr */*a*/, uint32 /*acaps*/); - +extern void tty_close(struct tty */*tty*/); +/* --- @tty_resized@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * + * Returns: Zero if the size hasn't changed, %$+1$% if the size has + * changed, or %$-1$% on error. + * + * Use: Update the terminal width and height. Call this after + * receiving @SIGWINCH@, or otherwise periodically, to avoid + * making a mess. + */ extern int tty_resized(struct tty */*tty*/); -#define TTF_OPEN 256u -extern struct tty *tty_open(FILE */*fp*/, unsigned /*f*/, - const unsigned */*backends*/); +/*----- Output and control functions --------------------------------------*/ -extern void tty_close(struct tty */*tty*/); +/* These functions come in pairs. The unmarked version sends output directly + * to the terminal's output stream, and will obviously fail (though not + * crash) if this is null; the version whose names ends with @...g@ accepts + * an additional pair of arguments @gops@ and @go@ giving an alternative + * destination for output. + */ + +/* --- @tty_setattr@, @tty_setattrg@--- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @const struct tty_attr *a@ = pointer to attributes to set, or + * null + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Set the indicated formatting attributes on following output. + * If @a@ is null, then clear all attributes, just as if + * @a->f == 0@. + * + * If you only ever request attributes which are advertised in + * the terminals capapbility masked, then you'll always get what + * you asked for. Otherwise, the provided attributes will be + * `clamped', i.e., modified so as to accommodate the terminal's + * shortcomings. In simple cases, unsupported attributes may + * just be dropped; but they can also be substituted, e.g., + * single underlining for double, or approximate colours for + * unsupported colours. + */ +extern int tty_setattr(struct tty */*tty*/, const struct tty_attr */*a*/); extern int tty_setattrg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, const struct tty_attr */*a*/); -extern int tty_setattr(struct tty */*tty*/, const struct tty_attr */*a*/); +/* --- @tty_setattrlist@, @tty_setattrlistg@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @const struct tty_attrlist *aa@ = pointer to attribute list + * `menu' + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Search the list for an entry matching the terminal's + * capabilities, i.e., @tty->acaps&a->cap_mask == a->cap_eq@. + * The attributes in the first such entry are set, as if by + * @tty_setattr@. + * + * The list is terminated by an entry with @cap_mask == 0@ -- + * though it will be checked like any other before ending the + * search. In particular, this means that an entry with + * @cap_mask == cap_eq == 0@ is a `catch-all', and its + * attributes will be set if no earlier matching entry could be + * found, while an entry with @cap_mask == 0@ and @cap_eq != 0@ + * terminates the search without setting any attributes. + */ +extern int tty_setattrlist(struct tty */*tty*/, + const struct tty_attrlist */*aa*/); extern int tty_setattrlistg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, const struct tty_attrlist */*aa*/); -extern int tty_setattrlist(struct tty */*tty*/, - const struct tty_attrlist */*aa*/); +/* --- @tty_setmodes@, @tty_setmodesg@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @uint32 modes_bic, modes_xor@ = masks to apply to the modes + * settings + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Adjust the terminal display modes: specifically, the modes + * are adjusted to be @(modes&~modes_bic) ^ modes_xor@. + * Mode bits which aren't supported by the terminal are + * ignored. + */ +extern int tty_setmodes(struct tty */*tty*/, + uint32 /*modes_bic*/, uint32 /*modes_xor*/); extern int tty_setmodesg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, uint32 /*modes_bic*/, uint32 /*modes_xor*/); -extern int tty_setmodes(struct tty */*tty*/, - uint32 /*modes_bic*/, uint32 /*modes_xor*/); +/* --- @tty_move@, @tty_moveg@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @unsigned orig@ = origin + * @int y, x@ = new cursor position + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Move the cursor. Coordinates are numbered starting with 0. + * + * The @y@ position is interpreted relative to the origin given + * by @orig@: if @TTOF_YCUR@ is set, then the motion is relative + * to the current cursor row; otherwise, it is relative to + * the top `home' row. Similarly, the @x@ position is + * interpreted relative to the current cursor column if + * @TTOF_XCUR is set, otherwise relative to the leftmost + * column. + * + * Not all terminals are capable of all kinds of motions: + * @TTCF_ABSMV@ is set if absolute motion is possible, and + * @TTCF_RELMV@ is set if relative motion is possible. The + * @TTCF_MIXMV@ bit indicates that the combination of absolute-y + * and relative-x motion is possible; note that the combination + * of relative-y and absolute-x is always possible if relative + * motion is possible at all. + * + * The above notwithstanding, all terminals are assumed capable + * of moving the cursor to the start of either the current line + * @tty_move(tty, TTOF_YCUR | TTOF_XHOME, 0, 0)@, or of the next + * line @tty_move(tty, TTOF_YCUR | TTOF_XHOME, +1, 0)@. + */ +extern int tty_move(struct tty */*tty*/, + unsigned /*orig*/, int /*y*/, int /*x*/); extern int tty_moveg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, unsigned /*orig*/, int /*y*/, int /*x*/); -extern int tty_move(struct tty */*tty*/, - unsigned /*orig*/, int /*y*/, int /*x*/); +/* --- @tty_repeat@, @tty_repeatg@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @int ch@ = character to write + * @unsigned n@ = number of copies + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Write @n@ copies of the character @ch@ to the terminal. + * (Some terminals have a special control sequence for doing + * this.) + */ +extern int tty_repeat(struct tty */*tty*/, int /*ch*/, unsigned /*n*/); extern int tty_repeatg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, int /*ch*/, unsigned /*n*/); -extern int tty_repeat(struct tty */*tty*/, int /*ch*/, unsigned /*n*/); +/* --- @tty_erase@, @tty_eraseg@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @unsigned f@ = flags + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Erase portions of the current line or the whole display. + * + * If @TTEF_DSP@ is set, then the whole display is affected. If + * @TTEF_BEGIN@ is set, then the display is erased starting from + * the top left and ending at and including the cursor + * position. If @TTEF_END@ is set, then the display is erased + * starting from and including the cursor position, and ending + * at the bottom right. If both flags are set, then, + * additionally, the cursor is moved to its `home' position at + * the top left. + * + * If @TTF_DSP@ is not set, then the current line is affected. + * If @TTEF_BEGIN@ is set, then the line is erased starting from + * the left and ending at and including the cursor position. If + * @TTEF_END@ is set, then the line is erased starting from and + * including the cursor position, and ending at the right hand + * side. + * + * If the @TTCF_BGER@ capability is set, then the erased + * positions take on the current background colour; otherwise, + * they have the default background colour. + */ +extern int tty_erase(struct tty */*tty*/, unsigned /*f*/); extern int tty_eraseg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, unsigned /*f*/); -extern int tty_erase(struct tty */*tty*/, unsigned /*f*/); +/* --- @tty_erch@, @tty_erchg@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @unsigned n@ = number of characters to erase + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Erase a number of characters, starting from and including the + * current cursor position. + * + * If the @TTCF_BGER@ capability is set, then the erased + * positions take on the current background colour; otherwise, + * they have the default background colour. + */ +extern int tty_erch(struct tty */*tty*/, unsigned /*n*/); extern int tty_erchg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, unsigned /*n*/); -extern int tty_erch(struct tty */*tty*/, unsigned /*n*/); +/* --- @tty_ins@, @tty_insg@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @unsigned f@ = flags + * @unsigned n@ = number of items to insert + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Insert a number of blank characters or lines. + * + * If @TTIDF_LN@ is set, then insert @n@ blank lines above the + * current line. The cursor must be at the far left of the + * line. + * + * Otherwise, insert @n@ empty character spaces at the cursor + * position. + */ +extern int tty_ins(struct tty */*tty*/, unsigned /*f*/, unsigned /*n*/); extern int tty_insg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, unsigned /*f*/, unsigned /*n*/); -extern int tty_ins(struct tty */*tty*/, unsigned /*f*/, unsigned /*n*/); +/* --- @tty_inch@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @int ch@ = character to insert + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Insert a single character. + * + * If the @TTMF_INS@ mode is advertised, then insert mode must + * be set before calling this function. + */ +extern int tty_inch(struct tty */*tty*/, int /*ch*/); extern int tty_inchg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, int /*ch*/); -extern int tty_inch(struct tty */*tty*/, int /*ch*/); +/* --- @tty_del@, @tty_delg@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @unsigned f@ = flags + * @unsigned n@ = number of items to delete + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Delete a number of characters or lines. + * + * If @TTIDF_LN@ is set, then delete @n@ blank lines, starting + * with the current line line. The cursor must be at the far + * left of the line. + * + * Otherwise, delete @n@ characters at the cursor position. + */ +extern int tty_del(struct tty */*tty*/, unsigned /*f*/, unsigned /*n*/); extern int tty_delg(struct tty */*tty*/, const struct gprintf_ops */*gops*/, void */*go*/, unsigned /*f*/, unsigned /*n*/); -extern int tty_del(struct tty */*tty*/, unsigned /*f*/, unsigned /*n*/); +/* --- @tty_restore@, @tty_restoreg@ --- * + * + * Arguments: @struct tty *tty@ = control block pointer + * @const struct gprintf_ops *gops, void *go@ = output + * destination + * @const struct tty_state *st@ = state to restore + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Restore the terminal modes and attributes to match a + * state previously captured by copying @tty->st@. + */ + +extern int tty_restore(struct tty */*tty*/, const struct tty_state */*st*/); +extern int tty_restoreg(struct tty */*tty*/, + const struct gprintf_ops */*gops*/, void */*go*/, + const struct tty_state */*st*/); /*----- That's all, folks -------------------------------------------------*/ diff --git a/ui/ttycolour.c b/ui/ttycolour.c index d2658e7..3deaec8 100644 --- a/ui/ttycolour.c +++ b/ui/ttycolour.c @@ -44,8 +44,8 @@ * Returns: Nonzero if the variable is set to a non-empty value, * otherwise zero. * - * Use: This is the recommended way to check the `NO_COLOR', - * `CLICOLOR' and `CLICOLOR_FORCE' variables. + * Use: This is the recommended way to check the %|NO_COLOR|%, + * `%|CLICOLOR|% and %|CLICOLOR_FORCE|% variables. */ static int env_setting_p(const char *var) @@ -69,19 +69,19 @@ static int env_setting_p(const char *var) * this function is to abide by common conventions and to be * convenient for users, these details may change in future.) * - * * If the `NO_COLOR' environment variable is non-empty, then + * * If the %|NO_COLOR|% environment variable is non-empty, * colour is disabled (%%\url{https://no-color.org/}%%). * - * * If the `TERM' variable is set to `dumb', then colour is - * disabled (Emacs). + * * If the %|TERM|% variable is set to %|dumb|%, then colour + * is disabled (Emacs). * - * * If the `FORCE_COLOR' environment variable is non-empty, + * * If the %|FORCE_COLOR|% environment variable is non-empty, * then colour is enabled, unless the value is 0, in which * case colour is disabled (apparently from the Node * community, %%\url{%%https://force-color.org/}%% and * %%\url{https://nodejs.org/api/tty.html#writestreamgetcolordepthenv}%%). * - * * If the `CLICOLOR_FORCE' environment variable is + * * If the %|CLICOLOR_FORCE|% environment variable is * non-empty, then colour is enabled (apparently from * Mac OS, (%%\url{http://bixense.com/clicolors/}%%). * @@ -89,8 +89,8 @@ static int env_setting_p(const char *var) * * * If the @TCEF_DFLT@ flag is set, then colour is enabled. * - * * If the `CLICOLOR' environment variable is non-empty, then - * colour is enabled (again, apparently from Mac OS, + * * If the %|CLICOLOR|% environment variable is non-empty, + * then colour is enabled (again, apparently from Mac OS, * (%%\url{http://bixense.com/clicolors/}%%). * * * Otherwise, colour is disabled. diff --git a/ui/ttycolour.h b/ui/ttycolour.h index 535248f..0d946ae 100644 --- a/ui/ttycolour.h +++ b/ui/ttycolour.h @@ -121,19 +121,19 @@ struct ttycolour_style { * this function is to abide by common conventions and to be * convenient for users, these details may change in future.) * - * * If the `NO_COLOR' environment variable is non-empty, then + * * If the %|NO_COLOR|% environment variable is non-empty, * colour is disabled (%%\url{https://no-color.org/}%%). * - * * If the `TERM' variable is set to `dumb', then colour is - * disabled (Emacs). + * * If the %|TERM|% variable is set to %|dumb|%, then colour + * is disabled (Emacs). * - * * If the `FORCE_COLOR' environment variable is non-empty, + * * If the %|FORCE_COLOR|% environment variable is non-empty, * then colour is enabled, unless the value is 0, in which * case colour is disabled (apparently from the Node * community, %%\url{%%https://force-color.org/}%% and * %%\url{https://nodejs.org/api/tty.html#writestreamgetcolordepthenv}%%). * - * * If the `CLICOLOR_FORCE' environment variable is + * * If the %|CLICOLOR_FORCE|% environment variable is * non-empty, then colour is enabled (apparently from * Mac OS, (%%\url{http://bixense.com/clicolors/}%%). * @@ -141,8 +141,8 @@ struct ttycolour_style { * * * If the @TCEF_DFLT@ flag is set, then colour is enabled. * - * * If the `CLICOLOR' environment variable is non-empty, then - * colour is enabled (again, apparently from Mac OS, + * * If the %|CLICOLOR|% environment variable is non-empty, + * then colour is enabled (again, apparently from Mac OS, * (%%\url{http://bixense.com/clicolors/}%%). * * * Otherwise, colour is disabled. diff --git a/ui/ttyprogress.c b/ui/ttyprogress.c index dc023d5..768e276 100644 --- a/ui/ttyprogress.c +++ b/ui/ttyprogress.c @@ -433,8 +433,7 @@ int ttyprogress_update(struct ttyprogress *progress) struct ttyprogress_render render; struct ttyprogress_item *item; struct tty *tty = progress->tty; - struct tty_attr save; - uint32 modes; + struct tty_state save; unsigned f = 0; #define f_any 1u @@ -442,8 +441,7 @@ int ttyprogress_update(struct ttyprogress *progress) setup_render_state(progress, &render); clear_progress(progress, 0); - modes = tty->st.modes; tty_setmodes(tty, TTMF_AUTOM, 0); - save = tty->st.attr; + save = tty->st; for (item = progress->items; item; item = item->next) { if (f&f_any) tty_move(tty, TTOF_YCUR | TTOF_XHOME, 1, 0); @@ -452,7 +450,7 @@ int ttyprogress_update(struct ttyprogress *progress) item->render(item, &render); progress->last_lines++; f |= f_any; if (progress->last_lines > tty->ht) break; } - tty_setmodes(tty, MASK32, modes); tty_setattr(tty, &save); + tty_restore(tty, &save); fflush(tty->fpout); return (0);