#include "config.h"
+#include <ctype.h>
#include <errno.h>
+#include <limits.h>
#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/time.h>
#include <unistd.h>
#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
#ifdef HAVE_TERMCAP
# include <termcap.h>
#endif
#ifdef HAVE_TERMINFO
+# include <curses.h>
# include <term.h>
+# undef erase
+# undef inch
+# undef move
#endif
#ifdef HAVE_UNIBILIUM
# include <unibilium.h>
#endif
+#include "alloc.h"
#include "gprintf.h"
#include "macros.h"
+#include "str.h"
#include "tty.h"
-/*----- Data structures ---------------------------------------------------*/
-
/*----- Common support machinery ------------------------------------------*/
-static uint32 basic_colour_map[] = {
- /* standard black */ 0x000000,
- /* standard red */ 0xcc0000,
- /* standard green */ 0x00cc00,
- /* standard yellow */ 0xcccc00,
- /* standard blue */ 0x0000cc,
- /* standard magenta */ 0xcc00cc,
- /* standard cyan */ 0x00cccc,
- /* standard white */ 0xcccccc
-}, bright_colour_map[] = {
- /* bright black */ 0x333333,
- /* bright red */ 0xff3333,
- /* bright green */ 0x33ff33,
- /* bright yellow */ 0xffff33,
- /* bright blue */ 0x3333ff,
- /* bright magenta */ 0xff33ff,
- /* bright cyan */ 0x33ffff,
- /* bright white */ 0xffffff
-};
+/* --- @debug@ --- *
+ *
+ * Arguments: @const char *fmt@ = format control string
+ * @...@ = format arguemnts
+ *
+ * Returns: ---
+ *
+ * Use: Maybe report a debugging message to standard error.
+ */
-static void clamp_colours(uint32 *space_out, uint32 *colour_out,
- uint32 space, uint32 colour, uint32 acaps)
+static PRINTF_LIKE(1, 2) void debug(const char *fmt, ...)
+{
+ const char *p;
+ va_list ap;
+
+ p = getenv("MLIB_TTY_DEBUG");
+ if (p && *p != 'n' && *p != '0') {
+ va_start(ap, fmt);
+ fputs("mLib TTY: ", stderr);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+ va_end(ap);
+ }
+}
+
+static void common_init(struct tty *tty, FILE *fp)
+{
+ static const struct baudtab { speed_t code; unsigned baud; } baudtab[] = {
+ /*
+ ;;; The baud-rate table is very boring to type. To make life less
+ ;;; awful, put the rates in this list and evaluate the code to get Emacs
+ ;;; to regenerate it.
+
+ (let ((bauds '(50 75 110 134 150 200 300 600 1200 1800 2400 4800 9600
+ 19200 38400 57600 115200 230400 460800 500000 576000
+ 921600 1000000 1152000 1500000 2000000 2500000 3000000
+ 3500000 4000000)))
+ (save-excursion
+ (goto-char (point-min))
+ (search-forward (concat "***" "BEGIN baudlist" "***"))
+ (beginning-of-line 2)
+ (delete-region (point)
+ (progn
+ (search-forward "***END***")
+ (beginning-of-line)
+ (point)))
+ (dolist (baud (sort (copy-list bauds) #'<))
+ (insert (format "#ifdef B%d\n { B%d, %d },\n#endif\n"
+ baud baud baud)))))
+ */
+ /***BEGIN baudlist***/
+#ifdef B50
+ { B50, 50 },
+#endif
+#ifdef B75
+ { B75, 75 },
+#endif
+#ifdef B110
+ { B110, 110 },
+#endif
+#ifdef B134
+ { B134, 134 },
+#endif
+#ifdef B150
+ { B150, 150 },
+#endif
+#ifdef B200
+ { B200, 200 },
+#endif
+#ifdef B300
+ { B300, 300 },
+#endif
+#ifdef B600
+ { B600, 600 },
+#endif
+#ifdef B1200
+ { B1200, 1200 },
+#endif
+#ifdef B1800
+ { B1800, 1800 },
+#endif
+#ifdef B2400
+ { B2400, 2400 },
+#endif
+#ifdef B4800
+ { B4800, 4800 },
+#endif
+#ifdef B9600
+ { B9600, 9600 },
+#endif
+#ifdef B19200
+ { B19200, 19200 },
+#endif
+#ifdef B38400
+ { B38400, 38400 },
+#endif
+#ifdef B57600
+ { B57600, 57600 },
+#endif
+#ifdef B115200
+ { B115200, 115200 },
+#endif
+#ifdef B230400
+ { B230400, 230400 },
+#endif
+#ifdef B460800
+ { B460800, 460800 },
+#endif
+#ifdef B500000
+ { B500000, 500000 },
+#endif
+#ifdef B576000
+ { B576000, 576000 },
+#endif
+#ifdef B921600
+ { B921600, 921600 },
+#endif
+#ifdef B1000000
+ { B1000000, 1000000 },
+#endif
+#ifdef B1152000
+ { B1152000, 1152000 },
+#endif
+#ifdef B1500000
+ { B1500000, 1500000 },
+#endif
+#ifdef B2000000
+ { B2000000, 2000000 },
+#endif
+#ifdef B2500000
+ { B2500000, 2500000 },
+#endif
+#ifdef B3000000
+ { B3000000, 3000000 },
+#endif
+#ifdef B3500000
+ { B3500000, 3500000 },
+#endif
+#ifdef B4000000
+ { B4000000, 4000000 },
+#endif
+ /***END***/
+ { 0, 0 }
+ };
+
+ const struct baudtab *b;
+ const char *p; int n;
+ struct termios c;
+ speed_t code;
+
+ tty->fpout = fp;
+ if (!fp || tcgetattr(fileno(fp), &c))
+ tty->baud = 0;
+ else {
+ code = cfgetospeed(&c);
+ for (b = baudtab; b->baud; b++)
+ if (b->code == code) { tty->baud = b->baud; goto found_baud; }
+ tty->baud = 0;
+ found_baud:
+ tty_resized(tty);
+ }
+
+ if (!tty->wd)
+ { p = getenv("COLUMNS"); if (p) { n = atoi(p); if (n) tty->wd = n; } }
+ if (!tty->ht)
+ { p = getenv("LINES"); if (p) { n = atoi(p); if (n) tty->ht = n; } }
+}
+
+static void env_colour_caps(unsigned *caps_inout)
{
- unsigned r, g, b, y, t, range;
- uint32 best_colour, best_space; int best_error;
+ const char *p;
+ unsigned caps = *caps_inout;
-#define COMMON_SCALE 3825
+ p = getenv("FORCE_COLOR");
+ if (p) switch (*p) {
+ case '0':
+ caps &= TTACF_CSPCMASK | TTACF_FG | TTACF_BG;
+ break;
+ case '1':
+ caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG |
+ TTACF_1BPC | TTACF_1BPCBR;
+ break;
+ case '2':
+ caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG |
+ TTACF_1BPC | TTACF_1BPCBR | TTACF_6LPC | TTACF_8LGS;
+ break;
+ case '3':
+ caps = (caps&~TTACF_CSPCMASK) | TTACF_FG | TTACF_BG |
+ TTACF_1BPC | TTACF_1BPCBR | TTACF_8BPC;
+ break;
+ }
+ *caps_inout = caps;
+}
-#define CHECK_RANGE(r) do { \
- STATIC_ASSERT(COMMON_SCALE%(r) == 0, "common scale doesn't cover " #r); \
-} while (0)
+/* --- @clamp_colours@ --- *
+ *
+ * Arguments: @uint32 *space_out, *colour_out@ = selected space and colour
+ * @uint32 space, colour@ = requested space and colour
+ * @uint32 acaps@ = terminal's attribute capability mask
+ *
+ * Returns: ---
+ *
+ * Use: Select the best approximation to the requested colour which
+ * can be accommodated by the terminal.
+ */
+
+/* #define DEBUG_CLAMP */
+
+static void clamp_colours(uint32 *space_out, uint32 *colour_out,
+ uint32 space, uint32 colour, uint32 acaps)
+{
+ unsigned r, g, b, rr, gg, bb, y, t, u;
+ uint32 best_colour = 0, best_space; int best_error;
-#define SET_RANGE(r) do { CHECK_RANGE(r); range = (r); } while (0)
+#ifdef DEBUG_CLAMP
+# define D(x) x
+#else
+# define D(x)
+#endif
/* Check the colour space. If it's one that the terminal can handle, then
* return the colour unchanged. Otherwise, extract the channel components
* for the next step.
*/
switch (space) {
+
case TTCSPC_NONE:
- *space_out = TTCSPC_NONE; *colour_out = 0; return;
+ /* No colour wanted at all. There's nothing to do here. */
+
+ *space_out = TTCSPC_NONE; *colour_out = 0;
+ return;
+
case TTCSPC_1BPC:
+ /* One-bit-per-channel colour.
+ *
+ * There's no standardized mapping for these; indeed, they're commonly
+ * configurable by users. Since there are also `bright' versions of
+ * each, it's probably not right to just map zero to zero and one to
+ * full-scale.
+ */
+
if (colour >= 8) goto inval;
- if (acaps&TTACF_1BPC)
- { *space_out = TTACF_1BPC; *colour_out = colour; return; }
- colour = basic_colour_map[colour];
- r = TTCOL_8BR(colour); g = TTCOL_8BG(colour); b = TTCOL_8BB(colour);
- SET_RANGE(255);
+ if (acaps&(TTACF_1BPC | TTACF_1BPCBR))
+ { *space_out = TTCSPC_1BPC; *colour_out = colour; return; }
+
+#define C1BPC_FULL 0xcc
+#define C1BPC_CVT(col, bit) \
+ (C1BPC_FULL&((((col)&(1 << (bit))) << (8 - (bit))) - 1))
+ r = C1BPC_CVT(colour, 0);
+ g = C1BPC_CVT(colour, 1);
+ b = C1BPC_CVT(colour, 2);
break;
+
case TTCSPC_1BPCBR:
- if (colour >= 8) goto inval;
- if (acaps&TTACF_1BPCBR)
- { *space_out = TTACF_1BPCBR; *colour_out = colour; return; }
- colour = bright_colour_map[colour];
- r = TTCOL_8BR(colour); g = TTCOL_8BG(colour); b = TTCOL_8BB(colour);
- SET_RANGE(255);
+ /* One-bit-per-channel colour, with global brightness. Again, there's
+ * no standardized mapping. Apply a boost across all three channels.
+ */
+
+ if (colour >= 16) goto inval;
+ if (!(colour&TT1BPC_BRI) && (acaps&(TTACF_1BPC | TTACF_1BPCBR)))
+ { *space_out = TTCSPC_1BPC; *colour_out = colour; return; }
+ else if (acaps&TTACF_1BPCBR)
+ { *space_out = TTCSPC_1BPCBR; *colour_out = colour; return; }
+
+#define C1BPC_BRIGHT 0x33
+ r = C1BPC_CVT(colour, 0) + C1BPC_BRIGHT;
+ g = C1BPC_CVT(colour, 1) + C1BPC_BRIGHT;
+ b = C1BPC_CVT(colour, 2) + C1BPC_BRIGHT;
+#undef C1BPC_CVT
break;
- case TTCSPC_2BPC:
+
+ case TTCSPC_4LPC:
+ /* Four-levels-per-channel colour. These are part of an `indexed'
+ * colour space which is theoretically controlled by applications but
+ * (a) that's rare, and (b) worrying about that won't do us any good.
+ *
+ * Each channel has four levels, but they're not assigned linearly.
+ */
+
if (colour >= 64) goto inval;
- if (acaps&TTACF_2BPC)
- { *space_out = TTACF_2BPC; *colour_out = colour; return; }
- r = TTCOL_2BR(colour); g = TTCOL_2BG(colour); b = TTCOL_2BB(colour);
- SET_RANGE(3);
+ if (acaps&TTACF_4LPC)
+ { *space_out = TTCSPC_4LPC; *colour_out = colour; return; }
+
+#define C4LPC_L0 0
+#define C4LPC_L1 139
+#define C4LPC_L2 205
+#define C4LPC_L3 255
+#define C4LPC_CVT(ch) (t = (ch), t == 0 ? C4LPC_L0 : \
+ t == 1 ? C4LPC_L1 : \
+ t == 2 ? C4LPC_L2 : \
+ C4LPC_L3)
+ r = C4LPC_CVT(TTCOL_2BR(colour));
+ g = C4LPC_CVT(TTCOL_2BG(colour));
+ b = C4LPC_CVT(TTCOL_2BB(colour));
+#undef C4LPC_CVT
break;
+
case TTCSPC_8LGS:
+ /* Eight-levels greyscale. Again, these are part of an `indexed'
+ * colour space which is under application control, and, again, the
+ * levels aren't linear, but they're not far off linear.
+ */
if (colour >= 8) goto inval;
if (acaps&TTACF_8LGS)
- { *space_out = TTACF_8LGS; *colour_out = colour; return; }
- r = g = b = colour + 1;
- SET_RANGE(9);
+ { *space_out = TTCSPC_8LGS; *colour_out = colour; return; }
+
+ r = g = b = 255*(colour ? colour + 3 : 2)/11;
break;
+
case TTCSPC_6LPC:
+ /* Six-levels-per-channel colour. Again, `indexed' colour space under
+ * application control. This time the mapping is essentially liner.
+ */
+
if (colour >= 216) goto inval;
if (acaps&TTACF_6LPC)
- { *space_out = TTACF_6LPC; *colour_out = colour; return; }
- r = TTCOL_6LR(colour); g = TTCOL_6LG(colour); b = TTCOL_6LB(colour);
- SET_RANGE(5);
+ { *space_out = TTCSPC_6LPC; *colour_out = colour; return; }
+
+#define C6LPC_CVT(ch) (t = (ch), t ? (40*t + 55) : 0)
+ r = C6LPC_CVT(TTCOL_6LR(colour));
+ g = C6LPC_CVT(TTCOL_6LG(colour));
+ b = C6LPC_CVT(TTCOL_6LB(colour));
+#undef C6LPC_CVT
break;
+
case TTCSPC_24LGS:
+ /* Twenty-four-levels greyscale. Same story. */
+
if (colour >= 24) goto inval;
if (acaps&TTACF_24LGS)
- { *space_out = TTACF_24LGS; *colour_out = colour; return; }
- r = g = b = colour + 1;
- SET_RANGE(25);
+ { *space_out = TTCSPC_24LGS; *colour_out = colour; return; }
+
+ r = g = b = 10*colour + 8;
break;
+
case TTCSPC_8BPC:
+ /* Eight-bits-per-channel colour. No conversion to apply here. */
+
if (colour >= 0x01000000) goto inval;
if (acaps&TTACF_8BPC)
- { *space_out = TTACF_8BPC; *colour_out = colour; return; }
+ { *space_out = TTCSPC_8BPC; *colour_out = colour; return; }
+
r = TTCOL_8BR(colour); g = TTCOL_8BG(colour); b = TTCOL_8BB(colour);
- SET_RANGE(255);
break;
- default:
- goto inval;
- }
-#undef SET_RANGE
+ default:
+ /* Anything else. */
+
+ goto inval;
+ }
/* We didn't get an exact match, so we'll have to make do with what we've
* got.
*/
- best_error = -1;
+ best_error = -1; best_space = TTCSPC_NONE; best_colour = 0;
+ D( fprintf(stderr, "\n;; APPROX space %u, colour 0x%lx = %u/%u/%u\n",
+ space, (unsigned long)colour, r, g, b); )
+ /* Approximate colour weightings for human colour vision. */
#define RWT 2
#define GWT 4
#define BWT 1
+#define TOTWT (RWT + GWT + BWT)
- t = COMMON_SCALE/range; r *= t; g *= t; b *= t;
- y = (RWT*r + GWT*g + BWT*b + (RWT + GWT + BWT)/2)/(RWT + GWT + BWT);
+ /* Determine the optimal grey approximation for this colour. */
+ y = (RWT*r + GWT*g + BWT*b + TOTWT/2)/TOTWT;
-#define TRY_APPROX(red, grn, blu, spc, clr) do { \
- int r_err = (red) - r, g_err = (grn) - g, b_err = (blu) - b; \
- int err; \
+#define TRY_APPROX(rr, gg, bb, spc, clr) do { \
+ /* If the approximation (RR, GG, BB) is closer to the current best \
+ * then accept SPC and CLR as the new best space/colour option. \
+ */ \
+ \
+ int _r_err = (rr) - r, _g_err = (gg) - g, _b_err = (bb) - b; \
+ int _err; \
\
- if (r_err < 0) r_err = -r_err; \
- if (g_err < 0) g_err = -g_err; \
- if (b_err < 0) b_err = -b_err; \
+ if (_r_err < 0) _r_err = -_r_err; \
+ if (_g_err < 0) _g_err = -_g_err; \
+ if (_b_err < 0) _b_err = -_b_err; \
\
- err = r_err + g_err + b_err; \
- if (best_error < 0 || err < best_error) \
- { best_error = err; best_space = (spc); best_colour = (clr); } \
+ _err = RWT*_r_err + GWT*_g_err + BWT*_b_err; \
+ D( fprintf(stderr, \
+ ";; candidate space %u, colour 0x%lx = %u/%u/%u; " \
+ "error = %d\n", \
+ (spc), (unsigned long)(clr), (rr), (gg), (bb), _err); ) \
+ if (best_error < 0 || _err < best_error) { \
+ best_error = _err; best_space = (spc); best_colour = (clr); \
+ D( fprintf(stderr, ";;\tNEW BEST APPROXIMATION\n"); ) \
+ } \
+} while (0)
+
+ if (!(acaps&(TTACF_4LPC | TTACF_6LPC | TTACF_8BPC))) {
+ /* The one-bit-per-channel colours are very variable, but there's little
+ * choice, so we'll have to try. We assume the same mapping as on the
+ * way in.
+ */
+
+ if (acaps&(TTACF_1BPC | TTACF_1BPCBR)) {
+ /* One-bit-per-channel colour. */
+
+#define C1BPC_APPROX(cc, c, bit) do { \
+ if ((c) <= C1BPC_FULL/2) (cc) = 0; \
+ else { (cc) = C1BPC_FULL; t |= (bit); } \
+} while (0)
+ t = 0;
+ C1BPC_APPROX(rr, r, TT1BPC_RED);
+ C1BPC_APPROX(gg, g, TT1BPC_GRN);
+ C1BPC_APPROX(bb, b, TT1BPC_BLU);
+#undef C1BPC_APPROX
+ TRY_APPROX(rr, gg, bb, TTCSPC_1BPC, t);
+ }
+
+ if (acaps&TTACF_1BPCBR) {
+ /* One-bit-per-channel colour, with global brightness. */
+
+#define C1BPCBR_APPROX(cc, c, bit) do { \
+ if ((c) <= C1BPC_FULL/2 + C1BPC_BRIGHT) (cc) = C1BPC_BRIGHT; \
+ else { (cc) = 255; t |= (bit); } \
} while (0)
+ t = TT1BPC_BRI;
+ C1BPCBR_APPROX(rr, r, TT1BPC_RED);
+ C1BPCBR_APPROX(gg, g, TT1BPC_GRN);
+ C1BPCBR_APPROX(bb, b, TT1BPC_BLU);
+#undef C1BPCBR_APPROX
+ TRY_APPROX(rr, gg, bb, TTCSPC_1BPCBR, t);
+ }
+ }
-#define TRY_RGB(spc, lvls) do { \
- CHECK_RANGE((lvls) - 1); \
- unsigned sc = COMMON_SCALE/((lvls) - 1); \
- unsigned rr, gg, bb, clr; \
+ if (acaps&TTACF_4LPC) {
+ /* Four-levels-per-channel colour. */
+
+#define C4LPC_APPROX(cc, c, sh) do { \
+ unsigned _c = (c); \
\
- rr = (r + sc/2)/sc; gg = (g + sc/2)/sc; bb = (b + sc/2)/sc; \
- TRY_APPROX(rr*sc, gg*sc, bb*sc, (spc), (rr*(lvls) + bb)*(lvls) + gg); \
+ if (_c > (C4LPC_L2 + C4LPC_L3)/2) \
+ { (cc) = C4LPC_L3; t |= 3 << (sh); } \
+ else if (_c > (C4LPC_L1 + C4LPC_L2)/2) \
+ { (cc) = C4LPC_L2; t |= 2 << (sh); } \
+ else if (_c > (C4LPC_L0 + C4LPC_L1)/2) \
+ { (cc) = C4LPC_L1; t |= 1 << (sh); } \
+ else \
+ (cc) = C4LPC_L0; \
} while (0)
+ t = 0;
+ C4LPC_APPROX(rr, r, 4);
+ C4LPC_APPROX(gg, g, 2);
+ C4LPC_APPROX(bb, b, 0);
+#undef C4LPC_APPROX
+ TRY_APPROX(rr, gg, bb, TTCSPC_4LPC, t);
+ }
+
+ if (acaps&TTACF_8LGS) {
+ /* Eight-levels greyscale. */
+
+ u = (11*y)/255;
+ if (u <= 2) { u = 2; t = 0; }
+ else if (u == 3) { u = 4; t = 1; }
+ else if (u == 11) { u = 10; t = 7; }
+ else t = u - 3;
+ u = (255*u)/11; TRY_APPROX(u, u, u, TTCSPC_8LGS, t);
+ }
+
+ if (acaps&TTACF_6LPC) {
+ /* Six-levels-per-channel colour. */
-#define TRY_GREY(spc, lvls) do { \
- CHECK_RANGE((lvls) + 1); \
- unsigned sc = COMMON_SCALE/((lvls) + 1); \
- unsigned yy, grey; \
+#define C6LPC_APPROX(cc, c, f) do { \
+ unsigned _c = (c); \
\
- yy = (y + sc/2)/sc; \
- if (yy >= 1 && yy <= (lvls)) \
- { grey = yy*sc; TRY_APPROX(grey, grey, grey, (spc), grey - 1); } \
+ if (_c < 36) (cc) = 0; \
+ else { u = (_c - 36)/40; t += (f)*u; (cc) = 40*u + 55; } \
} while (0)
+ t = 0;
+ C6LPC_APPROX(rr, r, 36);
+ C6LPC_APPROX(gg, g, 6);
+ C6LPC_APPROX(bb, b, 1);
+#undef C6LPC_APPROX
+ TRY_APPROX(rr, gg, bb, TTCSPC_6LPC, t);
+ }
- if (acaps&TTACF_1BPC)
- for (i = 0; i < 8; i++) {
- t = basic_colour_map[i];
- TRY_APPROX(TTCOL_8BR(t), TTCOL_8BG(t), TTCOL_8BB(t), TTCSPC_1BPC, i);
- }
- if (acaps&TTACF_1BPCBR)
- for (i = 0; i < 8; i++) {
- t = bright_colour_map[i];
- TRY_APPROX(TTCOL_8BR(t), TTCOL_8BG(t), TTCOL_8BB(t), TTCSPC_1BPCBR, i);
+ if (acaps&TTACF_24LGS) {
+ /* Twenty-four-levels greyscale. */
+
+ if (y < 3) { t = 0; u = 8; }
+ else if (y >= 243) { t = 23; u = 238; }
+ else { t = (y - 3)/10; u = 10*t + 8; }
+ TRY_APPROX(u, u, u, TTCSPC_24LGS, t);
+ }
+
+ if (acaps&TTACF_8BPC) {
+ /* Eight-bits-per-channel colour. */
+
+ if (best_error) {
+ D( fprintf(stderr, ";; accept exact 8bpc colour\n"); )
+ best_error = 0; best_space = TTCSPC_8BPC;
+ best_colour = TTCOL_MK8B(r, g, b);
}
- if (acaps&TTACF_2BPC) TRY_RGB(TTCSPC_2BPC, 4);
- if (acaps&TTACF_8LGS) TRY_GREY(TTCSPC_8LGS, 8);
- if (acaps&TTACF_6LPC) TRY_RGB(TTCSPC_6LPC, 6);
- if (acaps&TTACF_24LGS) TRY_GREY(TTCSPC_24LGS, 24);
- if (acaps&TTACF_8BPC) TRY_RGB(TTCSPC_8BPC, 256);
+ }
+
+ /* Done. */
+ *space_out = best_space; *colour_out = best_colour;
+ return;
+
+inval:
+ /* Invalid colour selection. Ignore this. */
+ *space_out = TTCSPC_NONE; *colour_out = 0;
+
+#undef C1BPC_FULL
+#undef C1BPC_BRIGHT
+
+#undef C4LPC_L0
+#undef C4LPC_L1
+#undef C4LPC_L2
+#undef C4LPC_L3
#undef TRY_APPROX
#undef TRY_RGB
#undef RWT
#undef GWT
#undef BWT
+#undef TOTWT
- *space_out = best_space; *colour_out = best_colour;
- return;
+#undef SET_RANGE
+#undef CHECK_RANGE
-inval:
- *space_out = TTCSPC_NONE; *colour_out = 0;
+#undef D
}
-void tty_clampattr(struct tty_attr *a_out, const struct tty_attr *a,
- uint32 acaps)
+/* --- @tty_clampattr@ --- *
+ *
+ * Arguments: @struct tty_attr *a_out@ = selected attributes
+ * @const struct tty_attr *a@ = requested attributes
+ * @uint32 acaps@ = terminal's attribute capability mask
+ *
+ * Returns: ---
+ *
+ * Use: Select the closest approximation to the requested attributes
+ * which can be accommodated by the terminal.
+ */
+
+void tty_clampattr(struct tty_attr *a_out,
+ const struct tty_attr *a, uint32 acaps)
{
- uint32 ff = 0, f = a->f, t;
+ uint32 ff = 0, f = a ? a->f : 0, t;
+ /* Line attributes. */
t = (f&TTAF_LNMASK) >> TTAF_LNSHIFT;
switch (t) {
case TTLN_NONE:
break;
case TTLN_UULINE:
if (acaps&TTACF_UULINE) ;
- else if (acaps&TTACF_ULINE) t = TTLN_ULNE;
+ else if (acaps&TTACF_ULINE) t = TTLN_ULINE;
else t = TTLN_NONE;
break;
- case TTLN_STRIKE:
- if (!acaps&TTACF_STRIKE) t = TTLN_NONE;
- break;
default:
t = TTLN_NONE;
}
ff |= t << TTAF_LNSHIFT;
+ /* Text weight. */
t = (f&TTAF_WTMASK) >> TTAF_WTSHIFT;
switch (t) {
case TTWT_MED: break;
case TTWT_BOLD: if (!(acaps&TTACF_BOLD)) t = TTWT_MED; break;
case TTWT_DIM: if (!(acaps&TTACF_DIM)) t = TTWT_MED; break;
- default: t = TTWD_MED; break;
+ default: t = TTWT_MED; break;
}
ff |= t << TTAF_WTSHIFT;
+ /* Other text attributes. */
+ if (acaps&TTACF_STRIKE) ff |= f&TTAF_STRIKE;
if (acaps&TTACF_ITAL) ff |= f&TTAF_ITAL;
if (acaps&TTACF_INVV) ff |= f&TTAF_INVV;
+ /* Foreground and background colours. */
if (acaps&TTACF_FG) {
clamp_colours(&t, &a_out->fg,
- (f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a->fg);
+ (f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a ? a->fg : 0,
+ acaps);
ff |= t << TTAF_FGSPCSHIFT;
}
if (acaps&TTACF_BG) {
clamp_colours(&t, &a_out->bg,
- (f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a->bg);
+ (f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a ? a->bg : 0,
+ acaps);
ff |= t << TTAF_BGSPCSHIFT;
}
- a_out->f = ff; a_out->_res = 0;
+ /* All done. */
+ a_out->f = ff; a_out->_res0 = 0;
+}
+
+int tty_resized(struct tty *tty)
+{
+ struct winsize ws;
+
+ if (!tty->fpout) { errno = ENOTTY; return (-1); }
+ if (ioctl(fileno(tty->fpout), TIOCGWINSZ, &ws)) return (-1);
+ tty->wd = ws.ws_col; tty->ht = ws.ws_row; return (0);
}
/*----- Common machinery for `termcap' and `terminfo' ---------------------*/
static const struct gprintf_ops *global_gops;
static void *global_gout;
+static struct tty *global_lock = 0;
-static int term_putch(int ch)
+static void caps_release(struct tty *tty)
+ { assert(global_lock == tty); global_lock = 0; }
+
+static int caps_putch(int ch)
{ return (global_gops->putch(global_gout, ch)); }
#endif
#ifdef HAVE_UNIBILIUM
-# define UNIBI_(x) unibi_##x
+# define UNIBI_(x) unibi##x
#else
# define UNIBI_(x) 0
#endif
#define BASICCAPS(_bool, _int, _str) \
- _str(carriage_return, cr, cr) \
- _int(lines, lines, li) _int(columns, cols, co)
+ _str(_repeat_char, rep, rp) \
+ _int(_padding_baud_rate, pb, pb) _str(_pad_char, pad, pc) \
+ _bool(_no_pad_char, npc, NP) _bool(_xon_xoff, xon, xo) \
+ _bool(_move_insert_mode, mir, mi) _bool(_move_standout_mode, msgr, ms)
#define ATTRCAPS(_bool, _int, _str) \
- _str(exit_attribute_mode, sgr0, me) \
- _str(enter_underline_mode, smul, us) _str(exit_underline_mode, rmul, ue) \
- _str(enter_italics_mode, sitm, ZH) _str(exit_italics_mode, ritm, ZR) \
- _str(enter_bold_mode, bold, md) _str(enter_dim_mode, dim, mh) \
- _str(enter_reverse_mode, rev, mr) \
+ _str(_exit_attribute_mode, sgr0, me) \
+ _str(_enter_underline_mode, smul, us) \
+ _str(_exit_underline_mode, rmul, ue) \
+ _str(_enter_italics_mode, sitm, ZH) _str(_exit_italics_mode, ritm, ZR) \
+ _str(_enter_bold_mode, bold, md) _str(_enter_dim_mode, dim, mh) \
+ _str(_enter_reverse_mode, rev, mr) \
COLOURCAPS(_bool, _int, _str)
#define COLOURCAPS(_bool, _int, _str) \
- _str(set_a_foreground, setaf, AF) _str(set_a_background, setab, AB) \
- _str(orig_pair, op, op) _int(max_colors, colors, Co)
+ _str(_set_a_foreground, setaf, AF) _str(_set_a_background, setab, AB) \
+ _str(_orig_pair, op, op) _int(_max_colors, colors, Co)
#define MODECAPS(_bool, _int, _str) \
- _str(enter_am_mode, smam, SA) _str(exit_am_mode, rmam, RA) \
- _str(enter_ca_mode, smcup, ti) _str(exit_ca_mode, rmcup, te) \
- _str(enter_insert_mode, smir, im) _str(exit_insert_mode, rmir, ei)
+ _str(_carriage_return, cr, cr) _str(_newline, nel, nw) \
+ _str(_enter_am_mode, smam, SA) _str(_exit_am_mode, rmam, RA) \
+ _str(_enter_ca_mode, smcup, ti) _str(_exit_ca_mode, rmcup, te) \
+ _str(_cursor_normal, cnorm, vs) _str(_cursor_invisible, civis, vi) \
+ _str(_enter_insert_mode, smir, im) _str(_exit_insert_mode, rmir, ei) \
+ _str(_enter_delete_mode, smdc, dm) _str(_exit_delete_mode, rmdc, ed)
#define MOVECAPS(_bool, _int, _str) \
- _str(carriage_return, cr, cr) \
- _str(cursor_home, home, ho) \
- _str(cusor_address, cup, cm) \
- _str(row_address, vpa, cv) _str(column_address, hpa, ch) \
- _str(cursor_left, cub1, le) _(parm_left_cursor, cub, LE) \
- _str(cursor_right, cuf1, nd) _(parm_right_cursor, cuf, RI) \
- _str(cursor_up, cuu1, up) _str(parm_up_cursor, cuu, UP) \
- _str(cursor_down, cud1, do) _str(parm_down_cursor, cud, DO)
+ _str(_cursor_home, home, ho) \
+ _str(_cursor_address, cup, cm) \
+ _str(_row_address, vpa, cv) _str(_column_address, hpa, ch) \
+ _str(_cursor_left, cub1, le) _str(_parm_left_cursor, cub, LE) \
+ _str(_cursor_right, cuf1, nd) _str(_parm_right_cursor, cuf, RI) \
+ _str(_cursor_up, cuu1, up) _str(_parm_up_cursor, cuu, UP) \
+ _str(_cursor_down, cud1, do) _str(_parm_down_cursor, cud, DO)
#define SCROLLCAPS(_bool, _int, _str) \
- _str(change_scroll_region, csr, cs) \
- _str(scroll_forward, ind, sf) _str(parm_index, indn, SF) \
- _str(scroll_reverse, ri, sr) _str(parm_rindex, rin, SR)
+ _str(_change_scroll_region, csr, cs) \
+ _str(_scroll_forward, ind, sf) _str(_parm_index, indn, SF) \
+ _str(_scroll_reverse, ri, sr) _str(_parm_rindex, rin, SR)
#define ERASECAPS(_bool, _int, _str) \
- _str(clr_bol, el1, cb) \
- _str(clr_eol, el, ce) \
- _str(clr_eos, ed, cd)
+ _str(_erase_chars, ech, ec) \
+ _str(_clr_bol, el1, cb) _str(_clr_eol, el, ce) \
+ _str(_clr_eos, ed, cd) _str(_clear_screen, clear, cl)
#define INSDELCAPS(_bool, _int, _str) \
- _str(insert_character, ich1, ic) _str(parm_ich, ich, IC) \
- _str(insert_line, il1, al) _str(parm_insert_line, il, AL) \
- _str(delete_character, dch1, dc) _str(parm_dch, dch, DC) \
- _str(delete_line, dl1, dl) _str(parm_delete_line, dl, DL)
+ _str(_insert_character, ich1, ic) _str(_parm_ich, ich, IC) \
+ _str(_insert_padding, ip, ip) \
+ _str(_insert_line, il1, al) _str(_parm_insert_line, il, AL) \
+ _str(_delete_character, dch1, dc) _str(_parm_dch, dch, DC) \
+ _str(_delete_line, dl1, dl) _str(_parm_delete_line, dl, DL)
#define STORECAPS(_bool, _int, _str) \
+ BASICCAPS(_bool, _int, _str) \
ATTRCAPS(_bool, _int, _str) \
MODECAPS(_bool, _int, _str) \
MOVECAPS(_bool, _int, _str) \
ERASECAPS(_bool, _int, _str) \
INSDELCAPS(_bool, _int, _str)
-#define CAP_XMC magic_cookie_glitch, xmc, sg
-#define CAP_BCE back_color_erase, bce, ut
-#define CAP_XHPA row_addr_glitch, xvpa, YD
-#define CAP_XVPA col_addr_glitch, xhpa, YA
-#define CAP_MIR move_insert_mode, mir, mi
-#define CAP_MSGR move_standout_mode, msgr, ms
-#define CAP_NPC no_pad_char, npc, NP
-#define CAP_AM auto_left_margin, am, am
-#define CAP_XENL eat_newline_glitch, xenl, xn
-
#define CAPREF(var, info, cap) UNIBI_(var), #info, #cap
-struct ttycaps {
-#define DEF_STRCAP(info, cap) const char *cap;
-#define DEF_INTCAP(info, cap) int cap;
- STORECAPS(DEF_STRCAP, DEF_INTCAP)
-#undef DEF_STRCAP
+#define CAP_XMC CAPREF(_magic_cookie_glitch, xmc, sg)
+#define CAP_BCE CAPREF(_back_color_erase, bce, ut)
+#define CAP_XHPA CAPREF(_row_addr_glitch, xvpa, YD)
+#define CAP_XVPA CAPREF(_col_addr_glitch, xhpa, YA)
+#define CAP_AM CAPREF(_auto_right_margin, am, am)
+#define CAP_XENL CAPREF(_eat_newline_glitch, xenl, xn)
+#define CAP_HT CAPREF(_lines, lines, li)
+#define CAP_WD CAPREF(_columns, cols, co)
+
+#define TTY_BASEOPSPFX struct tty_ops tty
+#define TTY_BASEOPSUXFX struct tty_ops tty
+#define TTY_BASEPFX struct tty tty
+#define TTY_BASEUSFX struct tty tty
+
+struct tty_capopslots {
+ int (*boolcap)(struct tty */*tty*/,
+ int /*uix*/, const char */*info*/, const char */*cap*/);
+ int (*intcap)(struct tty */*tty*/,
+ int /*uix*/, const char */*info*/, const char */*cap*/);
+ const char *(*strcap)(struct tty */*tty*/,
+ int /*uix*/, const char */*info*/,
+ const char */*cap*/);
+ int (*put0)(struct tty */*tty*/,
+ const struct gprintf_ops */*gops*/, void */*go*/,
+ unsigned /*npad*/, const char */*cap*/);
+ int (*put1i)(struct tty */*tty*/,
+ const struct gprintf_ops */*gops*/, void */*go*/,
+ unsigned /*npad*/, const char */*cap*/, int /*i0*/);
+ int (*put2i)(struct tty */*tty*/,
+ const struct gprintf_ops */*gops*/, void */*go*/,
+ unsigned /*npad*/,
+ const char */*cap*/, int /*i0*/, int /*i1*/);
+};
+#define TTY_CAPOPSPFX TTY_BASEOPSPFX; struct tty_capopslots cap
+struct tty_capops { TTY_CAPOPSPFX; };
+#define TTY_CAPOPSUSFX struct tty_capops cap; TTY_BASEOPSUXFX
+union tty_capopsu { TTY_CAPOPSUSFX; };
+
+struct tty_capslots {
+#define DEF_BOOLCAP(uix, info, cap) unsigned info : 1;
+#define DEF_INTCAP(uix, info, cap) int info;
+#define DEF_STRCAP(uix, info, cap) const char *info;
+ STORECAPS(DEF_BOOLCAP, DEF_INTCAP, DEF_STRCAP)
+#undef DEF_BOOLCAP
#undef DEF_INTCAP
+#undef DEF_STRCAP
};
-
-typedef int boolcapfn(int uix, const char *info, const char *cap, void *arg);
-typedef int intcapfn(int uix, const char *info, const char *cap, void *arg);
-typedef const char *strcapfn(int uix, const char *info, const char *cap,
- void *arg);
-
-#define DEFINE_CAPISH(PRE, pre)
-
-static int init_caps(struct tty *tty, struct tty_caps *caps,
- boolcapfn *boolcap, intcapfn *intcap, strcapfn *strcap,
- void *arg)
+#define TTY_CAPSPFX \
+ struct tty tty; \
+ struct tty_capslots cap
+struct tty_caps { TTY_CAPSPFX; };
+#define TTY_CAPSUSFX \
+ struct tty_caps cap; \
+ struct tty tty
+union tty_capsu { TTY_CAPSUSFX; };
+
+static void init_caps(struct tty_caps *t)
{
- tty->acaps = tty->ocaps = 0;
- tty->st.modes = 0;
- tty->st.attr.f = 0;
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+ int wd, ht;
+
+ t->tty.acaps = t->tty.ocaps = 0;
+ t->tty.st.modes = 0; t->tty.st.attr.f = 0;
+ t->tty.st.attr.fg = t->tty.st.attr.bg = 0;
/* Inhale all of the interesting terminal capabilities. */
-#define GETBOOL(var, info, cap) \
- caps->info = boolcap(CAPREF(var, info, cap), arg);
-#define GETINT(var, info, cap) \
- caps->info = intcap(CAPREF(var, info, cap), arg);
-#define GETSTR(var, info, cap) \
- caps->info = strcap(CAPREF(var, info, cap), arg);
+#define GETBOOL(var, info, cap_) \
+ t->cap.info = ops->cap.boolcap(&t->tty, CAPREF(var, info, cap_));
+#define GETINT(var, info, cap_) \
+ t->cap.info = ops->cap.intcap(&t->tty, CAPREF(var, info, cap_));
+#define GETSTR(var, info, cap_) \
+ t->cap.info = ops->cap.strcap(&t->tty, CAPREF(var, info, cap_));
STORECAPS(GETBOOL, GETINT, GETSTR)
#undef GETBOOL
#undef GETINT
#undef GETSTR
-#define CLEARCAP(var, info, cap) caps->info = 0;
-#define CLEAR_CAPS(caplist) do { caplist(CLEARCAP) } while (0)
+#define CLEAR_BOOL(uix, info, cap_) t->cap.info = 0;
+#define CLEAR_INT(uix, info, cap_) t->cap.info = 0;
+#define CLEAR_STR(uix, info, cap_) t->cap.info = 0;
+#define CLEAR_CAPS(caplist) \
+ do { caplist(CLEAR_BOOL, CLEAR_INT, CLEAR_STR) } while (0)
/* Basic capabilities. */
- if (!caps->cr) caps->cr = "\r";
+ if (!t->cap.cr) t->cap.cr = "\r";
+ if (!t->cap.nel) t->cap.nel = "\r\n";
/* Attribute capabilities. */
- if (intcap(CAPREF(CAP_XMC)) || !caps->sgr0)
+ if (ops->cap.intcap(&t->tty, CAP_XMC) > 0 || !t->cap.sgr0)
CLEAR_CAPS(ATTRCAPS);
else {
- if (caps->smul) tty->acaps |= TTACF_ULINE;
- if (caps->bold) tty->acaps |= TTACF_BOLD;
- if (caps->dim) tty->acaps |= TTACF_DIM;
- if (caps->sitm) tty->acaps |= TTACF_ITAL;
- if (caps->rev) tty->acaps |= TTACF_INVV;
+ if (t->cap.smul) t->tty.acaps |= TTACF_ULINE;
+ if (t->cap.bold) t->tty.acaps |= TTACF_BOLD;
+ if (t->cap.dim) t->tty.acaps |= TTACF_DIM;
+ if (t->cap.sitm) t->tty.acaps |= TTACF_ITAL;
+ if (t->cap.rev) t->tty.acaps |= TTACF_INVV;
- if (!caps->colors >= 8 || (!caps->setaf && !caps->setbf))
+ if (!t->cap.op || (!t->cap.setaf && !t->cap.setab))
CLEAR_CAPS(COLOURCAPS);
else {
- if (caps->setaf) tty->acaps |= TTACF_FG;
- if (caps->setab) tty->acaps |= TTACF_BG;
- tty->acaps |= TTACF_1BPC;
- if (caps->colors >= 16) tty->acaps |= TTACF_1BPCBR;
- if (caps->colors == 88) tty->acaps |= TTACF_2BPC | TTACF_8LGS;
- else if (caps->colors >= 256) tty->acaps |= TTACF_6LPC | TTACF_24LGS;
- if (caps->colors >= 16777216) tty->acaps |= TTACF_8BPC;
- if (boolcap(CAPREF(CAP_BCE))) tty->ocaps |= TTCF_BGER;
+ if (t->cap.setaf) t->tty.acaps |= TTACF_FG;
+ if (t->cap.setab) t->tty.acaps |= TTACF_BG;
+ t->tty.acaps |= TTACF_1BPC;
+ if (t->cap.colors >= 16777216)
+ t->tty.acaps |= TTACF_1BPC | TTACF_8BPC;
+ else if (t->cap.colors >= 256)
+ t->tty.acaps |= TTACF_1BPCBR | TTACF_6LPC | TTACF_24LGS;
+ else if (t->cap.colors == 88)
+ t->tty.acaps |= TTACF_1BPCBR | TTACF_4LPC | TTACF_8LGS;
+ else if (t->cap.colors >= 16)
+ t->tty.acaps |= TTACF_1BPCBR;
+ if (ops->cap.boolcap(&t->tty, CAP_BCE)) t->tty.ocaps |= TTCF_BGER;
+ env_colour_caps(&t->tty.acaps);
}
}
/* Motion capabilities. */
- if (boolcap(CAPREF(CAP_XVPA))) caps->vpa = 0;
- if (boolcap(CAPREF(CAP_XHPA))) caps->hpa = 0;
- if (!caps->cub1) caps->cub1 = "\b";
- if (!caps->cud1) caps->cud1 = "\n";
- if ((caps->cuf || caps->cuf1) && (caps->cuu || caps->cuu1)) {
- tty->ocaps |= TTCF_RELMV;
- if (caps->vpa) tty->ocaps |= TTCF_MIXMV;
- }
- if (caps->cup || (caps->hpa && caps->vpa)) tty->ocaps |= TTCF_ABSMV;
+ if (ops->cap.boolcap(&t->tty, CAP_XHPA)) t->cap.hpa = 0;
+ if (ops->cap.boolcap(&t->tty, CAP_XVPA)) t->cap.vpa = 0;
+ if (!t->cap.cub1) t->cap.cub1 = "\b";
+ if ((t->cap.cuf || t->cap.cuf1) &&
+ (t->cap.cuu || t->cap.cuu1) &&
+ (t->cap.cud || t->cap.cud1)) {
+ t->tty.ocaps |= TTCF_RELMV;
+ if (t->cap.vpa) t->tty.ocaps |= TTCF_ABSMV | TTCF_MIXMV;
+ else if (t->cap.home) t->tty.ocaps |= TTCF_ABSMV;
+ } else if (t->cap.cup ||
+ (t->cap.hpa && t->cap.vpa) ||
+ (t->cap.home && (t->cap.cuf || t->cap.cuf1)))
+ t->tty.ocaps |= TTCF_ABSMV;
/* Mode capabilities. */
- if (caps->smam && caps->rmam) tty->ocaps |= TTMF_AUTOM;
- if (caps->smir && caps->rmir) tty->ocaps |= TTMF_INS;
- if (boolcap(CAPREF(CAP_AM))) {
- tty->st.modes |= TTMF_AUTOM;
- if (boolcap(CAPREF(CAP_XENL))) tty->ocaps |= TTCF_MMARG;
+ if (t->cap.smam && t->cap.rmam) t->tty.ocaps |= TTMF_AUTOM;
+ if (t->cap.smcup && t->cap.rmcup) t->tty.ocaps |= TTMF_FSCRN;
+ if (t->cap.smir && t->cap.rmir) t->tty.ocaps |= TTMF_INS;
+ if (t->cap.smdc && t->cap.rmdc) t->tty.ocaps |= TTMF_DEL;
+ if (t->cap.cnorm && t->cap.civis)
+ { t->tty.ocaps |= TTMF_CVIS; t->tty.st.modes |= TTMF_CVIS; }
+ if (ops->cap.boolcap(&t->tty, CAP_AM)) {
+ t->tty.st.modes |= TTMF_AUTOM;
+ if (ops->cap.boolcap(&t->tty, CAP_XENL)) t->tty.ocaps |= TTCF_MMARG;
}
- /* Scrolling. */
- if (caps->csr) tty->ocaps |= TTCF_SCRGN;
- if ((caps->ind || caps->indn) && (caps->ri || caps->rin))
- tty->ocaps |= TTCF_SCROLL;
-
/* Erasure. */
- if (caps->ech) tty->ocaps |= TTCF_ERCH;
- if (caps->el1) tty->ocaps |= TTCF_ERBOL;
- if (caps->el) tty->ocaps |= TTCF_EREOL;
- if (caps->ed) tty->ocaps |= TTCF_EREOD;
+ if (t->cap.ech) t->tty.ocaps |= TTCF_ERCH;
+ if (t->cap.el1) t->tty.ocaps |= TTCF_ERBOL;
+ if (t->cap.el) t->tty.ocaps |= TTCF_EREOL;
+ if (t->cap.ed) t->tty.ocaps |= TTCF_EREOD;
+ if (t->cap.clear || (t->cap.ed && t->cap.home)) t->tty.ocaps |= TTCF_ERDSP;
/* Insertion and deletion. */
- if (caps->ich || caps->ich1) tty->ocaps |= TTCF_INSCH;
- if (caps->il || caps->il1) tty->ocaps |= TTCF_INSLN;
- if (caps->dch || caps->dch1) tty->ocaps |= TTCF_DELCH;
- if (caps->dl || caps->dl1) tty->ocaps |= TTCF_DELLN;
+ if (t->cap.ich || t->cap.ich1) t->tty.ocaps |= TTCF_INSCH;
+ if (t->cap.il || t->cap.il1) t->tty.ocaps |= TTCF_INSLN;
+ if (t->cap.dch || t->cap.dch1) t->tty.ocaps |= TTCF_DELCH;
+ if (t->cap.dl || t->cap.dl1) t->tty.ocaps |= TTCF_DELLN;
+
+ /* Geometry. */
+ if (!t->tty.wd)
+ { wd = ops->cap.intcap(&t->tty, CAP_WD); if (wd > 0) t->tty.wd = wd; }
+ if (!t->tty.ht)
+ { ht = ops->cap.intcap(&t->tty, CAP_HT); if (ht > 0) t->tty.ht = ht; }
+}
+
+#define CHECK(expr) do { if ((expr) < 0) { rc = -1; goto end; } } while (0)
+
+#define PUT0V(npad, cap_) \
+ CHECK(ops->cap.put0(&t->tty, gops, go, (npad), (cap_)))
+#define PUT1IV(npad, cap_, i0) \
+ CHECK(ops->cap.put1i(&t->tty, gops, go, (npad), (cap_), (i0)))
+#define PUT2IV(npad, cap_, i0, i1) \
+ CHECK(ops->cap.put2i(&t->tty, gops, go, (npad), (cap_), (i0), (i1)))
+
+#define PUT0(npad, name) PUT0V(npad, t->cap.name)
+#define PUT1I(npad, name, i0) PUT1IV(npad, t->cap.name, i0)
+#define PUT2I(npad, name, i0, i1) PUT2IV(npad, t->cap.name, i0, i1)
+
+static int caps_setcolour(struct tty_caps *t,
+ const struct gprintf_ops *gops, void *go,
+ const char *cap, uint32 spc, uint32 clr)
+{
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+ int rc;
+
+ switch (spc) {
+ case TTCSPC_1BPC: case TTCSPC_1BPCBR: PUT1IV(0, cap, clr); break;
+ case TTCSPC_4LPC: case TTCSPC_6LPC: PUT1IV(0, cap, clr + 16); break;
+ case TTCSPC_8LGS: PUT1IV(0, cap, clr + 80); break;
+ case TTCSPC_24LGS: PUT1IV(0, cap, clr + 232); break;
+
+ case TTCSPC_8BPC:
+ /* There's an unfortunate ambiguity in the `setaf' conventions. The
+ * first eight colours should be dark shades of blue, but in fact
+ * they're interpreted as the one-bit-per-channel basic colours by
+ * common `terminfo' settings. Notice and kludge by adding a little
+ * red. This will tinge the colour magenta, but all such colours are
+ * so dark as to be effectively black anyway, so I doubt that this will
+ * be noticeable.
+ */
+ if (spc == TTCSPC_8BPC && clr < 8) clr += 65536;
+ PUT1IV(0, cap, clr); break;
+
+ /* case TTCSPC_NONE: */
+ default: rc = -1; goto end;
+ }
+ rc = 0;
+end:
+ return (rc);
}
-static int caps_setattr(struct tty *tty, const struct tty_caps *caps,
+static int caps_setattr(struct tty *tty,
const struct gprintf_ops *gops, void *go,
const struct tty_attr *a)
{
+ struct tty_caps *t = (struct tty_caps *)tty;
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
struct tty_attr aa;
+ uint32 diff;
+ int rc;
+
+ /* Work out what needs doing. */
+ tty_clampattr(&aa, a, t->tty.acaps);
+ diff = aa.f ^ t->tty.st.attr.f;
+
+ /* Some terminals might not be able to clear individual attributes that
+ * they can set, and some capabilities for turning attributes on don't even
+ * have a corresponding attribute for turning them off again individually,
+ * so we have to use `sgr0' to start from scratch. Of course, if we need
+ * to do that, we need to restore the other active attributes, so we must
+ * check up front.
+ */
+ if (((diff&TTAF_LNMASK) && !(aa.f&TTAF_LNMASK) && !t->cap.rmul) ||
+ ((diff&TTAF_WTMASK) && !(aa.f&TTAF_WTMASK)) ||
+ ((diff&~aa.f&TTAF_ITAL) && !t->cap.ritm) ||
+ (diff&~aa.f&TTAF_INVV))
+ { PUT0(0, sgr0); diff = aa.f; }
+
+ /* Line style. */
+ if (diff&TTAF_LNMASK)
+ switch ((aa.f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
+ case TTLN_NONE: PUT0(0, rmul); break;
+ case TTLN_ULINE: PUT0(0, smul); break;
+ /* case TTLN_UULINE: */
+ /* case TTLN_STRIKE: */
+ default: rc = -1; goto end;
+ }
+
+ /* Text weight. */
+ if (diff&TTAF_WTMASK)
+ switch ((aa.f&TTAF_WTMASK) >> TTAF_WTSHIFT) {
+ /* case TTWT_MED: */
+ case TTWT_BOLD: PUT0(0, bold); break;
+ case TTWT_DIM: PUT0(0, dim); break;
+ default: rc = -1; goto end;
+ }
- tty_clampattr(&aa, a, tty->acaps);
-
+ /* Other text effects. */
+ if (diff&TTAF_ITAL) {
+ if (aa.f&TTAF_ITAL) PUT0(0, sitm);
+ else PUT0(0, ritm);
+ }
+ if (diff&aa.f&TTAF_INVV) PUT0(0, rev);
+
+ /* Colours. */
+ if (((diff&TTAF_FGSPCMASK) && !(aa.f&TTAF_FGSPCMASK)) ||
+ ((diff&TTAF_BGSPCMASK) && !(aa.f&TTAF_BGSPCMASK))) {
+ /* There's no capability string for resetting just the foreground
+ * or background colours to the defaults, so deal with that here.
+ */
+
+ PUT0(0, op);
+ diff = (diff&~(TTAF_FGSPCMASK | TTAF_BGSPCMASK)) |
+ (aa.f&(TTAF_FGSPCMASK | TTAF_BGSPCMASK));
+ }
+ if ((diff&TTAF_FGSPCMASK) || aa.fg != t->tty.st.attr.fg)
+ CHECK(caps_setcolour(t, gops, go, t->cap.setaf,
+ (aa.f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, aa.fg));
+ if ((diff&TTAF_BGSPCMASK) || aa.bg != t->tty.st.attr.bg)
+ CHECK(caps_setcolour(t, gops, go, t->cap.setab,
+ (aa.f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, aa.bg));
+
+ /* All done. */
+ rc = 0;
+end:
+ t->tty.st.attr = aa; return (rc);
}
static int caps_setmodes(struct tty *tty,
const struct gprintf_ops *gops, void *go,
- uint32 modes_bic, uint32 modes_xor);
+ uint32 modes_bic, uint32 modes_xor)
+{
+ struct tty_caps *t = (struct tty_caps *)tty;
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+ uint32 modes, diff;
+ const char *cap;
+ int rc;
+
+ /* Figure out which modes to set. */
+ modes = (t->tty.st.modes&~modes_bic) ^ modes_xor;
+ diff = modes ^ t->tty.st.modes;
+
+ /* Automatic margins. */
+ if (diff&TTMF_AUTOM) {
+ if (modes&TTMF_AUTOM) PUT0(0, smam);
+ else PUT0(0, rmam);
+ }
+
+ /* Full-screen. */
+ if (diff&TTMF_FSCRN) {
+ if (modes&TTMF_FSCRN) PUT0(0, smcup);
+ else PUT0(0, rmcup);
+ }
+
+ /* Cursor visibility. */
+ if (diff&TTMF_CVIS) {
+ if (modes&TTMF_CVIS) PUT0(0, civis);
+ else PUT0(0, cnorm);
+ }
+
+ /* Auto-insert. */
+ if (diff&TTMF_INS) {
+ cap = modes&TTMF_INS ? t->cap.smir : t->cap.rmir;
+ if (cap) PUT0V(0, cap);
+ else if (!t->cap.ich) { rc = -1; goto end; }
+ }
+
+ /* Delete characters. */
+ if (diff&TTMF_DEL) {
+ cap = modes&TTMF_DEL ? t->cap.smdc : t->cap.rmdc;
+ if (cap) PUT0V(0, cap);
+ else if (!t->cap.dch && !t->cap.dch1) { rc = -1; goto end; }
+ }
+
+ /* Done. */
+ rc = 0;
+end:
+ t->tty.st.modes = modes; return (rc);
+}
+
+#define CIF_PADMUL 1u
+
+static int caps_iterate(struct tty_caps *t,
+ const struct gprintf_ops *gops, void *go,
+ const char *cap1, const char *capn,
+ unsigned f, unsigned npad, unsigned n)
+{
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+ unsigned max, nn;
+ int rc;
+
+ if (cap1 && (n == 1 || !capn))
+ while (n--) PUT0V(npad, cap1);
+ else {
+ max = npad && (f&CIF_PADMUL) ? INT_MAX/npad : INT_MAX;
+ while (n) {
+ nn = n; if (nn > max) nn = max;
+ PUT1IV(npad, capn, nn);
+ n -= nn;
+ }
+ }
+ rc = 0;
+end:
+ return (rc);
+}
+
+static int caps_move_relative(struct tty_caps *t,
+ const struct gprintf_ops *gops, void *go,
+ int delta,
+ const char *fw1, const char *fwn,
+ const char *rv1, const char *rvn)
+{
+ const char *mv1, *mvn;
+
+ if (!delta) return (0);
+ else if (delta > 0) { mv1 = fw1; mvn = fwn; }
+ else { mv1 = rv1; mvn = rvn; delta = - delta; }
+ return (caps_iterate(t, gops, go, mv1, mvn, 0, 0, delta));
+}
+
static int caps_move(struct tty *tty,
const struct gprintf_ops *gops, void *go,
- unsigned orig, int y, int x);
+ unsigned orig, int y, int x)
+{
+ struct tty_caps *t = (struct tty_caps *)tty;
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+ struct tty_attr a;
+ int rc;
+
+ if (!t->cap.mir && (t->tty.st.modes&TTMF_INS)) PUT0(0, rmir);
+
+ a = t->tty.st.attr;
+ if (!t->cap.msgr && a.f) { PUT0(0, sgr0); t->tty.st.attr.f = 0; }
+
+ switch (orig) {
+ case TTORG_HOME:
+ if (t->cap.home && !x && !y)
+ PUT0(1, home);
+ else if (t->cap.cup)
+ PUT2I(1, cup, y, x);
+ else if (t->cap.vpa) {
+ PUT1I(1, vpa, y);
+ if (t->cap.hpa)
+ PUT1I(1, hpa, x);
+ else {
+ PUT0(1, cr);
+ CHECK(caps_move_relative(t, gops, go, x,
+ t->cap.cuf1, t->cap.cuf,
+ t->cap.cub1, t->cap.cub));
+ }
+ } else if (t->cap.home) {
+ PUT0(1, home);
+ CHECK(caps_iterate(t, gops, go, t->cap.cud1, t->cap.cud, 0, 1, y));
+ CHECK(caps_iterate(t, gops, go, t->cap.cuf1, t->cap.cuf, 0, 1, x));
+ } else
+ { rc = -1; goto end; }
+ break;
+
+ case TTORG_CUR:
+ CHECK(caps_move_relative(t, gops, go, y,
+ t->cap.cud1, t->cap.cud,
+ t->cap.cuu1, t->cap.cuu));
+ CHECK(caps_move_relative(t, gops, go, x,
+ t->cap.cuf1, t->cap.cuf,
+ t->cap.cub1, t->cap.cub));
+ break;
+
+ case TTOF_XHOME | TTOF_YCUR:
+ if (x == 0 && y == 1)
+ PUT0(1, nel);
+ else {
+ CHECK(caps_move_relative(t, gops, go, y,
+ t->cap.cud1, t->cap.cud,
+ t->cap.cuu1, t->cap.cuu));
+ if (t->cap.hpa && x)
+ PUT1I(1, hpa, x);
+ else {
+ PUT0(1, cr);
+ CHECK(caps_iterate(t, gops, go, t->cap.cuf1, t->cap.cuf, 0, 1, x));
+ }
+ }
+ break;
+
+ case TTOF_XCUR | TTOF_YHOME:
+ PUT1I(1, vpa, y);
+ CHECK(caps_move_relative(t, gops, go, x,
+ t->cap.cuf1, t->cap.cuf,
+ t->cap.cub1, t->cap.cub));
+ break;
+
+ default:
+ rc = -1; goto end;
+ break;
+ }
+
+ if (!t->cap.mir && (t->tty.st.modes&TTMF_INS)) PUT0(0, smir);
+
+ if (!t->cap.msgr && t->tty.st.attr.f)
+ CHECK(caps_setattr(&t->tty, gops, go, &a));
+
+ rc = 0;
+end:
+ return (rc);
+}
+
static int caps_repeat(struct tty *tty,
const struct gprintf_ops *gops, void *go,
- int ch, unsigned n);
+ int ch, unsigned n)
+{
+ struct tty_caps *t = (struct tty_caps *)tty;
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+ unsigned wd, nn;
+ int rc;
+
+ if (!t->cap.rep)
+ while (n--) CHECK(gops->putch(go, ch));
+ else {
+ wd = t->tty.wd;
+ while (n) {
+ nn = n; if (nn > INT_MAX) nn = INT_MAX;
+ PUT2I((nn + wd - 1)/wd, rep, ch, nn);
+ n -= nn;
+ }
+ }
+ rc = 0;
+end:
+ return (rc);
+}
+
static int caps_erase(struct tty *tty,
const struct gprintf_ops *gops, void *go,
- unsigned f);
+ unsigned f)
+{
+ struct tty_caps *t = (struct tty_caps *)tty;
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+ int rc;
+
+ if (f&TTEF_DSP)
+ switch (f&(TTEF_BEGIN | TTEF_END)) {
+ case 0:
+ break;
+ case TTEF_BEGIN | TTEF_END:
+ if (t->cap.clear) PUT0(t->tty.ht, clear);
+ else { PUT0(1, home); PUT0(t->tty.ht, ed); }
+ break;
+ case TTEF_END:
+ PUT0(t->tty.ht, ed);
+ break;
+ default:
+ rc = -1; goto end;
+ break;
+ }
+ else {
+ if (f&TTEF_BEGIN) PUT0(1, el1);
+ if (f&TTEF_END) PUT0(1, el);
+ }
+ rc = 0;
+end:
+ return (rc);
+}
+
static int caps_erch(struct tty *tty,
const struct gprintf_ops *gops, void *go,
- unsigned n);
+ unsigned n)
+{
+ struct tty_caps *t = (struct tty_caps *)tty;
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+ int rc;
+
+ if (n) PUT1I(1, ech, n);
+ rc = 0;
+end:
+ return (rc);
+}
+
static int caps_ins(struct tty *tty,
const struct gprintf_ops *gops, void *go,
- unsigned f, unsigned n);
+ unsigned f, unsigned n)
+{
+ struct tty_caps *t = (struct tty_caps *)tty;
+ int rc;
+
+ if (f&TTIDF_LN)
+ CHECK(caps_iterate(t, gops, go,
+ t->cap.il1, t->cap.il, CIF_PADMUL, 1, n));
+ else
+ CHECK(caps_iterate(t, gops, go,
+ t->cap.ich1, t->cap.ich, 0, 1, n));
+ rc = 0;
+end:
+ return (rc);
+}
+
+static int caps_inch(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ int ch)
+{
+ struct tty_caps *t = (struct tty_caps *)tty;
+ const struct tty_capops *ops = (const struct tty_capops *)t->tty.ops;
+ int rc;
+
+ if (t->cap.smir ? !(t->tty.st.modes&TTMF_INS) : !t->cap.ich)
+ { rc = -1; goto end; }
+ if (t->cap.ich) PUT0(1, ich);
+ CHECK(gops->putch(go, ch));
+ if (t->cap.ip) PUT0(1, ip);
+ rc = 0;
+end:
+ return (rc);
+}
+
static int caps_del(struct tty *tty,
const struct gprintf_ops *gops, void *go,
- unsigned f, unsigned n);
-static int caps_setscrgn(struct tty *tty,
- const struct gprintf_ops *gops, void *go,
- unsigned y0, unsigned y1);
-static int caps_scroll(struct tty *tty,
- const struct gprintf_ops *gops, void *go,
- int n);
+ unsigned f, unsigned n)
+{
+ struct tty_caps *t = (struct tty_caps *)tty;
+ int rc;
+
+ if (n) {
+ if (f&TTIDF_LN)
+ CHECK(caps_iterate(t, gops, go,
+ t->cap.dl1, t->cap.dl, CIF_PADMUL, 1, n));
+ else
+ CHECK(caps_iterate(t, gops, go,
+ t->cap.dch1, t->cap.dch, 0, 1, n));
+ }
+ rc = 0;
+end:
+ return (rc);
+}
+
+#undef CHECK
+#undef PUT0V
+#undef PUT1IV
+#undef PUT2IV
+#undef PUT0
+#undef PUT1I
+#undef PUT2I
+#define TTY_CAPOPS \
+ caps_setattr, caps_setmodes, \
+ caps_move, caps_repeat, \
+ caps_erase, caps_erch, caps_ins, caps_inch, caps_del, \
+ 0, 0, 0, 0
#endif
+/*----- Termcap -----------------------------------------------------------*/
+
+#ifdef HAVE_TERMCAP
+
+struct tty_termcapslots {
+ char termbuf[4096], capbuf[4096], *capcur;
+};
+struct tty_termcap { TTY_CAPSPFX; struct tty_termcapslots tc; };
+union tty_termcapu { struct tty_termcap tc; TTY_CAPSUSFX; };
+
+static int termcap_boolcap(struct tty *tty,
+ int uix, const char *info, const char *cap)
+{
+ int p;
+
+ p = tgetflag(cap); assert(p >= 0);
+ return (p);
+}
+
+static int termcap_intcap(struct tty *tty,
+ int uix, const char *info, const char *cap)
+{
+ int n;
+
+ n = tgetnum(cap); assert(n >= -1);
+ return (n);
+}
+
+static const char *termcap_strcap(struct tty *tty,
+ int uix, const char *info,
+ const char *cap)
+{
+ struct tty_termcap *t = (struct tty_termcap *)tty;
+ const char *p;
+
+ p = tgetstr(cap, &t->tc.capcur); assert(p != (const char *)-1);
+ return (p);
+}
+
+static int termcap_put0(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned npad, const char *cap)
+{
+ if (!cap) return (-1);
+ global_gops = gops; global_gout = go;
+ return (tputs(cap, npad, caps_putch));
+}
+
+static int termcap_put1i(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned npad, const char *cap, int i0)
+{
+ if (!cap) return (-1);
+ global_gops = gops; global_gout = go;
+ return (tputs(tgoto(cap, -1, i0), npad, caps_putch) == OK ? 0 : -1);
+}
+
+static int termcap_put2i(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned npad,
+ const char *cap, int i0, int i1)
+{
+ if (!cap) return (-1);
+ global_gops = gops; global_gout = go;
+ return (tputs(tgoto(cap, i1, i0), npad, caps_putch) == OK ? 0 : -1);
+}
+
+static const union tty_capopsu termcap_ops = { {
+ { caps_release, TTY_CAPOPS },
+ { termcap_boolcap, termcap_intcap, termcap_strcap,
+ termcap_put0, termcap_put1i, termcap_put2i }
+} };
+
+static struct tty *termcap_init(FILE *fp)
+{
+ union tty_termcapu *u = 0; struct tty *ret = 0;
+ const char *term;
+
+ if (global_lock)
+ { debug("termcap/terminfo terminal already open"); goto end; }
+ term = getenv("TERM"); if (!term) goto end;
+ XNEW(u);
+ if (tgetent(u->tc.tc.termbuf, term) < 1) goto end;
+ u->tc.tc.capcur = u->tc.tc.capbuf;
+ u->tty.ops = &termcap_ops.tty;
+ common_init(&u->tty, fp);
+ init_caps(&u->cap);
+ ret = &u->tty; u = 0;
+end:
+ xfree(u); global_lock = ret; return (ret);
+}
+
+#endif
+
+/*----- Terminfo ----------------------------------------------------------*/
+
+#ifdef HAVE_TERMINFO
+
+static int terminfo_boolcap(struct tty *tty,
+ int uix, const char *info, const char *cap)
+{
+ int p;
+
+ p = tigetflag(info); assert(p >= 0);
+ return (p);
+}
+
+static int terminfo_intcap(struct tty *tty,
+ int uix, const char *info, const char *cap)
+{
+ int n;
+
+ n = tigetnum(info); assert(n >= -1);
+ return (n);
+}
+
+static const char *terminfo_strcap(struct tty *tty,
+ int uix, const char *info,
+ const char *cap)
+{
+ const char *p;
+
+ p = tigetstr(info); assert(p != (const char *)-1);
+ return (p);
+}
+
+static int terminfo_put0(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned npad, const char *cap)
+{
+ if (!cap) return (-1);
+ global_gops = gops; global_gout = go;
+ return (tputs(cap, npad, caps_putch));
+}
+
+static int terminfo_put1i(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned npad, const char *cap, int i0)
+{
+ if (!cap) return (-1);
+ global_gops = gops; global_gout = go;
+ return (tputs(tparm(cap, i0), npad, caps_putch) == OK ? 0 : -1);
+}
+
+static int terminfo_put2i(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned npad,
+ const char *cap, int i0, int i1)
+{
+ if (!cap) return (-1);
+ global_gops = gops; global_gout = go;
+ return (tputs(tparm(cap, i0, i1), npad, caps_putch) == OK ? 0 : -1);
+}
+
+static const union tty_capopsu terminfo_ops = { {
+ { caps_release, TTY_CAPOPS },
+ { terminfo_boolcap, terminfo_intcap, terminfo_strcap,
+ terminfo_put0, terminfo_put1i, terminfo_put2i }
+} };
+
+static struct tty *terminfo_init(FILE *fp)
+{
+ union tty_capsu *u = 0; struct tty *ret = 0;
+ int err;
+
+ if (global_lock)
+ { debug("termcap/terminfo terminal already open"); goto end; }
+ if (setupterm(0, fp ? fileno(fp) : -1, &err) != OK || err < 1) goto end;
+ XNEW(u);
+ u->tty.ops = &terminfo_ops.tty;
+ common_init(&u->tty, fp);
+ init_caps(&u->cap);
+ ret = &u->tty; u = 0;
+end:
+ xfree(u); global_lock = ret; return (ret);
+}
+
+#endif
+
+/*----- Unibilium ---------------------------------------------------------*/
+
+#ifdef HAVE_UNIBILIUM
+
+struct tty_unibislots {
+ unibi_term *ut;
+ unibi_var_t dy[26], st[26];
+};
+struct tty_unibilium { TTY_CAPSPFX; struct tty_unibislots u; };
+union tty_unibiliumu { struct tty_unibilium u; TTY_CAPSUSFX; };
+
+static int termunibi_boolcap(struct tty *tty,
+ int uix, const char *info, const char *cap)
+{
+ struct tty_unibilium *t = (struct tty_unibilium *)tty;
+
+ return (unibi_get_bool(t->u.ut, uix));
+}
+
+static int termunibi_intcap(struct tty *tty,
+ int uix, const char *info, const char *cap)
+{
+ struct tty_unibilium *t = (struct tty_unibilium *)tty;
+
+ return (unibi_get_num(t->u.ut, uix));
+}
+
+static const char *termunibi_strcap(struct tty *tty,
+ int uix, const char *info,
+ const char *cap)
+{
+ struct tty_unibilium *t = (struct tty_unibilium *)tty;
+
+ return (unibi_get_str(t->u.ut, uix));
+}
+
+struct termunibi_outctx {
+ struct tty_unibilium *t;
+ const struct gprintf_ops *gops; void *go;
+ char pad[128];
+ int rc;
+};
+
+static void termunibi_putch(void *ctx, const char *p, size_t sz)
+{
+ struct termunibi_outctx *out = ctx;
+
+ if (out->gops->putm(out->go, p, sz)) out->rc = -1;
+}
+
+static void termunibi_pad(void *ctx, size_t ms, int mulp, int forcep)
+{
+ char pad[128];
+ struct termunibi_outctx *out = ctx;
+ struct tty_unibilium *t = out->t;
+ struct timeval tv;
+ size_t n, nn;
+
+ /* Based on 7 data bits, 1 stop bit, 1 parity bit. */
+#define BITS_PER_KB 9000
+
+ if (forcep || t->tty.baud >= t->cap.pb) {
+ if (t->cap.npc) {
+ tv.tv_sec = ms/1000; tv.tv_usec = 1000*(ms%1000);
+ if (t->tty.fpout) fflush(t->tty.fpout);
+ select(0, 0, 0, 0, &tv);
+ } else {
+ n = (ms*t->tty.baud + BITS_PER_KB - 1)/BITS_PER_KB;
+ while (n) {
+ if (n < sizeof(out->pad)) nn = n;
+ else nn = sizeof(out->pad);
+ if (out->gops->putm(out->go, pad, nn)) out->rc = -1;
+ n -= nn;
+ }
+ }
+ }
+
+#undef BITS_PER_KB
+}
+
+static void setup_termunibi_outctx(struct tty_unibilium *t,
+ struct termunibi_outctx *out,
+ const struct gprintf_ops *gops, void *go)
+{
+ out->t = t; out->rc = 0;
+ out->gops = gops; out->go = go;
+ if (!t->cap.npc)
+ memset(out->pad, t->cap.pad ? *t->cap.pad : 0, sizeof(out->pad));
+}
+
+static int termunibi_put0(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned npad, const char *cap)
+{
+ struct tty_unibilium *t = (struct tty_unibilium *)tty;
+ struct termunibi_outctx out;
+ unibi_var_t arg[9];
+
+ if (!cap) return (-1);
+ setup_termunibi_outctx(t, &out, gops, go);
+ unibi_format(t->u.dy, t->u.st, cap, arg,
+ termunibi_putch, &out,
+ termunibi_pad, &out);
+ return (out.rc);
+}
+
+static int termunibi_put1i(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned npad, const char *cap, int i0)
+{
+ struct tty_unibilium *t = (struct tty_unibilium *)tty;
+ struct termunibi_outctx out;
+ unibi_var_t arg[9];
+
+ if (!cap) return (-1);
+ setup_termunibi_outctx(t, &out, gops, go);
+ arg[0] = unibi_var_from_num(i0);
+ unibi_format(t->u.dy, t->u.st, cap, arg,
+ termunibi_putch, &out,
+ termunibi_pad, &out);
+ return (out.rc);
+}
+
+static int termunibi_put2i(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned npad,
+ const char *cap, int i0, int i1)
+{
+ struct tty_unibilium *t = (struct tty_unibilium *)tty;
+ struct termunibi_outctx out;
+ unibi_var_t arg[9];
+
+ if (!cap) return (-1);
+ setup_termunibi_outctx(t, &out, gops, go);
+ arg[0] = unibi_var_from_num(i0);
+ arg[1] = unibi_var_from_num(i1);
+ unibi_format(t->u.dy, t->u.st, cap, arg,
+ termunibi_putch, &out,
+ termunibi_pad, &out);
+ return (out.rc);
+}
+
+static void termunibi_release(struct tty *tty)
+{
+ struct tty_unibilium *t = (struct tty_unibilium *)tty;
+
+ unibi_destroy(t->u.ut);
+}
+
+static const union tty_capopsu termunibi_ops = { {
+ { termunibi_release, TTY_CAPOPS },
+ { termunibi_boolcap, termunibi_intcap, termunibi_strcap,
+ termunibi_put0, termunibi_put1i, termunibi_put2i }
+} };
+
+static struct tty *termunibi_init(FILE *fp)
+{
+ union tty_unibiliumu *u = 0; struct tty *ret = 0;
+ unibi_term *ut = 0;
+ const char *term;
+
+ term = getenv("TERM"); if (!term) goto end;
+ ut = unibi_from_term(term); if (!ut) goto end;
+ XNEW(u);
+ u->tty.ops = &termunibi_ops.tty;
+ u->u.u.ut = ut; ut = 0;
+ common_init(&u->tty, fp);
+ init_caps(&u->cap);
+ ret = &u->tty; u = 0;
+end:
+ xfree(u); if (ut) unibi_destroy(ut);
+ return (ret);
+}
+
+#endif
+
+/*----- ANSI terminals ----------------------------------------------------*/
+
+struct tty_ansislots {
+ unsigned f;
+#define TAF_CNCATTR 1u /* attributes can be cancelled */
+#define TAF_EDITN 2u /* insert/delete multiple */
+#define TAF_SEMI 4u /* semicolons in CSI 38 m colour */
+};
+struct tty_ansi { TTY_BASEPFX; struct tty_ansislots ansi; };
+union tty_ansiu { struct tty_ansi ansi; TTY_BASEUSFX; };
+
+/* Control sequences.
+ *
+ * * CUP: \33 [ Y ; X H `cursor position' [vt100]
+ *
+ * * CUU/CUD/CUR/CUL: \33 [ N A/B/C/D `cursor up/down/right/left'
+ *
+ * * DCH: \33 [ N P `delete character' [vt220]
+ * (single char only in vt102?)
+ *
+ * * DL: \33 [ N M `delete line' [vt220]
+ * (single line only in vt102?)
+ *
+ * * ECH: \33 [ N X `erase characters' [vt220]
+ *
+ * * ED: \33 [ P J `erase in display'
+ * P = 0 erase to end-of-screen [vt100]
+ * P = 1 erase from start-of-screen [vt100]
+ * P = 2 erase entire screen [vt100]
+ *
+ * * EL: \33 [ P K `erase in line'
+ * P = 0 erase to end-of-line [vt100]
+ * P = 1 erase from start-of-line [vt100]
+ * P = 2 erase entire line [vt100]
+ *
+ * * HPA/VPA: \33 [ I G/d `horizontal/vertical position
+ * absolute' [ecma48-4]
+ *
+ * * ICH: \33 [ N @ `insert character' [vt220]
+ * (single char only in vt102?)
+ *
+ * * IL: \33 [ N L `insert line' [vt220]
+ * (single line only in vt102?)
+ *
+ * * SGR: \33 [ P ; ... m `select graphics rendition'
+ * P = 0 cancel all attributes [vt100]
+ * P = 1 bold [vt100]
+ * P = 2 dim [ecma48-4]
+ * P = 3 italics [ecma48-4]
+ * P = 4 underline [vt100]
+ * P = 7 inverse video [vt100]
+ * P = 9 strikeout [ecma48-4]
+ * P = 21 double underline [ecma48-4]
+ * P = 22 cancal bold/dim [vt220]
+ * P = 24 cancel underline [vt220]
+ * P = 27 cancel inverse video [vt220]
+ * P = 30 + 4 R + 2 G + B set 1BPC foreground [ecma48-4]
+ * P = 38 : 2 : ? : R : G : B set foreground [iso8613-6]
+ * P = 38 : 5 : N set foreground [iso8613-6, xterm]
+ * P = 39 cancel foreground [ecma48-4]
+ * P = 40--49 as above, for background
+ * P = 90 + 4 R + 2 G + B set bright 1BPC foreground [xterm]
+ * P = 100 + 4 R + 2 G + B set bright 1BPC background [xterm]
+ *
+ * * SM/RM: \33 [ P ; ... h/l `set/reset modes'
+ * M = 4 insert [vt220]
+ *
+ * * SM, RM: \33 [ ? P ; ... h/l `set/reset private modes'
+ * M = 7 auto right margin [vt100]
+ * M = 25 visible cursor [vt220]
+ * M = 1049 alternate screen [xterm]
+ *
+ * * \33 [ P ; X ; Y t `window manipulation'
+ * P = 22, X = 0 save title and icon [xterm]
+ * P = 23, X = 0 restore title and icon [xterm]
+ */
+
+static void ansi_release(struct tty *tty) { ; }
+
+#define CHECK(expr) do { if ((expr) < 0) { rc = -1; goto end; } } while (0)
+
+#define PUTCH(ch) CHECK(gops->putch(go, (ch)))
+#define PUTLIT(lit) CHECK(gops->putm(go, (lit), sizeof(lit) - 1))
+#define SEMI do { \
+ if (!(f&TAF_SEMI)) f |= TAF_SEMI; \
+ else PUTCH(';'); \
+} while (0)
+
+static int ansi_setcolour(struct tty_ansi *t, unsigned *f_inout,
+ const struct gprintf_ops *gops, void *go,
+ int norm, int br,
+ uint32 spc, uint32 clr)
+{
+ unsigned f = *f_inout;
+ int rc;
+
+ switch (spc) {
+ case TTCSPC_NONE:
+ SEMI; CHECK(gprintf(gops, go, "%d", norm + 9));
+ break;
+ case TTCSPC_1BPC:
+ SEMI; CHECK(gprintf(gops, go, "%d", norm + clr));
+ break;
+ case TTCSPC_1BPCBR:
+ SEMI; CHECK(gprintf(gops, go, "%d", br + (clr&~TT1BPC_BRI)));
+ break;
+ case TTCSPC_4LPC: case TTCSPC_6LPC:
+ SEMI;
+ if (t->ansi.f&TAF_SEMI)
+ CHECK(gprintf(gops, go, "%d;5;%d", norm + 8, clr + 16));
+ else
+ CHECK(gprintf(gops, go, "%d:5:%d", norm + 8, clr + 16));
+ break;
+ case TTCSPC_8LGS:
+ SEMI;
+ if (t->ansi.f&TAF_SEMI)
+ CHECK(gprintf(gops, go, "%d;5;%d", norm + 8, clr + 80));
+ else
+ CHECK(gprintf(gops, go, "%d:5:%d", norm + 8, clr + 80));
+ break;
+ case TTCSPC_24LGS:
+ SEMI;
+ if (t->ansi.f&TAF_SEMI)
+ CHECK(gprintf(gops, go, "%d;5;%d", norm + 8, clr + 232));
+ else
+ CHECK(gprintf(gops, go, "%d:5:%d", norm + 8, clr + 232));
+ break;
+ case TTCSPC_8BPC:
+ SEMI;
+ if (t->ansi.f&TAF_SEMI)
+ CHECK(gprintf(gops, go, "%d;2;%d;%d;%d", norm + 8,
+ TTCOL_8BR(clr), TTCOL_8BG(clr), TTCOL_8BB(clr)));
+ else
+ CHECK(gprintf(gops, go, "%d:2::%d:%d:%d", norm + 8,
+ TTCOL_8BR(clr), TTCOL_8BG(clr), TTCOL_8BB(clr)));
+ break;
+ default:
+ rc = -1; goto end;
+ }
+
+ rc = 0;
+end:
+ *f_inout = f; return (rc);
+}
+
+static int ansi_setattr(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ const struct tty_attr *a)
+{
+ struct tty_ansi *t = (struct tty_ansi *)tty;
+ struct tty_attr aa;
+ uint32 diff;
+ int rc = 0;
+ unsigned z, c, f = 0;
+
+ tty_clampattr(&aa, a, t->tty.acaps);
+ diff = aa.f ^ t->tty.st.attr.f;
+ if (!diff && aa.fg == t->tty.st.attr.fg && aa.bg == t->tty.st.attr.bg)
+ return (0);
+
+ c = 0;
+#define CLEARP(mask) ((diff&(mask)) && !(aa.f&(mask)))
+ if (CLEARP(TTAF_LNMASK)) c += 3;
+ if (CLEARP(TTAF_WTMASK)) c += 3;
+ if (diff&~aa.f&TTAF_INVV) c += 3;
+ if (diff&~aa.f&TTAF_STRIKE) c += 3;
+ if (diff&~aa.f&TTAF_ITAL) c += 3;
+ if (CLEARP(TTAF_FGSPCMASK)) c += 3;
+ if (CLEARP(TTAF_BGSPCMASK)) c += 3;
+#undef CLEARP
+
+ z = 0;
+ switch ((aa.f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
+ case TTLN_ULINE: z += 2; break;
+ case TTLN_UULINE: z += 3; break;
+ }
+ if (aa.f&TTAF_WTMASK) z += 2;
+ if (aa.f&TTAF_INVV) z += 2;
+ if (aa.f&TTAF_STRIKE) z += 2;
+ if (aa.f&TTAF_ITAL) z += 2;
+#define COLOURCOST(col) do { \
+ switch ((aa.f&TTAF_##col##SPCMASK) >> TTAF_##col##SPCSHIFT) { \
+ case TTCSPC_1BPC: case TTCSPC_1BPCBR: z += 3; break; \
+ case TTCSPC_4LPC: case TTCSPC_8LGS: z += 8; break; \
+ case TTCSPC_6LPC: case TTCSPC_24LGS: z += 9; break; \
+ case TTCSPC_8BPC: z += 16; break; \
+ } \
+} while (0)
+ COLOURCOST(FG); COLOURCOST(BG);
+#undef COLOURCOST
+
+ PUTLIT("\33[");
+
+ if (z <= c) { SEMI; diff = aa.f; }
+
+ if (diff&TTAF_LNMASK)
+ switch ((aa.f&TTAF_LNMASK) >> TTAF_LNSHIFT) {
+ case TTLN_NONE: SEMI; PUTLIT("24"); break;
+ case TTLN_ULINE: SEMI; PUTCH('4'); break;
+ case TTLN_UULINE: SEMI; PUTLIT("21"); break;
+ default: rc = -1; goto end;
+ }
+
+ if (diff&TTAF_WTMASK)
+ switch ((aa.f&TTAF_WTMASK) >> TTAF_WTSHIFT) {
+ case TTWT_MED: SEMI; PUTLIT("22"); break;
+ case TTWT_BOLD: SEMI; PUTCH('1'); break;
+ case TTWT_DIM: SEMI; PUTCH('2'); break;
+ default: rc = -1; goto end;
+ }
+
+ if (diff&TTAF_INVV)
+ { SEMI; if (aa.f&TTAF_INVV) PUTCH('7'); else PUTLIT("27"); }
+ if (diff&TTAF_STRIKE)
+ { SEMI; if (aa.f&TTAF_STRIKE) PUTCH('9'); else PUTLIT("29"); }
+ if (diff&TTAF_ITAL)
+ { SEMI; if (aa.f&TTAF_ITAL) PUTCH('3'); else PUTLIT("23"); }
+
+ if (diff&TTAF_FGSPCMASK || aa.fg != tty->st.attr.fg)
+ CHECK(ansi_setcolour(t, &f, gops, go, 30, 90,
+ (aa.f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, aa.fg));
+ if (diff&TTAF_BGSPCMASK || aa.bg != tty->st.attr.bg)
+ CHECK(ansi_setcolour(t, &f, gops, go, 40, 100,
+ (aa.f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, aa.bg));
+
+ PUTCH('m'); rc = 0;
+end:
+ t->tty.st.attr = aa; return (rc);
+
+}
+
+static int ansi_setmodes(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ uint32 modes_bic, uint32 modes_xor)
+{
+ uint32 modes, diff;
+ int rc;
+
+ /* Figure out which modes to set. */
+ modes = (tty->st.modes&~modes_bic) ^ modes_xor;
+ diff = modes ^ tty->st.modes;
+
+ if (diff&TTMF_AUTOM) {
+ if (modes&TTMF_AUTOM) PUTLIT("\33[?7h");
+ else PUTLIT("\33[?7l");
+ }
+
+ if (diff&TTMF_FSCRN) {
+ if (modes&TTMF_FSCRN) PUTLIT("\33[?1049h\33[22;0;0t");
+ else PUTLIT("\33[?1049l\33[23;0;0t");
+ }
+
+ if (diff&TTMF_CVIS) {
+ if (modes&TTMF_CVIS) PUTLIT("\33[?25h");
+ else PUTLIT("\33[?25l");
+ }
+
+ if (diff&TTMF_INS) {
+ if (modes&TTMF_INS) PUTLIT("\33[4h");
+ else PUTLIT("\33[4l");
+ }
+
+ rc = 0;
+end:
+ tty->st.modes = modes;
+ return (rc);
+}
+
+static int ansi_move(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned orig, int y, int x)
+{
+ int rc;
+
+ if (orig == TTORG_HOME) {
+ if (!x) {
+ if (!y) PUTLIT("\33[H");
+ else CHECK(gprintf(gops, go, "\33[%dH", y + 1));
+ } else {
+ if (!y) CHECK(gprintf(gops, go, "\33[;%dH", x + 1));
+ else CHECK(gprintf(gops, go, "\33[%d,%dH", y + 1, x + 1));
+ }
+ } else if (orig == (TTOF_XHOME | TTOF_YCUR) && x == 0 && y == 1)
+ PUTLIT("\r\n");
+ else {
+ if (!(orig&TTOF_YCUR)) CHECK(gprintf(gops, go, "\33[%dd", y + 1));
+ else if (y == -1) PUTLIT("\33[A");
+ else if (y < 0) CHECK(gprintf(gops, go, "\33[%dA", -y));
+ else if (y == +1) PUTLIT("\33[B"); /* not `^J'! */
+ else if (y > 1) CHECK(gprintf(gops, go, "\33[%dB", y));
+ if (!(orig&TTOF_XCUR)) {
+ if (!x)
+ PUTCH('\r');
+ else if (tty->ocaps&TTCF_MIXMV)
+ CHECK(gprintf(gops, go, "\33[%dG", x + 1));
+ else
+ CHECK(gprintf(gops, go, "\r\33[%dC", x));
+ } else {
+ if (x == -1) PUTCH('\b');
+ else if (x < 0) CHECK(gprintf(gops, go, "\33[%dD", -x));
+ else if (x == +1) PUTLIT("\33[C");
+ else if (x > 0) CHECK(gprintf(gops, go, "\33[%dC", x));
+ }
+ }
+ rc = 0;
+end:
+ return (rc);
+}
+
+static int ansi_repeat(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ int ch, unsigned n)
+{
+ int rc;
+
+ while (n--) PUTCH(ch);
+ rc = 0;
+end:
+ return (rc);
+}
+
+static int ansi_erase(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned f)
+{
+ int rc;
+
+ if (f&TTEF_DSP)
+ switch (f&(TTEF_BEGIN | TTEF_END)) {
+ case 0: break;
+ case TTEF_BEGIN: PUTLIT("\33[1J"); break;
+ case TTEF_END: PUTLIT("\33[J"); break;
+ case TTEF_BEGIN | TTEF_END: PUTLIT("\33[2J"); break;
+ }
+ else
+ switch (f&(TTEF_BEGIN | TTEF_END)) {
+ case 0: break;
+ case TTEF_BEGIN: PUTLIT("\33[1K"); break;
+ case TTEF_END: PUTLIT("\33[K"); break;
+ case TTEF_BEGIN | TTEF_END: PUTLIT("\33[2K"); break;
+ }
+ rc = 0;
+end:
+ return (rc);
+}
+
+static int ansi_erch(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned n)
+{
+ int rc;
+
+ if (n == 1) PUTLIT("\33[X");
+ else if (n) CHECK(gprintf(gops, go, "\33[%uX", n));
+ rc = 0;
+end:
+ return (rc);
+}
+
+static int ansi_ins(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned f, unsigned n)
+{
+ int rc;
+
+ if (f&TTIDF_LN) {
+ if (n == 1) PUTLIT("\33[L");
+ else if (n) CHECK(gprintf(gops, go, "\33[%uL", n));
+ } else {
+ if (n == 1) PUTLIT("\33[@");
+ else if (n) CHECK(gprintf(gops, go, "\33[%u@", n));
+ }
+ rc = 0;
+end:
+ return (rc);
+}
+
+static int ansi_inch(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ int ch)
+{
+ if (!(tty->st.modes&TTMF_INS)) return (-1);
+ else return (gops->putch(go, ch));
+}
+
+static int ansi_del(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned f, unsigned n)
+{
+ int rc;
+
+ if (f&TTIDF_LN) {
+ if (n == 1) PUTLIT("\33[M");
+ else if (n) CHECK(gprintf(gops, go, "\33[%uM", n));
+ } else {
+ if (n == 1) PUTLIT("\33[P");
+ else if (n) CHECK(gprintf(gops, go, "\33[%uP", n));
+ }
+ rc = 0;
+end:
+ return (rc);
+}
+
+#undef PUTCH
+#undef PUTLIT
+#undef SEMI
+
+#undef CHECK
+
+static const struct tty_ops ansi_ops = {
+ ansi_release,
+ ansi_setattr, ansi_setmodes,
+ ansi_move, ansi_repeat,
+ ansi_erase, ansi_erch, ansi_ins, ansi_inch, ansi_del,
+ 0, 0, 0, 0
+};
+
+static struct tty *ansi_init(FILE *fp)
+{
+#define COLS_NO 0
+#define COLS_8 (TTACF_FG | TTACF_BG | TTACF_1BPC)
+#define COLS_16 (COLS_8 | TTACF_1BPCBR)
+#define COLS_88 (COLS_16 | TTACF_4LPC | TTACF_8LGS)
+#define COLS_256 (COLS_16 | TTACF_6LPC | TTACF_24LGS)
+#define COLS_16M (COLS_256 | TTACF_8BPC)
+
+#define EDIT_OPS (TTCF_ERCH | \
+ TTCF_DELCH | TTCF_DELLN | \
+ TTCF_INSCH | TTCF_INSLN)
+
+ static const struct flagmap {
+ const char *name;
+ uint32 acaps, ocaps;
+ unsigned tf;
+ } flagmap[] = {
+ { "dim", TTACF_DIM, 0, 0 },
+ { "uuline", TTACF_UULINE, 0, 0 },
+ { "strike", TTACF_STRIKE, 0, 0 },
+ { "ital", TTACF_ITAL, 0, 0 },
+ { "cvis", 0, TTMF_CVIS, 0 },
+ { "fscrn", 0, TTMF_FSCRN, 0 },
+ { "insmode", 0, TTMF_INS, 0 },
+ { "hvpa" , 0, TTCF_MIXMV, 0 },
+ { "edit", 0, EDIT_OPS, 0 },
+ { "cncattr", 0, 0, TAF_CNCATTR },
+ { "editn", 0, 0, TAF_EDITN },
+ { "semi", 0, 0, TAF_SEMI },
+ { 0, 0, 0, 0 }
+ };
+
+#undef EDIT_OPS
+
+ static const struct kw { const char *name; uint32 val; }
+ kw_colours[] = {
+ { "no", COLS_NO },
+ { "8", COLS_8 },
+ { "16", COLS_16 },
+ { "88", COLS_88 },
+ { "256", COLS_256 },
+ { "16m", COLS_16M },
+ { 0, 0 }
+ };
+
+ static const struct enummap {
+ const char *name;
+ uint32 mask;
+ const struct kw *kw;
+ } enummap[] = {
+ { "colours", TTACF_CSPCMASK, kw_colours },
+ { 0, 0, 0 }
+ };
+
+
+ static const struct termmap {
+ const char *pat;
+ unsigned acaps, ocaps, tf;
+ } termmap[] = {
+
+#define VT100_ACAPS (TTACF_ULINE | TTACF_BOLD | TTACF_INVV)
+#define VT100_OCAPS (TTMF_AUTOM | \
+ TTCF_RELMV | TTCF_ABSMV | \
+ TTCF_MMARG | \
+ TTCF_ERBOL | TTCF_EREOL | \
+ TTCF_ERBOD | TTCF_EREOD | TTCF_ERDSP)
+#define VT100_TF (0)
+
+#define VT102_ACAPS (VT100_ACAPS)
+#define VT102_OCAPS (VT100_OCAPS | \
+ TTMF_INS | \
+ TTCF_INSCH | TTCF_INSLN | TTCF_DELCH | TTCF_DELLN)
+#define VT102_TF (VT100_TF)
+
+#define VT220_ACAPS (VT102_ACAPS)
+#define VT220_OCAPS (VT102_OCAPS | TTMF_CVIS | TTCF_ERCH)
+#define VT220_TF (VT102_TF | TAF_CNCATTR | TAF_EDITN)
+
+#define ECMA48_ACAPS (VT220_ACAPS | TTACF_DIM)
+#define ECMA48_OCAPS (VT220_OCAPS | TTCF_MIXMV)
+#define ECMA48_TF (VT220_TF)
+
+#define XTERM_ACAPS (ECMA48_ACAPS)
+#define XTERM_OCAPS (ECMA48_OCAPS | TTMF_FSCRN)
+#define XTERM_TF (ECMA48_TF)
+
+#define STRIKE TTACF_STRIKE
+#define ITAL TTACF_ITAL
+#define SEMI TAF_SEMI
+
+#define T(pat, base, cols, acaps, ocaps, tf) \
+ { pat, \
+ base##_ACAPS | COLS_##cols | (acaps), \
+ base##_OCAPS | (ocaps), base##_TF | (tf) }
+
+ T("color_xterm", XTERM, 8, STRIKE | ITAL, 0, 0),
+
+ T("gnome", XTERM, 16M, STRIKE | ITAL, 0, SEMI),
+ /*T("gonme-*" XTERM, 16M, STRIKE | ITAL, 0, SEMI),*/
+
+ T("linux", XTERM, 16, 0, 0, 0),
+
+ T("putty", XTERM, 16M, 0, 0, SEMI),
+
+ T("vt100*", VT100, NO, 0, 0, 0),
+ T("vt102*", VT102, NO, 0, 0, 0),
+ T("vt[2-5][0-9][0-9]*", VT220, NO, 0, 0, 0),
+
+ T("vte", XTERM, 16M, STRIKE | ITAL, 0, SEMI),
+ /*T("vte-*" XTERM, 16M, STRIKE | ITAL, 0, SEMI),*/
+
+ T("win", XTERM, 16M, 0, 0, SEMI),
+
+ T("xterm", XTERM, 16M, STRIKE | ITAL, 0, 0),
+ T("xterm-color", XTERM, 8, STRIKE | ITAL, 0, 0),
+ T("xterm-16color", XTERM, 16, STRIKE | ITAL, 0, 0),
+ T("xterm-88color", XTERM, 88, STRIKE | ITAL, 0, SEMI),
+ T("xterm-256color", XTERM, 256, STRIKE | ITAL, 0, SEMI),
+ T("xterm-direct", XTERM, 16M, STRIKE | ITAL, 0, 0),
+ T("xterm-*", XTERM, 16M, STRIKE | ITAL, 0, 0),
+
+ /*T("*-color", XTERM, 16, 0, 0, 0),*/
+ /*T("*-16color", XTERM, 16, 0, 0, 0),*/
+ T("*-88color", XTERM, 88, 0, 0, SEMI),
+ T("*-256color", XTERM, 256, 0, 0, SEMI),
+ T("*-direct", XTERM, 16M, 0, 0, SEMI),
+
+ T("*", XTERM, 16, 0, 0, 0),
+ { 0, 0, 0, 0 }
+
+#undef VT100_ACAPS
+#undef VT100_OCAPS
+#undef VT100_TF
+
+#undef VT102_ACAPS
+#undef VT102_OCAPS
+#undef VT102_TF
+
+#undef VT220_ACAPS
+#undef VT220_OCAPS
+#undef VT220_TF
+
+#undef ECMA48_ACAPS
+#undef ECMA48_OCAPS
+#undef ECMA48_TF
+
+#undef XTERM_ACAPS
+#undef XTERM_OCAPS
+#undef XTERM_TF
+
+#undef STRIKE
+#undef ITAL
+#undef SEMI
+ };
+
+#undef COLS_NO
+#undef COLS_8
+#undef COLS_16
+#undef COLS_88
+#undef COLS_256
+#undef COLS_16M
+
+ union tty_ansiu *u = 0; struct tty *ret = 0;
+ const char *term, *config, *p, *l;
+ const struct kw *kw;
+ const struct enummap *em;
+ const struct flagmap *fm;
+ const struct termmap *tm;
+ size_t n, nn;
+ unsigned
+ acaps = 0, ocaps = 0, tf = 0,
+ acapset = 0, ocapset = 0, tfset = 0,
+ f = 0;
+#define f_sense 1u
+
+ config = getenv("MLIB_TTY_ANSICONFIG");
+ term = getenv("TERM");
+
+ if (term && STRCMP(term, ==, "dumb")) goto end;
+
+ if (config) {
+ l = config + strlen(config);
+ for (;;) {
+
+ for (;;)
+ if (config >= l) goto done_config;
+ else if (!ISSPACE(*config)) break;
+ else config++;
+
+ for (p = config + 1; p < l && !ISSPACE(*p); p++);
+ if (*config == '+' || *config == '-') {
+ if (*config == '+') f |= f_sense;
+ else f &= ~f_sense;
+ config++; n = p - config;
+
+ for (fm = flagmap; fm->name; fm++)
+ if (STRNCMP(config, ==, fm->name, n) && !fm->name[n])
+ goto found_flag;
+ debug("unknown flag `%.*s'", (int)n, config); goto next_config;
+ found_flag:
+ if ((acapset&fm->acaps) || (ocapset&fm->ocaps) || (tfset&fm->tf)) {
+ debug("duplicate setting for `%s'", fm->name);
+ goto next_config;
+ }
+ if (f&f_sense)
+ { acaps |= fm->acaps; ocaps |= fm->ocaps; tf |= fm->tf; }
+ acapset |= fm->acaps; ocapset |= fm->ocaps; tfset |= fm->tf;
+ } else {
+ n = p - config;
+ p = memchr(config, '=', n);
+ if (!p) {
+ debug("missing `=' in setting `%.*s'", (int)n, config);
+ goto next_config;
+ }
+ nn = p - config;
+ for (em = enummap; em->name; em++)
+ if (STRNCMP(config, ==, em->name, nn) && !em->name[nn])
+ goto found_enum;
+ debug("unknown setting `%.*s'", (int)nn, config); goto next_config;
+ found_enum:
+ p++; nn = n - nn - 1;
+ for (kw = em->kw; kw->name; kw++)
+ if (STRNCMP(p, ==, kw->name, nn) && !kw->name[nn])
+ goto found_kw;
+ debug("unknown `%s' value `%.*s", em->name, (int)nn, p);
+ goto next_config;
+ found_kw:
+ if (acapset&em->mask) {
+ debug("duplicate setting for `%s'", em->name);
+ goto next_config;
+ }
+ acaps |= kw->val; acapset |= em->mask;
+ }
+
+ next_config:
+ config += n;
+ }
+ done_config:;
+ }
+
+ if (term) {
+ for (tm = termmap; tm->pat; tm++)
+ if (str_match(tm->pat, term))
+ goto found_term;
+ assert(0);
+ found_term:
+ acaps |= tm->acaps&~acapset;
+ ocaps |= tm->ocaps&~ocapset;
+ tf |= tm->tf&~tfset;
+ }
+
+ env_colour_caps(&acaps);
+ if (acaps&TTACF_CSPCMASK) ocaps |= TTCF_BGER;
+
+ XNEW(u);
+ u->tty.ops = &ansi_ops;
+ u->tty.acaps = acaps;
+ u->tty.ocaps = ocaps;
+ u->ansi.ansi.f = tf;
+ u->tty.wd = 80; u->tty.ht = 25;
+ u->tty.st.modes = TTMF_AUTOM | (u->tty.ocaps&TTMF_CVIS);
+ u->tty.st.attr.f = 0; u->tty.st.attr.fg = u->tty.st.attr.bg = 0;
+ common_init(&u->ansi.tty, fp);
+ ret = &u->tty; u = 0;
+end:
+ xfree(u); return (ret);
+
+#undef f_sense
+}
+
+/*----- Backend selection -------------------------------------------------*/
+
+struct tty *tty_open(FILE *fp, unsigned f, const unsigned *backends)
+{
+ static const struct betab {
+ const char *name; unsigned code;
+ struct tty *(*init)(FILE */*fp*/);
+ } betab[] = {
+ { "unibilium", TTBK_UNIBI, termunibi_init },
+ { "terminfo", TTBK_TERMINFO, terminfo_init },
+ { "termcap", TTBK_TERMCAP, termcap_init },
+ { "ansi", TTBK_ANSI, ansi_init },
+ { 0, 0, 0 }
+ };
+
+ const struct betab *bt;
+ const char *config, *p, *l;
+ struct tty *tty = 0;
+ FILE *fpin = 0;
+ size_t n;
+
+ if (fp || !(f&TTF_OPEN))
+ fpin = fp != stdout ? fp : isatty(STDIN_FILENO) ? stdin : 0;
+ else {
+ if (isatty(STDIN_FILENO)) fpin = stdin;
+ else fpin = 0;
+ if (isatty(STDOUT_FILENO)) { fp = stdout; f |= TTF_BORROW; }
+ else if (isatty(STDERR_FILENO)) { fp = stderr; f |= TTF_BORROW; }
+ else {
+ fp = fopen("/dev/tty", "r+"); if (!fp) goto end;
+ f &= ~TTF_BORROW;
+ }
+ }
+
+ config = getenv("MLIB_TTY_BACKENDS");
+ if (config) {
+ l = config + strlen(config);
+ for (;;) {
+ for (;;)
+ if (config >= l) goto done_config;
+ else if (!ISSPACE(*config)) break;
+ else config++;
+
+ for (p = config + 1; p < l && !ISSPACE(*p); p++);
+ n = p - config;
+
+ for (bt = betab; bt->name; bt++)
+ if (STRNCMP(config, ==, bt->name, n) && !bt->name[n])
+ goto found_byname;
+ debug("unknown backend `%.*s'", (int)n, config); goto next_config;
+ found_byname:
+ tty = bt->init(fp); if (tty) goto found;
+ debug("failed to initialize `%s'", bt->name);
+ next_config:
+ config += n;
+ }
+ done_config:;
+ } else if (backends)
+ while (*backends) {
+ for (bt = betab; bt->name; bt++)
+ if (*backends == bt->code) goto found_bycode;
+ debug("unknown backend code %u", *backends); goto next_code;
+ found_bycode:
+ tty = bt->init(fp); if (tty) goto found;
+ debug("failed to initialize `%s'", bt->name);
+ next_code:
+ backends++;
+ }
+ else
+ for (bt = betab; bt->name; bt++) {
+ tty = bt->init(fp); if (tty) goto found;
+ debug("failed to initialize `%s'", bt->name);
+ }
+
+ debug("all backends failed"); goto end;
+found:
+ debug("selected backend `%s'", bt->name);
+ tty->fpin = fpin; tty->f = f; fp = 0;
+end:
+ if (fp && !(f&TTF_BORROW)) fclose(fp);
+ return (tty);
+}
+
+void tty_close(struct tty *tty)
+{
+ if (tty) {
+ if (tty->fpout && !(tty->f&TTF_BORROW)) fclose(tty->fpout);
+ tty->ops->release(tty); xfree(tty);
+ }
+}
+
+/*----- Terminal operations -----------------------------------------------*/
+
+int tty_setattrg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ const struct tty_attr *a)
+ { return (tty->ops->setattr(tty, gops, go, a)); }
+
+int tty_setattr(struct tty *tty, const struct tty_attr *a)
+ { return (tty->ops->setattr(tty, &file_printops, tty->fpout, a)); }
+
+int tty_setattrlistg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ const struct tty_attrlist *aa)
+{
+ for (;; aa++)
+ if ((tty->acaps&aa->cap_mask) == aa->cap_eq)
+ return (tty->ops->setattr(tty, gops, go, &aa->attr));
+ else if (!aa->cap_mask)
+ return (0);
+}
+
+int tty_setattrlist(struct tty *tty, const struct tty_attrlist *aa)
+ { return (tty_setattrlistg(tty, &file_printops, tty->fpout, aa)); }
+
+int tty_setmodesg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ uint32 modes_bic, uint32 modes_xor)
+ { return (tty->ops->setmodes(tty, gops, go, modes_bic, modes_xor)); }
+
+int tty_setmodes(struct tty *tty, uint32 modes_bic, uint32 modes_xor)
+{
+ return (tty->ops->setmodes(tty, &file_printops, tty->fpout,
+ modes_bic, modes_xor));
+}
+
+int tty_moveg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned orig, int y, int x)
+ { return (tty->ops->move(tty, gops, go, orig, y, x)); }
+
+int tty_move(struct tty *tty, unsigned orig, int y, int x)
+ { return (tty->ops->move(tty, &file_printops, tty->fpout, orig, y, x)); }
+
+int tty_repeatg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ int ch, unsigned n)
+ { return (tty->ops->repeat(tty, gops, go, ch, n)); }
+
+int tty_repeat(struct tty *tty, int ch, unsigned n)
+ { return (tty->ops->repeat(tty, &file_printops, tty->fpout, ch, n)); }
+
+int tty_eraseg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned f)
+ { return (tty->ops->erase(tty, gops, go, f)); }
+
+int tty_erase(struct tty *tty, unsigned f)
+ { return (tty->ops->erase(tty, &file_printops, tty->fpout, f)); }
+
+int tty_erchg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned n)
+ { return (tty->ops->erch(tty, gops, go, n)); }
+
+int tty_erch(struct tty *tty, unsigned n)
+ { return (tty->ops->erch(tty, &file_printops, tty->fpout, n)); }
+
+int tty_insg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned f, unsigned n)
+ { return (tty->ops->ins(tty, gops, go, f, n)); }
+
+int tty_ins(struct tty *tty, unsigned f, unsigned n)
+ { return (tty->ops->ins(tty, &file_printops, tty->fpout, f, n)); }
+
+int tty_inchg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ int ch)
+ { return (tty->ops->inch(tty, gops, go, ch)); }
+
+int tty_inch(struct tty *tty, int ch)
+ { return (tty->ops->inch(tty, &file_printops, tty->fpout, ch)); }
+
+int tty_delg(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned f, unsigned n)
+ { return (tty->ops->del(tty, gops, go, f, n)); }
+
+int tty_del(struct tty *tty, unsigned f, unsigned n)
+ { return (tty->ops->del(tty, &file_printops, tty->fpout, f, n)); }
+
/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Progress bars for terminal programs
+ *
+ * (c) 2025 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#define _XOPEN_SOURCE
+#include "config.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "alloc.h"
+#include "arena.h"
+#include "dstr.h"
+#include "gprintf.h"
+#include "growbuf.h"
+#include "tty.h"
+#include "ttycolour.h"
+#include "ttyprogress.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+int ttyprogress_init(struct ttyprogress *progress, struct tty *tty)
+{
+#define GENATTR(want, attrs, fgspc, fgcol, bgspc, bgcol) \
+ { (want), (want), \
+ { ((fgspc) << TTAF_FGSPCSHIFT) | \
+ ((bgspc) << TTAF_BGSPCSHIFT) | (attrs), \
+ 0, (fgcol), (bgcol) } }
+
+#define FGBG(want, attrs, fgcol, bgcol) \
+ GENATTR(want | TTACF_FG | TTACF_BG, attrs, \
+ TTCSPC_1BPCBR, TTCOL_##fgcol, TTCSPC_1BPCBR, TTCOL_##bgcol)
+#define ATTR(want, attrs) \
+ GENATTR(want, attrs, TTCSPC_NONE, 0, TTCSPC_NONE, 0)
+
+#define BOLD (TTWT_BOLD << TTAF_WTSHIFT)
+
+ static const struct tty_attrlist
+ bbar_attrs[] = { FGBG(0, 0, BLK, GRN), ATTR(0, TTAF_INVV) },
+ ebar_attrs[] = { FGBG(0, 0, BLK, YLW), TTY_ATTRLIST_CLEAR },
+ note_attrs[] = { FGBG(0, BOLD, WHT, BLU), ATTR(0, TTAF_INVV | BOLD) },
+ warn_attrs[] = { FGBG(0, BOLD, WHT, MGN), ATTR(0, TTAF_INVV | BOLD) },
+ err_attrs[] = { FGBG(0, BOLD, WHT, RED), ATTR(0, TTAF_INVV | BOLD) };
+ static const struct ttycolour_style hltab[] =
+ TTYCOLOUR_INITTAB(TTYPROGRESS_HIGHLIGHTS);
+
+#undef GENATTR
+#undef FGBG
+#undef ATTR
+#undef BOLD
+
+ /* Clear the progress state. */
+ progress->tty = tty;
+ progress->items = progress->end_item = 0;
+ progress->nitems = 0; progress->last_lines = 0;
+ progress->line.a = arena_global; dstr_create(&progress->line.t);
+ progress->line.p = 0; progress->line.sz = 0;
+ progress->tv_update.tv_sec = 0; progress->tv_update.tv_usec = 0;
+
+ /* Check that the terminal is sufficiently cromulent. */
+ if (!tty || !(tty->ocaps&TTCF_RELMV) || !(tty->ocaps&TTCF_EREOD))
+ { progress->tty = 0; return (-1); }
+
+ /* Configure the highlight attributes. */
+ ttycolour_config(progress->attr,
+ "MLIB_TTYPROGRESS_COLOURS", TCIF_GETENV, tty, hltab);
+
+ /* All done. */
+ return (0);
+}
+
+void ttyprogress_free(struct ttyprogress *progress)
+{
+ dstr_destroy(&progress->line.t);
+ x_free(progress->line.a, progress->line.p);
+}
+
+/*----- Active item list maintenance --------------------------------------*/
+
+int ttyprogress_additem(struct ttyprogress *progress,
+ struct ttyprogress_item *item)
+{
+ if (item->parent) return (-1);
+ item->prev = progress->end_item; item->next = 0;
+ if (progress->end_item) progress->end_item->next = item;
+ else progress->items = item;
+ progress->end_item = item; item->parent = progress;
+ progress->nitems++;
+
+ return (0);
+}
+
+int ttyprogress_removeitem(struct ttyprogress *progress,
+ struct ttyprogress_item *item)
+{
+ if (!item->parent) return (-1);
+ if (item->next) item->next->prev = item->prev;
+ else (progress->end_item) = item->prev;
+ if (item->prev) item->prev->next = item->next;
+ else (progress->items) = item->next;
+ progress->nitems--; item->parent = 0;
+
+ return (0);
+}
+
+/*----- Render state lifecycle --------------------------------------------*/
+
+static void setup_render_state(struct ttyprogress *progress,
+ struct ttyprogress_render *render)
+{
+ struct tty *tty = progress->tty;
+
+ /* Clear everything. */
+ render->tty = tty;
+
+ /* Update the current terminal size. */
+ tty_resized(tty);
+
+ /* We'll render progress bars with colour or standout if we can; otherwise,
+ * we'll just insert a `|' in the right place, but that takes up an extra
+ * column, so deduct one from the terminal's width to compensate. Deduct
+ * another one if the terminal doesn't let us leave the cursor in the final
+ * column without scrolling or wrapping.
+ */
+ render->width = tty->wd;
+ if (render->width && !(tty->acaps&(TTCF_MMARG | TTMF_AUTOM)))
+ render->width--;
+ if (render->width && !(tty->acaps&(TTACF_BG | TTACF_INVV)))
+ render->width--;
+
+ /* Borrow the line buffer and highlight table from the master state. */
+ render->line = &progress->line;
+ render->attr = progress->attr;
+}
+
+/*----- Measuring string widths -------------------------------------------*/
+
+#if defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH)
+
+#include <wchar.h>
+
+#define CONV_MORE ((size_t)-2)
+#define CONV_BAD ((size_t)-1)
+
+struct measure {
+ mbstate_t ps; /* conversion state */
+ const char *p; size_t i, sz; /* input string, and cursor */
+ unsigned wd; /* width accumulated so far */
+};
+
+static void init_measure(struct measure *m, const char *p, size_t sz)
+ /* Set up M to measure the SZ-byte string P. */
+{
+ m->p = p; m->sz = sz; m->i = 0; m->wd = 0;
+ memset(&m->ps, 0, sizeof(m->ps));
+}
+
+static int advance_measure(struct measure *m)
+ /* Advance the measurement in M by one character. Return zero if the
+ * end of the string has been reached, or nonzero if there is more to
+ * come.
+ */
+{
+ wchar_t wch;
+ unsigned chwd;
+ size_t n;
+
+ /* Determine the next character's code WCH, the length N of its encoding in
+ * P in bytes, and the character's width CHWD in columns.
+ */
+ n = mbrtowc(&wch, m->p + m->i, m->sz - m->i, &m->ps);
+ if (!n) { chwd = 0; n = m->sz - m->i; }
+ else if (n == CONV_MORE) { chwd = 2; n = m->sz - m->i; }
+ else if (n == CONV_BAD) { chwd = 2; n = 1; }
+ else chwd = wcwidth(wch);
+
+ /* Advance the state. */
+ m->i += n; m->wd += chwd;
+
+ /* Report whether there's more to come. */
+ return (m->i < m->sz);
+}
+
+static unsigned string_width(const char *p, size_t sz)
+ /* Return the width of the SZ-byte string P, in terminal columns. */
+{
+ struct measure m;
+
+ init_measure(&m, p, sz);
+ while (advance_measure(&m));
+ return (m.wd);
+}
+
+static size_t split_string(const char *p, size_t sz,
+ unsigned *wd_out, unsigned maxwd)
+ /* Return the size, in bytes, of the shortest prefix of the SZ-byte
+ * string P which is no less than MAXWD columns wide, or SZ if it's
+ * just too short. Store the actual width in *WD_OUT.
+ */
+{
+ struct measure m;
+ size_t i; unsigned wd;
+ int more;
+
+ init_measure(&m, p, sz);
+
+ /* Advance until we're past the bound. */
+ for (;;) {
+ if (!advance_measure(&m)) { *wd_out = m.wd; return (sz); }
+ if (m.wd >= maxwd) break;
+ }
+
+ /* Now /continue/ advancing past zero-width characters until we find
+ * something that wasn't zero-width. These might be combining accents or
+ * somesuch, and leaving them off would definitely be wrong.
+ */
+ wd = m.wd; i = m.i;
+ for (;;) {
+ more = advance_measure(&m);
+ if (m.wd > wd) break;
+ i = m.i;
+ if (!more) break;
+ }
+
+ /* All done. */
+ *wd_out = wd; return (i);
+}
+
+#else
+
+static unsigned string_width(const char *p, size_t sz) { return (sz); }
+
+static size_t split_string(const char *p, size_t sz,
+ unsigned *wd_out, unsigned maxwd)
+{
+ unsigned wd;
+
+ if (sz <= maxwd) wd = sz;
+ else wd = maxwd;
+ *wd_out = wd; return (wd);
+}
+
+#endif
+
+/*----- Output buffer handling --------------------------------------------*/
+
+static void grow_linebuf(struct ttyprogress_render *render, size_t want)
+ /* Extend the line buffer in RENDER so that it's at least WANT bytes
+ * long. Shuffle the accumulated left and right material in the
+ * buffer as necessary.
+ */
+{
+ struct ttyprogress_buffer *line = render->line;
+ char *newbuf; size_t newsz;
+
+ /* Return if there's already enough space. */
+ if (want <= line->sz) return;
+
+ /* Work out how much space to allocate. The initial size is a rough guess
+ * based on the size of UTF-8 encoded characters, though it's not an upper
+ * bound because many characters have zero width. Double the buffer size
+ * if it's too small. Sneakily insert a terminating zero byte just in
+ * case.
+ */
+ newsz = line->sz;
+ GROWBUF_SIZE(size_t, newsz, want, 4*render->width + 1, 1);
+ newbuf = x_alloc(line->a, newsz + 1);
+ newbuf[newsz] = 0;
+
+ /* Copy the left and right strings into the new buffer. */
+ if (render->leftsz)
+ memcpy(newbuf, line->p, render->leftsz);
+ if (render->rightsz)
+ memcpy(newbuf + newsz - render->rightsz,
+ line->p + line->sz - render->rightsz,
+ render->rightsz);
+
+ /* Free the old buffer and remember the new one. */
+ x_free(line->a, line->p); line->p = newbuf; line->sz = newsz;
+}
+
+enum { LEFT, RIGHT, STOP };
+
+static int putstr(struct ttyprogress_render *render, unsigned side,
+ const char *p, size_t n)
+ /* Add the N-byte string P to SIDE of the line buffer in RENDER.
+ * Return 0 on success or -1 if this fails for any reason.
+ */
+{
+ unsigned newwd = string_width(p, n);
+ size_t want;
+
+ if (newwd >= render->width - render->leftwd - render->rightwd) return (-1);
+ want = render->leftsz + render->rightsz + n;
+ if (want > render->line->sz) grow_linebuf(render, want);
+ switch (side) {
+ case LEFT:
+ memcpy(render->line->p + render->leftsz, p, n);
+ render->leftsz += n; render->leftwd += newwd;
+ break;
+ case RIGHT:
+ memcpy(render->line->p + render->line->sz - render->rightsz - n, p, n);
+ render->rightsz += n; render->rightwd += newwd;
+ break;
+ default:
+ assert(0);
+ }
+ return (0);
+}
+
+int ttyprogress_vputleft(struct ttyprogress_render *render,
+ const char *fmt, va_list *ap)
+{
+ dstr *t = &render->line->t;
+
+ DRESET(t); dstr_vputf(t, fmt, ap);
+ return (putstr(render, LEFT, t->buf, t->len));
+}
+
+int ttyprogress_vputright(struct ttyprogress_render *render,
+ const char *fmt, va_list *ap)
+{
+ dstr *t = &render->line->t;
+
+ DRESET(t); dstr_vputf(t, fmt, ap);
+ return (putstr(render, RIGHT, t->buf, t->len));
+}
+
+int ttyprogress_putleft(struct ttyprogress_render *render,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start(ap, fmt); rc = ttyprogress_vputleft(render, fmt, &ap); va_end(ap);
+ return (rc);
+}
+
+int ttyprogress_putright(struct ttyprogress_render *render,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start(ap, fmt); rc = ttyprogress_vputright(render, fmt, &ap); va_end(ap);
+ return (rc);
+}
+
+/*----- Maintaining the progress display ----------------------------------*/
+
+#define CLRF_ALL 1u /* clear everything */
+static void clear_progress(struct ttyprogress *progress, unsigned f)
+ /* Clear the current progress display maintained by PROGRESS.
+ *
+ * If `CLRF_ALL' is set in F, then clear the entire display.
+ * Otherwise, clear the bottom few lines if there are now fewer
+ * progress items than there were last time we rendered the display,
+ * and leave the cursor at the start of the top line ready to
+ * overwrite it.
+ */
+{
+ struct tty *tty = progress->tty;
+ int ndel, nleave;
+
+ if (progress->last_lines) {
+
+ /* Decide how many lines to delete. Set `ndel' to the number of lines
+ * that will be entirely erased, and `nleave' to the number that we'll
+ * leave.
+ */
+ if (f&CLRF_ALL)
+ { ndel = progress->last_lines; nleave = 0; }
+ else {
+ if (progress->nitems >= progress->last_lines) ndel = 0;
+ else ndel = progress->last_lines - progress->nitems;
+ nleave = progress->last_lines - ndel;
+ }
+
+ /* Now actually do the clearing. Remember that the cursor is still on
+ * the last line.
+ */
+ if (!ndel)
+ tty_move(tty, TTOF_YCUR | TTOF_XHOME, 1 - nleave, 0);
+ else {
+ tty_move(tty, TTOF_YCUR | TTOF_XHOME, 1 - ndel, 0);
+ tty_erase(tty, TTEF_DSP | TTEF_END);
+ tty_move(tty, TTORG_CUR, -nleave, 0);
+ }
+ }
+
+ /* Remember that we're now at the top of the display. */
+ progress->last_lines = 0;
+}
+
+int ttyprogress_clear(struct ttyprogress *progress)
+{
+ if (!progress->tty) return (-1);
+ clear_progress(progress, CLRF_ALL);
+ return (0);
+}
+
+int ttyprogress_update(struct ttyprogress *progress)
+{
+ struct ttyprogress_render render;
+ struct ttyprogress_item *item;
+ struct tty *tty = progress->tty;
+ struct tty_attr save;
+ uint32 modes;
+ unsigned f = 0;
+#define f_any 1u
+
+ if (!tty) return (-1);
+
+ setup_render_state(progress, &render);
+ clear_progress(progress, 0);
+ modes = tty->st.modes; tty_setmodes(tty, TTMF_AUTOM, 0);
+ save = tty->st.attr;
+
+ for (item = progress->items; item; item = item->next) {
+ if (f&f_any) tty_move(tty, TTOF_YCUR | TTOF_XHOME, 1, 0);
+ render.leftsz = render.rightsz = 0;
+ render.leftwd = render.rightwd = 0;
+ item->render(item, &render); progress->last_lines++; f |= f_any;
+ if (progress->last_lines > tty->ht) break;
+ }
+ tty_setmodes(tty, MASK32, modes); tty_setattr(tty, &save);
+ fflush(tty->fpout);
+ return (0);
+
+#undef f_any
+}
+
+/*----- Rendering progress bars -------------------------------------------*/
+
+/* The basic problem here is to render text, formed of several pieces, to the
+ * terminal, placing some marker in the middle of it to indicate how much
+ * progress has been made. This marker might be a colour change, switching
+ * off reverse-video mode, or a `|' character.
+ */
+
+struct bar {
+ /* State to track progress through the output of a progress bar, so
+ * that we insert the marker in the right place.
+ *
+ * This is a little state machine. We remember the current column
+ * position, the current state, and the column at which we'll next
+ * change state.
+ */
+
+ const struct ttyprogress_render *render; /* render state */
+ unsigned pos, nextpos, state; /* as described */
+};
+
+static void advance_bar_state(struct bar *bar)
+ /* If we've reached the column position for the next state change
+ * then arrange to do whatever it is we're meant to do, and update
+ * for the next change.
+ */
+{
+ const struct ttyprogress_render *render = bar->render;
+ struct tty *tty = render->tty;
+ size_t here = bar->nextpos;
+
+ while (bar->nextpos <= here) {
+ switch (bar->state) {
+ case LEFT:
+ if (!(tty->acaps&(TTACF_BG | TTACF_INVV))) putc('|', tty->fpout);
+ else tty_setattr(tty, &render->attr[TPHL_EBAR]);
+ bar->state = RIGHT; bar->nextpos = render->width;
+ break;
+ case RIGHT:
+ bar->state = STOP; bar->nextpos = UINT_MAX;
+ break;
+ }
+ }
+}
+
+/* Little utility to output a chunk of text. */
+static void put_str(FILE *fp, const char *p, size_t sz)
+ { while (sz--) putc(*p++, fp); }
+
+static void put_spc(struct tty *tty, unsigned n)
+{
+ if (!(~tty->ocaps&(TTCF_RELMV | TTCF_BGER | TTCF_ERCH)))
+ { tty_erch(tty, n); tty_move(tty, TTORG_CUR, 0, n); }
+ else
+ tty_repeat(tty, ' ', n);
+}
+
+static void put_barstr(struct bar *bar, const char *p, size_t sz)
+ /* Output the SZ-byte string P, driving the state machine BAR as we
+ * go.
+ */
+{
+ FILE *fp = bar->render->tty->fpout;
+ unsigned wd;
+ size_t n;
+
+ for (;;) {
+ /* Main loop. Determine how much space there is to the next state
+ * change, cut off that amount of space from the string, and advance.
+ */
+
+ n = split_string(p, sz, &wd, bar->nextpos - bar->pos);
+ if (n == sz && wd < bar->nextpos - bar->pos) break;
+ put_str(fp, p, n); bar->pos += wd; advance_bar_state(bar);
+ p += n; sz -= n;
+ }
+
+ /* Write out the rest of the string, and update the position. We know that
+ * this won't reach the next transition.
+ */
+ put_str(fp, p, sz); bar->pos += wd;
+}
+
+static void put_barspc(struct bar *bar, unsigned n)
+ /* Output N spaces, driving the state machine BAR as we go. */
+{
+ struct tty *tty = bar->render->tty;
+ unsigned step;
+
+ for (;;) {
+ step = bar->nextpos - bar->pos;
+ if (n < step) break;
+ put_spc(tty, step); bar->pos += step; n -= step;
+ advance_bar_state(bar);
+ }
+ put_spc(tty, n); bar->pos += n;
+}
+
+int ttyprogress_showbar(struct ttyprogress_render *render, double frac)
+{
+ struct tty *tty = render->tty;
+ const struct ttyprogress_buffer *line = render->line;
+ struct bar bar;
+
+ /* If there's no terminal, then there's nothing to do. */
+ if (!tty) return (-1);
+
+ /* Clamp the fraction. Rounding errors in the caller's calculation might
+ * legitimately leave it slightly out of bounds. The sense of the
+ * comparisons also catches QNaNs, which are silently squashed to zero so
+ * as to prevent anything untoward happening when we convert to integer
+ * arithmetic below.
+ */
+ frac = !(frac >= 0.0) ? 0.0 : !(frac <= 1.0) ? 1.0 : frac;
+
+ /* Set up the render state, with a transition where the bar should end. */
+ bar.render = render; bar.pos = 0; bar.nextpos = frac*render->width + 0.5;
+
+ /* Set the initial state for the render. */
+ if (tty->acaps&(TTACF_BG | TTACF_INVV)) {
+ /* We have highlighting. If we have made only negligible progress then
+ * advance the state machine immediately, which will set the correct
+ * highlighting; otherwise, set the beginning-of-bar highlight.
+ */
+
+ bar.state = LEFT;
+ if (bar.nextpos) tty_setattr(tty, &render->attr[TPHL_BBAR]);
+ else advance_bar_state(&bar);
+ } else {
+ /* Nothing fancy. We'll write `|' at the right place. */
+
+ bar.state = LEFT;
+ }
+
+ /* Write the left string, spaces to fill the gap, and the right string. */
+ put_barstr(&bar, line->p, render->leftsz);
+ put_barspc(&bar, render->width - render->leftwd - render->rightwd);
+ put_barstr(&bar, line->p + line->sz - render->rightsz, render->rightsz);
+
+ /* All done. */
+ return (0);
+}
+
+int ttyprogress_shownotice(struct ttyprogress_render *render,
+ const struct tty_attr *attr)
+{
+ struct tty *tty = render->tty;
+ const struct ttyprogress_buffer *line = render->line;
+
+ /* If there's no terminal, then there's nothing to do. */
+ if (!tty->fpout) return (-1);
+
+ /* Set the attributes. */
+ tty_setattr(tty, attr);
+
+ /* Print the left and right strings. */
+ put_str(tty->fpout, line->p, render->leftsz);
+ put_spc(tty, render->width - render->leftwd - render->rightwd);
+ put_str(tty->fpout, line->p + line->sz - render->rightsz, render->rightsz);
+
+ /* All done. */
+ return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/