chiark / gitweb /
@@@ tty cleanup
authorMark Wooding <mdw@distorted.org.uk>
Fri, 25 Apr 2025 01:19:44 +0000 (02:19 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 25 Apr 2025 01:53:22 +0000 (02:53 +0100)
ui/tty.c
ui/tty.h
ui/ttycolour.c
ui/ttycolour.h
ui/ttyprogress.c

index f3f526fa0d0de15dd12e38f4fa9e778b74274d6b..001878c27a4aec5ab777c8e3c49b64fff629c233 100644 (file)
--- a/ui/tty.c
+++ b/ui/tty.c
 #include "str.h"
 #include "tty.h"
 
 #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 ------------------------------------------*/
 
 /*----- 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
 /* --- @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[] = {
 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;
 
   struct termios c;
   speed_t code;
 
+  /* Save the output stream. */
   tty->fpout = fp;
   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 {
   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);
   }
 
     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; } }
 }
 
   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;
 {
   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':
     case '0':
-      caps &= TTACF_CSPCMASK | TTACF_FG | TTACF_BG;
+      mask = 0;
       break;
     case '1':
       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':
       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':
       break;
     case '3':
-      caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG |
+      mask = TTACF_FG | TTACF_BG |
             TTACF_1BPC | TTACF_1BPCBR | TTACF_8BPC;
       break;
             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;
 }
 
   *caps_inout = caps;
 }
 
@@ -594,7 +723,7 @@ inval:
 #undef D
 }
 
 #undef D
 }
 
-/* --- @tty_clampattr@ --- *
+/* --- @clamp_attr@ --- *
  *
  * Arguments:  @struct tty_attr *a_out@ = selected attributes
  *             @const struct tty_attr *a@ = requested attributes
  *
  * 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.
  */
 
  *             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;
 
 {
   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;
 }
 
   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) ||                                           \
 
 #if defined(HAVE_TERMINFO) ||                                          \
     defined(HAVE_TERMCAP) ||                                           \
@@ -679,27 +839,142 @@ int tty_resized(struct tty *tty)
 
 #if defined(HAVE_TERMINFO) || defined(HAVE_TERMCAP)
 
 
 #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; }
 
 
 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)
 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
 
 #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)                                          \
 #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)
 
   _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)
 
   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
 #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)
 #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 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 {
 struct tty_capopslots {
+
+  /* Retrieving capabilities. */
   int (*boolcap)(struct tty */*tty*/,
                 int /*uix*/, const char */*info*/, const char */*cap*/);
   int (*intcap)(struct tty */*tty*/,
   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*/);
   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*/,
   int (*put1i)(struct tty */*tty*/,
-              const struct gprintf_ops */*gops*/, void */*go*/,
               unsigned /*npad*/, const char */*cap*/, int /*i0*/);
   int (*put2i)(struct tty */*tty*/,
               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*/);
 };
               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; };
 
 #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;
 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; };
 
   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;
 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;
       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; }
 }
 
     { 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_)                                              \
 #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)                                         \
 #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)                                     \
 #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)
 
 
 #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,
 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;
                          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:
     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
        * 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);
 }
 
   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;
   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. */
   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,
 
   /* 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.
    */
    * 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)
 
   /* 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: */
       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)
 
   /* 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;
       /* 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) {
 
   /* 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);
   }
     else PUT0(0, ritm);
   }
-  if (diff&aa.f&TTAF_INVV) PUT0(0, rev);
+  if (diff&a->f&TTAF_INVV) PUT0(0, rev);
 
   /* Colours. */
 
   /* 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)) |
     /* 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:
 
   /* 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,
 }
 
 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;
 
   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);
   /* 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:
   /* Done. */
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   t->tty.st.modes = modes; return (rc);
 }
 
 #define CIF_PADMUL 1u
   t->tty.st.modes = modes; return (rc);
 }
 
 #define CIF_PADMUL 1u
-
 static int caps_iterate(struct tty_caps *t,
 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)
 {
                        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,
 }
 
 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)
                              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; }
   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,
 }
 
 static int caps_move(struct tty *tty,
@@ -1148,6 +1465,8 @@ static int caps_move(struct tty *tty,
   struct tty_attr a;
   int rc;
 
   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;
   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);
          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);
                                   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:
       } 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));
                               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;
                               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 {
       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);
                                 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);
        }
       }
       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;
                               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.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:
 
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
   return (rc);
 }
 
@@ -1233,8 +1553,9 @@ static int caps_repeat(struct tty *tty,
   unsigned wd, nn;
   int rc;
 
   unsigned wd, nn;
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
   if (!t->cap.rep)
   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) {
   else {
     wd = t->tty.wd;
     while (n) {
@@ -1245,6 +1566,7 @@ static int caps_repeat(struct tty *tty,
   }
   rc = 0;
 end:
   }
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
   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;
 
   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:
   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:
   }
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
   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;
 
   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 (n) PUT1I(1, ech, n);
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
   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;
                    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;
 
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
   if (f&TTIDF_LN)
   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
   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:
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
   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;
 
   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);
   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 (t->cap.ip) PUT0(1, ip);
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
   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;
                    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;
 
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
   if (n) {
     if (f&TTIDF_LN)
   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
     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:
   }
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
   return (rc);
 }
 
