chiark / gitweb /
@@@ tty cleanup
[mLib] / ui / tty.c
index f3f526fa0d0de15dd12e38f4fa9e778b74274d6b..001878c27a4aec5ab777c8e3c49b64fff629c233 100644 (file)
--- a/ui/tty.c
+++ b/ui/tty.c
 #include "str.h"
 #include "tty.h"
 
+/*----- Operations table --------------------------------------------------*/
+
+/* Incorporate the published control-block structure into our more elaborate
+ * object model.
+ */
+#define TTY_BASEPFX struct tty tty
+#define TTY_BASEUSFX struct tty tty
+
+struct tty_ops {
+  void (*release)(struct tty */*tty*/);
+       /* Free any resources held by the backend. */
+
+  /* The following operations handle the correspondingly named interface
+   * functions.
+   */
+  int (*setattr)(struct tty */*tty*/,
+                const struct gprintf_ops */*gops*/, void */*go*/,
+                const struct tty_attr */*a*/);
+  int (*setmodes)(struct tty */*tty*/,
+                 const struct gprintf_ops */*gops*/, void */*go*/,
+                 uint32 /*modes_bic*/, uint32 /*modes_xor*/);
+  int (*move)(struct tty */*tty*/,
+             const struct gprintf_ops */*gops*/, void */*go*/,
+             unsigned /*orig*/, int /*y*/, int /*x*/);
+  int (*repeat)(struct tty */*tty*/,
+               const struct gprintf_ops */*gops*/, void */*go*/,
+               int /*ch*/, unsigned /*n*/);
+  int (*erase)(struct tty */*tty*/,
+              const struct gprintf_ops */*gops*/, void */*go*/,
+              unsigned /*f*/);
+  int (*erch)(struct tty */*tty*/,
+             const struct gprintf_ops */*gops*/, void */*go*/,
+             unsigned /*n*/);
+  int (*ins)(struct tty */*tty*/,
+            const struct gprintf_ops */*gops*/, void */*go*/,
+            unsigned /*f*/, unsigned /*n*/);
+  int (*inch)(struct tty */*tty*/,
+             const struct gprintf_ops */*gops*/, void */*go*/,
+             int /*ch*/);
+  int (*del)(struct tty */*tty*/,
+            const struct gprintf_ops */*gops*/, void */*go*/,
+            unsigned /*f*/, unsigned /*n*/);
+};
+#define TTY_BASEOPSPFX struct tty_ops tty
+#define TTY_BASEOPSUXFX struct tty_ops tty
+
 /*----- Common support machinery ------------------------------------------*/
 
