+/*----- 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)); }
+