-#undef CHECK
 #undef PUT0V
 #undef PUT1IV
 #undef PUT2IV
 #undef PUT0V
 #undef PUT1IV
 #undef PUT2IV
@@ -1361,8 +1690,7 @@ end:
 #define TTY_CAPOPS                                                     \
   caps_setattr, caps_setmodes,                                         \
   caps_move, caps_repeat,                                              \
 #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
 
 
 #endif
 
@@ -1406,36 +1734,30 @@ static const char *termcap_strcap(struct tty *tty,
 }
 
 static int termcap_put0(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);
                        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,
   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);
                         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,
   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);
 {
   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,
   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 }
 } };
 
     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;
 
   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;
   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,
 }
 
 static int terminfo_put0(struct tty *tty,
-                        const struct gprintf_ops *gops, void *go,
                         unsigned npad, const char *cap)
 {
   if (!cap) return (-1);
                         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,
   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);
                          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,
   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);
 {
   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,
   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 }
 } };
 
     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;
 
   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;
   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];
 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; };
 };
 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;
 
 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)
 {
 }
 
 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;
   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
 
 
   /* 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->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 {
       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
 }
 
 #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,
 }
 
 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 tty_unibilium *t = (struct tty_unibilium *)tty;
-  struct termunibi_outctx out;
   unibi_var_t arg[9];
 
   if (!cap) return (-1);
   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,
   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,
 }
 
 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;
                           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);
   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,
   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,
 }
 
 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;
                           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);
   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,
   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)
 }
 
 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,
 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 }
 } };
 
     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;
   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;
   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) { ; }
 
 
 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 {                                                      \
 #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;
                        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;
 
   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;
     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 (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;
   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;
   }
     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 {                                           \
 #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;                        \
     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[");
 
 
   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)
 
   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;
       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)
     }
 
   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;
       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)
     }
 
   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)
   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)
   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,
     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,
     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:
 
   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));
     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)
     else if (y > 1) CHECK(gprintf(gops, go, "\33[%dB", y));
     if (!(orig&TTOF_XCUR)) {
       if (!x)
@@ -2030,18 +2376,6 @@ end:
   return (rc);
 }
 
   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)
 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,
 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)
 };
 
 static struct tty *ansi_init(FILE *fp)
@@ -2390,7 +2723,7 @@ static struct tty *ansi_init(FILE *fp)
     tf |= tm->tf&~tfset;
   }
 
     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);
   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;
     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 -----------------------------------------------*/
 
 /*----- 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)
 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)
 {
 
 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));
   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);
 }
 
       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)
 
 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)
 }
 
 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)
 
 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)
 
 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)
 
 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)
 
 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)
 
 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)
 
 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 -------------------------------------------------*/
 
 /*----- That's all, folks -------------------------------------------------*/
index e026c15a18de847d565e4512ba1ff4c85dd5d0d9..57ed4890bfbf03cef63110c44a13ab561c5b8338 100644 (file)
--- 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 */
 /* 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 */
 #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 {
 
 /* 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:    ---
  *
  *
  * 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*/);
 
 
 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_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_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_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_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_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_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_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_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_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_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 -------------------------------------------------*/
 
 
 /*----- That's all, folks -------------------------------------------------*/
 
index d2658e70cbadcb29f0e7cffd3142063ac0205671..3deaec86a1f08c570d9fe6105d850d95b820b942 100644 (file)
@@ -44,8 +44,8 @@
  * Returns:    Nonzero if the variable is set to a non-empty value,
  *             otherwise zero.
  *
  * 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)
  */
 
 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.)
  *
  *             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/}%%).
  *
  *                 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}%%).
  *
  *                 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/}%%).
  *
  *                 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 @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.
  *                 (%%\url{http://bixense.com/clicolors/}%%).
  *
  *               * Otherwise, colour is disabled.
index 535248f560a28f52d00d856cd917e04227b691b4..0d946ae378429879534198bea08db8915620274a 100644 (file)
@@ -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.)
  *
  *             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/}%%).
  *
  *                 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}%%).
  *
  *                 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/}%%).
  *
  *                 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 @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.
  *                 (%%\url{http://bixense.com/clicolors/}%%).
  *
  *               * Otherwise, colour is disabled.
index dc023d5b34260c3ad1a41adb4041834e1edaf2dc..768e2768f8afc1070bdba2b0da07d376f5c68217 100644 (file)
@@ -433,8 +433,7 @@ int ttyprogress_update(struct ttyprogress *progress)
   struct ttyprogress_render render;
   struct ttyprogress_item *item;
   struct tty *tty = progress->tty;
   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
 
   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);
 
   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);
 
   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;
   }
     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);
 
   fflush(tty->fpout);
   return (0);