+/* -*-c-*-
+ *
+ * Terminal handling
+ *
+ * (c) 2024 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 ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+#include <unistd.h>
+#include <termios.h>
+
+#ifdef HAVE_TERMCAP
+# include <termcap.h>
+#endif
+
+#ifdef HAVE_TERMINFO
+# include <term.h>
+#endif
+
+#ifdef HAVE_UNIBILIUM
+# include <unibilium.h>
+#endif
+
+#include "gprintf.h"
+#include "macros.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
+};
+
+static void clamp_colours(uint32 *space_out, uint32 *colour_out,
+ uint32 space, uint32 colour, uint32 acaps)
+{
+ unsigned r, g, b, y, t, range;
+ uint32 best_colour, best_space; int best_error;
+
+#define COMMON_SCALE 3825
+
+#define CHECK_RANGE(r) do { \
+ STATIC_ASSERT(COMMON_SCALE%(r) == 0, "common scale doesn't cover " #r); \
+} while (0)
+
+#define SET_RANGE(r) do { CHECK_RANGE(r); range = (r); } while (0)
+
+ /* 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;
+ case TTCSPC_1BPC:
+ 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);
+ 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);
+ break;
+ case TTCSPC_2BPC:
+ 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);
+ break;
+ case TTCSPC_8LGS:
+ 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);
+ break;
+ case TTCSPC_6LPC:
+ 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);
+ break;
+ case TTCSPC_24LGS:
+ 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);
+ break;
+ case TTCSPC_8BPC:
+ if (colour >= 0x01000000) goto inval;
+ if (acaps&TTACF_8BPC)
+ { *space_out = TTACF_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
+
+ /* We didn't get an exact match, so we'll have to make do with what we've
+ * got.
+ */
+ best_error = -1;
+
+#define RWT 2
+#define GWT 4
+#define BWT 1
+
+ t = COMMON_SCALE/range; r *= t; g *= t; b *= t;
+ y = (RWT*r + GWT*g + BWT*b + (RWT + GWT + BWT)/2)/(RWT + GWT + BWT);
+
+#define TRY_APPROX(red, grn, blu, spc, clr) do { \
+ int r_err = (red) - r, g_err = (grn) - g, b_err = (blu) - 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; \
+ \
+ err = r_err + g_err + b_err; \
+ if (best_error < 0 || err < best_error) \
+ { best_error = err; best_space = (spc); best_colour = (clr); } \
+} while (0)
+
+#define TRY_RGB(spc, lvls) do { \
+ CHECK_RANGE((lvls) - 1); \
+ unsigned sc = COMMON_SCALE/((lvls) - 1); \
+ unsigned rr, gg, bb, clr; \
+ \
+ 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); \
+} while (0)
+
+#define TRY_GREY(spc, lvls) do { \
+ CHECK_RANGE((lvls) + 1); \
+ unsigned sc = COMMON_SCALE/((lvls) + 1); \
+ unsigned yy, grey; \
+ \
+ yy = (y + sc/2)/sc; \
+ if (yy >= 1 && yy <= (lvls)) \
+ { grey = yy*sc; TRY_APPROX(grey, grey, grey, (spc), grey - 1); } \
+} while (0)
+
+ 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_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);
+
+#undef TRY_APPROX
+#undef TRY_RGB
+#undef TRY_GREY
+
+#undef RWT
+#undef GWT
+#undef BWT
+
+ *space_out = best_space; *colour_out = best_colour;
+ return;
+
+inval:
+ *space_out = TTCSPC_NONE; *colour_out = 0;
+}
+
+void tty_clampattr(struct tty_attr *a_out, const struct tty_attr *a,
+ uint32 acaps)
+{
+ uint32 ff = 0, f = a->f, t;
+
+ t = (f&TTAF_LNMASK) >> TTAF_LNSHIFT;
+ switch (t) {
+ case TTLN_NONE:
+ break;
+ case TTLN_ULINE:
+ if (!acaps&TTACF_ULINE) t = TTLN_NONE;
+ break;
+ case TTLN_UULINE:
+ if (acaps&TTACF_UULINE) ;
+ else if (acaps&TTACF_ULINE) t = TTLN_ULNE;
+ else t = TTLN_NONE;
+ break;
+ case TTLN_STRIKE:
+ if (!acaps&TTACF_STRIKE) t = TTLN_NONE;
+ break;
+ default:
+ t = TTLN_NONE;
+ }
+ ff |= t << TTAF_LNSHIFT;
+
+ 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;
+ }
+ ff |= t << TTAF_WTSHIFT;
+
+ if (acaps&TTACF_ITAL) ff |= f&TTAF_ITAL;
+ if (acaps&TTACF_INVV) ff |= f&TTAF_INVV;
+
+ if (acaps&TTACF_FG) {
+ clamp_colours(&t, &a_out->fg,
+ (f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a->fg);
+ ff |= t << TTAF_FGSPCSHIFT;
+ }
+ if (acaps&TTACF_BG) {
+ clamp_colours(&t, &a_out->bg,
+ (f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a->bg);
+ ff |= t << TTAF_BGSPCSHIFT;
+ }
+
+ a_out->f = ff; a_out->_res = 0;
+}
+
+/*----- Common machinery for `termcap' and `terminfo' ---------------------*/
+
+#if defined(HAVE_TERMINFO) || \
+ defined(HAVE_TERMCAP) || \
+ defined(HAVE_UNIBILIUM)
+
+#if defined(HAVE_TERMINFO) || defined(HAVE_TERMCAP)
+
+static const struct gprintf_ops *global_gops;
+static void *global_gout;
+
+static int term_putch(int ch)
+ { return (global_gops->putch(global_gout, ch)); }
+
+#endif
+
+#ifdef HAVE_UNIBILIUM
+# 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)
+
+#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) \
+ 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)
+
+#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)
+
+#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)
+
+#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)
+
+#define ERASECAPS(_bool, _int, _str) \
+ _str(clr_bol, el1, cb) \
+ _str(clr_eol, el, ce) \
+ _str(clr_eos, ed, cd)
+
+#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)
+
+#define STORECAPS(_bool, _int, _str) \
+ ATTRCAPS(_bool, _int, _str) \
+ MODECAPS(_bool, _int, _str) \
+ MOVECAPS(_bool, _int, _str) \
+ SCROLLCAPS(_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
+#undef DEF_INTCAP
+};
+
+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)
+{
+ tty->acaps = tty->ocaps = 0;
+ tty->st.modes = 0;
+ tty->st.attr.f = 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);
+ 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)
+
+ /* Basic capabilities. */
+ if (!caps->cr) caps->cr = "\r";
+
+ /* Attribute capabilities. */
+ if (intcap(CAPREF(CAP_XMC)) || !caps->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 (!caps->colors >= 8 || (!caps->setaf && !caps->setbf))
+ 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;
+ }
+ }
+
+ /* 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;
+
+ /* 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;
+ }
+
+ /* 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;
+
+ /* 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;
+}
+
+static int caps_setattr(struct tty *tty, const struct tty_caps *caps,
+ const struct gprintf_ops *gops, void *go,
+ const struct tty_attr *a)
+{
+ struct tty_attr aa;
+
+ tty_clampattr(&aa, a, tty->acaps);
+
+}
+
+static int caps_setmodes(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ uint32 modes_bic, uint32 modes_xor);
+static int caps_move(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned orig, int y, int x);
+static int caps_repeat(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ int ch, unsigned n);
+static int caps_erase(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned f);
+static int caps_erch(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned n);
+static int caps_ins(struct tty *tty,
+ const struct gprintf_ops *gops, void *go,
+ unsigned f, unsigned n);
+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);
+
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/