+/* --- @CHECK@ --- *
+ *
+ * Arguments@  @expr@ = expression to evaluate
+ *
+ * Use:                Evaluate @expr@.  If the result is (strictly) negative, then
+ *             set @rc = -1@ and transfer control to the label @end@.
+ */
+
+#define CHECK(expr) do { if ((expr) < 0) { rc = -1; goto end; } } while (0)
+
 /* --- @debug@ --- *
  *
  * Arguments:   @const char *fmt@ = format control string
@@ -91,6 +147,21 @@ static PRINTF_LIKE(1, 2) void debug(const char *fmt, ...)
   }
 }
 
+/* --- @common_init@ --- *
+ *
+ * Arguments:  @struct tty *tty@ = pointer to terminal control block
+ *             @FILE *fp@ = output file stream
+ *
+ * Returns:    ---
+ *
+ * Use:                Perform general initialization on the terminal control
+ *             block.
+ *
+ *             Specifically, this fills in the @fpout@, @baud@, @ht@, and
+ *             @wd@ slots.  The width and height come from the kernel, or,
+ *             failing that, the environment.
+ */
+
 static void common_init(struct tty *tty, FILE *fp)
 {
   static const struct baudtab { speed_t code; unsigned baud; } baudtab[] = {
@@ -216,7 +287,13 @@ static void common_init(struct tty *tty, FILE *fp)
   struct termios c;
   speed_t code;
 
+  /* Save the output stream. */
   tty->fpout = fp;
+
+  /* Determine the output baud rate.  Unhelpfully, the kernel provides a
+   * weird code, so we have to convert it into an actual rate in bits per
+   * second.
+   */
   if (!fp || tcgetattr(fileno(fp), &c))
     tty->baud = 0;
   else {
@@ -228,35 +305,87 @@ static void common_init(struct tty *tty, FILE *fp)
     tty_resized(tty);
   }
 
+  /* If the kernel didn't tell us the terminal dimensions, try to read them
+   * from the environment.
+   */
   if (!tty->wd)
     { p = getenv("COLUMNS"); if (p) { n = atoi(p); if (n) tty->wd = n; } }
   if (!tty->ht)
     { p = getenv("LINES");   if (p) { n = atoi(p); if (n) tty->ht = n; } }
 }
 
-static void env_colour_caps(unsigned *caps_inout)
+/* --- @env_colour_caps@ --- *
+ *
+ * Arguments:  @unsigned *caps_inout@ = attribute capabilities to update
+ *             @unsigned f@ = flags
+ *
+ * Returns:    ---
+ *
+ * Use:                Check the %|FORCE_COLOR|% environment variable and update the
+ *             capabilities as required.
+ *
+ *             The %|FORCE_COLOR|% variable originates with the Node
+ *             community, with two objectives: (a) to convey policy
+ *             regarding whether to produce coloured output, and (b) to
+ *             describe the colour capabilities of the terminal, because the
+ *             traditional mechanisms are deemed inadequate.
+ *
+ *             The following values have defined meanings.
+ *
+ *               * Unset or empty: no effect.
+ *
+ *               * %|0|%: monochrome; don't produce colour.
+ *
+ *               * %|1|%: 16 colours; 1-bit-per-channel colours are
+ *                 available, with an additional common brightness bit.
+ *
+ *               * %|2|%: 256 colours; the `xterm' 256-bit palette is
+ *                 available, consisting of the 1-bit-per-channel colours
+ *                 with common brightness bit, a 6 × 6 × 6 colour cube, and
+ *                 a 24-level greyscale ramp.
+ *
+ *               * %|3|%: full 24-bit colour.
+ *
+ *               * Anything else: a request to use colour if available.
+ *                 This is ignored here, in the expectation that it will be
+ *                 given effect elsewhere, e.g., by @ttycolour_enablep@.
+ *
+ *             If @ECCF_SET@ is set, then set or clear capabilities as
+ *             required.  Otherwise, clear capability bits which are denied
+ *             by the variable setting, but no bits will be set.  (This
+ *             latter is necessary for backends which use terminal
+ *             databases, since they can't be expected to make up the
+ *             necessary control sequences for themselves.)
+ */
+
+#define ECCF_SET 1u
+static void env_colour_caps(unsigned *caps_inout, unsigned f)
 {
   const char *p;
-  unsigned caps = *caps_inout;
+  unsigned caps = *caps_inout, mask;
 
-  p = getenv("FORCE_COLOR");
-  if (p) switch (*p) {
+  p = getenv("FORCE_COLOR"); if (!p) return;
+  switch (*p) {
     case '0':
-      caps &= TTACF_CSPCMASK | TTACF_FG | TTACF_BG;
+      mask = 0;
       break;
     case '1':
-      caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG |
-            TTACF_1BPC | TTACF_1BPCBR;
+      mask = TTACF_FG | TTACF_BG | TTACF_1BPC | TTACF_1BPCBR;
       break;
     case '2':
-      caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG |
-            TTACF_1BPC | TTACF_1BPCBR | TTACF_6LPC | TTACF_8LGS;
+      mask = TTACF_FG | TTACF_BG |
+            TTACF_1BPC | TTACF_1BPCBR | TTACF_6LPC | TTACF_24LGS;
       break;
     case '3':
-      caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG |
+      mask = TTACF_FG | TTACF_BG |
             TTACF_1BPC | TTACF_1BPCBR | TTACF_8BPC;
       break;
+    default:
+      return;
   }
+  if (!(f&ECCF_SET)) caps &= mask;
+  else caps = (caps&~(TTACF_CSPCMASK | TTACF_FG | TTACF_BG)) | mask;
+
   *caps_inout = caps;
 }
 
@@ -594,7 +723,7 @@ inval:
 #undef D
 }
 
-/* --- @tty_clampattr@ --- *
+/* --- @clamp_attr@ --- *
  *
  * Arguments:  @struct tty_attr *a_out@ = selected attributes
  *             @const struct tty_attr *a@ = requested attributes
@@ -606,8 +735,8 @@ inval:
  *             which can be accommodated by the terminal.
  */
 
-void tty_clampattr(struct tty_attr *a_out,
-                  const struct tty_attr *a, uint32 acaps)
+static void clamp_attr(struct tty_attr *a_out,
+                      const struct tty_attr *a, uint32 acaps)
 {
   uint32 ff = 0, f = a ? a->f : 0, t;
 
@@ -662,16 +791,47 @@ void tty_clampattr(struct tty_attr *a_out,
   a_out->f = ff; a_out->_res0 = 0;
 }
 
-int tty_resized(struct tty *tty)
+/* --- @stupid_repeat@ --- *
+ *
+ * Arguments:  @struct tty *tty@ = control block pointer
+ *             @const struct gprintf_ops *gops, void *go@ = output
+ *                     destination
+ *             @int ch@ = character to write
+ *             @unsigned n@ = number of copies
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Write @n@ copies of the character @ch@ to the terminal, the
+ *             hard way.  This function tries to be reasonably efficient, by
+ *             transmitting buffers rather than exercising the output
+ *             machinery for each individual character.
+ */
+
+static int stupid_repeat(struct tty *tty,
+                        const struct gprintf_ops *gops, void *go,
+                        int ch, unsigned n)
 {
-  struct winsize ws;
+  char buf[4096];
+  unsigned nn;
+  int rc;
 
-  if (!tty->fpout) { errno = ENOTTY; return (-1); }
-  if (ioctl(fileno(tty->fpout), TIOCGWINSZ, &ws)) return (-1);
-  tty->wd = ws.ws_col; tty->ht = ws.ws_row; return (0);
+  if (n < sizeof(buf))
+    { memset(buf, ch, n); CHECK(gops->putm(go, buf, n)); }
+  else {
+    memset(buf, ch, sizeof(buf));
+    nn = sizeof(buf);
+    for (;;) {
+      CHECK(gops->putm(go, buf, nn));
+      n -= nn; if (!n) break;
+      if (n < nn) nn = n;
+    }
+  }
+  rc = 0;
+end:
+  return (rc);
 }
 
-/*----- Common machinery for `termcap' and `terminfo' ---------------------*/
+/*----- Common machinery for %|termcap|% and %|terminfo|% -----------------*/
 
 #if defined(HAVE_TERMINFO) ||                                          \
     defined(HAVE_TERMCAP) ||                                           \
@@ -679,27 +839,142 @@ int tty_resized(struct tty *tty)
 
 #if defined(HAVE_TERMINFO) || defined(HAVE_TERMCAP)
 
-static const struct gprintf_ops *global_gops;
-static void *global_gout;
-static struct tty *global_lock = 0;
+/* Global state.
+ *
+ * The `termcap' and `terminfo' functions call a user-provided function to
+ * actually send control codes to the terminal.  The bad news is that
+ * `termcap' doesn't provide any way to pass information to the output
+ * function beyond the character to be sent, and `terminfo' doesn't fix this
+ * mistake.  So we must save the necessary context as global variables.  For
+ * good measure, at least some implementations ignore errors from the output
+ * function, so we must keep track of them ourselves.  More global variables.
+ *
+ * It's worse.  Both libraries maintain significant global state of their
+ * own.  And, at least with the `ncurses' implementation, the two share the
+ * same global state.  The only thing to do is maintain a big interlock to
+ * make sure that only one is active at a time.
+ */
+static const struct gprintf_ops *global_gops; /* output operations ... */
+static void *global_gout;              /* and context, for @caps_putch */
+static char global_buf[4096];          /* a big output buffer */
+static size_t global_len;              /* length of buffer used */
+static int global_err;                 /* error latch, zero if all ok */
+static struct tty *global_lock = 0;    /* interlock for global state */
+
+/* --- @caps_claim@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Zero on success, %$-1$% if already claimed.
+ *
+ * Use:                Return %$-1$% if the interlock is already held.  This is a
+ *             function to call near the beginning of initializing a new
+ *             control block, before the common global state gets
+ *             clobbered.  If initialization is successful, the caller is
+ *             expected to actually store the control block pointer in
+ *             @global_lock@ themselves.
+ */
+
+static int caps_claim(void)
+{
+  if (global_lock)
+    { debug("termcap/terminfo terminal already open"); return (-1); }
+  else
+    return (0);
+}
+
+/* --- @caps_claim@, @caps_release@ --- *
+ *
+ * Arguments:  @struct tty *tty@ = control block pointer for current lock
+ *                     holder
+ *
+ * Returns:    ---
+ *
+ * Use:                Release the lock.
+ */
 
 static void caps_release(struct tty *tty)
   { assert(global_lock == tty); global_lock = 0; }
 
+/* --- @caps_putch@ --- *
+ *
+ * Arguments:  @int ch@ = character to write
+ *
+ * Returns:    Nonnegative on success, negative on failure.  (But @tputs@
+ *             ignores this.)
+ *
+ * Use:                Output the character @ch@.
+ */
+
 static int caps_putch(int ch)
-  { return (global_gops->putch(global_gout, ch)); }
+{
+  if (global_len >= sizeof(global_buf)) {
+    if (global_gops->putm(global_gout, global_buf, global_len))
+      global_err = -1;
+    global_len = 0;
+  }
+  global_buf[global_len++] = ch;
+  return (0);
+}
 
-#endif
+/* --- @caps_prepout@ --- *
+ *
+ * Arguments:  @struct tty *tty@ = control block pointer (ignored)
+ *             @const struct gprintf_ops *gops, void *go@ = output
+ *                     destination
+ *
+ * Returns:    ---
+ *
+ * Use:                Prepare output to the given destination.
+ */
+
+static void caps_prepout(struct tty *tty,
+                        const struct gprintf_ops *gops, void *go)
+  { assert(!global_len); global_gops = gops; global_gout = go; }
+
+/* --- @caps_flush@ --- *
+ *
+ * Arguments:  @struct tty *tty@ = control block pointer (ignored)
+ *
+ * Returns:    Zero for success, %$-1$% if error pending.
+ *
+ * Use:                Flush the output buffer to the backend.  If an error is
+ *             pending, clear it and return failure.
+ */
+
+static int caps_flush(struct tty *tty)
+{
+  int rc = global_err;
+
+  if (global_len) {
+    if (global_gops->putm(global_gout, global_buf, global_len)) rc = -1;
+    global_len = 0;
+  }
+  global_err = 0; return (rc);
+}
 
-#ifdef HAVE_UNIBILIUM
-#  define UNIBI_(x) unibi##x
-#else
-#  define UNIBI_(x) 0
 #endif
 
+/* The list of interesting capabilities.
+ *
+ * We never actually need all of these: some are only needed if others are
+ * unavailable.  But the list isn't too huge, so we'll live with it.
+ *
+ * The main thing is that each capability has three different names: the
+ * `full' name (corresponding to a `terminfo' variable name), the `terminfo'
+ * capability name, as used in terminal descriptions, and the two-character
+ * `termcap' name.  Unibilium uses the long names, but to reduce typing, the
+ * `unibi_' prefix is omitted here.  (Annoyingly, in `ncurses', at least, the
+ * `variable' names are `secretly' macros referencing a current state, and
+ * premature expansion causes misery, so I've left the leading underscores in
+ * place as a countermeasure.)  Internally, we use the short `terminfo'
+ * names, since they generally express the most useful information in the
+ * smallest space.
+ */
+
 #define BASICCAPS(_bool, _int, _str)                                   \
   _str(_repeat_char, rep, rp)                                          \
-  _int(_padding_baud_rate, pb, pb) _str(_pad_char, pad, pc)            \
+  _str(_pad_char, pad, pc) _int(_padding_baud_rate, pb, pb)            \
   _bool(_no_pad_char, npc, NP) _bool(_xon_xoff, xon, xo)               \
   _bool(_move_insert_mode, mir, mi) _bool(_move_standout_mode, msgr, ms)
 
@@ -759,8 +1034,20 @@ static int caps_putch(int ch)
   ERASECAPS(_bool, _int, _str)                                         \
   INSDELCAPS(_bool, _int, _str)
 
+#ifdef HAVE_UNIBILIUM
+#  define UNIBI_(x) unibi##x
+#else
+#  define UNIBI_(x) 0
+#endif
+
 #define CAPREF(var, info, cap) UNIBI_(var), #info, #cap
+  /* Expand a capability triple into a group of three usable C arguments.  If
+   * Unibilium isn't available, then we can use nonsense for its cap index.
+   */
 
+/* Some other capabilities which we want to refer to during initialization,
+ * but don't need to keep around.
+ */
 #define CAP_XMC CAPREF(_magic_cookie_glitch, xmc, sg)
 #define CAP_BCE CAPREF(_back_color_erase, bce, ut)
 #define CAP_XHPA CAPREF(_row_addr_glitch, xvpa, YD)
@@ -770,12 +1057,12 @@ static int caps_putch(int ch)
 #define CAP_HT CAPREF(_lines, lines, li)
 #define CAP_WD CAPREF(_columns, cols, co)
 
-#define TTY_BASEOPSPFX struct tty_ops tty
-#define TTY_BASEOPSUXFX struct tty_ops tty
-#define TTY_BASEPFX struct tty tty
-#define TTY_BASEUSFX struct tty tty
-
+/* Additional operations required of terminal backends which make use of the
+ * common capability machinery.
+ */
 struct tty_capopslots {
+
+  /* Retrieving capabilities. */
   int (*boolcap)(struct tty */*tty*/,
                 int /*uix*/, const char */*info*/, const char */*cap*/);
   int (*intcap)(struct tty */*tty*/,
@@ -783,14 +1070,17 @@ struct tty_capopslots {
   const char *(*strcap)(struct tty */*tty*/,
                        int /*uix*/, const char */*info*/,
                        const char */*cap*/);
-  int (*put0)(struct tty */*tty*/,
-             const struct gprintf_ops */*gops*/, void */*go*/,
-             unsigned /*npad*/, const char */*cap*/);
+
+  /* Preparing and completing output. */
+  void (*prepout)(struct tty */*tty*/,
+                 const struct gprintf_ops */*gops*/, void */*go*/);
+  int (*flush)(struct tty */*tty*/);
+
+  /* Writing capabilities with various kinds of arguments. */
+  int (*put0)(struct tty */*tty*/, unsigned /*npad*/, const char */*cap*/);
   int (*put1i)(struct tty */*tty*/,
-              const struct gprintf_ops */*gops*/, void */*go*/,
               unsigned /*npad*/, const char */*cap*/, int /*i0*/);
   int (*put2i)(struct tty */*tty*/,
-              const struct gprintf_ops */*gops*/, void */*go*/,
               unsigned /*npad*/,
               const char */*cap*/, int /*i0*/, int /*i1*/);
 };
@@ -799,6 +1089,7 @@ struct tty_capops { TTY_CAPOPSPFX; };
 #define TTY_CAPOPSUSFX struct tty_capops cap; TTY_BASEOPSUXFX
 union tty_capopsu { TTY_CAPOPSUSFX; };
 
+/* An extension of the control block to track the above capabilities. */
 struct tty_capslots {
 #define DEF_BOOLCAP(uix, info, cap) unsigned info : 1;
 #define DEF_INTCAP(uix, info, cap) int info;
@@ -817,6 +1108,18 @@ struct tty_caps { TTY_CAPSPFX; };
   struct tty tty
 union tty_capsu { TTY_CAPSUSFX; };
 
+/* ---- @init_caps@ --- *
+ *
+ * Arguments:  @struct tty *tty@ = control block pointer
+ *
+ * Returns:    ---
+ *
+ * Use:                Populate the capabilities in the terminal control block, and
+ *             advertise the results to the public part.  Set @ht@ and @wd@
+ *             from the terminal description if they've not been set
+ *             already.
+ */
+
 static void init_caps(struct tty_caps *t)
 {
   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
@@ -873,7 +1176,7 @@ static void init_caps(struct tty_caps *t)
       else if (t->cap.colors >= 16)
        t->tty.acaps |= TTACF_1BPCBR;
       if (ops->cap.boolcap(&t->tty, CAP_BCE)) t->tty.ocaps |= TTCF_BGER;
-      env_colour_caps(&t->tty.acaps);
+      env_colour_caps(&t->tty.acaps, 0);
     }
   }
 
@@ -924,21 +1227,24 @@ static void init_caps(struct tty_caps *t)
     { ht = ops->cap.intcap(&t->tty, CAP_HT); if (ht > 0) t->tty.ht = ht; }
 }
 
-#define CHECK(expr) do { if ((expr) < 0) { rc = -1; goto end; } } while (0)
-
+/* Macros for formatting capabilities. */
 #define PUT0V(npad, cap_)                                              \
-  CHECK(ops->cap.put0(&t->tty, gops, go, (npad), (cap_)))
+  CHECK(ops->cap.put0(&t->tty, (npad), (cap_)))
 #define PUT1IV(npad, cap_, i0)                                         \
-  CHECK(ops->cap.put1i(&t->tty, gops, go, (npad), (cap_), (i0)))
+  CHECK(ops->cap.put1i(&t->tty, (npad), (cap_), (i0)))
 #define PUT2IV(npad, cap_, i0, i1)                                     \
-  CHECK(ops->cap.put2i(&t->tty, gops, go, (npad), (cap_), (i0), (i1)))
+  CHECK(ops->cap.put2i(&t->tty, (npad), (cap_), (i0), (i1)))
 
 #define PUT0(npad, name) PUT0V(npad, t->cap.name)
 #define PUT1I(npad, name, i0) PUT1IV(npad, t->cap.name, i0)
 #define PUT2I(npad, name, i0, i1) PUT2IV(npad, t->cap.name, i0, i1)
 
+/* --- @caps_setcolour@ --- *
+ *
+ * Arguments:  @struct tty_caps *t@ = extended control block pointer
+ *             
+ */
 static int caps_setcolour(struct tty_caps *t,
-                         const struct gprintf_ops *gops, void *go,
                          const char *cap, uint32 spc, uint32 clr)
 {
   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
@@ -951,7 +1257,7 @@ static int caps_setcolour(struct tty_caps *t,
     case TTCSPC_24LGS:                   PUT1IV(0, cap, clr + 232); break;
 
     case TTCSPC_8BPC:
-      /* There's an unfortunate ambiguity in the `setaf' conventions.  The
+      /* There's an unfortunate ambiguity in the %|setaf|% conventions.  The
        * first eight colours should be dark shades of blue, but in fact
        * they're interpreted as the one-bit-per-channel basic colours by
        * common `terminfo' settings.  Notice and kludge by adding a little
@@ -970,36 +1276,32 @@ end:
   return (rc);
 }
 
-static int caps_setattr(struct tty *tty,
-                       const struct gprintf_ops *gops, void *go,
-                       const struct tty_attr *a)
+static int caps_setattr_internal(struct tty_caps *t,
+                                const struct tty_attr *a)
 {
-  struct tty_caps *t = (struct tty_caps *)tty;
   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
-  struct tty_attr aa;
   uint32 diff;
   int rc;
 
   /* Work out what needs doing. */
-  tty_clampattr(&aa, a, t->tty.acaps);
-  diff = aa.f ^ t->tty.st.attr.f;
+  diff = a->f ^ t->tty.st.attr.f;
 
   /* Some terminals might not be able to clear individual attributes that
    * they can set, and some capabilities for turning attributes on don't even
    * have a corresponding attribute for turning them off again individually,
-   * so we have to use `sgr0' to start from scratch.  Of course, if we need
+   * so we have to use %|sgr0|% to start from scratch.  Of course, if we need
    * to do that, we need to restore the other active attributes, so we must
    * check up front.
    */
-  if (((diff&TTAF_LNMASK) && !(aa.f&TTAF_LNMASK) && !t->cap.rmul) ||
-      ((diff&TTAF_WTMASK) && !(aa.f&TTAF_WTMASK)) ||
-      ((diff&~aa.f&TTAF_ITAL) && !t->cap.ritm) ||
-      (diff&~aa.f&TTAF_INVV))
-    { PUT0(0, sgr0); diff = aa.f; }
+  if (((diff&TTAF_LNMASK) && !(a->f&TTAF_LNMASK) && !t->cap.rmul) ||
+      ((diff&TTAF_WTMASK) && !(a->f&TTAF_WTMASK)) ||
+      ((diff&~a->f&TTAF_ITAL) && !t->cap.ritm) ||
+      (diff&~a->f&TTAF_INVV))
+    { PUT0(0, sgr0); diff = a->f; }
 
   /* Line style. */
   if (diff&TTAF_LNMASK)
-    switch ((aa.f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
+    switch ((a->f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
       case TTLN_NONE: PUT0(0, rmul); break;
       case TTLN_ULINE: PUT0(0, smul); break;
       /* case TTLN_UULINE: */
@@ -1009,7 +1311,7 @@ static int caps_setattr(struct tty *tty,
 
   /* Text weight. */
   if (diff&TTAF_WTMASK)
-    switch ((aa.f&TTAF_WTMASK) >> TTAF_WTSHIFT) {
+    switch ((a->f&TTAF_WTMASK) >> TTAF_WTSHIFT) {
       /* case TTWT_MED: */
       case TTWT_BOLD: PUT0(0, bold); break;
       case TTWT_DIM: PUT0(0, dim); break;
@@ -1018,33 +1320,47 @@ static int caps_setattr(struct tty *tty,
 
   /* Other text effects. */
   if (diff&TTAF_ITAL) {
-    if (aa.f&TTAF_ITAL) PUT0(0, sitm);
+    if (a->f&TTAF_ITAL) PUT0(0, sitm);
     else PUT0(0, ritm);
   }
-  if (diff&aa.f&TTAF_INVV) PUT0(0, rev);
+  if (diff&a->f&TTAF_INVV) PUT0(0, rev);
 
   /* Colours. */
-  if (((diff&TTAF_FGSPCMASK) && !(aa.f&TTAF_FGSPCMASK)) ||
-      ((diff&TTAF_BGSPCMASK) && !(aa.f&TTAF_BGSPCMASK))) {
+  if (((diff&TTAF_FGSPCMASK) && !(a->f&TTAF_FGSPCMASK)) ||
+      ((diff&TTAF_BGSPCMASK) && !(a->f&TTAF_BGSPCMASK))) {
     /* There's no capability string for resetting just the foreground
      * or background colours to the defaults, so deal with that here.
      */
 
     PUT0(0, op);
     diff = (diff&~(TTAF_FGSPCMASK | TTAF_BGSPCMASK)) |
-          (aa.f&(TTAF_FGSPCMASK | TTAF_BGSPCMASK));
+          (a->f&(TTAF_FGSPCMASK | TTAF_BGSPCMASK));
   }
-  if ((diff&TTAF_FGSPCMASK) || aa.fg != t->tty.st.attr.fg)
-    CHECK(caps_setcolour(t, gops, go, t->cap.setaf,
-                        (aa.f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, aa.fg));
-  if ((diff&TTAF_BGSPCMASK) || aa.bg != t->tty.st.attr.bg)
-    CHECK(caps_setcolour(t, gops, go, t->cap.setab,
-                        (aa.f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, aa.bg));
+  if ((diff&TTAF_FGSPCMASK) || a->fg != t->tty.st.attr.fg)
+    CHECK(caps_setcolour(t, t->cap.setaf,
+                        (a->f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a->fg));
+  if ((diff&TTAF_BGSPCMASK) || a->bg != t->tty.st.attr.bg)
+    CHECK(caps_setcolour(t, t->cap.setab,
+                        (a->f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a->bg));
 
   /* All done. */
   rc = 0;
 end:
-  t->tty.st.attr = aa; return (rc);
+  t->tty.st.attr = *a; return (rc);
+}
+
+static int caps_setattr(struct tty *tty,
+                       const struct gprintf_ops *gops, void *go,
+                       const struct tty_attr *a)
+{
+  struct tty_caps *t = (struct tty_caps *)tty;
+  const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+  int rc;
+
+  ops->cap.prepout(&t->tty, gops, go);
+  rc = caps_setattr_internal(t, a);
+  if (ops->cap.flush(&t->tty)) rc = -1;
+  return (rc);
 }
 
 static int caps_setmodes(struct tty *tty,
@@ -1061,6 +1377,9 @@ static int caps_setmodes(struct tty *tty,
   modes = (t->tty.st.modes&~modes_bic) ^ modes_xor;
   diff = modes ^ t->tty.st.modes;
 
+  /* Prepare output. */
+  ops->cap.prepout(&t->tty, gops, go);
+
   /* Automatic margins. */
   if (diff&TTMF_AUTOM) {
     if (modes&TTMF_AUTOM) PUT0(0, smam);
@@ -1096,13 +1415,12 @@ static int caps_setmodes(struct tty *tty,
   /* Done. */
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   t->tty.st.modes = modes; return (rc);
 }
 
 #define CIF_PADMUL 1u
-
 static int caps_iterate(struct tty_caps *t,
-                       const struct gprintf_ops *gops, void *go,
                        const char *cap1, const char *capn,
                        unsigned f, unsigned npad, unsigned n)
 {
@@ -1126,7 +1444,6 @@ end:
 }
 
 static int caps_move_relative(struct tty_caps *t,
-                             const struct gprintf_ops *gops, void *go,
                              int delta,
                              const char *fw1, const char *fwn,
                              const char *rv1, const char *rvn)
@@ -1136,7 +1453,7 @@ static int caps_move_relative(struct tty_caps *t,
   if (!delta) return (0);
   else if (delta > 0) { mv1 = fw1; mvn = fwn; }
   else { mv1 = rv1; mvn = rvn; delta = - delta; }
-  return (caps_iterate(t, gops, go, mv1, mvn, 0, 0, delta));
+  return (caps_iterate(t, mv1, mvn, 0, 0, delta));
 }
 
 static int caps_move(struct tty *tty,
@@ -1148,6 +1465,8 @@ static int caps_move(struct tty *tty,
   struct tty_attr a;
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
+
   if (!t->cap.mir && (t->tty.st.modes&TTMF_INS)) PUT0(0, rmir);
 
   a = t->tty.st.attr;
@@ -1165,23 +1484,23 @@ static int caps_move(struct tty *tty,
          PUT1I(1, hpa, x);
        else {
          PUT0(1, cr);
-         CHECK(caps_move_relative(t, gops, go, x,
+         CHECK(caps_move_relative(t, x,
                                   t->cap.cuf1, t->cap.cuf,
                                   t->cap.cub1, t->cap.cub));
        }
       } else if (t->cap.home) {
        PUT0(1, home);
-       CHECK(caps_iterate(t, gops, go, t->cap.cud1, t->cap.cud, 0, 1, y));
-       CHECK(caps_iterate(t, gops, go, t->cap.cuf1, t->cap.cuf, 0, 1, x));
+       CHECK(caps_iterate(t, t->cap.cud1, t->cap.cud, 0, 1, y));
+       CHECK(caps_iterate(t, t->cap.cuf1, t->cap.cuf, 0, 1, x));
       } else
        { rc = -1; goto end; }
       break;
 
     case TTORG_CUR:
-      CHECK(caps_move_relative(t, gops, go, y,
+      CHECK(caps_move_relative(t, y,
                               t->cap.cud1, t->cap.cud,
                               t->cap.cuu1, t->cap.cuu));
-      CHECK(caps_move_relative(t, gops, go, x,
+      CHECK(caps_move_relative(t, x,
                               t->cap.cuf1, t->cap.cuf,
                               t->cap.cub1, t->cap.cub));
       break;
@@ -1190,21 +1509,21 @@ static int caps_move(struct tty *tty,
       if (x == 0 && y == 1)
        PUT0(1, nel);
       else {
-       CHECK(caps_move_relative(t, gops, go, y,
+       CHECK(caps_move_relative(t, y,
                                 t->cap.cud1, t->cap.cud,
                                 t->cap.cuu1, t->cap.cuu));
        if (t->cap.hpa && x)
          PUT1I(1, hpa, x);
        else {
          PUT0(1, cr);
-         CHECK(caps_iterate(t, gops, go, t->cap.cuf1, t->cap.cuf, 0, 1, x));
+         CHECK(caps_iterate(t, t->cap.cuf1, t->cap.cuf, 0, 1, x));
        }
       }
       break;
 
     case TTOF_XCUR | TTOF_YHOME:
       PUT1I(1, vpa, y);
-      CHECK(caps_move_relative(t, gops, go, x,
+      CHECK(caps_move_relative(t, x,
                               t->cap.cuf1, t->cap.cuf,
                               t->cap.cub1, t->cap.cub));
       break;
@@ -1216,11 +1535,12 @@ static int caps_move(struct tty *tty,
 
   if (!t->cap.mir && (t->tty.st.modes&TTMF_INS)) PUT0(0, smir);
 
-  if (!t->cap.msgr && t->tty.st.attr.f)
-    CHECK(caps_setattr(&t->tty, gops, go, &a));
+  if (!t->cap.msgr && a.f)
+    CHECK(caps_setattr_internal(t, &a));
 
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
@@ -1233,8 +1553,9 @@ static int caps_repeat(struct tty *tty,
   unsigned wd, nn;
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
   if (!t->cap.rep)
-    while (n--) CHECK(gops->putch(go, ch));
+    CHECK(stupid_repeat(tty, gops, go, ch, n));
   else {
     wd = t->tty.wd;
     while (n) {
@@ -1245,6 +1566,7 @@ static int caps_repeat(struct tty *tty,
   }
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
@@ -1256,6 +1578,7 @@ static int caps_erase(struct tty *tty,
   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
   if (f&TTEF_DSP)
     switch (f&(TTEF_BEGIN | TTEF_END)) {
       case 0:
@@ -1277,6 +1600,7 @@ static int caps_erase(struct tty *tty,
   }
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
@@ -1288,9 +1612,11 @@ static int caps_erch(struct tty *tty,
   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
   if (n) PUT1I(1, ech, n);
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
@@ -1299,16 +1625,17 @@ static int caps_ins(struct tty *tty,
                    unsigned f, unsigned n)
 {
   struct tty_caps *t = (struct tty_caps *)tty;
+  const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
   if (f&TTIDF_LN)
-    CHECK(caps_iterate(t, gops, go,
-                      t->cap.il1, t->cap.il, CIF_PADMUL, 1, n));
+    CHECK(caps_iterate(t, t->cap.il1, t->cap.il, CIF_PADMUL, 1, n));
   else
-    CHECK(caps_iterate(t, gops, go,
-                      t->cap.ich1, t->cap.ich, 0, 1, n));
+    CHECK(caps_iterate(t, t->cap.ich1, t->cap.ich, 0, 1, n));
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
@@ -1320,6 +1647,7 @@ static int caps_inch(struct tty *tty,
   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
   if (t->cap.smir ? !(t->tty.st.modes&TTMF_INS) : !t->cap.ich)
     { rc = -1; goto end; }
   if (t->cap.ich) PUT0(1, ich);
@@ -1327,6 +1655,7 @@ static int caps_inch(struct tty *tty,
   if (t->cap.ip) PUT0(1, ip);
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
@@ -1335,22 +1664,22 @@ static int caps_del(struct tty *tty,
                    unsigned f, unsigned n)
 {
   struct tty_caps *t = (struct tty_caps *)tty;
+  const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
   int rc;
 
+  ops->cap.prepout(&t->tty, gops, go);
   if (n) {
     if (f&TTIDF_LN)
-      CHECK(caps_iterate(t, gops, go,
-                        t->cap.dl1, t->cap.dl, CIF_PADMUL, 1, n));
+      CHECK(caps_iterate(t, t->cap.dl1, t->cap.dl, CIF_PADMUL, 1, n));
     else
-      CHECK(caps_iterate(t, gops, go,
-                        t->cap.dch1, t->cap.dch, 0, 1, n));
+      CHECK(caps_iterate(t, t->cap.dch1, t->cap.dch, 0, 1, n));
   }
   rc = 0;
 end:
+  if (ops->cap.flush(&t->tty)) rc = -1;
   return (rc);
 }
 
-#undef CHECK
 #undef PUT0V
 #undef PUT1IV
 #undef PUT2IV
@@ -1361,8 +1690,7 @@ end:
 #define TTY_CAPOPS                                                     \
   caps_setattr, caps_setmodes,                                         \
   caps_move, caps_repeat,                                              \
-  caps_erase, caps_erch, caps_ins, caps_inch, caps_del,                        \
-  0, 0, 0, 0
+  caps_erase, caps_erch, caps_ins, caps_inch, caps_del
 
 #endif
 
@@ -1406,36 +1734,30 @@ static const char *termcap_strcap(struct tty *tty,
 }
 
 static int termcap_put0(struct tty *tty,
-                       const struct gprintf_ops *gops, void *go,
                        unsigned npad, const char *cap)
 {
   if (!cap) return (-1);
-  global_gops = gops; global_gout = go;
   return (tputs(cap, npad, caps_putch));
 }
 
 static int termcap_put1i(struct tty *tty,
-                        const struct gprintf_ops *gops, void *go,
                         unsigned npad, const char *cap, int i0)
 {
   if (!cap) return (-1);
-  global_gops = gops; global_gout = go;
   return (tputs(tgoto(cap, -1, i0), npad, caps_putch) == OK ? 0 : -1);
 }
 
 static int termcap_put2i(struct tty *tty,
-                        const struct gprintf_ops *gops, void *go,
-                        unsigned npad,
-                        const char *cap, int i0, int i1)
+                        unsigned npad, const char *cap, int i0, int i1)
 {
   if (!cap) return (-1);
-  global_gops = gops; global_gout = go;
   return (tputs(tgoto(cap, i1, i0), npad, caps_putch) == OK ? 0 : -1);
 }
 
 static const union tty_capopsu termcap_ops = { {
   { caps_release, TTY_CAPOPS },
   { termcap_boolcap, termcap_intcap, termcap_strcap,
+    caps_prepout, caps_flush,
     termcap_put0, termcap_put1i, termcap_put2i }
 } };
 
@@ -1444,8 +1766,7 @@ static struct tty *termcap_init(FILE *fp)
   union tty_termcapu *u = 0; struct tty *ret = 0;
   const char *term;
 
-  if (global_lock)
-    { debug("termcap/terminfo terminal already open"); goto end; }
+  if (caps_claim()) goto end;
   term = getenv("TERM"); if (!term) goto end;
   XNEW(u);
   if (tgetent(u->tc.tc.termbuf, term) < 1) goto end;
@@ -1493,36 +1814,30 @@ static const char *terminfo_strcap(struct tty *tty,
 }
 
 static int terminfo_put0(struct tty *tty,
-                        const struct gprintf_ops *gops, void *go,
                         unsigned npad, const char *cap)
 {
   if (!cap) return (-1);
-  global_gops = gops; global_gout = go;
   return (tputs(cap, npad, caps_putch));
 }
 
 static int terminfo_put1i(struct tty *tty,
-                         const struct gprintf_ops *gops, void *go,
                          unsigned npad, const char *cap, int i0)
 {
   if (!cap) return (-1);
-  global_gops = gops; global_gout = go;
   return (tputs(tparm(cap, i0), npad, caps_putch) == OK ? 0 : -1);
 }
 
 static int terminfo_put2i(struct tty *tty,
-                         const struct gprintf_ops *gops, void *go,
-                         unsigned npad,
-                         const char *cap, int i0, int i1)
+                         unsigned npad, const char *cap, int i0, int i1)
 {
   if (!cap) return (-1);
-  global_gops = gops; global_gout = go;
   return (tputs(tparm(cap, i0, i1), npad, caps_putch) == OK ? 0 : -1);
 }
 
 static const union tty_capopsu terminfo_ops = { {
   { caps_release, TTY_CAPOPS },
   { terminfo_boolcap, terminfo_intcap, terminfo_strcap,
+    caps_prepout, caps_flush,
     terminfo_put0, terminfo_put1i, terminfo_put2i }
 } };
 
@@ -1531,8 +1846,7 @@ static struct tty *terminfo_init(FILE *fp)
   union tty_capsu *u = 0; struct tty *ret = 0;
   int err;
 
-  if (global_lock)
-    { debug("termcap/terminfo terminal already open"); goto end; }
+  if (caps_claim()) goto end;
   if (setupterm(0, fp ? fileno(fp) : -1, &err) != OK || err < 1) goto end;
   XNEW(u);
   u->tty.ops = &terminfo_ops.tty;
@@ -1552,6 +1866,9 @@ end:
 struct tty_unibislots {
   unibi_term *ut;
   unibi_var_t dy[26], st[26];
+  const struct gprintf_ops *gops; void *go;
+  char buf[4096]; size_t n;
+  int err;
 };
 struct tty_unibilium { TTY_CAPSPFX; struct tty_unibislots u; };
 union tty_unibiliumu { struct tty_unibilium u; TTY_CAPSUSFX; };
@@ -1583,41 +1900,70 @@ static const char *termunibi_strcap(struct tty *tty,
 
 struct termunibi_outctx {
   struct tty_unibilium *t;
-  const struct gprintf_ops *gops; void *go;
-  char pad[128];
-  int rc;
 };
 
-static void termunibi_putch(void *ctx, const char *p, size_t sz)
+static void termunibi_putm(void *ctx, const char *p, size_t sz)
 {
-  struct termunibi_outctx *out = ctx;
+  struct tty_unibilium *t = ctx;
+  size_t n;
 
-  if (out->gops->putm(out->go, p, sz)) out->rc = -1;
+  n = sizeof(t->u.buf) - t->u.n;
+  if (sz <= n)
+    { memcpy(t->u.buf + t->u.n, p, sz); t->u.n += sz; }
+  else {
+    if (n) { memcpy(t->u.buf + t->u.n, p, n); p += n; sz -= n; }
+    for (;;) {
+      if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf)))
+       t->u.err = -1;
+      if (sz <= sizeof(t->u.buf)) break;
+      memcpy(t->u.buf, p, sizeof(t->u.buf));
+       p += sizeof(t->u.buf); sz -= sizeof(t->u.buf);
+    }
+    memcpy(t->u.buf, p, sz); t->u.n = sz;
+  }
 }
 
 static void termunibi_pad(void *ctx, size_t ms, int mulp, int forcep)
 {
-  char pad[128];
-  struct termunibi_outctx *out = ctx;
-  struct tty_unibilium *t = out->t;
+  struct tty_unibilium *t = ctx;
   struct timeval tv;
-  size_t n, nn;
+  int pc;
+  size_t sz, n;
 
   /* Based on 7 data bits, 1 stop bit, 1 parity bit. */
 #define BITS_PER_KB 9000
 
-  if (forcep || t->tty.baud >= t->cap.pb) {
+  if (forcep || (t->tty.baud >= t->cap.pb && !t->cap.xon)) {
     if (t->cap.npc) {
       tv.tv_sec = ms/1000; tv.tv_usec = 1000*(ms%1000);
+      if (t->u.n) {
+       if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf)))
+         t->u.err = -1;
+       t->u.n = 0;
+      }
       if (t->tty.fpout) fflush(t->tty.fpout);
       select(0, 0, 0, 0, &tv);
     } else {
-      n = (ms*t->tty.baud + BITS_PER_KB - 1)/BITS_PER_KB;
-      while (n) {
-       if (n < sizeof(out->pad)) nn = n;
-       else nn = sizeof(out->pad);
-       if (out->gops->putm(out->go, pad, nn)) out->rc = -1;
-       n -= nn;
+      pc = t->cap.pad ? *t->cap.pad : 0;
+      sz = (ms*t->tty.baud + BITS_PER_KB - 1)/BITS_PER_KB;
+      n = sizeof(t->u.buf) - t->u.n;
+      if (sz <= n)
+       { memset(t->u.buf + t->u.n, pc, sz); t->u.n += sz; }
+      else {
+       if (n) { memset(t->u.buf + t->u.n, pc, sz); sz -= n; }
+       if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf)))
+         t->u.err = -1;
+       if (sz < sizeof(t->u.buf))
+         memset(t->u.buf, pc, sz);
+       else {
+         memset(t->u.buf, pc, sizeof(t->u.buf));
+         do {
+           if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf)))
+             t->u.err = -1;
+           sz -= sizeof(t->u.buf);
+         } while (sz > sizeof(t->u.buf));
+       }
+       t->u.n = sz;
       }
     }
   }
@@ -1625,66 +1971,67 @@ static void termunibi_pad(void *ctx, size_t ms, int mulp, int forcep)
 #undef BITS_PER_KB
 }
 
-static void setup_termunibi_outctx(struct tty_unibilium *t,
-                                  struct termunibi_outctx *out,
-                                  const struct gprintf_ops *gops, void *go)
+static void termunibi_prepout(struct tty *tty,
+                             const struct gprintf_ops *gops, void *go)
 {
-  out->t = t; out->rc = 0;
-  out->gops = gops; out->go = go;
-  if (!t->cap.npc)
-    memset(out->pad, t->cap.pad ? *t->cap.pad : 0, sizeof(out->pad));
+  struct tty_unibilium *t = (struct tty_unibilium *)tty;
+
+  assert(!t->u.n); t->u.gops = gops; t->u.go = go;
+}
+
+static int termunibi_flush(struct tty *tty)
+{
+  struct tty_unibilium *t = (struct tty_unibilium *)tty;
+  int rc = t->u.err;
+
+  if (t->u.n) {
+    if (t->u.gops->putm(t->u.go, t->u.buf, t->u.n)) rc = -1;
+    t->u.n = 0;
+  }
+  t->u.err = 0; return (rc);
 }
 
 static int termunibi_put0(struct tty *tty,
-                        const struct gprintf_ops *gops, void *go,
-                        unsigned npad, const char *cap)
+                         unsigned npad, const char *cap)
 {
   struct tty_unibilium *t = (struct tty_unibilium *)tty;
-  struct termunibi_outctx out;
   unibi_var_t arg[9];
 
   if (!cap) return (-1);
-  setup_termunibi_outctx(t, &out, gops, go);
   unibi_format(t->u.dy, t->u.st, cap, arg,
-              termunibi_putch, &out,
-              termunibi_pad, &out);
-  return (out.rc);
+              termunibi_putm, t,
+              termunibi_pad, t);
+  return (0);
 }
 
 static int termunibi_put1i(struct tty *tty,
-                          const struct gprintf_ops *gops, void *go,
                           unsigned npad, const char *cap, int i0)
 {
   struct tty_unibilium *t = (struct tty_unibilium *)tty;
-  struct termunibi_outctx out;
   unibi_var_t arg[9];
 
   if (!cap) return (-1);
-  setup_termunibi_outctx(t, &out, gops, go);
   arg[0] = unibi_var_from_num(i0);
   unibi_format(t->u.dy, t->u.st, cap, arg,
-              termunibi_putch, &out,
-              termunibi_pad, &out);
-  return (out.rc);
+              termunibi_putm, t,
+              termunibi_pad, t);
+  return (0);
 }
 
 static int termunibi_put2i(struct tty *tty,
-                          const struct gprintf_ops *gops, void *go,
                           unsigned npad,
                           const char *cap, int i0, int i1)
 {
   struct tty_unibilium *t = (struct tty_unibilium *)tty;
-  struct termunibi_outctx out;
   unibi_var_t arg[9];
 
   if (!cap) return (-1);
-  setup_termunibi_outctx(t, &out, gops, go);
   arg[0] = unibi_var_from_num(i0);
   arg[1] = unibi_var_from_num(i1);
   unibi_format(t->u.dy, t->u.st, cap, arg,
-              termunibi_putch, &out,
-              termunibi_pad, &out);
-  return (out.rc);
+              termunibi_putm, t,
+              termunibi_pad, t);
+  return (0);
 }
 
 static void termunibi_release(struct tty *tty)
@@ -1697,6 +2044,7 @@ static void termunibi_release(struct tty *tty)
 static const union tty_capopsu termunibi_ops = { {
   { termunibi_release, TTY_CAPOPS },
   { termunibi_boolcap, termunibi_intcap, termunibi_strcap,
+    termunibi_prepout, termunibi_flush,
     termunibi_put0, termunibi_put1i, termunibi_put2i }
 } };
 
@@ -1711,6 +2059,7 @@ static struct tty *termunibi_init(FILE *fp)
   XNEW(u);
   u->tty.ops = &termunibi_ops.tty;
   u->u.u.ut = ut; ut = 0;
+  u->u.u.n = 0; u->u.u.err = 0;
   common_init(&u->tty, fp);
   init_caps(&u->cap);
   ret = &u->tty; u = 0;
@@ -1800,8 +2149,6 @@ union tty_ansiu { struct tty_ansi ansi; TTY_BASEUSFX; };
 
 static void ansi_release(struct tty *tty) { ; }
 
-#define CHECK(expr) do { if ((expr) < 0) { rc = -1; goto end; } } while (0)
-
 #define PUTCH(ch) CHECK(gops->putch(go, (ch)))
 #define PUTLIT(lit) CHECK(gops->putm(go, (lit), sizeof(lit) - 1))
 #define SEMI do {                                                      \
@@ -1871,38 +2218,36 @@ static int ansi_setattr(struct tty *tty,
                        const struct tty_attr *a)
 {
   struct tty_ansi *t = (struct tty_ansi *)tty;
-  struct tty_attr aa;
   uint32 diff;
   int rc = 0;
   unsigned z, c, f = 0;
 
-  tty_clampattr(&aa, a, t->tty.acaps);
-  diff = aa.f ^ t->tty.st.attr.f;
-  if (!diff && aa.fg == t->tty.st.attr.fg && aa.bg == t->tty.st.attr.bg)
+  diff = a->f ^ t->tty.st.attr.f;
+  if (!diff && a->fg == t->tty.st.attr.fg && a->bg == t->tty.st.attr.bg)
     return (0);
 
   c = 0;
-#define CLEARP(mask) ((diff&(mask)) && !(aa.f&(mask)))
+#define CLEARP(mask) ((diff&(mask)) && !(a->f&(mask)))
   if (CLEARP(TTAF_LNMASK)) c += 3;
   if (CLEARP(TTAF_WTMASK)) c += 3;
-  if (diff&~aa.f&TTAF_INVV) c += 3;
-  if (diff&~aa.f&TTAF_STRIKE) c += 3;
-  if (diff&~aa.f&TTAF_ITAL) c += 3;
+  if (diff&~a->f&TTAF_INVV) c += 3;
+  if (diff&~a->f&TTAF_STRIKE) c += 3;
+  if (diff&~a->f&TTAF_ITAL) c += 3;
   if (CLEARP(TTAF_FGSPCMASK)) c += 3;
   if (CLEARP(TTAF_BGSPCMASK)) c += 3;
 #undef CLEARP
 
   z = 0;
-  switch ((aa.f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
+  switch ((a->f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
     case TTLN_ULINE: z += 2; break;
     case TTLN_UULINE: z += 3; break;
   }
-  if (aa.f&TTAF_WTMASK) z += 2;
-  if (aa.f&TTAF_INVV) z += 2;
-  if (aa.f&TTAF_STRIKE) z += 2;
-  if (aa.f&TTAF_ITAL) z += 2;
+  if (a->f&TTAF_WTMASK) z += 2;
+  if (a->f&TTAF_INVV) z += 2;
+  if (a->f&TTAF_STRIKE) z += 2;
+  if (a->f&TTAF_ITAL) z += 2;
 #define COLOURCOST(col) do {                                           \
-  switch ((aa.f&TTAF_##col##SPCMASK) >> TTAF_##col##SPCSHIFT) {                \
+  switch ((a->f&TTAF_##col##SPCMASK) >> TTAF_##col##SPCSHIFT) {                \
     case TTCSPC_1BPC: case TTCSPC_1BPCBR: z += 3; break;               \
     case TTCSPC_4LPC: case TTCSPC_8LGS: z += 8; break;                 \
     case TTCSPC_6LPC: case TTCSPC_24LGS: z += 9; break;                        \
@@ -1914,10 +2259,11 @@ static int ansi_setattr(struct tty *tty,
 
   PUTLIT("\33[");
 
-  if (z <= c) { SEMI; diff = aa.f; }
+  if (z <= c)
+    { SEMI; diff = a->f; t->tty.st.attr.fg = t->tty.st.attr.bg = 0; }
 
   if (diff&TTAF_LNMASK)
-    switch ((aa.f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
+    switch ((a->f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
       case TTLN_NONE: SEMI; PUTLIT("24"); break;
       case TTLN_ULINE: SEMI; PUTCH('4'); break;
       case TTLN_UULINE: SEMI; PUTLIT("21"); break;
@@ -1925,7 +2271,7 @@ static int ansi_setattr(struct tty *tty,
     }
 
   if (diff&TTAF_WTMASK)
-    switch ((aa.f&TTAF_WTMASK) >> TTAF_WTSHIFT) {
+    switch ((a->f&TTAF_WTMASK) >> TTAF_WTSHIFT) {
       case TTWT_MED: SEMI; PUTLIT("22"); break;
       case TTWT_BOLD: SEMI; PUTCH('1'); break;
       case TTWT_DIM: SEMI; PUTCH('2'); break;
@@ -1933,22 +2279,22 @@ static int ansi_setattr(struct tty *tty,
     }
 
   if (diff&TTAF_INVV)
-    { SEMI; if (aa.f&TTAF_INVV) PUTCH('7'); else PUTLIT("27"); }
+    { SEMI; if (a->f&TTAF_INVV) PUTCH('7'); else PUTLIT("27"); }
   if (diff&TTAF_STRIKE)
-    { SEMI; if (aa.f&TTAF_STRIKE) PUTCH('9'); else PUTLIT("29"); }
+    { SEMI; if (a->f&TTAF_STRIKE) PUTCH('9'); else PUTLIT("29"); }
   if (diff&TTAF_ITAL)
-    { SEMI; if (aa.f&TTAF_ITAL) PUTCH('3'); else PUTLIT("23"); }
+    { SEMI; if (a->f&TTAF_ITAL) PUTCH('3'); else PUTLIT("23"); }
 
-  if (diff&TTAF_FGSPCMASK || aa.fg != tty->st.attr.fg)
+  if (diff&TTAF_FGSPCMASK || a->fg != tty->st.attr.fg)
     CHECK(ansi_setcolour(t, &f, gops, go, 30, 90,
-                        (aa.f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, aa.fg));
-  if (diff&TTAF_BGSPCMASK || aa.bg != tty->st.attr.bg)
+                        (a->f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a->fg));
+  if (diff&TTAF_BGSPCMASK || a->bg != tty->st.attr.bg)
     CHECK(ansi_setcolour(t, &f, gops, go, 40, 100,
-                        (aa.f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, aa.bg));
+                        (a->f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a->bg));
 
   PUTCH('m'); rc = 0;
 end:
-  t->tty.st.attr = aa; return (rc);
+  t->tty.st.attr = *a; return (rc);
 
 }
 
@@ -2009,7 +2355,7 @@ static int ansi_move(struct tty *tty,
     if (!(orig&TTOF_YCUR)) CHECK(gprintf(gops, go, "\33[%dd", y + 1));
     else if (y == -1) PUTLIT("\33[A");
     else if (y < 0) CHECK(gprintf(gops, go, "\33[%dA", -y));
-    else if (y == +1) PUTLIT("\33[B"); /* not `^J'! */
+    else if (y == +1) PUTLIT("\33[B"); /* not %|^J|%! */
     else if (y > 1) CHECK(gprintf(gops, go, "\33[%dB", y));
     if (!(orig&TTOF_XCUR)) {
       if (!x)
@@ -2030,18 +2376,6 @@ end:
   return (rc);
 }
 
-static int ansi_repeat(struct tty *tty,
-                      const struct gprintf_ops *gops, void *go,
-                      int ch, unsigned n)
-{
-  int rc;
-
-  while (n--) PUTCH(ch);
-  rc = 0;
-end:
-  return (rc);
-}
-
 static int ansi_erase(struct tty *tty,
                      const struct gprintf_ops *gops, void *go,
                      unsigned f)
@@ -2133,9 +2467,8 @@ end:
 static const struct tty_ops ansi_ops = {
   ansi_release,
   ansi_setattr, ansi_setmodes,
-  ansi_move, ansi_repeat,
-  ansi_erase, ansi_erch, ansi_ins, ansi_inch, ansi_del,
-  0, 0, 0, 0
+  ansi_move, stupid_repeat,
+  ansi_erase, ansi_erch, ansi_ins, ansi_inch, ansi_del
 };
 
 static struct tty *ansi_init(FILE *fp)
@@ -2390,7 +2723,7 @@ static struct tty *ansi_init(FILE *fp)
     tf |= tm->tf&~tfset;
   }
 
-  env_colour_caps(&acaps);
+  if (!(acapset&TTACF_CSPCMASK)) env_colour_caps(&acaps, ECCF_SET);
   if (acaps&TTACF_CSPCMASK) ocaps |= TTCF_BGER;
 
   XNEW(u);
@@ -2439,7 +2772,7 @@ struct tty *tty_open(FILE *fp, unsigned f, const unsigned *backends)
     else if (isatty(STDERR_FILENO)) { fp = stderr; f |= TTF_BORROW; }
     else {
       fp = fopen("/dev/tty", "r+"); if (!fp) goto end;
-      f &= ~TTF_BORROW;
+      fpin = fp; f &= ~TTF_BORROW;
     }
   }
 
@@ -2500,20 +2833,56 @@ void tty_close(struct tty *tty)
   }
 }
 
+
+int tty_resized(struct tty *tty)
+{
+  struct winsize ws;
+
+  if (!tty || !tty->fpout) { errno = ENOTTY; return (-1); }
+  else if (ioctl(fileno(tty->fpout), TIOCGWINSZ, &ws)) return (-1);
+  else if (tty->wd == ws.ws_col && tty->ht == ws.ws_row) return (0);
+  else { tty->wd = ws.ws_col; tty->ht = ws.ws_row; return (1); }
+}
+
 /*----- Terminal operations -----------------------------------------------*/
 
+int tty_setattr(struct tty *tty, const struct tty_attr *a)
+{
+  struct tty_attr aa;
+
+  if (!tty || !tty->fpout)
+    return (-1);
+  else {
+    clamp_attr(&aa, a, tty->acaps);
+    return (tty->ops->setattr(tty, &file_printops, tty->fpout, &aa));
+  }
+}
+
 int tty_setattrg(struct tty *tty,
                 const struct gprintf_ops *gops, void *go,
                 const struct tty_attr *a)
-  { return (tty->ops->setattr(tty, gops, go, a)); }
+{
+  struct tty_attr aa;
 
-int tty_setattr(struct tty *tty, const struct tty_attr *a)
-  { return (tty->ops->setattr(tty, &file_printops, tty->fpout, a)); }
+  if (!tty)
+    return (-1);
+  else {
+    clamp_attr(&aa, a, tty->acaps);
+    return (tty->ops->setattr(tty, gops, go, &aa));
+  }
+}
+
+int tty_setattrlist(struct tty *tty, const struct tty_attrlist *aa)
+{
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty_setattrlistg(tty, &file_printops, tty->fpout, aa));
+}
 
 int tty_setattrlistg(struct tty *tty,
                     const struct gprintf_ops *gops, void *go,
                     const struct tty_attrlist *aa)
 {
+  if (!tty) return (-1);
   for (;; aa++)
     if ((tty->acaps&aa->cap_mask) == aa->cap_eq)
       return (tty->ops->setattr(tty, gops, go, &aa->attr));
@@ -2521,74 +2890,138 @@ int tty_setattrlistg(struct tty *tty,
       return (0);
 }
 
-int tty_setattrlist(struct tty *tty, const struct tty_attrlist *aa)
-  { return (tty_setattrlistg(tty, &file_printops, tty->fpout, aa)); }
+int tty_setmodes(struct tty *tty, uint32 modes_bic, uint32 modes_xor)
+{
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty->ops->setmodes(tty, &file_printops, tty->fpout,
+                                 modes_bic, modes_xor));
+}
 
 int tty_setmodesg(struct tty *tty,
                  const struct gprintf_ops *gops, void *go,
                  uint32 modes_bic, uint32 modes_xor)
-  { return (tty->ops->setmodes(tty, gops, go, modes_bic, modes_xor)); }
+{
+  if (!tty) return (-1);
+  else return (tty->ops->setmodes(tty, gops, go, modes_bic, modes_xor));
+}
 
-int tty_setmodes(struct tty *tty, uint32 modes_bic, uint32 modes_xor)
+int tty_move(struct tty *tty, unsigned orig, int y, int x)
 {
-  return (tty->ops->setmodes(tty, &file_printops, tty->fpout,
-                            modes_bic, modes_xor));
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty->ops->move(tty, &file_printops, tty->fpout, orig, y, x));
 }
 
 int tty_moveg(struct tty *tty,
              const struct gprintf_ops *gops, void *go,
              unsigned orig, int y, int x)
-  { return (tty->ops->move(tty, gops, go, orig, y, x)); }
+{
+  if (!tty) return (-1);
+  else return (tty->ops->move(tty, gops, go, orig, y, x));
+}
 
-int tty_move(struct tty *tty, unsigned orig, int y, int x)
-  { return (tty->ops->move(tty, &file_printops, tty->fpout, orig, y, x)); }
+int tty_repeat(struct tty *tty, int ch, unsigned n)
+{
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty->ops->repeat(tty, &file_printops, tty->fpout, ch, n));
+}
 
 int tty_repeatg(struct tty *tty,
                const struct gprintf_ops *gops, void *go,
                int ch, unsigned n)
-  { return (tty->ops->repeat(tty, gops, go, ch, n)); }
+{
+  if (!tty) return (-1);
+  else return (tty->ops->repeat(tty, gops, go, ch, n));
+}
 
-int tty_repeat(struct tty *tty, int ch, unsigned n)
-  { return (tty->ops->repeat(tty, &file_printops, tty->fpout, ch, n)); }
+int tty_erase(struct tty *tty, unsigned f)
+{
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty->ops->erase(tty, &file_printops, tty->fpout, f));
+}
 
 int tty_eraseg(struct tty *tty,
               const struct gprintf_ops *gops, void *go,
               unsigned f)
-  { return (tty->ops->erase(tty, gops, go, f)); }
+{
+  if (!tty) return (-1);
+  else return (tty->ops->erase(tty, gops, go, f));
+}
 
-int tty_erase(struct tty *tty, unsigned f)
-  { return (tty->ops->erase(tty, &file_printops, tty->fpout, f)); }
+int tty_erch(struct tty *tty, unsigned n)
+{
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty->ops->erch(tty, &file_printops, tty->fpout, n));
+}
 
 int tty_erchg(struct tty *tty,
              const struct gprintf_ops *gops, void *go,
              unsigned n)
-  { return (tty->ops->erch(tty, gops, go, n)); }
+{
+  if (!tty) return (-1);
+  else return (tty->ops->erch(tty, gops, go, n));
+}
 
-int tty_erch(struct tty *tty, unsigned n)
-  { return (tty->ops->erch(tty, &file_printops, tty->fpout, n)); }
+int tty_ins(struct tty *tty, unsigned f, unsigned n)
+{
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty->ops->ins(tty, &file_printops, tty->fpout, f, n));
+}
 
 int tty_insg(struct tty *tty,
             const struct gprintf_ops *gops, void *go,
             unsigned f, unsigned n)
-  { return (tty->ops->ins(tty, gops, go, f, n)); }
+{
+  if (!tty) return (-1);
+  else return (tty->ops->ins(tty, gops, go, f, n));
+}
 
-int tty_ins(struct tty *tty, unsigned f, unsigned n)
-  { return (tty->ops->ins(tty, &file_printops, tty->fpout, f, n)); }
+int tty_inch(struct tty *tty, int ch)
+{
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty->ops->inch(tty, &file_printops, tty->fpout, ch));
+}
 
 int tty_inchg(struct tty *tty,
              const struct gprintf_ops *gops, void *go,
              int ch)
-  { return (tty->ops->inch(tty, gops, go, ch)); }
+{
+  if (!tty) return (-1);
+  else return (tty->ops->inch(tty, gops, go, ch));
+}
 
-int tty_inch(struct tty *tty, int ch)
-  { return (tty->ops->inch(tty, &file_printops, tty->fpout, ch)); }
+int tty_del(struct tty *tty, unsigned f, unsigned n)
+{
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty->ops->del(tty, &file_printops, tty->fpout, f, n));
+}
 
 int tty_delg(struct tty *tty,
             const struct gprintf_ops *gops, void *go,
             unsigned f, unsigned n)
-  { return (tty->ops->del(tty, gops, go, f, n)); }
+{
+  if (!tty) return (-1);
+  else return (tty->ops->del(tty, gops, go, f, n));
+}
 
-int tty_del(struct tty *tty, unsigned f, unsigned n)
-  { return (tty->ops->del(tty, &file_printops, tty->fpout, f, n)); }
+int tty_restore(struct tty *tty, const struct tty_state *st)
+{
+  if (!tty || !tty->fpout) return (-1);
+  else return (tty_restoreg(tty, &file_printops, tty->fpout, st));
+}
+
+int tty_restoreg(struct tty *tty,
+                const struct gprintf_ops *gops, void *go,
+                const struct tty_state *st)
+{
+  int rc;
+
+  if (!tty ||
+      tty->ops->setmodes(tty, gops, go, MASK32, st->modes) ||
+      tty->ops->setattr(tty, gops, go, &st->attr))
+    { rc = -1; goto end; }
+  rc = 0;
+end:
+  return (rc);
+}
 
 /*----- That's all, folks -------------------------------------------------*/