chiark / gitweb /
@@@ tty cleanup
[mLib] / ui / tty.c
1 /* -*-c-*-
2  *
3  * Terminal handling
4  *
5  * (c) 2024 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the mLib utilities library.
11  *
12  * mLib is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU Library General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or (at
15  * your option) any later version.
16  *
17  * mLib is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
20  * License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with mLib.  If not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25  * USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "config.h"
31
32 #include <ctype.h>
33 #include <errno.h>
34 #include <limits.h>
35 #include <stdio.h>
36 #include <string.h>
37
38 #include <sys/types.h>
39 #include <sys/time.h>
40 #include <unistd.h>
41 #include <termios.h>
42 #include <sys/ioctl.h>
43 #include <sys/select.h>
44
45 #ifdef HAVE_TERMCAP
46 #  include <termcap.h>
47 #endif
48
49 #ifdef HAVE_TERMINFO
50 #  include <curses.h>
51 #  include <term.h>
52 #  undef erase
53 #  undef inch
54 #  undef move
55 #endif
56
57 #ifdef HAVE_UNIBILIUM
58 #  include <unibilium.h>
59 #endif
60
61 #include "alloc.h"
62 #include "gprintf.h"
63 #include "macros.h"
64 #include "str.h"
65 #include "tty.h"
66
67 /*----- Operations table --------------------------------------------------*/
68
69 /* Incorporate the published control-block structure into our more elaborate
70  * object model.
71  */
72 #define TTY_BASEPFX struct tty tty
73 #define TTY_BASEUSFX struct tty tty
74
75 struct tty_ops {
76   void (*release)(struct tty */*tty*/);
77         /* Free any resources held by the backend. */
78
79   /* The following operations handle the correspondingly named interface
80    * functions.
81    */
82   int (*setattr)(struct tty */*tty*/,
83                  const struct gprintf_ops */*gops*/, void */*go*/,
84                  const struct tty_attr */*a*/);
85   int (*setmodes)(struct tty */*tty*/,
86                   const struct gprintf_ops */*gops*/, void */*go*/,
87                   uint32 /*modes_bic*/, uint32 /*modes_xor*/);
88   int (*move)(struct tty */*tty*/,
89               const struct gprintf_ops */*gops*/, void */*go*/,
90               unsigned /*orig*/, int /*y*/, int /*x*/);
91   int (*repeat)(struct tty */*tty*/,
92                 const struct gprintf_ops */*gops*/, void */*go*/,
93                 int /*ch*/, unsigned /*n*/);
94   int (*erase)(struct tty */*tty*/,
95                const struct gprintf_ops */*gops*/, void */*go*/,
96                unsigned /*f*/);
97   int (*erch)(struct tty */*tty*/,
98               const struct gprintf_ops */*gops*/, void */*go*/,
99               unsigned /*n*/);
100   int (*ins)(struct tty */*tty*/,
101              const struct gprintf_ops */*gops*/, void */*go*/,
102              unsigned /*f*/, unsigned /*n*/);
103   int (*inch)(struct tty */*tty*/,
104               const struct gprintf_ops */*gops*/, void */*go*/,
105               int /*ch*/);
106   int (*del)(struct tty */*tty*/,
107              const struct gprintf_ops */*gops*/, void */*go*/,
108              unsigned /*f*/, unsigned /*n*/);
109 };
110 #define TTY_BASEOPSPFX struct tty_ops tty
111 #define TTY_BASEOPSUXFX struct tty_ops tty
112
113 /*----- Common support machinery ------------------------------------------*/
114
115 /* --- @CHECK@ --- *
116  *
117  * Arguments@   @expr@ = expression to evaluate
118  *
119  * Use:         Evaluate @expr@.  If the result is (strictly) negative, then
120  *              set @rc = -1@ and transfer control to the label @end@.
121  */
122
123 #define CHECK(expr) do { if ((expr) < 0) { rc = -1; goto end; } } while (0)
124
125 /* --- @debug@ --- *
126  *
127  * Arguments:   @const char *fmt@ = format control string
128  *              @...@ = format arguemnts
129  *
130  * Returns:     ---
131  *
132  * Use:         Maybe report a debugging message to standard error.
133  */
134
135 static PRINTF_LIKE(1, 2) void debug(const char *fmt, ...)
136 {
137   const char *p;
138   va_list ap;
139
140   p = getenv("MLIB_TTY_DEBUG");
141   if (p && *p != 'n' && *p != '0') {
142     va_start(ap, fmt);
143     fputs("mLib TTY: ", stderr);
144     vfprintf(stderr, fmt, ap);
145     fputc('\n', stderr);
146     va_end(ap);
147   }
148 }
149
150 /* --- @common_init@ --- *
151  *
152  * Arguments:   @struct tty *tty@ = pointer to terminal control block
153  *              @FILE *fp@ = output file stream
154  *
155  * Returns:     ---
156  *
157  * Use:         Perform general initialization on the terminal control
158  *              block.
159  *
160  *              Specifically, this fills in the @fpout@, @baud@, @ht@, and
161  *              @wd@ slots.  The width and height come from the kernel, or,
162  *              failing that, the environment.
163  */
164
165 static void common_init(struct tty *tty, FILE *fp)
166 {
167   static const struct baudtab { speed_t code; unsigned baud; } baudtab[] = {
168   /*
169      ;;; The baud-rate table is very boring to type.  To make life less
170      ;;; awful, put the rates in this list and evaluate the code to get Emacs
171      ;;; to regenerate it.
172
173      (let ((bauds '(50 75 110 134 150 200 300 600 1200 1800 2400 4800 9600
174                     19200 38400 57600 115200 230400 460800 500000 576000
175                     921600 1000000 1152000 1500000 2000000 2500000 3000000
176                     3500000 4000000)))
177        (save-excursion
178          (goto-char (point-min))
179          (search-forward (concat "***" "BEGIN baudlist" "***"))
180          (beginning-of-line 2)
181          (delete-region (point)
182                         (progn
183                           (search-forward "***END***")
184                           (beginning-of-line)
185                           (point)))
186          (dolist (baud (sort (copy-list bauds) #'<))
187            (insert (format "#ifdef B%d\n    { B%d, %d },\n#endif\n"
188                            baud baud baud)))))
189   */
190     /***BEGIN baudlist***/
191 #ifdef B50
192     { B50, 50 },
193 #endif
194 #ifdef B75
195     { B75, 75 },
196 #endif
197 #ifdef B110
198     { B110, 110 },
199 #endif
200 #ifdef B134
201     { B134, 134 },
202 #endif
203 #ifdef B150
204     { B150, 150 },
205 #endif
206 #ifdef B200
207     { B200, 200 },
208 #endif
209 #ifdef B300
210     { B300, 300 },
211 #endif
212 #ifdef B600
213     { B600, 600 },
214 #endif
215 #ifdef B1200
216     { B1200, 1200 },
217 #endif
218 #ifdef B1800
219     { B1800, 1800 },
220 #endif
221 #ifdef B2400
222     { B2400, 2400 },
223 #endif
224 #ifdef B4800
225     { B4800, 4800 },
226 #endif
227 #ifdef B9600
228     { B9600, 9600 },
229 #endif
230 #ifdef B19200
231     { B19200, 19200 },
232 #endif
233 #ifdef B38400
234     { B38400, 38400 },
235 #endif
236 #ifdef B57600
237     { B57600, 57600 },
238 #endif
239 #ifdef B115200
240     { B115200, 115200 },
241 #endif
242 #ifdef B230400
243     { B230400, 230400 },
244 #endif
245 #ifdef B460800
246     { B460800, 460800 },
247 #endif
248 #ifdef B500000
249     { B500000, 500000 },
250 #endif
251 #ifdef B576000
252     { B576000, 576000 },
253 #endif
254 #ifdef B921600
255     { B921600, 921600 },
256 #endif
257 #ifdef B1000000
258     { B1000000, 1000000 },
259 #endif
260 #ifdef B1152000
261     { B1152000, 1152000 },
262 #endif
263 #ifdef B1500000
264     { B1500000, 1500000 },
265 #endif
266 #ifdef B2000000
267     { B2000000, 2000000 },
268 #endif
269 #ifdef B2500000
270     { B2500000, 2500000 },
271 #endif
272 #ifdef B3000000
273     { B3000000, 3000000 },
274 #endif
275 #ifdef B3500000
276     { B3500000, 3500000 },
277 #endif
278 #ifdef B4000000
279     { B4000000, 4000000 },
280 #endif
281     /***END***/
282     { 0, 0 }
283   };
284
285   const struct baudtab *b;
286   const char *p; int n;
287   struct termios c;
288   speed_t code;
289
290   /* Save the output stream. */
291   tty->fpout = fp;
292
293   /* Determine the output baud rate.  Unhelpfully, the kernel provides a
294    * weird code, so we have to convert it into an actual rate in bits per
295    * second.
296    */
297   if (!fp || tcgetattr(fileno(fp), &c))
298     tty->baud = 0;
299   else {
300     code = cfgetospeed(&c);
301     for (b = baudtab; b->baud; b++)
302       if (b->code == code) { tty->baud = b->baud; goto found_baud; }
303     tty->baud = 0;
304   found_baud:
305     tty_resized(tty);
306   }
307
308   /* If the kernel didn't tell us the terminal dimensions, try to read them
309    * from the environment.
310    */
311   if (!tty->wd)
312     { p = getenv("COLUMNS"); if (p) { n = atoi(p); if (n) tty->wd = n; } }
313   if (!tty->ht)
314     { p = getenv("LINES");   if (p) { n = atoi(p); if (n) tty->ht = n; } }
315 }
316
317 /* --- @env_colour_caps@ --- *
318  *
319  * Arguments:   @unsigned *caps_inout@ = attribute capabilities to update
320  *              @unsigned f@ = flags
321  *
322  * Returns:     ---
323  *
324  * Use:         Check the %|FORCE_COLOR|% environment variable and update the
325  *              capabilities as required.
326  *
327  *              The %|FORCE_COLOR|% variable originates with the Node
328  *              community, with two objectives: (a) to convey policy
329  *              regarding whether to produce coloured output, and (b) to
330  *              describe the colour capabilities of the terminal, because the
331  *              traditional mechanisms are deemed inadequate.
332  *
333  *              The following values have defined meanings.
334  *
335  *                * Unset or empty: no effect.
336  *
337  *                * %|0|%: monochrome; don't produce colour.
338  *
339  *                * %|1|%: 16 colours; 1-bit-per-channel colours are
340  *                  available, with an additional common brightness bit.
341  *
342  *                * %|2|%: 256 colours; the `xterm' 256-bit palette is
343  *                  available, consisting of the 1-bit-per-channel colours
344  *                  with common brightness bit, a 6 Ã— 6 Ã— 6 colour cube, and
345  *                  a 24-level greyscale ramp.
346  *
347  *                * %|3|%: full 24-bit colour.
348  *
349  *                * Anything else: a request to use colour if available.
350  *                  This is ignored here, in the expectation that it will be
351  *                  given effect elsewhere, e.g., by @ttycolour_enablep@.
352  *
353  *              If @ECCF_SET@ is set, then set or clear capabilities as
354  *              required.  Otherwise, clear capability bits which are denied
355  *              by the variable setting, but no bits will be set.  (This
356  *              latter is necessary for backends which use terminal
357  *              databases, since they can't be expected to make up the
358  *              necessary control sequences for themselves.)
359  */
360
361 #define ECCF_SET 1u
362 static void env_colour_caps(unsigned *caps_inout, unsigned f)
363 {
364   const char *p;
365   unsigned caps = *caps_inout, mask;
366
367   p = getenv("FORCE_COLOR"); if (!p) return;
368   switch (*p) {
369     case '0':
370       mask = 0;
371       break;
372     case '1':
373       mask = TTACF_FG | TTACF_BG | TTACF_1BPC | TTACF_1BPCBR;
374       break;
375     case '2':
376       mask = TTACF_FG | TTACF_BG |
377              TTACF_1BPC | TTACF_1BPCBR | TTACF_6LPC | TTACF_24LGS;
378       break;
379     case '3':
380       mask = TTACF_FG | TTACF_BG |
381              TTACF_1BPC | TTACF_1BPCBR | TTACF_8BPC;
382       break;
383     default:
384       return;
385   }
386   if (!(f&ECCF_SET)) caps &= mask;
387   else caps = (caps&~(TTACF_CSPCMASK | TTACF_FG | TTACF_BG)) | mask;
388
389   *caps_inout = caps;
390 }
391
392 /* --- @clamp_colours@ --- *
393  *
394  * Arguments:   @uint32 *space_out, *colour_out@ = selected space and colour
395  *              @uint32 space, colour@ = requested space and colour
396  *              @uint32 acaps@ = terminal's attribute capability mask
397  *
398  * Returns:     ---
399  *
400  * Use:         Select the best approximation to the requested colour which
401  *              can be accommodated by the terminal.
402  */
403
404 /* #define DEBUG_CLAMP */
405
406 static void clamp_colours(uint32 *space_out, uint32 *colour_out,
407                           uint32 space, uint32 colour, uint32 acaps)
408 {
409   unsigned r, g, b, rr, gg, bb, y, t, u;
410   uint32 best_colour = 0, best_space; int best_error;
411
412 #ifdef DEBUG_CLAMP
413 #  define D(x) x
414 #else
415 #  define D(x)
416 #endif
417
418   /* Check the colour space.  If it's one that the terminal can handle, then
419    * return the colour unchanged.  Otherwise, extract the channel components
420    * for the next step.
421    */
422   switch (space) {
423
424     case TTCSPC_NONE:
425       /* No colour wanted at all.  There's nothing to do here. */
426
427       *space_out = TTCSPC_NONE; *colour_out = 0;
428       return;
429
430     case TTCSPC_1BPC:
431       /* One-bit-per-channel colour.
432        *
433        * There's no standardized mapping for these; indeed, they're commonly
434        * configurable by users.  Since there are also `bright' versions of
435        * each, it's probably not right to just map zero to zero and one to
436        * full-scale.
437        */
438
439       if (colour >= 8) goto inval;
440       if (acaps&(TTACF_1BPC | TTACF_1BPCBR))
441         { *space_out = TTCSPC_1BPC; *colour_out = colour; return; }
442
443 #define C1BPC_FULL 0xcc
444 #define C1BPC_CVT(col, bit)                                             \
445         (C1BPC_FULL&((((col)&(1 << (bit))) << (8 - (bit))) - 1))
446       r = C1BPC_CVT(colour, 0);
447       g = C1BPC_CVT(colour, 1);
448       b = C1BPC_CVT(colour, 2);
449       break;
450
451     case TTCSPC_1BPCBR:
452       /* One-bit-per-channel colour, with global brightness.  Again, there's
453        * no standardized mapping.  Apply a boost across all three channels.
454        */
455
456       if (colour >= 16) goto inval;
457       if (!(colour&TT1BPC_BRI) && (acaps&(TTACF_1BPC | TTACF_1BPCBR)))
458         { *space_out = TTCSPC_1BPC; *colour_out = colour; return; }
459       else if (acaps&TTACF_1BPCBR)
460         { *space_out = TTCSPC_1BPCBR; *colour_out = colour; return; }
461
462 #define C1BPC_BRIGHT 0x33
463       r = C1BPC_CVT(colour, 0) + C1BPC_BRIGHT;
464       g = C1BPC_CVT(colour, 1) + C1BPC_BRIGHT;
465       b = C1BPC_CVT(colour, 2) + C1BPC_BRIGHT;
466 #undef C1BPC_CVT
467       break;
468
469     case TTCSPC_4LPC:
470       /* Four-levels-per-channel colour.  These are part of an `indexed'
471        * colour space which is theoretically controlled by applications but
472        * (a) that's rare, and (b) worrying about that won't do us any good.
473        *
474        * Each channel has four levels, but they're not assigned linearly.
475        */
476
477       if (colour >= 64) goto inval;
478       if (acaps&TTACF_4LPC)
479         { *space_out = TTCSPC_4LPC; *colour_out = colour; return; }
480
481 #define C4LPC_L0 0
482 #define C4LPC_L1 139
483 #define C4LPC_L2 205
484 #define C4LPC_L3 255
485 #define C4LPC_CVT(ch) (t = (ch), t == 0 ? C4LPC_L0 :                    \
486                                  t == 1 ? C4LPC_L1 :                    \
487                                  t == 2 ? C4LPC_L2 :                    \
488                                           C4LPC_L3)
489       r = C4LPC_CVT(TTCOL_2BR(colour));
490       g = C4LPC_CVT(TTCOL_2BG(colour));
491       b = C4LPC_CVT(TTCOL_2BB(colour));
492 #undef C4LPC_CVT
493       break;
494
495     case TTCSPC_8LGS:
496       /* Eight-levels greyscale.  Again, these are part of an `indexed'
497        * colour space which is under application control, and, again, the
498        * levels aren't linear, but they're not far off linear.
499        */
500       if (colour >= 8) goto inval;
501       if (acaps&TTACF_8LGS)
502         { *space_out = TTCSPC_8LGS; *colour_out = colour; return; }
503
504       r = g = b = 255*(colour ? colour + 3 : 2)/11;
505       break;
506
507     case TTCSPC_6LPC:
508       /* Six-levels-per-channel colour.  Again, `indexed' colour space under
509        * application control.  This time the mapping is essentially liner.
510        */
511
512       if (colour >= 216) goto inval;
513       if (acaps&TTACF_6LPC)
514         { *space_out = TTCSPC_6LPC; *colour_out = colour; return; }
515
516 #define C6LPC_CVT(ch) (t = (ch), t ? (40*t + 55) : 0)
517       r = C6LPC_CVT(TTCOL_6LR(colour));
518       g = C6LPC_CVT(TTCOL_6LG(colour));
519       b = C6LPC_CVT(TTCOL_6LB(colour));
520 #undef C6LPC_CVT
521       break;
522
523     case TTCSPC_24LGS:
524       /* Twenty-four-levels greyscale.  Same story. */
525
526       if (colour >= 24) goto inval;
527       if (acaps&TTACF_24LGS)
528         { *space_out = TTCSPC_24LGS; *colour_out = colour; return; }
529
530       r = g = b = 10*colour + 8;
531       break;
532
533     case TTCSPC_8BPC:
534       /* Eight-bits-per-channel colour.  No conversion to apply here. */
535
536       if (colour >= 0x01000000) goto inval;
537       if (acaps&TTACF_8BPC)
538         { *space_out = TTCSPC_8BPC; *colour_out = colour; return; }
539
540       r = TTCOL_8BR(colour); g = TTCOL_8BG(colour); b = TTCOL_8BB(colour);
541       break;
542
543     default:
544       /* Anything else. */
545
546       goto inval;
547   }
548
549   /* We didn't get an exact match, so we'll have to make do with what we've
550    * got.
551    */
552   best_error = -1; best_space = TTCSPC_NONE; best_colour = 0;
553   D( fprintf(stderr, "\n;; APPROX space %u, colour 0x%lx = %u/%u/%u\n",
554              space, (unsigned long)colour, r, g, b); )
555
556   /* Approximate colour weightings for human colour vision. */
557 #define RWT 2
558 #define GWT 4
559 #define BWT 1
560 #define TOTWT (RWT + GWT + BWT)
561
562   /* Determine the optimal grey approximation for this colour. */
563   y = (RWT*r + GWT*g + BWT*b + TOTWT/2)/TOTWT;
564
565 #define TRY_APPROX(rr, gg, bb, spc, clr) do {                           \
566   /* If the approximation (RR, GG, BB) is closer to the current best    \
567    * then accept SPC and CLR as the new best space/colour option.       \
568    */                                                                   \
569                                                                         \
570   int _r_err = (rr) - r, _g_err = (gg) - g, _b_err = (bb) - b;          \
571   int _err;                                                             \
572                                                                         \
573   if (_r_err < 0) _r_err = -_r_err;                                     \
574   if (_g_err < 0) _g_err = -_g_err;                                     \
575   if (_b_err < 0) _b_err = -_b_err;                                     \
576                                                                         \
577   _err = RWT*_r_err + GWT*_g_err + BWT*_b_err;                          \
578   D( fprintf(stderr,                                                    \
579              ";;   candidate space %u, colour 0x%lx = %u/%u/%u; "       \
580                   "error = %d\n",                                       \
581              (spc), (unsigned long)(clr), (rr), (gg), (bb), _err); )    \
582   if (best_error < 0 || _err < best_error) {                            \
583     best_error = _err; best_space = (spc); best_colour = (clr);         \
584     D( fprintf(stderr, ";;\tNEW BEST APPROXIMATION\n"); )               \
585   }                                                                     \
586 } while (0)
587
588   if (!(acaps&(TTACF_4LPC | TTACF_6LPC | TTACF_8BPC))) {
589     /* The one-bit-per-channel colours are very variable, but there's little
590      * choice, so we'll have to try.  We assume the same mapping as on the
591      * way in.
592      */
593
594     if (acaps&(TTACF_1BPC | TTACF_1BPCBR)) {
595       /* One-bit-per-channel colour. */
596
597 #define C1BPC_APPROX(cc, c, bit) do {                                   \
598       if ((c) <= C1BPC_FULL/2) (cc) = 0;                                \
599       else { (cc) = C1BPC_FULL; t |= (bit); }                           \
600 } while (0)
601       t = 0;
602       C1BPC_APPROX(rr, r, TT1BPC_RED);
603       C1BPC_APPROX(gg, g, TT1BPC_GRN);
604       C1BPC_APPROX(bb, b, TT1BPC_BLU);
605 #undef C1BPC_APPROX
606       TRY_APPROX(rr, gg, bb, TTCSPC_1BPC, t);
607     }
608
609     if (acaps&TTACF_1BPCBR) {
610       /* One-bit-per-channel colour, with global brightness. */
611
612 #define C1BPCBR_APPROX(cc, c, bit) do {                                 \
613       if ((c) <= C1BPC_FULL/2 + C1BPC_BRIGHT) (cc) = C1BPC_BRIGHT;      \
614       else { (cc) = 255; t |= (bit); }                                  \
615 } while (0)
616       t = TT1BPC_BRI;
617       C1BPCBR_APPROX(rr, r, TT1BPC_RED);
618       C1BPCBR_APPROX(gg, g, TT1BPC_GRN);
619       C1BPCBR_APPROX(bb, b, TT1BPC_BLU);
620 #undef C1BPCBR_APPROX
621       TRY_APPROX(rr, gg, bb, TTCSPC_1BPCBR, t);
622     }
623   }
624
625   if (acaps&TTACF_4LPC) {
626     /* Four-levels-per-channel colour. */
627
628 #define C4LPC_APPROX(cc, c, sh) do {                                    \
629     unsigned _c = (c);                                                  \
630                                                                         \
631     if (_c > (C4LPC_L2 + C4LPC_L3)/2)                                   \
632       { (cc) = C4LPC_L3; t |= 3 << (sh); }                              \
633     else if (_c > (C4LPC_L1 + C4LPC_L2)/2)                              \
634       { (cc) = C4LPC_L2; t |= 2 << (sh); }                              \
635     else if (_c > (C4LPC_L0 + C4LPC_L1)/2)                              \
636       { (cc) = C4LPC_L1; t |= 1 << (sh); }                              \
637     else                                                                \
638       (cc) = C4LPC_L0;                                                  \
639 } while (0)
640     t = 0;
641     C4LPC_APPROX(rr, r, 4);
642     C4LPC_APPROX(gg, g, 2);
643     C4LPC_APPROX(bb, b, 0);
644 #undef C4LPC_APPROX
645     TRY_APPROX(rr, gg, bb, TTCSPC_4LPC, t);
646   }
647
648   if (acaps&TTACF_8LGS) {
649     /* Eight-levels greyscale. */
650
651     u = (11*y)/255;
652     if (u <= 2) { u = 2; t = 0; }
653     else if (u == 3) { u = 4; t = 1; }
654     else if (u == 11) { u = 10; t = 7; }
655     else t = u - 3;
656     u = (255*u)/11; TRY_APPROX(u, u, u, TTCSPC_8LGS, t);
657   }
658
659   if (acaps&TTACF_6LPC) {
660     /* Six-levels-per-channel colour. */
661
662 #define C6LPC_APPROX(cc, c, f) do {                                     \
663     unsigned _c = (c);                                                  \
664                                                                         \
665     if (_c < 36) (cc) = 0;                                              \
666     else { u = (_c - 36)/40; t += (f)*u; (cc) = 40*u + 55; }            \
667 } while (0)
668     t = 0;
669     C6LPC_APPROX(rr, r, 36);
670     C6LPC_APPROX(gg, g,  6);
671     C6LPC_APPROX(bb, b,  1);
672 #undef C6LPC_APPROX
673     TRY_APPROX(rr, gg, bb, TTCSPC_6LPC, t);
674   }
675
676   if (acaps&TTACF_24LGS) {
677     /* Twenty-four-levels greyscale. */
678
679     if (y < 3) { t = 0; u = 8; }
680     else if (y >= 243) { t = 23; u = 238; }
681     else { t = (y - 3)/10; u = 10*t + 8; }
682     TRY_APPROX(u, u, u, TTCSPC_24LGS, t);
683   }
684
685   if (acaps&TTACF_8BPC) {
686     /* Eight-bits-per-channel colour. */
687
688     if (best_error) {
689       D( fprintf(stderr, ";;   accept exact 8bpc colour\n"); )
690       best_error = 0; best_space = TTCSPC_8BPC;
691       best_colour = TTCOL_MK8B(r, g, b);
692     }
693   }
694
695   /* Done. */
696   *space_out = best_space; *colour_out = best_colour;
697   return;
698
699 inval:
700   /* Invalid colour selection.  Ignore this. */
701   *space_out = TTCSPC_NONE; *colour_out = 0;
702
703 #undef C1BPC_FULL
704 #undef C1BPC_BRIGHT
705
706 #undef C4LPC_L0
707 #undef C4LPC_L1
708 #undef C4LPC_L2
709 #undef C4LPC_L3
710
711 #undef TRY_APPROX
712 #undef TRY_RGB
713 #undef TRY_GREY
714
715 #undef RWT
716 #undef GWT
717 #undef BWT
718 #undef TOTWT
719
720 #undef SET_RANGE
721 #undef CHECK_RANGE
722
723 #undef D
724 }
725
726 /* --- @clamp_attr@ --- *
727  *
728  * Arguments:   @struct tty_attr *a_out@ = selected attributes
729  *              @const struct tty_attr *a@ = requested attributes
730  *              @uint32 acaps@ = terminal's attribute capability mask
731  *
732  * Returns:     ---
733  *
734  * Use:         Select the closest approximation to the requested attributes
735  *              which can be accommodated by the terminal.
736  */
737
738 static void clamp_attr(struct tty_attr *a_out,
739                        const struct tty_attr *a, uint32 acaps)
740 {
741   uint32 ff = 0, f = a ? a->f : 0, t;
742
743   /* Line attributes. */
744   t = (f&TTAF_LNMASK) >> TTAF_LNSHIFT;
745   switch (t) {
746     case TTLN_NONE:
747       break;
748     case TTLN_ULINE:
749       if (!acaps&TTACF_ULINE) t = TTLN_NONE;
750       break;
751     case TTLN_UULINE:
752       if (acaps&TTACF_UULINE) ;
753       else if (acaps&TTACF_ULINE) t = TTLN_ULINE;
754       else t = TTLN_NONE;
755       break;
756     default:
757       t = TTLN_NONE;
758   }
759   ff |= t << TTAF_LNSHIFT;
760
761   /* Text weight. */
762   t = (f&TTAF_WTMASK) >> TTAF_WTSHIFT;
763   switch (t) {
764     case TTWT_MED: break;
765     case TTWT_BOLD: if (!(acaps&TTACF_BOLD)) t = TTWT_MED; break;
766     case TTWT_DIM: if (!(acaps&TTACF_DIM)) t = TTWT_MED; break;
767     default: t = TTWT_MED; break;
768   }
769   ff |= t << TTAF_WTSHIFT;
770
771   /* Other text attributes. */
772   if (acaps&TTACF_STRIKE) ff |= f&TTAF_STRIKE;
773   if (acaps&TTACF_ITAL) ff |= f&TTAF_ITAL;
774   if (acaps&TTACF_INVV) ff |= f&TTAF_INVV;
775
776   /* Foreground and background colours. */
777   if (acaps&TTACF_FG) {
778     clamp_colours(&t, &a_out->fg,
779                   (f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a ? a->fg : 0,
780                   acaps);
781     ff |= t << TTAF_FGSPCSHIFT;
782   }
783   if (acaps&TTACF_BG) {
784     clamp_colours(&t, &a_out->bg,
785                   (f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a ? a->bg : 0,
786                   acaps);
787     ff |= t << TTAF_BGSPCSHIFT;
788   }
789
790   /* All done. */
791   a_out->f = ff; a_out->_res0 = 0;
792 }
793
794 /* --- @stupid_repeat@ --- *
795  *
796  * Arguments:   @struct tty *tty@ = control block pointer
797  *              @const struct gprintf_ops *gops, void *go@ = output
798  *                      destination
799  *              @int ch@ = character to write
800  *              @unsigned n@ = number of copies
801  *
802  * Returns:     Zero on success, %$-1$% on error.
803  *
804  * Use:         Write @n@ copies of the character @ch@ to the terminal, the
805  *              hard way.  This function tries to be reasonably efficient, by
806  *              transmitting buffers rather than exercising the output
807  *              machinery for each individual character.
808  */
809
810 static int stupid_repeat(struct tty *tty,
811                          const struct gprintf_ops *gops, void *go,
812                          int ch, unsigned n)
813 {
814   char buf[4096];
815   unsigned nn;
816   int rc;
817
818   if (n < sizeof(buf))
819     { memset(buf, ch, n); CHECK(gops->putm(go, buf, n)); }
820   else {
821     memset(buf, ch, sizeof(buf));
822     nn = sizeof(buf);
823     for (;;) {
824       CHECK(gops->putm(go, buf, nn));
825       n -= nn; if (!n) break;
826       if (n < nn) nn = n;
827     }
828   }
829   rc = 0;
830 end:
831   return (rc);
832 }
833
834 /*----- Common machinery for %|termcap|% and %|terminfo|% -----------------*/
835
836 #if defined(HAVE_TERMINFO) ||                                           \
837     defined(HAVE_TERMCAP) ||                                            \
838     defined(HAVE_UNIBILIUM)
839
840 #if defined(HAVE_TERMINFO) || defined(HAVE_TERMCAP)
841
842 /* Global state.
843  *
844  * The `termcap' and `terminfo' functions call a user-provided function to
845  * actually send control codes to the terminal.  The bad news is that
846  * `termcap' doesn't provide any way to pass information to the output
847  * function beyond the character to be sent, and `terminfo' doesn't fix this
848  * mistake.  So we must save the necessary context as global variables.  For
849  * good measure, at least some implementations ignore errors from the output
850  * function, so we must keep track of them ourselves.  More global variables.
851  *
852  * It's worse.  Both libraries maintain significant global state of their
853  * own.  And, at least with the `ncurses' implementation, the two share the
854  * same global state.  The only thing to do is maintain a big interlock to
855  * make sure that only one is active at a time.
856  */
857 static const struct gprintf_ops *global_gops; /* output operations ... */
858 static void *global_gout;               /* and context, for @caps_putch */
859 static char global_buf[4096];           /* a big output buffer */
860 static size_t global_len;               /* length of buffer used */
861 static int global_err;                  /* error latch, zero if all ok */
862 static struct tty *global_lock = 0;     /* interlock for global state */
863
864 /* --- @caps_claim@ --- *
865  *
866  * Arguments:   ---
867  *
868  * Returns:     Zero on success, %$-1$% if already claimed.
869  *
870  * Use:         Return %$-1$% if the interlock is already held.  This is a
871  *              function to call near the beginning of initializing a new
872  *              control block, before the common global state gets
873  *              clobbered.  If initialization is successful, the caller is
874  *              expected to actually store the control block pointer in
875  *              @global_lock@ themselves.
876  */
877
878 static int caps_claim(void)
879 {
880   if (global_lock)
881     { debug("termcap/terminfo terminal already open"); return (-1); }
882   else
883     return (0);
884 }
885
886 /* --- @caps_claim@, @caps_release@ --- *
887  *
888  * Arguments:   @struct tty *tty@ = control block pointer for current lock
889  *                      holder
890  *
891  * Returns:     ---
892  *
893  * Use:         Release the lock.
894  */
895
896 static void caps_release(struct tty *tty)
897   { assert(global_lock == tty); global_lock = 0; }
898
899 /* --- @caps_putch@ --- *
900  *
901  * Arguments:   @int ch@ = character to write
902  *
903  * Returns:     Nonnegative on success, negative on failure.  (But @tputs@
904  *              ignores this.)
905  *
906  * Use:         Output the character @ch@.
907  */
908
909 static int caps_putch(int ch)
910 {
911   if (global_len >= sizeof(global_buf)) {
912     if (global_gops->putm(global_gout, global_buf, global_len))
913       global_err = -1;
914     global_len = 0;
915   }
916   global_buf[global_len++] = ch;
917   return (0);
918 }
919
920 /* --- @caps_prepout@ --- *
921  *
922  * Arguments:   @struct tty *tty@ = control block pointer (ignored)
923  *              @const struct gprintf_ops *gops, void *go@ = output
924  *                      destination
925  *
926  * Returns:     ---
927  *
928  * Use:         Prepare output to the given destination.
929  */
930
931 static void caps_prepout(struct tty *tty,
932                          const struct gprintf_ops *gops, void *go)
933   { assert(!global_len); global_gops = gops; global_gout = go; }
934
935 /* --- @caps_flush@ --- *
936  *
937  * Arguments:   @struct tty *tty@ = control block pointer (ignored)
938  *
939  * Returns:     Zero for success, %$-1$% if error pending.
940  *
941  * Use:         Flush the output buffer to the backend.  If an error is
942  *              pending, clear it and return failure.
943  */
944
945 static int caps_flush(struct tty *tty)
946 {
947   int rc = global_err;
948
949   if (global_len) {
950     if (global_gops->putm(global_gout, global_buf, global_len)) rc = -1;
951     global_len = 0;
952   }
953   global_err = 0; return (rc);
954 }
955
956 #endif
957
958 /* The list of interesting capabilities.
959  *
960  * We never actually need all of these: some are only needed if others are
961  * unavailable.  But the list isn't too huge, so we'll live with it.
962  *
963  * The main thing is that each capability has three different names: the
964  * `full' name (corresponding to a `terminfo' variable name), the `terminfo'
965  * capability name, as used in terminal descriptions, and the two-character
966  * `termcap' name.  Unibilium uses the long names, but to reduce typing, the
967  * `unibi_' prefix is omitted here.  (Annoyingly, in `ncurses', at least, the
968  * `variable' names are `secretly' macros referencing a current state, and
969  * premature expansion causes misery, so I've left the leading underscores in
970  * place as a countermeasure.)  Internally, we use the short `terminfo'
971  * names, since they generally express the most useful information in the
972  * smallest space.
973  */
974
975 #define BASICCAPS(_bool, _int, _str)                                    \
976   _str(_repeat_char, rep, rp)                                           \
977   _str(_pad_char, pad, pc) _int(_padding_baud_rate, pb, pb)             \
978   _bool(_no_pad_char, npc, NP) _bool(_xon_xoff, xon, xo)                \
979   _bool(_move_insert_mode, mir, mi) _bool(_move_standout_mode, msgr, ms)
980
981 #define ATTRCAPS(_bool, _int, _str)                                     \
982   _str(_exit_attribute_mode, sgr0, me)                                  \
983   _str(_enter_underline_mode, smul, us)                                 \
984     _str(_exit_underline_mode, rmul, ue)                                \
985   _str(_enter_italics_mode, sitm, ZH) _str(_exit_italics_mode, ritm, ZR) \
986   _str(_enter_bold_mode, bold, md) _str(_enter_dim_mode, dim, mh)       \
987   _str(_enter_reverse_mode, rev, mr)                                    \
988   COLOURCAPS(_bool, _int, _str)
989
990 #define COLOURCAPS(_bool, _int, _str)                                   \
991   _str(_set_a_foreground, setaf, AF) _str(_set_a_background, setab, AB) \
992   _str(_orig_pair, op, op) _int(_max_colors, colors, Co)
993
994 #define MODECAPS(_bool, _int, _str)                                     \
995   _str(_carriage_return, cr, cr) _str(_newline, nel, nw)                \
996   _str(_enter_am_mode, smam, SA) _str(_exit_am_mode, rmam, RA)          \
997   _str(_enter_ca_mode, smcup, ti) _str(_exit_ca_mode, rmcup, te)        \
998   _str(_cursor_normal, cnorm, vs) _str(_cursor_invisible, civis, vi)    \
999   _str(_enter_insert_mode, smir, im) _str(_exit_insert_mode, rmir, ei)  \
1000   _str(_enter_delete_mode, smdc, dm) _str(_exit_delete_mode, rmdc, ed)
1001
1002 #define MOVECAPS(_bool, _int, _str)                                     \
1003   _str(_cursor_home, home, ho)                                          \
1004   _str(_cursor_address, cup, cm)                                        \
1005   _str(_row_address, vpa, cv) _str(_column_address, hpa, ch)            \
1006   _str(_cursor_left, cub1, le) _str(_parm_left_cursor, cub, LE)         \
1007   _str(_cursor_right, cuf1, nd) _str(_parm_right_cursor, cuf, RI)       \
1008   _str(_cursor_up, cuu1, up) _str(_parm_up_cursor, cuu, UP)             \
1009   _str(_cursor_down, cud1, do) _str(_parm_down_cursor, cud, DO)
1010
1011 #define SCROLLCAPS(_bool, _int, _str)                                   \
1012   _str(_change_scroll_region, csr, cs)                                  \
1013   _str(_scroll_forward, ind, sf) _str(_parm_index, indn, SF)            \
1014   _str(_scroll_reverse, ri, sr) _str(_parm_rindex, rin, SR)
1015
1016 #define ERASECAPS(_bool, _int, _str)                                    \
1017   _str(_erase_chars, ech, ec)                                           \
1018   _str(_clr_bol, el1, cb) _str(_clr_eol, el, ce)                        \
1019   _str(_clr_eos, ed, cd) _str(_clear_screen, clear, cl)
1020
1021 #define INSDELCAPS(_bool, _int, _str)                                   \
1022   _str(_insert_character, ich1, ic) _str(_parm_ich, ich, IC)            \
1023   _str(_insert_padding, ip, ip)                                         \
1024   _str(_insert_line, il1, al) _str(_parm_insert_line, il, AL)           \
1025   _str(_delete_character, dch1, dc) _str(_parm_dch, dch, DC)            \
1026   _str(_delete_line, dl1, dl) _str(_parm_delete_line, dl, DL)
1027
1028 #define STORECAPS(_bool, _int, _str)                                    \
1029   BASICCAPS(_bool, _int, _str)                                          \
1030   ATTRCAPS(_bool, _int, _str)                                           \
1031   MODECAPS(_bool, _int, _str)                                           \
1032   MOVECAPS(_bool, _int, _str)                                           \
1033   SCROLLCAPS(_bool, _int, _str)                                         \
1034   ERASECAPS(_bool, _int, _str)                                          \
1035   INSDELCAPS(_bool, _int, _str)
1036
1037 #ifdef HAVE_UNIBILIUM
1038 #  define UNIBI_(x) unibi##x
1039 #else
1040 #  define UNIBI_(x) 0
1041 #endif
1042
1043 #define CAPREF(var, info, cap) UNIBI_(var), #info, #cap
1044   /* Expand a capability triple into a group of three usable C arguments.  If
1045    * Unibilium isn't available, then we can use nonsense for its cap index.
1046    */
1047
1048 /* Some other capabilities which we want to refer to during initialization,
1049  * but don't need to keep around.
1050  */
1051 #define CAP_XMC CAPREF(_magic_cookie_glitch, xmc, sg)
1052 #define CAP_BCE CAPREF(_back_color_erase, bce, ut)
1053 #define CAP_XHPA CAPREF(_row_addr_glitch, xvpa, YD)
1054 #define CAP_XVPA CAPREF(_col_addr_glitch, xhpa, YA)
1055 #define CAP_AM CAPREF(_auto_right_margin, am, am)
1056 #define CAP_XENL CAPREF(_eat_newline_glitch, xenl, xn)
1057 #define CAP_HT CAPREF(_lines, lines, li)
1058 #define CAP_WD CAPREF(_columns, cols, co)
1059
1060 /* Additional operations required of terminal backends which make use of the
1061  * common capability machinery.
1062  */
1063 struct tty_capopslots {
1064
1065   /* Retrieving capabilities. */
1066   int (*boolcap)(struct tty */*tty*/,
1067                  int /*uix*/, const char */*info*/, const char */*cap*/);
1068   int (*intcap)(struct tty */*tty*/,
1069                 int /*uix*/, const char */*info*/, const char */*cap*/);
1070   const char *(*strcap)(struct tty */*tty*/,
1071                         int /*uix*/, const char */*info*/,
1072                         const char */*cap*/);
1073
1074   /* Preparing and completing output. */
1075   void (*prepout)(struct tty */*tty*/,
1076                   const struct gprintf_ops */*gops*/, void */*go*/);
1077   int (*flush)(struct tty */*tty*/);
1078
1079   /* Writing capabilities with various kinds of arguments. */
1080   int (*put0)(struct tty */*tty*/, unsigned /*npad*/, const char */*cap*/);
1081   int (*put1i)(struct tty */*tty*/,
1082                unsigned /*npad*/, const char */*cap*/, int /*i0*/);
1083   int (*put2i)(struct tty */*tty*/,
1084                unsigned /*npad*/,
1085                const char */*cap*/, int /*i0*/, int /*i1*/);
1086 };
1087 #define TTY_CAPOPSPFX TTY_BASEOPSPFX; struct tty_capopslots cap
1088 struct tty_capops { TTY_CAPOPSPFX; };
1089 #define TTY_CAPOPSUSFX struct tty_capops cap; TTY_BASEOPSUXFX
1090 union tty_capopsu { TTY_CAPOPSUSFX; };
1091
1092 /* An extension of the control block to track the above capabilities. */
1093 struct tty_capslots {
1094 #define DEF_BOOLCAP(uix, info, cap) unsigned info : 1;
1095 #define DEF_INTCAP(uix, info, cap) int info;
1096 #define DEF_STRCAP(uix, info, cap) const char *info;
1097   STORECAPS(DEF_BOOLCAP, DEF_INTCAP, DEF_STRCAP)
1098 #undef DEF_BOOLCAP
1099 #undef DEF_INTCAP
1100 #undef DEF_STRCAP
1101 };
1102 #define TTY_CAPSPFX                                                     \
1103   struct tty tty;                                                       \
1104   struct tty_capslots cap
1105 struct tty_caps { TTY_CAPSPFX; };
1106 #define TTY_CAPSUSFX                                                    \
1107   struct tty_caps cap;                                                  \
1108   struct tty tty
1109 union tty_capsu { TTY_CAPSUSFX; };
1110
1111 /* ---- @init_caps@ --- *
1112  *
1113  * Arguments:   @struct tty *tty@ = control block pointer
1114  *
1115  * Returns:     ---
1116  *
1117  * Use:         Populate the capabilities in the terminal control block, and
1118  *              advertise the results to the public part.  Set @ht@ and @wd@
1119  *              from the terminal description if they've not been set
1120  *              already.
1121  */
1122
1123 static void init_caps(struct tty_caps *t)
1124 {
1125   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1126   int wd, ht;
1127
1128   t->tty.acaps = t->tty.ocaps = 0;
1129   t->tty.st.modes = 0; t->tty.st.attr.f = 0;
1130   t->tty.st.attr.fg = t->tty.st.attr.bg = 0;
1131
1132   /* Inhale all of the interesting terminal capabilities. */
1133 #define GETBOOL(var, info, cap_)                                        \
1134     t->cap.info = ops->cap.boolcap(&t->tty, CAPREF(var, info, cap_));
1135 #define GETINT(var, info, cap_)                                         \
1136     t->cap.info = ops->cap.intcap(&t->tty, CAPREF(var, info, cap_));
1137 #define GETSTR(var, info, cap_)                                         \
1138     t->cap.info = ops->cap.strcap(&t->tty, CAPREF(var, info, cap_));
1139   STORECAPS(GETBOOL, GETINT, GETSTR)
1140 #undef GETBOOL
1141 #undef GETINT
1142 #undef GETSTR
1143
1144 #define CLEAR_BOOL(uix, info, cap_) t->cap.info = 0;
1145 #define CLEAR_INT(uix, info, cap_) t->cap.info = 0;
1146 #define CLEAR_STR(uix, info, cap_) t->cap.info = 0;
1147 #define CLEAR_CAPS(caplist)                                             \
1148     do { caplist(CLEAR_BOOL, CLEAR_INT, CLEAR_STR) } while (0)
1149
1150   /* Basic capabilities. */
1151   if (!t->cap.cr) t->cap.cr = "\r";
1152   if (!t->cap.nel) t->cap.nel = "\r\n";
1153
1154   /* Attribute capabilities. */
1155   if (ops->cap.intcap(&t->tty, CAP_XMC) > 0 || !t->cap.sgr0)
1156     CLEAR_CAPS(ATTRCAPS);
1157   else {
1158     if (t->cap.smul) t->tty.acaps |= TTACF_ULINE;
1159     if (t->cap.bold) t->tty.acaps |= TTACF_BOLD;
1160     if (t->cap.dim) t->tty.acaps |= TTACF_DIM;
1161     if (t->cap.sitm) t->tty.acaps |= TTACF_ITAL;
1162     if (t->cap.rev) t->tty.acaps |= TTACF_INVV;
1163
1164     if (!t->cap.op || (!t->cap.setaf && !t->cap.setab))
1165       CLEAR_CAPS(COLOURCAPS);
1166     else {
1167       if (t->cap.setaf) t->tty.acaps |= TTACF_FG;
1168       if (t->cap.setab) t->tty.acaps |= TTACF_BG;
1169       t->tty.acaps |= TTACF_1BPC;
1170       if (t->cap.colors >= 16777216)
1171         t->tty.acaps |= TTACF_1BPC | TTACF_8BPC;
1172       else if (t->cap.colors >= 256)
1173         t->tty.acaps |= TTACF_1BPCBR | TTACF_6LPC | TTACF_24LGS;
1174       else if (t->cap.colors == 88)
1175         t->tty.acaps |= TTACF_1BPCBR | TTACF_4LPC | TTACF_8LGS;
1176       else if (t->cap.colors >= 16)
1177         t->tty.acaps |= TTACF_1BPCBR;
1178       if (ops->cap.boolcap(&t->tty, CAP_BCE)) t->tty.ocaps |= TTCF_BGER;
1179       env_colour_caps(&t->tty.acaps, 0);
1180     }
1181   }
1182
1183   /* Motion capabilities. */
1184   if (ops->cap.boolcap(&t->tty, CAP_XHPA)) t->cap.hpa = 0;
1185   if (ops->cap.boolcap(&t->tty, CAP_XVPA)) t->cap.vpa = 0;
1186   if (!t->cap.cub1) t->cap.cub1 = "\b";
1187   if ((t->cap.cuf || t->cap.cuf1) &&
1188       (t->cap.cuu || t->cap.cuu1) &&
1189       (t->cap.cud || t->cap.cud1)) {
1190     t->tty.ocaps |= TTCF_RELMV;
1191     if (t->cap.vpa) t->tty.ocaps |= TTCF_ABSMV | TTCF_MIXMV;
1192     else if (t->cap.home) t->tty.ocaps |= TTCF_ABSMV;
1193   } else if (t->cap.cup ||
1194              (t->cap.hpa && t->cap.vpa) ||
1195              (t->cap.home && (t->cap.cuf || t->cap.cuf1)))
1196     t->tty.ocaps |= TTCF_ABSMV;
1197
1198   /* Mode capabilities. */
1199   if (t->cap.smam && t->cap.rmam) t->tty.ocaps |= TTMF_AUTOM;
1200   if (t->cap.smcup && t->cap.rmcup) t->tty.ocaps |= TTMF_FSCRN;
1201   if (t->cap.smir && t->cap.rmir) t->tty.ocaps |= TTMF_INS;
1202   if (t->cap.smdc && t->cap.rmdc) t->tty.ocaps |= TTMF_DEL;
1203   if (t->cap.cnorm && t->cap.civis)
1204     { t->tty.ocaps |= TTMF_CVIS; t->tty.st.modes |= TTMF_CVIS; }
1205   if (ops->cap.boolcap(&t->tty, CAP_AM)) {
1206     t->tty.st.modes |= TTMF_AUTOM;
1207     if (ops->cap.boolcap(&t->tty, CAP_XENL)) t->tty.ocaps |= TTCF_MMARG;
1208   }
1209
1210   /* Erasure. */
1211   if (t->cap.ech) t->tty.ocaps |= TTCF_ERCH;
1212   if (t->cap.el1) t->tty.ocaps |= TTCF_ERBOL;
1213   if (t->cap.el) t->tty.ocaps |= TTCF_EREOL;
1214   if (t->cap.ed) t->tty.ocaps |= TTCF_EREOD;
1215   if (t->cap.clear || (t->cap.ed && t->cap.home)) t->tty.ocaps |= TTCF_ERDSP;
1216
1217   /* Insertion and deletion. */
1218   if (t->cap.ich || t->cap.ich1) t->tty.ocaps |= TTCF_INSCH;
1219   if (t->cap.il || t->cap.il1) t->tty.ocaps |= TTCF_INSLN;
1220   if (t->cap.dch || t->cap.dch1) t->tty.ocaps |= TTCF_DELCH;
1221   if (t->cap.dl || t->cap.dl1) t->tty.ocaps |= TTCF_DELLN;
1222
1223   /* Geometry. */
1224   if (!t->tty.wd)
1225     { wd = ops->cap.intcap(&t->tty, CAP_WD); if (wd > 0) t->tty.wd = wd; }
1226   if (!t->tty.ht)
1227     { ht = ops->cap.intcap(&t->tty, CAP_HT); if (ht > 0) t->tty.ht = ht; }
1228 }
1229
1230 /* Macros for formatting capabilities. */
1231 #define PUT0V(npad, cap_)                                               \
1232   CHECK(ops->cap.put0(&t->tty, (npad), (cap_)))
1233 #define PUT1IV(npad, cap_, i0)                                          \
1234   CHECK(ops->cap.put1i(&t->tty, (npad), (cap_), (i0)))
1235 #define PUT2IV(npad, cap_, i0, i1)                                      \
1236   CHECK(ops->cap.put2i(&t->tty, (npad), (cap_), (i0), (i1)))
1237
1238 #define PUT0(npad, name) PUT0V(npad, t->cap.name)
1239 #define PUT1I(npad, name, i0) PUT1IV(npad, t->cap.name, i0)
1240 #define PUT2I(npad, name, i0, i1) PUT2IV(npad, t->cap.name, i0, i1)
1241
1242 /* --- @caps_setcolour@ --- *
1243  *
1244  * Arguments:   @struct tty_caps *t@ = extended control block pointer
1245  *              
1246  */
1247 static int caps_setcolour(struct tty_caps *t,
1248                           const char *cap, uint32 spc, uint32 clr)
1249 {
1250   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1251   int rc;
1252
1253   switch (spc) {
1254     case TTCSPC_1BPC: case TTCSPC_1BPCBR: PUT1IV(0, cap, clr);       break;
1255     case TTCSPC_4LPC: case TTCSPC_6LPC:   PUT1IV(0, cap, clr +  16); break;
1256     case TTCSPC_8LGS:                     PUT1IV(0, cap, clr +  80); break;
1257     case TTCSPC_24LGS:                    PUT1IV(0, cap, clr + 232); break;
1258
1259     case TTCSPC_8BPC:
1260       /* There's an unfortunate ambiguity in the %|setaf|% conventions.  The
1261        * first eight colours should be dark shades of blue, but in fact
1262        * they're interpreted as the one-bit-per-channel basic colours by
1263        * common `terminfo' settings.  Notice and kludge by adding a little
1264        * red.  This will tinge the colour magenta, but all such colours are
1265        * so dark as to be effectively black anyway, so I doubt that this will
1266        * be noticeable.
1267        */
1268       if (spc == TTCSPC_8BPC && clr < 8) clr += 65536;
1269       PUT1IV(0, cap, clr); break;
1270
1271     /* case TTCSPC_NONE: */
1272     default: rc = -1; goto end;
1273   }
1274   rc = 0;
1275 end:
1276   return (rc);
1277 }
1278
1279 static int caps_setattr_internal(struct tty_caps *t,
1280                                  const struct tty_attr *a)
1281 {
1282   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1283   uint32 diff;
1284   int rc;
1285
1286   /* Work out what needs doing. */
1287   diff = a->f ^ t->tty.st.attr.f;
1288
1289   /* Some terminals might not be able to clear individual attributes that
1290    * they can set, and some capabilities for turning attributes on don't even
1291    * have a corresponding attribute for turning them off again individually,
1292    * so we have to use %|sgr0|% to start from scratch.  Of course, if we need
1293    * to do that, we need to restore the other active attributes, so we must
1294    * check up front.
1295    */
1296   if (((diff&TTAF_LNMASK) && !(a->f&TTAF_LNMASK) && !t->cap.rmul) ||
1297       ((diff&TTAF_WTMASK) && !(a->f&TTAF_WTMASK)) ||
1298       ((diff&~a->f&TTAF_ITAL) && !t->cap.ritm) ||
1299       (diff&~a->f&TTAF_INVV))
1300     { PUT0(0, sgr0); diff = a->f; }
1301
1302   /* Line style. */
1303   if (diff&TTAF_LNMASK)
1304     switch ((a->f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
1305       case TTLN_NONE: PUT0(0, rmul); break;
1306       case TTLN_ULINE: PUT0(0, smul); break;
1307       /* case TTLN_UULINE: */
1308       /* case TTLN_STRIKE: */
1309       default: rc = -1; goto end;
1310     }
1311
1312   /* Text weight. */
1313   if (diff&TTAF_WTMASK)
1314     switch ((a->f&TTAF_WTMASK) >> TTAF_WTSHIFT) {
1315       /* case TTWT_MED: */
1316       case TTWT_BOLD: PUT0(0, bold); break;
1317       case TTWT_DIM: PUT0(0, dim); break;
1318       default: rc = -1; goto end;
1319     }
1320
1321   /* Other text effects. */
1322   if (diff&TTAF_ITAL) {
1323     if (a->f&TTAF_ITAL) PUT0(0, sitm);
1324     else PUT0(0, ritm);
1325   }
1326   if (diff&a->f&TTAF_INVV) PUT0(0, rev);
1327
1328   /* Colours. */
1329   if (((diff&TTAF_FGSPCMASK) && !(a->f&TTAF_FGSPCMASK)) ||
1330       ((diff&TTAF_BGSPCMASK) && !(a->f&TTAF_BGSPCMASK))) {
1331     /* There's no capability string for resetting just the foreground
1332      * or background colours to the defaults, so deal with that here.
1333      */
1334
1335     PUT0(0, op);
1336     diff = (diff&~(TTAF_FGSPCMASK | TTAF_BGSPCMASK)) |
1337            (a->f&(TTAF_FGSPCMASK | TTAF_BGSPCMASK));
1338   }
1339   if ((diff&TTAF_FGSPCMASK) || a->fg != t->tty.st.attr.fg)
1340     CHECK(caps_setcolour(t, t->cap.setaf,
1341                          (a->f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a->fg));
1342   if ((diff&TTAF_BGSPCMASK) || a->bg != t->tty.st.attr.bg)
1343     CHECK(caps_setcolour(t, t->cap.setab,
1344                          (a->f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a->bg));
1345
1346   /* All done. */
1347   rc = 0;
1348 end:
1349   t->tty.st.attr = *a; return (rc);
1350 }
1351
1352 static int caps_setattr(struct tty *tty,
1353                         const struct gprintf_ops *gops, void *go,
1354                         const struct tty_attr *a)
1355 {
1356   struct tty_caps *t = (struct tty_caps *)tty;
1357   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1358   int rc;
1359
1360   ops->cap.prepout(&t->tty, gops, go);
1361   rc = caps_setattr_internal(t, a);
1362   if (ops->cap.flush(&t->tty)) rc = -1;
1363   return (rc);
1364 }
1365
1366 static int caps_setmodes(struct tty *tty,
1367                          const struct gprintf_ops *gops, void *go,
1368                          uint32 modes_bic, uint32 modes_xor)
1369 {
1370   struct tty_caps *t = (struct tty_caps *)tty;
1371   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1372   uint32 modes, diff;
1373   const char *cap;
1374   int rc;
1375
1376   /* Figure out which modes to set. */
1377   modes = (t->tty.st.modes&~modes_bic) ^ modes_xor;
1378   diff = modes ^ t->tty.st.modes;
1379
1380   /* Prepare output. */
1381   ops->cap.prepout(&t->tty, gops, go);
1382
1383   /* Automatic margins. */
1384   if (diff&TTMF_AUTOM) {
1385     if (modes&TTMF_AUTOM) PUT0(0, smam);
1386     else PUT0(0, rmam);
1387   }
1388
1389   /* Full-screen. */
1390   if (diff&TTMF_FSCRN) {
1391     if (modes&TTMF_FSCRN) PUT0(0, smcup);
1392     else PUT0(0, rmcup);
1393   }
1394
1395   /* Cursor visibility. */
1396   if (diff&TTMF_CVIS) {
1397     if (modes&TTMF_CVIS) PUT0(0, civis);
1398     else PUT0(0, cnorm);
1399   }
1400
1401   /* Auto-insert. */
1402   if (diff&TTMF_INS) {
1403     cap = modes&TTMF_INS ? t->cap.smir : t->cap.rmir;
1404     if (cap) PUT0V(0, cap);
1405     else if (!t->cap.ich) { rc = -1; goto end; }
1406   }
1407
1408   /* Delete characters. */
1409   if (diff&TTMF_DEL) {
1410     cap = modes&TTMF_DEL ? t->cap.smdc : t->cap.rmdc;
1411     if (cap) PUT0V(0, cap);
1412     else if (!t->cap.dch && !t->cap.dch1) { rc = -1; goto end; }
1413   }
1414
1415   /* Done. */
1416   rc = 0;
1417 end:
1418   if (ops->cap.flush(&t->tty)) rc = -1;
1419   t->tty.st.modes = modes; return (rc);
1420 }
1421
1422 #define CIF_PADMUL 1u
1423 static int caps_iterate(struct tty_caps *t,
1424                         const char *cap1, const char *capn,
1425                         unsigned f, unsigned npad, unsigned n)
1426 {
1427   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1428   unsigned max, nn;
1429   int rc;
1430
1431   if (cap1 && (n == 1 || !capn))
1432     while (n--) PUT0V(npad, cap1);
1433   else {
1434     max = npad && (f&CIF_PADMUL) ? INT_MAX/npad : INT_MAX;
1435     while (n) {
1436       nn = n; if (nn > max) nn = max;
1437       PUT1IV(npad, capn, nn);
1438       n -= nn;
1439     }
1440   }
1441   rc = 0;
1442 end:
1443   return (rc);
1444 }
1445
1446 static int caps_move_relative(struct tty_caps *t,
1447                               int delta,
1448                               const char *fw1, const char *fwn,
1449                               const char *rv1, const char *rvn)
1450 {
1451   const char *mv1, *mvn;
1452
1453   if (!delta) return (0);
1454   else if (delta > 0) { mv1 = fw1; mvn = fwn; }
1455   else { mv1 = rv1; mvn = rvn; delta = - delta; }
1456   return (caps_iterate(t, mv1, mvn, 0, 0, delta));
1457 }
1458
1459 static int caps_move(struct tty *tty,
1460                      const struct gprintf_ops *gops, void *go,
1461                      unsigned orig, int y, int x)
1462 {
1463   struct tty_caps *t = (struct tty_caps *)tty;
1464   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1465   struct tty_attr a;
1466   int rc;
1467
1468   ops->cap.prepout(&t->tty, gops, go);
1469
1470   if (!t->cap.mir && (t->tty.st.modes&TTMF_INS)) PUT0(0, rmir);
1471
1472   a = t->tty.st.attr;
1473   if (!t->cap.msgr && a.f) { PUT0(0, sgr0); t->tty.st.attr.f = 0; }
1474
1475   switch (orig) {
1476     case TTORG_HOME:
1477       if (t->cap.home && !x && !y)
1478         PUT0(1, home);
1479       else if (t->cap.cup)
1480         PUT2I(1, cup, y, x);
1481       else if (t->cap.vpa) {
1482         PUT1I(1, vpa, y);
1483         if (t->cap.hpa)
1484           PUT1I(1, hpa, x);
1485         else {
1486           PUT0(1, cr);
1487           CHECK(caps_move_relative(t, x,
1488                                    t->cap.cuf1, t->cap.cuf,
1489                                    t->cap.cub1, t->cap.cub));
1490         }
1491       } else if (t->cap.home) {
1492         PUT0(1, home);
1493         CHECK(caps_iterate(t, t->cap.cud1, t->cap.cud, 0, 1, y));
1494         CHECK(caps_iterate(t, t->cap.cuf1, t->cap.cuf, 0, 1, x));
1495       } else
1496         { rc = -1; goto end; }
1497       break;
1498
1499     case TTORG_CUR:
1500       CHECK(caps_move_relative(t, y,
1501                                t->cap.cud1, t->cap.cud,
1502                                t->cap.cuu1, t->cap.cuu));
1503       CHECK(caps_move_relative(t, x,
1504                                t->cap.cuf1, t->cap.cuf,
1505                                t->cap.cub1, t->cap.cub));
1506       break;
1507
1508     case TTOF_XHOME | TTOF_YCUR:
1509       if (x == 0 && y == 1)
1510         PUT0(1, nel);
1511       else {
1512         CHECK(caps_move_relative(t, y,
1513                                  t->cap.cud1, t->cap.cud,
1514                                  t->cap.cuu1, t->cap.cuu));
1515         if (t->cap.hpa && x)
1516           PUT1I(1, hpa, x);
1517         else {
1518           PUT0(1, cr);
1519           CHECK(caps_iterate(t, t->cap.cuf1, t->cap.cuf, 0, 1, x));
1520         }
1521       }
1522       break;
1523
1524     case TTOF_XCUR | TTOF_YHOME:
1525       PUT1I(1, vpa, y);
1526       CHECK(caps_move_relative(t, x,
1527                                t->cap.cuf1, t->cap.cuf,
1528                                t->cap.cub1, t->cap.cub));
1529       break;
1530
1531     default:
1532       rc = -1; goto end;
1533       break;
1534   }
1535
1536   if (!t->cap.mir && (t->tty.st.modes&TTMF_INS)) PUT0(0, smir);
1537
1538   if (!t->cap.msgr && a.f)
1539     CHECK(caps_setattr_internal(t, &a));
1540
1541   rc = 0;
1542 end:
1543   if (ops->cap.flush(&t->tty)) rc = -1;
1544   return (rc);
1545 }
1546
1547 static int caps_repeat(struct tty *tty,
1548                        const struct gprintf_ops *gops, void *go,
1549                        int ch, unsigned n)
1550 {
1551   struct tty_caps *t = (struct tty_caps *)tty;
1552   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1553   unsigned wd, nn;
1554   int rc;
1555
1556   ops->cap.prepout(&t->tty, gops, go);
1557   if (!t->cap.rep)
1558     CHECK(stupid_repeat(tty, gops, go, ch, n));
1559   else {
1560     wd = t->tty.wd;
1561     while (n) {
1562       nn = n; if (nn > INT_MAX) nn = INT_MAX;
1563       PUT2I((nn + wd - 1)/wd, rep, ch, nn);
1564       n -= nn;
1565     }
1566   }
1567   rc = 0;
1568 end:
1569   if (ops->cap.flush(&t->tty)) rc = -1;
1570   return (rc);
1571 }
1572
1573 static int caps_erase(struct tty *tty,
1574                       const struct gprintf_ops *gops, void *go,
1575                       unsigned f)
1576 {
1577   struct tty_caps *t = (struct tty_caps *)tty;
1578   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1579   int rc;
1580
1581   ops->cap.prepout(&t->tty, gops, go);
1582   if (f&TTEF_DSP)
1583     switch (f&(TTEF_BEGIN | TTEF_END)) {
1584       case 0:
1585         break;
1586       case TTEF_BEGIN | TTEF_END:
1587         if (t->cap.clear) PUT0(t->tty.ht, clear);
1588         else { PUT0(1, home); PUT0(t->tty.ht, ed); }
1589         break;
1590       case TTEF_END:
1591         PUT0(t->tty.ht, ed);
1592         break;
1593       default:
1594         rc = -1; goto end;
1595         break;
1596     }
1597   else {
1598     if (f&TTEF_BEGIN) PUT0(1, el1);
1599     if (f&TTEF_END) PUT0(1, el);
1600   }
1601   rc = 0;
1602 end:
1603   if (ops->cap.flush(&t->tty)) rc = -1;
1604   return (rc);
1605 }
1606
1607 static int caps_erch(struct tty *tty,
1608                      const struct gprintf_ops *gops, void *go,
1609                      unsigned n)
1610 {
1611   struct tty_caps *t = (struct tty_caps *)tty;
1612   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1613   int rc;
1614
1615   ops->cap.prepout(&t->tty, gops, go);
1616   if (n) PUT1I(1, ech, n);
1617   rc = 0;
1618 end:
1619   if (ops->cap.flush(&t->tty)) rc = -1;
1620   return (rc);
1621 }
1622
1623 static int caps_ins(struct tty *tty,
1624                     const struct gprintf_ops *gops, void *go,
1625                     unsigned f, unsigned n)
1626 {
1627   struct tty_caps *t = (struct tty_caps *)tty;
1628   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1629   int rc;
1630
1631   ops->cap.prepout(&t->tty, gops, go);
1632   if (f&TTIDF_LN)
1633     CHECK(caps_iterate(t, t->cap.il1, t->cap.il, CIF_PADMUL, 1, n));
1634   else
1635     CHECK(caps_iterate(t, t->cap.ich1, t->cap.ich, 0, 1, n));
1636   rc = 0;
1637 end:
1638   if (ops->cap.flush(&t->tty)) rc = -1;
1639   return (rc);
1640 }
1641
1642 static int caps_inch(struct tty *tty,
1643                      const struct gprintf_ops *gops, void *go,
1644                      int ch)
1645 {
1646   struct tty_caps *t = (struct tty_caps *)tty;
1647   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1648   int rc;
1649
1650   ops->cap.prepout(&t->tty, gops, go);
1651   if (t->cap.smir ? !(t->tty.st.modes&TTMF_INS) : !t->cap.ich)
1652     { rc = -1; goto end; }
1653   if (t->cap.ich) PUT0(1, ich);
1654   CHECK(gops->putch(go, ch));
1655   if (t->cap.ip) PUT0(1, ip);
1656   rc = 0;
1657 end:
1658   if (ops->cap.flush(&t->tty)) rc = -1;
1659   return (rc);
1660 }
1661
1662 static int caps_del(struct tty *tty,
1663                     const struct gprintf_ops *gops, void *go,
1664                     unsigned f, unsigned n)
1665 {
1666   struct tty_caps *t = (struct tty_caps *)tty;
1667   const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
1668   int rc;
1669
1670   ops->cap.prepout(&t->tty, gops, go);
1671   if (n) {
1672     if (f&TTIDF_LN)
1673       CHECK(caps_iterate(t, t->cap.dl1, t->cap.dl, CIF_PADMUL, 1, n));
1674     else
1675       CHECK(caps_iterate(t, t->cap.dch1, t->cap.dch, 0, 1, n));
1676   }
1677   rc = 0;
1678 end:
1679   if (ops->cap.flush(&t->tty)) rc = -1;
1680   return (rc);
1681 }
1682
1683 #undef PUT0V
1684 #undef PUT1IV
1685 #undef PUT2IV
1686 #undef PUT0
1687 #undef PUT1I
1688 #undef PUT2I
1689
1690 #define TTY_CAPOPS                                                      \
1691   caps_setattr, caps_setmodes,                                          \
1692   caps_move, caps_repeat,                                               \
1693   caps_erase, caps_erch, caps_ins, caps_inch, caps_del
1694
1695 #endif
1696
1697 /*----- Termcap -----------------------------------------------------------*/
1698
1699 #ifdef HAVE_TERMCAP
1700
1701 struct tty_termcapslots {
1702   char termbuf[4096], capbuf[4096], *capcur;
1703 };
1704 struct tty_termcap { TTY_CAPSPFX; struct tty_termcapslots tc; };
1705 union tty_termcapu { struct tty_termcap tc; TTY_CAPSUSFX; };
1706
1707 static int termcap_boolcap(struct tty *tty,
1708                            int uix, const char *info, const char *cap)
1709 {
1710   int p;
1711
1712   p = tgetflag(cap); assert(p >= 0);
1713   return (p);
1714 }
1715
1716 static int termcap_intcap(struct tty *tty,
1717                           int uix, const char *info, const char *cap)
1718 {
1719   int n;
1720
1721   n = tgetnum(cap); assert(n >= -1);
1722   return (n);
1723 }
1724
1725 static const char *termcap_strcap(struct tty *tty,
1726                                   int uix, const char *info,
1727                                   const char *cap)
1728 {
1729   struct tty_termcap *t = (struct tty_termcap *)tty;
1730   const char *p;
1731
1732   p = tgetstr(cap, &t->tc.capcur); assert(p != (const char *)-1);
1733   return (p);
1734 }
1735
1736 static int termcap_put0(struct tty *tty,
1737                         unsigned npad, const char *cap)
1738 {
1739   if (!cap) return (-1);
1740   return (tputs(cap, npad, caps_putch));
1741 }
1742
1743 static int termcap_put1i(struct tty *tty,
1744                          unsigned npad, const char *cap, int i0)
1745 {
1746   if (!cap) return (-1);
1747   return (tputs(tgoto(cap, -1, i0), npad, caps_putch) == OK ? 0 : -1);
1748 }
1749
1750 static int termcap_put2i(struct tty *tty,
1751                          unsigned npad, const char *cap, int i0, int i1)
1752 {
1753   if (!cap) return (-1);
1754   return (tputs(tgoto(cap, i1, i0), npad, caps_putch) == OK ? 0 : -1);
1755 }
1756
1757 static const union tty_capopsu termcap_ops = { {
1758   { caps_release, TTY_CAPOPS },
1759   { termcap_boolcap, termcap_intcap, termcap_strcap,
1760     caps_prepout, caps_flush,
1761     termcap_put0, termcap_put1i, termcap_put2i }
1762 } };
1763
1764 static struct tty *termcap_init(FILE *fp)
1765 {
1766   union tty_termcapu *u = 0; struct tty *ret = 0;
1767   const char *term;
1768
1769   if (caps_claim()) goto end;
1770   term = getenv("TERM"); if (!term) goto end;
1771   XNEW(u);
1772   if (tgetent(u->tc.tc.termbuf, term) < 1) goto end;
1773   u->tc.tc.capcur = u->tc.tc.capbuf;
1774   u->tty.ops = &termcap_ops.tty;
1775   common_init(&u->tty, fp);
1776   init_caps(&u->cap);
1777   ret = &u->tty; u = 0;
1778 end:
1779   xfree(u); global_lock = ret; return (ret);
1780 }
1781
1782 #endif
1783
1784 /*----- Terminfo ----------------------------------------------------------*/
1785
1786 #ifdef HAVE_TERMINFO
1787
1788 static int terminfo_boolcap(struct tty *tty,
1789                             int uix, const char *info, const char *cap)
1790 {
1791   int p;
1792
1793   p = tigetflag(info); assert(p >= 0);
1794   return (p);
1795 }
1796
1797 static int terminfo_intcap(struct tty *tty,
1798                            int uix, const char *info, const char *cap)
1799 {
1800   int n;
1801
1802   n = tigetnum(info); assert(n >= -1);
1803   return (n);
1804 }
1805
1806 static const char *terminfo_strcap(struct tty *tty,
1807                                    int uix, const char *info,
1808                                    const char *cap)
1809 {
1810   const char *p;
1811
1812   p = tigetstr(info); assert(p != (const char *)-1);
1813   return (p);
1814 }
1815
1816 static int terminfo_put0(struct tty *tty,
1817                          unsigned npad, const char *cap)
1818 {
1819   if (!cap) return (-1);
1820   return (tputs(cap, npad, caps_putch));
1821 }
1822
1823 static int terminfo_put1i(struct tty *tty,
1824                           unsigned npad, const char *cap, int i0)
1825 {
1826   if (!cap) return (-1);
1827   return (tputs(tparm(cap, i0), npad, caps_putch) == OK ? 0 : -1);
1828 }
1829
1830 static int terminfo_put2i(struct tty *tty,
1831                           unsigned npad, const char *cap, int i0, int i1)
1832 {
1833   if (!cap) return (-1);
1834   return (tputs(tparm(cap, i0, i1), npad, caps_putch) == OK ? 0 : -1);
1835 }
1836
1837 static const union tty_capopsu terminfo_ops = { {
1838   { caps_release, TTY_CAPOPS },
1839   { terminfo_boolcap, terminfo_intcap, terminfo_strcap,
1840     caps_prepout, caps_flush,
1841     terminfo_put0, terminfo_put1i, terminfo_put2i }
1842 } };
1843
1844 static struct tty *terminfo_init(FILE *fp)
1845 {
1846   union tty_capsu *u = 0; struct tty *ret = 0;
1847   int err;
1848
1849   if (caps_claim()) goto end;
1850   if (setupterm(0, fp ? fileno(fp) : -1, &err) != OK || err < 1) goto end;
1851   XNEW(u);
1852   u->tty.ops = &terminfo_ops.tty;
1853   common_init(&u->tty, fp);
1854   init_caps(&u->cap);
1855   ret = &u->tty; u = 0;
1856 end:
1857   xfree(u); global_lock = ret; return (ret);
1858 }
1859
1860 #endif
1861
1862 /*----- Unibilium ---------------------------------------------------------*/
1863
1864 #ifdef HAVE_UNIBILIUM
1865
1866 struct tty_unibislots {
1867   unibi_term *ut;
1868   unibi_var_t dy[26], st[26];
1869   const struct gprintf_ops *gops; void *go;
1870   char buf[4096]; size_t n;
1871   int err;
1872 };
1873 struct tty_unibilium { TTY_CAPSPFX; struct tty_unibislots u; };
1874 union tty_unibiliumu { struct tty_unibilium u; TTY_CAPSUSFX; };
1875
1876 static int termunibi_boolcap(struct tty *tty,
1877                             int uix, const char *info, const char *cap)
1878 {
1879   struct tty_unibilium *t = (struct tty_unibilium *)tty;
1880
1881   return (unibi_get_bool(t->u.ut, uix));
1882 }
1883
1884 static int termunibi_intcap(struct tty *tty,
1885                            int uix, const char *info, const char *cap)
1886 {
1887   struct tty_unibilium *t = (struct tty_unibilium *)tty;
1888
1889   return (unibi_get_num(t->u.ut, uix));
1890 }
1891
1892 static const char *termunibi_strcap(struct tty *tty,
1893                                    int uix, const char *info,
1894                                    const char *cap)
1895 {
1896   struct tty_unibilium *t = (struct tty_unibilium *)tty;
1897
1898   return (unibi_get_str(t->u.ut, uix));
1899 }
1900
1901 struct termunibi_outctx {
1902   struct tty_unibilium *t;
1903 };
1904
1905 static void termunibi_putm(void *ctx, const char *p, size_t sz)
1906 {
1907   struct tty_unibilium *t = ctx;
1908   size_t n;
1909
1910   n = sizeof(t->u.buf) - t->u.n;
1911   if (sz <= n)
1912     { memcpy(t->u.buf + t->u.n, p, sz); t->u.n += sz; }
1913   else {
1914     if (n) { memcpy(t->u.buf + t->u.n, p, n); p += n; sz -= n; }
1915     for (;;) {
1916       if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf)))
1917         t->u.err = -1;
1918       if (sz <= sizeof(t->u.buf)) break;
1919       memcpy(t->u.buf, p, sizeof(t->u.buf));
1920         p += sizeof(t->u.buf); sz -= sizeof(t->u.buf);
1921     }
1922     memcpy(t->u.buf, p, sz); t->u.n = sz;
1923   }
1924 }
1925
1926 static void termunibi_pad(void *ctx, size_t ms, int mulp, int forcep)
1927 {
1928   struct tty_unibilium *t = ctx;
1929   struct timeval tv;
1930   int pc;
1931   size_t sz, n;
1932
1933   /* Based on 7 data bits, 1 stop bit, 1 parity bit. */
1934 #define BITS_PER_KB 9000
1935
1936   if (forcep || (t->tty.baud >= t->cap.pb && !t->cap.xon)) {
1937     if (t->cap.npc) {
1938       tv.tv_sec = ms/1000; tv.tv_usec = 1000*(ms%1000);
1939       if (t->u.n) {
1940         if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf)))
1941           t->u.err = -1;
1942         t->u.n = 0;
1943       }
1944       if (t->tty.fpout) fflush(t->tty.fpout);
1945       select(0, 0, 0, 0, &tv);
1946     } else {
1947       pc = t->cap.pad ? *t->cap.pad : 0;
1948       sz = (ms*t->tty.baud + BITS_PER_KB - 1)/BITS_PER_KB;
1949       n = sizeof(t->u.buf) - t->u.n;
1950       if (sz <= n)
1951         { memset(t->u.buf + t->u.n, pc, sz); t->u.n += sz; }
1952       else {
1953         if (n) { memset(t->u.buf + t->u.n, pc, sz); sz -= n; }
1954         if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf)))
1955           t->u.err = -1;
1956         if (sz < sizeof(t->u.buf))
1957           memset(t->u.buf, pc, sz);
1958         else {
1959           memset(t->u.buf, pc, sizeof(t->u.buf));
1960           do {
1961             if (t->u.gops->putm(t->u.go, t->u.buf, sizeof(t->u.buf)))
1962               t->u.err = -1;
1963             sz -= sizeof(t->u.buf);
1964           } while (sz > sizeof(t->u.buf));
1965         }
1966         t->u.n = sz;
1967       }
1968     }
1969   }
1970
1971 #undef BITS_PER_KB
1972 }
1973
1974 static void termunibi_prepout(struct tty *tty,
1975                               const struct gprintf_ops *gops, void *go)
1976 {
1977   struct tty_unibilium *t = (struct tty_unibilium *)tty;
1978
1979   assert(!t->u.n); t->u.gops = gops; t->u.go = go;
1980 }
1981
1982 static int termunibi_flush(struct tty *tty)
1983 {
1984   struct tty_unibilium *t = (struct tty_unibilium *)tty;
1985   int rc = t->u.err;
1986
1987   if (t->u.n) {
1988     if (t->u.gops->putm(t->u.go, t->u.buf, t->u.n)) rc = -1;
1989     t->u.n = 0;
1990   }
1991   t->u.err = 0; return (rc);
1992 }
1993
1994 static int termunibi_put0(struct tty *tty,
1995                           unsigned npad, const char *cap)
1996 {
1997   struct tty_unibilium *t = (struct tty_unibilium *)tty;
1998   unibi_var_t arg[9];
1999
2000   if (!cap) return (-1);
2001   unibi_format(t->u.dy, t->u.st, cap, arg,
2002                termunibi_putm, t,
2003                termunibi_pad, t);
2004   return (0);
2005 }
2006
2007 static int termunibi_put1i(struct tty *tty,
2008                            unsigned npad, const char *cap, int i0)
2009 {
2010   struct tty_unibilium *t = (struct tty_unibilium *)tty;
2011   unibi_var_t arg[9];
2012
2013   if (!cap) return (-1);
2014   arg[0] = unibi_var_from_num(i0);
2015   unibi_format(t->u.dy, t->u.st, cap, arg,
2016                termunibi_putm, t,
2017                termunibi_pad, t);
2018   return (0);
2019 }
2020
2021 static int termunibi_put2i(struct tty *tty,
2022                            unsigned npad,
2023                            const char *cap, int i0, int i1)
2024 {
2025   struct tty_unibilium *t = (struct tty_unibilium *)tty;
2026   unibi_var_t arg[9];
2027
2028   if (!cap) return (-1);
2029   arg[0] = unibi_var_from_num(i0);
2030   arg[1] = unibi_var_from_num(i1);
2031   unibi_format(t->u.dy, t->u.st, cap, arg,
2032                termunibi_putm, t,
2033                termunibi_pad, t);
2034   return (0);
2035 }
2036
2037 static void termunibi_release(struct tty *tty)
2038 {
2039   struct tty_unibilium *t = (struct tty_unibilium *)tty;
2040
2041   unibi_destroy(t->u.ut);
2042 }
2043
2044 static const union tty_capopsu termunibi_ops = { {
2045   { termunibi_release, TTY_CAPOPS },
2046   { termunibi_boolcap, termunibi_intcap, termunibi_strcap,
2047     termunibi_prepout, termunibi_flush,
2048     termunibi_put0, termunibi_put1i, termunibi_put2i }
2049 } };
2050
2051 static struct tty *termunibi_init(FILE *fp)
2052 {
2053   union tty_unibiliumu *u = 0; struct tty *ret = 0;
2054   unibi_term *ut = 0;
2055   const char *term;
2056
2057   term = getenv("TERM"); if (!term) goto end;
2058   ut = unibi_from_term(term); if (!ut) goto end;
2059   XNEW(u);
2060   u->tty.ops = &termunibi_ops.tty;
2061   u->u.u.ut = ut; ut = 0;
2062   u->u.u.n = 0; u->u.u.err = 0;
2063   common_init(&u->tty, fp);
2064   init_caps(&u->cap);
2065   ret = &u->tty; u = 0;
2066 end:
2067   xfree(u); if (ut) unibi_destroy(ut);
2068   return (ret);
2069 }
2070
2071 #endif
2072
2073 /*----- ANSI terminals ----------------------------------------------------*/
2074
2075 struct tty_ansislots {
2076   unsigned f;
2077 #define TAF_CNCATTR 1u                  /*   attributes can be cancelled */
2078 #define TAF_EDITN 2u                    /*   insert/delete multiple */
2079 #define TAF_SEMI 4u                     /*   semicolons in CSI 38 m colour */
2080 };
2081 struct tty_ansi { TTY_BASEPFX; struct tty_ansislots ansi; };
2082 union tty_ansiu { struct tty_ansi ansi; TTY_BASEUSFX; };
2083
2084 /* Control sequences.
2085  *
2086  *   * CUP: \33 [ Y ; X H               `cursor position' [vt100]
2087  *
2088  *   * CUU/CUD/CUR/CUL: \33 [ N A/B/C/D `cursor up/down/right/left'
2089  *
2090  *   * DCH: \33 [ N P                   `delete character' [vt220]
2091  *                                              (single char only in vt102?)
2092  *
2093  *   * DL: \33 [ N M                    `delete line' [vt220]
2094  *                                              (single line only in vt102?)
2095  *
2096  *   * ECH: \33 [ N X                   `erase characters' [vt220]
2097  *
2098  *   * ED: \33 [ P J                    `erase in display'
2099  *       P = 0                          erase to end-of-screen [vt100]
2100  *       P = 1                          erase from start-of-screen [vt100]
2101  *       P = 2                          erase entire screen [vt100]
2102  *
2103  *   * EL: \33 [ P K                    `erase in line'
2104  *       P = 0                          erase to end-of-line [vt100]
2105  *       P = 1                          erase from start-of-line [vt100]
2106  *       P = 2                          erase entire line [vt100]
2107  *
2108  *   * HPA/VPA: \33 [ I G/d             `horizontal/vertical position
2109  *                                              absolute' [ecma48-4]
2110  *
2111  *   * ICH: \33 [ N @                   `insert character' [vt220]
2112  *                                              (single char only in vt102?)
2113  *
2114  *   * IL: \33 [ N L                    `insert line' [vt220]
2115  *                                              (single line only in vt102?)
2116  *
2117  *   * SGR: \33 [ P ; ... m             `select graphics rendition'
2118  *       P = 0                          cancel all attributes [vt100]
2119  *       P = 1                          bold [vt100]
2120  *       P = 2                          dim [ecma48-4]
2121  *       P = 3                          italics [ecma48-4]
2122  *       P = 4                          underline [vt100]
2123  *       P = 7                          inverse video [vt100]
2124  *       P = 9                          strikeout [ecma48-4]
2125  *       P = 21                         double underline [ecma48-4]
2126  *       P = 22                         cancal bold/dim [vt220]
2127  *       P = 24                         cancel underline [vt220]
2128  *       P = 27                         cancel inverse video [vt220]
2129  *       P = 30 + 4 R + 2 G + B         set 1BPC foreground [ecma48-4]
2130  *       P = 38 : 2 : ? : R : G : B     set foreground [iso8613-6]
2131  *       P = 38 : 5 : N                 set foreground [iso8613-6, xterm]
2132  *       P = 39                         cancel foreground [ecma48-4]
2133  *       P = 40--49                     as above, for background
2134  *       P = 90 + 4 R + 2 G + B         set bright 1BPC foreground [xterm]
2135  *       P = 100 + 4 R + 2 G + B        set bright 1BPC background [xterm]
2136  *
2137  *   * SM/RM: \33 [ P ; ... h/l `set/reset modes'
2138  *       M = 4                          insert [vt220]
2139  *
2140  *   * SM, RM: \33 [ ? P ; ... h/l      `set/reset private modes'
2141  *       M = 7                          auto right margin [vt100]
2142  *       M = 25                         visible cursor [vt220]
2143  *       M = 1049                       alternate screen [xterm]
2144  *
2145  *   * \33 [ P ; X ; Y t                `window manipulation'
2146  *       P = 22, X = 0                  save title and icon [xterm]
2147  *       P = 23, X = 0                  restore title and icon [xterm]
2148  */
2149
2150 static void ansi_release(struct tty *tty) { ; }
2151
2152 #define PUTCH(ch) CHECK(gops->putch(go, (ch)))
2153 #define PUTLIT(lit) CHECK(gops->putm(go, (lit), sizeof(lit) - 1))
2154 #define SEMI do {                                                       \
2155   if (!(f&TAF_SEMI)) f |= TAF_SEMI;                                     \
2156   else PUTCH(';');                                                      \
2157 } while (0)
2158
2159 static int ansi_setcolour(struct tty_ansi *t, unsigned *f_inout,
2160                           const struct gprintf_ops *gops, void *go,
2161                           int norm, int br,
2162                           uint32 spc, uint32 clr)
2163 {
2164   unsigned f = *f_inout;
2165   int rc;
2166
2167   switch (spc) {
2168     case TTCSPC_NONE:
2169       SEMI; CHECK(gprintf(gops, go, "%d", norm + 9));
2170       break;
2171     case TTCSPC_1BPC:
2172       SEMI; CHECK(gprintf(gops, go, "%d", norm + clr));
2173       break;
2174     case TTCSPC_1BPCBR:
2175       SEMI; CHECK(gprintf(gops, go, "%d", br + (clr&~TT1BPC_BRI)));
2176       break;
2177     case TTCSPC_4LPC: case TTCSPC_6LPC:
2178       SEMI;
2179       if (t->ansi.f&TAF_SEMI)
2180         CHECK(gprintf(gops, go, "%d;5;%d", norm + 8, clr + 16));
2181       else
2182         CHECK(gprintf(gops, go, "%d:5:%d", norm + 8, clr + 16));
2183       break;
2184     case TTCSPC_8LGS:
2185       SEMI;
2186       if (t->ansi.f&TAF_SEMI)
2187         CHECK(gprintf(gops, go, "%d;5;%d", norm + 8, clr + 80));
2188       else
2189         CHECK(gprintf(gops, go, "%d:5:%d", norm + 8, clr + 80));
2190       break;
2191     case TTCSPC_24LGS:
2192       SEMI;
2193       if (t->ansi.f&TAF_SEMI)
2194         CHECK(gprintf(gops, go, "%d;5;%d", norm + 8, clr + 232));
2195       else
2196         CHECK(gprintf(gops, go, "%d:5:%d", norm + 8, clr + 232));
2197       break;
2198     case TTCSPC_8BPC:
2199       SEMI;
2200       if (t->ansi.f&TAF_SEMI)
2201         CHECK(gprintf(gops, go, "%d;2;%d;%d;%d", norm + 8,
2202                       TTCOL_8BR(clr), TTCOL_8BG(clr), TTCOL_8BB(clr)));
2203       else
2204         CHECK(gprintf(gops, go, "%d:2::%d:%d:%d", norm + 8,
2205                       TTCOL_8BR(clr), TTCOL_8BG(clr), TTCOL_8BB(clr)));
2206       break;
2207     default:
2208       rc = -1; goto end;
2209   }
2210
2211   rc = 0;
2212 end:
2213   *f_inout = f; return (rc);
2214 }
2215
2216 static int ansi_setattr(struct tty *tty,
2217                         const struct gprintf_ops *gops, void *go,
2218                         const struct tty_attr *a)
2219 {
2220   struct tty_ansi *t = (struct tty_ansi *)tty;
2221   uint32 diff;
2222   int rc = 0;
2223   unsigned z, c, f = 0;
2224
2225   diff = a->f ^ t->tty.st.attr.f;
2226   if (!diff && a->fg == t->tty.st.attr.fg && a->bg == t->tty.st.attr.bg)
2227     return (0);
2228
2229   c = 0;
2230 #define CLEARP(mask) ((diff&(mask)) && !(a->f&(mask)))
2231   if (CLEARP(TTAF_LNMASK)) c += 3;
2232   if (CLEARP(TTAF_WTMASK)) c += 3;
2233   if (diff&~a->f&TTAF_INVV) c += 3;
2234   if (diff&~a->f&TTAF_STRIKE) c += 3;
2235   if (diff&~a->f&TTAF_ITAL) c += 3;
2236   if (CLEARP(TTAF_FGSPCMASK)) c += 3;
2237   if (CLEARP(TTAF_BGSPCMASK)) c += 3;
2238 #undef CLEARP
2239
2240   z = 0;
2241   switch ((a->f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
2242     case TTLN_ULINE: z += 2; break;
2243     case TTLN_UULINE: z += 3; break;
2244   }
2245   if (a->f&TTAF_WTMASK) z += 2;
2246   if (a->f&TTAF_INVV) z += 2;
2247   if (a->f&TTAF_STRIKE) z += 2;
2248   if (a->f&TTAF_ITAL) z += 2;
2249 #define COLOURCOST(col) do {                                            \
2250   switch ((a->f&TTAF_##col##SPCMASK) >> TTAF_##col##SPCSHIFT) {         \
2251     case TTCSPC_1BPC: case TTCSPC_1BPCBR: z += 3; break;                \
2252     case TTCSPC_4LPC: case TTCSPC_8LGS: z += 8; break;                  \
2253     case TTCSPC_6LPC: case TTCSPC_24LGS: z += 9; break;                 \
2254     case TTCSPC_8BPC: z += 16; break;                                   \
2255   }                                                                     \
2256 } while (0)
2257   COLOURCOST(FG); COLOURCOST(BG);
2258 #undef COLOURCOST
2259
2260   PUTLIT("\33[");
2261
2262   if (z <= c)
2263     { SEMI; diff = a->f; t->tty.st.attr.fg = t->tty.st.attr.bg = 0; }
2264
2265   if (diff&TTAF_LNMASK)
2266     switch ((a->f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
2267       case TTLN_NONE: SEMI; PUTLIT("24"); break;
2268       case TTLN_ULINE: SEMI; PUTCH('4'); break;
2269       case TTLN_UULINE: SEMI; PUTLIT("21"); break;
2270       default: rc = -1; goto end;
2271     }
2272
2273   if (diff&TTAF_WTMASK)
2274     switch ((a->f&TTAF_WTMASK) >> TTAF_WTSHIFT) {
2275       case TTWT_MED: SEMI; PUTLIT("22"); break;
2276       case TTWT_BOLD: SEMI; PUTCH('1'); break;
2277       case TTWT_DIM: SEMI; PUTCH('2'); break;
2278       default: rc = -1; goto end;
2279     }
2280
2281   if (diff&TTAF_INVV)
2282     { SEMI; if (a->f&TTAF_INVV) PUTCH('7'); else PUTLIT("27"); }
2283   if (diff&TTAF_STRIKE)
2284     { SEMI; if (a->f&TTAF_STRIKE) PUTCH('9'); else PUTLIT("29"); }
2285   if (diff&TTAF_ITAL)
2286     { SEMI; if (a->f&TTAF_ITAL) PUTCH('3'); else PUTLIT("23"); }
2287
2288   if (diff&TTAF_FGSPCMASK || a->fg != tty->st.attr.fg)
2289     CHECK(ansi_setcolour(t, &f, gops, go, 30, 90,
2290                          (a->f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a->fg));
2291   if (diff&TTAF_BGSPCMASK || a->bg != tty->st.attr.bg)
2292     CHECK(ansi_setcolour(t, &f, gops, go, 40, 100,
2293                          (a->f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a->bg));
2294
2295   PUTCH('m'); rc = 0;
2296 end:
2297   t->tty.st.attr = *a; return (rc);
2298
2299 }
2300
2301 static int ansi_setmodes(struct tty *tty,
2302                          const struct gprintf_ops *gops, void *go,
2303                          uint32 modes_bic, uint32 modes_xor)
2304 {
2305   uint32 modes, diff;
2306   int rc;
2307
2308   /* Figure out which modes to set. */
2309   modes = (tty->st.modes&~modes_bic) ^ modes_xor;
2310   diff = modes ^ tty->st.modes;
2311
2312   if (diff&TTMF_AUTOM) {
2313     if (modes&TTMF_AUTOM) PUTLIT("\33[?7h");
2314     else PUTLIT("\33[?7l");
2315   }
2316
2317   if (diff&TTMF_FSCRN) {
2318     if (modes&TTMF_FSCRN) PUTLIT("\33[?1049h\33[22;0;0t");
2319     else PUTLIT("\33[?1049l\33[23;0;0t");
2320   }
2321
2322   if (diff&TTMF_CVIS) {
2323     if (modes&TTMF_CVIS) PUTLIT("\33[?25h");
2324     else PUTLIT("\33[?25l");
2325   }
2326
2327   if (diff&TTMF_INS) {
2328     if (modes&TTMF_INS) PUTLIT("\33[4h");
2329     else PUTLIT("\33[4l");
2330   }
2331
2332   rc = 0;
2333 end:
2334   tty->st.modes = modes;
2335   return (rc);
2336 }
2337
2338 static int ansi_move(struct tty *tty,
2339                      const struct gprintf_ops *gops, void *go,
2340                      unsigned orig, int y, int x)
2341 {
2342   int rc;
2343
2344   if (orig == TTORG_HOME) {
2345     if (!x) {
2346       if (!y) PUTLIT("\33[H");
2347       else CHECK(gprintf(gops, go, "\33[%dH", y + 1));
2348     } else {
2349       if (!y) CHECK(gprintf(gops, go, "\33[;%dH", x + 1));
2350       else CHECK(gprintf(gops, go, "\33[%d,%dH", y + 1, x + 1));
2351     }
2352   } else if (orig == (TTOF_XHOME | TTOF_YCUR) && x == 0 && y == 1)
2353     PUTLIT("\r\n");
2354   else {
2355     if (!(orig&TTOF_YCUR)) CHECK(gprintf(gops, go, "\33[%dd", y + 1));
2356     else if (y == -1) PUTLIT("\33[A");
2357     else if (y < 0) CHECK(gprintf(gops, go, "\33[%dA", -y));
2358     else if (y == +1) PUTLIT("\33[B"); /* not %|^J|%! */
2359     else if (y > 1) CHECK(gprintf(gops, go, "\33[%dB", y));
2360     if (!(orig&TTOF_XCUR)) {
2361       if (!x)
2362         PUTCH('\r');
2363       else if (tty->ocaps&TTCF_MIXMV)
2364         CHECK(gprintf(gops, go, "\33[%dG", x + 1));
2365       else
2366         CHECK(gprintf(gops, go, "\r\33[%dC", x));
2367     } else {
2368       if (x == -1) PUTCH('\b');
2369       else if (x < 0) CHECK(gprintf(gops, go, "\33[%dD", -x));
2370       else if (x == +1) PUTLIT("\33[C");
2371       else if (x > 0) CHECK(gprintf(gops, go, "\33[%dC", x));
2372     }
2373   }
2374   rc = 0;
2375 end:
2376   return (rc);
2377 }
2378
2379 static int ansi_erase(struct tty *tty,
2380                       const struct gprintf_ops *gops, void *go,
2381                       unsigned f)
2382 {
2383   int rc;
2384
2385   if (f&TTEF_DSP)
2386     switch (f&(TTEF_BEGIN | TTEF_END)) {
2387       case 0: break;
2388       case TTEF_BEGIN: PUTLIT("\33[1J"); break;
2389       case TTEF_END: PUTLIT("\33[J"); break;
2390       case TTEF_BEGIN | TTEF_END: PUTLIT("\33[2J"); break;
2391     }
2392   else
2393     switch (f&(TTEF_BEGIN | TTEF_END)) {
2394       case 0: break;
2395       case TTEF_BEGIN: PUTLIT("\33[1K"); break;
2396       case TTEF_END: PUTLIT("\33[K"); break;
2397       case TTEF_BEGIN | TTEF_END: PUTLIT("\33[2K"); break;
2398     }
2399   rc = 0;
2400 end:
2401   return (rc);
2402 }
2403
2404 static int ansi_erch(struct tty *tty,
2405                      const struct gprintf_ops *gops, void *go,
2406                      unsigned n)
2407 {
2408   int rc;
2409
2410   if (n == 1) PUTLIT("\33[X");
2411   else if (n) CHECK(gprintf(gops, go, "\33[%uX", n));
2412   rc = 0;
2413 end:
2414   return (rc);
2415 }
2416
2417 static int ansi_ins(struct tty *tty,
2418                     const struct gprintf_ops *gops, void *go,
2419                     unsigned f, unsigned n)
2420 {
2421   int rc;
2422
2423   if (f&TTIDF_LN) {
2424     if (n == 1) PUTLIT("\33[L");
2425     else if (n) CHECK(gprintf(gops, go, "\33[%uL", n));
2426   } else {
2427     if (n == 1) PUTLIT("\33[@");
2428     else if (n) CHECK(gprintf(gops, go, "\33[%u@", n));
2429   }
2430   rc = 0;
2431 end:
2432   return (rc);
2433 }
2434
2435 static int ansi_inch(struct tty *tty,
2436                      const struct gprintf_ops *gops, void *go,
2437                      int ch)
2438 {
2439   if (!(tty->st.modes&TTMF_INS)) return (-1);
2440   else return (gops->putch(go, ch));
2441 }
2442
2443 static int ansi_del(struct tty *tty,
2444                     const struct gprintf_ops *gops, void *go,
2445                     unsigned f, unsigned n)
2446 {
2447   int rc;
2448
2449   if (f&TTIDF_LN) {
2450     if (n == 1) PUTLIT("\33[M");
2451     else if (n) CHECK(gprintf(gops, go, "\33[%uM", n));
2452   } else {
2453     if (n == 1) PUTLIT("\33[P");
2454     else if (n) CHECK(gprintf(gops, go, "\33[%uP", n));
2455   }
2456   rc = 0;
2457 end:
2458   return (rc);
2459 }
2460
2461 #undef PUTCH
2462 #undef PUTLIT
2463 #undef SEMI
2464
2465 #undef CHECK
2466
2467 static const struct tty_ops ansi_ops = {
2468   ansi_release,
2469   ansi_setattr, ansi_setmodes,
2470   ansi_move, stupid_repeat,
2471   ansi_erase, ansi_erch, ansi_ins, ansi_inch, ansi_del
2472 };
2473
2474 static struct tty *ansi_init(FILE *fp)
2475 {
2476 #define COLS_NO 0
2477 #define COLS_8 (TTACF_FG | TTACF_BG | TTACF_1BPC)
2478 #define COLS_16 (COLS_8 | TTACF_1BPCBR)
2479 #define COLS_88 (COLS_16 | TTACF_4LPC | TTACF_8LGS)
2480 #define COLS_256 (COLS_16 | TTACF_6LPC | TTACF_24LGS)
2481 #define COLS_16M (COLS_256 | TTACF_8BPC)
2482
2483 #define EDIT_OPS (TTCF_ERCH |                                           \
2484                   TTCF_DELCH | TTCF_DELLN |                             \
2485                   TTCF_INSCH | TTCF_INSLN)
2486
2487   static const struct flagmap {
2488     const char *name;
2489     uint32 acaps, ocaps;
2490     unsigned tf;
2491   } flagmap[] = {
2492     { "dim",            TTACF_DIM,      0,              0 },
2493     { "uuline",         TTACF_UULINE,   0,              0 },
2494     { "strike",         TTACF_STRIKE,   0,              0 },
2495     { "ital",           TTACF_ITAL,     0,              0 },
2496     { "cvis",           0,              TTMF_CVIS,      0 },
2497     { "fscrn",          0,              TTMF_FSCRN,     0 },
2498     { "insmode",        0,              TTMF_INS,       0 },
2499     { "hvpa"    ,       0,              TTCF_MIXMV,     0 },
2500     { "edit",           0,              EDIT_OPS,       0 },
2501     { "cncattr",        0,              0,              TAF_CNCATTR },
2502     { "editn",          0,              0,              TAF_EDITN },
2503     { "semi",           0,              0,              TAF_SEMI },
2504     { 0,                0,              0,              0 }
2505   };
2506
2507 #undef EDIT_OPS
2508
2509   static const struct kw { const char *name; uint32 val; }
2510     kw_colours[] = {
2511       { "no",                   COLS_NO },
2512       { "8",                    COLS_8 },
2513       { "16",                   COLS_16 },
2514       { "88",                   COLS_88 },
2515       { "256",                  COLS_256 },
2516       { "16m",                  COLS_16M },
2517       { 0,                      0 }
2518     };
2519
2520   static const struct enummap {
2521     const char *name;
2522     uint32 mask;
2523     const struct kw *kw;
2524   } enummap[] = {
2525     { "colours",                TTACF_CSPCMASK,         kw_colours },
2526     { 0,                        0,                      0 }
2527   };
2528
2529
2530   static const struct termmap {
2531     const char *pat;
2532     unsigned acaps, ocaps, tf;
2533   } termmap[] = {
2534
2535 #define VT100_ACAPS (TTACF_ULINE | TTACF_BOLD | TTACF_INVV)
2536 #define VT100_OCAPS (TTMF_AUTOM |                                       \
2537                      TTCF_RELMV | TTCF_ABSMV |                          \
2538                      TTCF_MMARG |                                       \
2539                      TTCF_ERBOL | TTCF_EREOL |                          \
2540                      TTCF_ERBOD | TTCF_EREOD | TTCF_ERDSP)
2541 #define VT100_TF (0)
2542
2543 #define VT102_ACAPS (VT100_ACAPS)
2544 #define VT102_OCAPS (VT100_OCAPS |                                      \
2545                      TTMF_INS |                                         \
2546                      TTCF_INSCH | TTCF_INSLN | TTCF_DELCH | TTCF_DELLN)
2547 #define VT102_TF (VT100_TF)
2548
2549 #define VT220_ACAPS (VT102_ACAPS)
2550 #define VT220_OCAPS (VT102_OCAPS | TTMF_CVIS | TTCF_ERCH)
2551 #define VT220_TF (VT102_TF | TAF_CNCATTR | TAF_EDITN)
2552
2553 #define ECMA48_ACAPS (VT220_ACAPS | TTACF_DIM)
2554 #define ECMA48_OCAPS (VT220_OCAPS | TTCF_MIXMV)
2555 #define ECMA48_TF (VT220_TF)
2556
2557 #define XTERM_ACAPS (ECMA48_ACAPS)
2558 #define XTERM_OCAPS (ECMA48_OCAPS | TTMF_FSCRN)
2559 #define XTERM_TF (ECMA48_TF)
2560
2561 #define STRIKE TTACF_STRIKE
2562 #define ITAL TTACF_ITAL
2563 #define SEMI TAF_SEMI
2564
2565 #define T(pat, base, cols, acaps, ocaps, tf)                            \
2566   { pat,                                                                \
2567     base##_ACAPS | COLS_##cols | (acaps),                               \
2568     base##_OCAPS | (ocaps), base##_TF | (tf) }
2569
2570     T("color_xterm",    XTERM,  8,      STRIKE | ITAL,  0,      0),
2571
2572     T("gnome",          XTERM,  16M,    STRIKE | ITAL,  0,      SEMI),
2573   /*T("gonme-*"         XTERM,  16M,    STRIKE | ITAL,  0,      SEMI),*/
2574
2575     T("linux",          XTERM,  16,     0,              0,      0),
2576
2577     T("putty",          XTERM,  16M,    0,              0,      SEMI),
2578
2579     T("vt100*",         VT100,  NO,     0,              0,      0),
2580     T("vt102*",         VT102,  NO,     0,              0,      0),
2581     T("vt[2-5][0-9][0-9]*", VT220, NO,  0,              0,      0),
2582
2583     T("vte",            XTERM,  16M,    STRIKE | ITAL,  0,      SEMI),
2584   /*T("vte-*"           XTERM,  16M,    STRIKE | ITAL,  0,      SEMI),*/
2585
2586     T("win",            XTERM,  16M,    0,              0,      SEMI),
2587
2588     T("xterm",          XTERM,  16M,    STRIKE | ITAL,  0,      0),
2589     T("xterm-color",    XTERM,  8,      STRIKE | ITAL,  0,      0),
2590     T("xterm-16color",  XTERM,  16,     STRIKE | ITAL,  0,      0),
2591     T("xterm-88color",  XTERM,  88,     STRIKE | ITAL,  0,      SEMI),
2592     T("xterm-256color", XTERM,  256,    STRIKE | ITAL,  0,      SEMI),
2593     T("xterm-direct",   XTERM,  16M,    STRIKE | ITAL,  0,      0),
2594     T("xterm-*",        XTERM,  16M,    STRIKE | ITAL,  0,      0),
2595
2596   /*T("*-color",        XTERM,  16,     0,              0,      0),*/
2597   /*T("*-16color",      XTERM,  16,     0,              0,      0),*/
2598     T("*-88color",      XTERM,  88,     0,              0,      SEMI),
2599     T("*-256color",     XTERM,  256,    0,              0,      SEMI),
2600     T("*-direct",       XTERM,  16M,    0,              0,      SEMI),
2601
2602     T("*",              XTERM,  16,     0,              0,      0),
2603     { 0,                                0,              0,      0 }
2604
2605 #undef VT100_ACAPS
2606 #undef VT100_OCAPS
2607 #undef VT100_TF
2608
2609 #undef VT102_ACAPS
2610 #undef VT102_OCAPS
2611 #undef VT102_TF
2612
2613 #undef VT220_ACAPS
2614 #undef VT220_OCAPS
2615 #undef VT220_TF
2616
2617 #undef ECMA48_ACAPS
2618 #undef ECMA48_OCAPS
2619 #undef ECMA48_TF
2620
2621 #undef XTERM_ACAPS
2622 #undef XTERM_OCAPS
2623 #undef XTERM_TF
2624
2625 #undef STRIKE
2626 #undef ITAL
2627 #undef SEMI
2628   };
2629
2630 #undef COLS_NO
2631 #undef COLS_8
2632 #undef COLS_16
2633 #undef COLS_88
2634 #undef COLS_256
2635 #undef COLS_16M
2636
2637   union tty_ansiu *u = 0; struct tty *ret = 0;
2638   const char *term, *config, *p, *l;
2639   const struct kw *kw;
2640   const struct enummap *em;
2641   const struct flagmap *fm;
2642   const struct termmap *tm;
2643   size_t n, nn;
2644   unsigned
2645     acaps = 0, ocaps = 0, tf = 0,
2646     acapset = 0, ocapset = 0, tfset = 0,
2647     f = 0;
2648 #define f_sense 1u
2649
2650   config = getenv("MLIB_TTY_ANSICONFIG");
2651   term = getenv("TERM");
2652
2653   if (term && STRCMP(term, ==, "dumb")) goto end;
2654
2655   if (config) {
2656     l = config + strlen(config);
2657     for (;;) {
2658
2659       for (;;)
2660         if (config >= l) goto done_config;
2661         else if (!ISSPACE(*config)) break;
2662         else config++;
2663
2664       for (p = config + 1; p < l && !ISSPACE(*p); p++);
2665       if (*config == '+' || *config == '-') {
2666         if (*config == '+') f |= f_sense;
2667         else f &= ~f_sense;
2668         config++; n = p - config;
2669
2670         for (fm = flagmap; fm->name; fm++)
2671           if (STRNCMP(config, ==, fm->name, n) && !fm->name[n])
2672             goto found_flag;
2673         debug("unknown flag `%.*s'", (int)n, config); goto next_config;
2674       found_flag:
2675         if ((acapset&fm->acaps) || (ocapset&fm->ocaps) || (tfset&fm->tf)) {
2676           debug("duplicate setting for `%s'", fm->name);
2677           goto next_config;
2678         }
2679         if (f&f_sense)
2680           { acaps |= fm->acaps; ocaps |= fm->ocaps; tf |= fm->tf; }
2681         acapset |= fm->acaps; ocapset |= fm->ocaps; tfset |= fm->tf;
2682       } else {
2683         n = p - config;
2684         p = memchr(config, '=', n);
2685         if (!p) {
2686           debug("missing `=' in setting `%.*s'", (int)n, config);
2687           goto next_config;
2688         }
2689         nn = p - config;
2690         for (em = enummap; em->name; em++)
2691           if (STRNCMP(config, ==, em->name, nn) && !em->name[nn])
2692             goto found_enum;
2693         debug("unknown setting `%.*s'", (int)nn, config); goto next_config;
2694       found_enum:
2695         p++; nn = n - nn - 1;
2696         for (kw = em->kw; kw->name; kw++)
2697           if (STRNCMP(p, ==, kw->name, nn) && !kw->name[nn])
2698             goto found_kw;
2699         debug("unknown `%s' value `%.*s", em->name, (int)nn, p);
2700         goto next_config;
2701       found_kw:
2702         if (acapset&em->mask) {
2703           debug("duplicate setting for `%s'", em->name);
2704           goto next_config;
2705         }
2706         acaps |= kw->val; acapset |= em->mask;
2707       }
2708
2709     next_config:
2710       config += n;
2711     }
2712   done_config:;
2713   }
2714
2715   if (term) {
2716     for (tm = termmap; tm->pat; tm++)
2717       if (str_match(tm->pat, term))
2718         goto found_term;
2719     assert(0);
2720   found_term:
2721     acaps |= tm->acaps&~acapset;
2722     ocaps |= tm->ocaps&~ocapset;
2723     tf |= tm->tf&~tfset;
2724   }
2725
2726   if (!(acapset&TTACF_CSPCMASK)) env_colour_caps(&acaps, ECCF_SET);
2727   if (acaps&TTACF_CSPCMASK) ocaps |= TTCF_BGER;
2728
2729   XNEW(u);
2730   u->tty.ops = &ansi_ops;
2731   u->tty.acaps = acaps;
2732   u->tty.ocaps = ocaps;
2733   u->ansi.ansi.f = tf;
2734   u->tty.wd = 80; u->tty.ht = 25;
2735   u->tty.st.modes = TTMF_AUTOM | (u->tty.ocaps&TTMF_CVIS);
2736   u->tty.st.attr.f = 0; u->tty.st.attr.fg = u->tty.st.attr.bg = 0;
2737   common_init(&u->ansi.tty, fp);
2738   ret = &u->tty; u = 0;
2739 end:
2740   xfree(u); return (ret);
2741
2742 #undef f_sense
2743 }
2744
2745 /*----- Backend selection -------------------------------------------------*/
2746
2747 struct tty *tty_open(FILE *fp, unsigned f, const unsigned *backends)
2748 {
2749   static const struct betab {
2750     const char *name; unsigned code;
2751     struct tty *(*init)(FILE */*fp*/);
2752   } betab[] = {
2753     { "unibilium",      TTBK_UNIBI,     termunibi_init },
2754     { "terminfo",       TTBK_TERMINFO,  terminfo_init },
2755     { "termcap",        TTBK_TERMCAP,   termcap_init },
2756     { "ansi",           TTBK_ANSI,      ansi_init },
2757     { 0,                0,              0 }
2758   };
2759
2760   const struct betab *bt;
2761   const char *config, *p, *l;
2762   struct tty *tty = 0;
2763   FILE *fpin = 0;
2764   size_t n;
2765
2766   if (fp || !(f&TTF_OPEN))
2767     fpin = fp != stdout ? fp : isatty(STDIN_FILENO) ? stdin : 0;
2768   else {
2769     if (isatty(STDIN_FILENO)) fpin = stdin;
2770     else fpin = 0;
2771     if (isatty(STDOUT_FILENO)) { fp = stdout; f |= TTF_BORROW; }
2772     else if (isatty(STDERR_FILENO)) { fp = stderr; f |= TTF_BORROW; }
2773     else {
2774       fp = fopen("/dev/tty", "r+"); if (!fp) goto end;
2775       fpin = fp; f &= ~TTF_BORROW;
2776     }
2777   }
2778
2779   config = getenv("MLIB_TTY_BACKENDS");
2780   if (config) {
2781     l = config + strlen(config);
2782     for (;;) {
2783       for (;;)
2784         if (config >= l) goto done_config;
2785         else if (!ISSPACE(*config)) break;
2786         else config++;
2787
2788       for (p = config + 1; p < l && !ISSPACE(*p); p++);
2789       n = p - config;
2790
2791       for (bt = betab; bt->name; bt++)
2792         if (STRNCMP(config, ==, bt->name, n) && !bt->name[n])
2793           goto found_byname;
2794       debug("unknown backend `%.*s'", (int)n, config); goto next_config;
2795     found_byname:
2796       tty = bt->init(fp); if (tty) goto found;
2797       debug("failed to initialize `%s'", bt->name);
2798     next_config:
2799       config += n;
2800     }
2801   done_config:;
2802   } else if (backends)
2803     while (*backends) {
2804       for (bt = betab; bt->name; bt++)
2805         if (*backends == bt->code) goto found_bycode;
2806       debug("unknown backend code %u", *backends); goto next_code;
2807     found_bycode:
2808       tty = bt->init(fp); if (tty) goto found;
2809       debug("failed to initialize `%s'", bt->name);
2810     next_code:
2811       backends++;
2812     }
2813   else
2814     for (bt = betab; bt->name; bt++) {
2815       tty = bt->init(fp); if (tty) goto found;
2816       debug("failed to initialize `%s'", bt->name);
2817     }
2818
2819   debug("all backends failed"); goto end;
2820 found:
2821   debug("selected backend `%s'", bt->name);
2822   tty->fpin = fpin; tty->f = f; fp = 0;
2823 end:
2824   if (fp && !(f&TTF_BORROW)) fclose(fp);
2825   return (tty);
2826 }
2827
2828 void tty_close(struct tty *tty)
2829 {
2830   if (tty) {
2831     if (tty->fpout && !(tty->f&TTF_BORROW)) fclose(tty->fpout);
2832     tty->ops->release(tty); xfree(tty);
2833   }
2834 }
2835
2836
2837 int tty_resized(struct tty *tty)
2838 {
2839   struct winsize ws;
2840
2841   if (!tty || !tty->fpout) { errno = ENOTTY; return (-1); }
2842   else if (ioctl(fileno(tty->fpout), TIOCGWINSZ, &ws)) return (-1);
2843   else if (tty->wd == ws.ws_col && tty->ht == ws.ws_row) return (0);
2844   else { tty->wd = ws.ws_col; tty->ht = ws.ws_row; return (1); }
2845 }
2846
2847 /*----- Terminal operations -----------------------------------------------*/
2848
2849 int tty_setattr(struct tty *tty, const struct tty_attr *a)
2850 {
2851   struct tty_attr aa;
2852
2853   if (!tty || !tty->fpout)
2854     return (-1);
2855   else {
2856     clamp_attr(&aa, a, tty->acaps);
2857     return (tty->ops->setattr(tty, &file_printops, tty->fpout, &aa));
2858   }
2859 }
2860
2861 int tty_setattrg(struct tty *tty,
2862                  const struct gprintf_ops *gops, void *go,
2863                  const struct tty_attr *a)
2864 {
2865   struct tty_attr aa;
2866
2867   if (!tty)
2868     return (-1);
2869   else {
2870     clamp_attr(&aa, a, tty->acaps);
2871     return (tty->ops->setattr(tty, gops, go, &aa));
2872   }
2873 }
2874
2875 int tty_setattrlist(struct tty *tty, const struct tty_attrlist *aa)
2876 {
2877   if (!tty || !tty->fpout) return (-1);
2878   else return (tty_setattrlistg(tty, &file_printops, tty->fpout, aa));
2879 }
2880
2881 int tty_setattrlistg(struct tty *tty,
2882                      const struct gprintf_ops *gops, void *go,
2883                      const struct tty_attrlist *aa)
2884 {
2885   if (!tty) return (-1);
2886   for (;; aa++)
2887     if ((tty->acaps&aa->cap_mask) == aa->cap_eq)
2888       return (tty->ops->setattr(tty, gops, go, &aa->attr));
2889     else if (!aa->cap_mask)
2890       return (0);
2891 }
2892
2893 int tty_setmodes(struct tty *tty, uint32 modes_bic, uint32 modes_xor)
2894 {
2895   if (!tty || !tty->fpout) return (-1);
2896   else return (tty->ops->setmodes(tty, &file_printops, tty->fpout,
2897                                   modes_bic, modes_xor));
2898 }
2899
2900 int tty_setmodesg(struct tty *tty,
2901                   const struct gprintf_ops *gops, void *go,
2902                   uint32 modes_bic, uint32 modes_xor)
2903 {
2904   if (!tty) return (-1);
2905   else return (tty->ops->setmodes(tty, gops, go, modes_bic, modes_xor));
2906 }
2907
2908 int tty_move(struct tty *tty, unsigned orig, int y, int x)
2909 {
2910   if (!tty || !tty->fpout) return (-1);
2911   else return (tty->ops->move(tty, &file_printops, tty->fpout, orig, y, x));
2912 }
2913
2914 int tty_moveg(struct tty *tty,
2915               const struct gprintf_ops *gops, void *go,
2916               unsigned orig, int y, int x)
2917 {
2918   if (!tty) return (-1);
2919   else return (tty->ops->move(tty, gops, go, orig, y, x));
2920 }
2921
2922 int tty_repeat(struct tty *tty, int ch, unsigned n)
2923 {
2924   if (!tty || !tty->fpout) return (-1);
2925   else return (tty->ops->repeat(tty, &file_printops, tty->fpout, ch, n));
2926 }
2927
2928 int tty_repeatg(struct tty *tty,
2929                 const struct gprintf_ops *gops, void *go,
2930                 int ch, unsigned n)
2931 {
2932   if (!tty) return (-1);
2933   else return (tty->ops->repeat(tty, gops, go, ch, n));
2934 }
2935
2936 int tty_erase(struct tty *tty, unsigned f)
2937 {
2938   if (!tty || !tty->fpout) return (-1);
2939   else return (tty->ops->erase(tty, &file_printops, tty->fpout, f));
2940 }
2941
2942 int tty_eraseg(struct tty *tty,
2943                const struct gprintf_ops *gops, void *go,
2944                unsigned f)
2945 {
2946   if (!tty) return (-1);
2947   else return (tty->ops->erase(tty, gops, go, f));
2948 }
2949
2950 int tty_erch(struct tty *tty, unsigned n)
2951 {
2952   if (!tty || !tty->fpout) return (-1);
2953   else return (tty->ops->erch(tty, &file_printops, tty->fpout, n));
2954 }
2955
2956 int tty_erchg(struct tty *tty,
2957               const struct gprintf_ops *gops, void *go,
2958               unsigned n)
2959 {
2960   if (!tty) return (-1);
2961   else return (tty->ops->erch(tty, gops, go, n));
2962 }
2963
2964 int tty_ins(struct tty *tty, unsigned f, unsigned n)
2965 {
2966   if (!tty || !tty->fpout) return (-1);
2967   else return (tty->ops->ins(tty, &file_printops, tty->fpout, f, n));
2968 }
2969
2970 int tty_insg(struct tty *tty,
2971              const struct gprintf_ops *gops, void *go,
2972              unsigned f, unsigned n)
2973 {
2974   if (!tty) return (-1);
2975   else return (tty->ops->ins(tty, gops, go, f, n));
2976 }
2977
2978 int tty_inch(struct tty *tty, int ch)
2979 {
2980   if (!tty || !tty->fpout) return (-1);
2981   else return (tty->ops->inch(tty, &file_printops, tty->fpout, ch));
2982 }
2983
2984 int tty_inchg(struct tty *tty,
2985               const struct gprintf_ops *gops, void *go,
2986               int ch)
2987 {
2988   if (!tty) return (-1);
2989   else return (tty->ops->inch(tty, gops, go, ch));
2990 }
2991
2992 int tty_del(struct tty *tty, unsigned f, unsigned n)
2993 {
2994   if (!tty || !tty->fpout) return (-1);
2995   else return (tty->ops->del(tty, &file_printops, tty->fpout, f, n));
2996 }
2997
2998 int tty_delg(struct tty *tty,
2999              const struct gprintf_ops *gops, void *go,
3000              unsigned f, unsigned n)
3001 {
3002   if (!tty) return (-1);
3003   else return (tty->ops->del(tty, gops, go, f, n));
3004 }
3005
3006 int tty_restore(struct tty *tty, const struct tty_state *st)
3007 {
3008   if (!tty || !tty->fpout) return (-1);
3009   else return (tty_restoreg(tty, &file_printops, tty->fpout, st));
3010 }
3011
3012 int tty_restoreg(struct tty *tty,
3013                  const struct gprintf_ops *gops, void *go,
3014                  const struct tty_state *st)
3015 {
3016   int rc;
3017
3018   if (!tty ||
3019       tty->ops->setmodes(tty, gops, go, MASK32, st->modes) ||
3020       tty->ops->setattr(tty, gops, go, &st->attr))
3021     { rc = -1; goto end; }
3022   rc = 0;
3023 end:
3024   return (rc);
3025 }
3026
3027 /*----- That's all, folks -------------------------------------------------*/