chiark / gitweb /
@@@ more mess
[mLib] / ui / tty.c
1 /* -*-c-*-
2  *
3  * Terminal handling
4  *
5  * (c) 2024 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the mLib utilities library.
11  *
12  * mLib is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU Library General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or (at
15  * your option) any later version.
16  *
17  * mLib is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
20  * License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with mLib.  If not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25  * USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "config.h"
31
32 #include <errno.h>
33 #include <stdio.h>
34
35 #include <unistd.h>
36 #include <termios.h>
37
38 #ifdef HAVE_TERMCAP
39 #  include <termcap.h>
40 #endif
41
42 #ifdef HAVE_TERMINFO
43 #  include <term.h>
44 #endif
45
46 #ifdef HAVE_UNIBILIUM
47 #  include <unibilium.h>
48 #endif
49
50 #include "gprintf.h"
51 #include "macros.h"
52 #include "tty.h"
53
54 /*----- Data structures ---------------------------------------------------*/
55
56 /*----- Common support machinery ------------------------------------------*/
57
58 static uint32 basic_colour_map[] = {
59   /* standard black     */ 0x000000,
60   /* standard red       */ 0xcc0000,
61   /* standard green     */ 0x00cc00,
62   /* standard yellow    */ 0xcccc00,
63   /* standard blue      */ 0x0000cc,
64   /* standard magenta   */ 0xcc00cc,
65   /* standard cyan      */ 0x00cccc,
66   /* standard white     */ 0xcccccc
67 }, bright_colour_map[] = {
68   /* bright black       */ 0x333333,
69   /* bright red         */ 0xff3333,
70   /* bright green       */ 0x33ff33,
71   /* bright yellow      */ 0xffff33,
72   /* bright blue        */ 0x3333ff,
73   /* bright magenta     */ 0xff33ff,
74   /* bright cyan        */ 0x33ffff,
75   /* bright white       */ 0xffffff
76 };
77
78 static void clamp_colours(uint32 *space_out, uint32 *colour_out,
79                           uint32 space, uint32 colour, uint32 acaps)
80 {
81   unsigned r, g, b, y, t, range;
82   uint32 best_colour, best_space; int best_error;
83
84 #define COMMON_SCALE 3825
85
86 #define CHECK_RANGE(r) do {                                             \
87   STATIC_ASSERT(COMMON_SCALE%(r) == 0, "common scale doesn't cover " #r); \
88 } while (0)
89
90 #define SET_RANGE(r) do { CHECK_RANGE(r); range = (r); } while (0)
91
92   /* Check the colour space.  If it's one that the terminal can handle, then
93    * return the colour unchanged.  Otherwise, extract the channel components
94    * for the next step.
95    */
96   switch (space) {
97     case TTCSPC_NONE:
98       *space_out = TTCSPC_NONE; *colour_out = 0; return;
99     case TTCSPC_1BPC:
100       if (colour >= 8) goto inval;
101       if (acaps&TTACF_1BPC)
102         { *space_out = TTACF_1BPC; *colour_out = colour; return; }
103       colour = basic_colour_map[colour];
104       r = TTCOL_8BR(colour); g = TTCOL_8BG(colour); b = TTCOL_8BB(colour);
105       SET_RANGE(255);
106       break;
107     case TTCSPC_1BPCBR:
108       if (colour >= 8) goto inval;
109       if (acaps&TTACF_1BPCBR)
110         { *space_out = TTACF_1BPCBR; *colour_out = colour; return; }
111       colour = bright_colour_map[colour];
112       r = TTCOL_8BR(colour); g = TTCOL_8BG(colour); b = TTCOL_8BB(colour);
113       SET_RANGE(255);
114       break;
115     case TTCSPC_2BPC:
116       if (colour >= 64) goto inval;
117       if (acaps&TTACF_2BPC)
118         { *space_out = TTACF_2BPC; *colour_out = colour; return; }
119       r = TTCOL_2BR(colour); g = TTCOL_2BG(colour); b = TTCOL_2BB(colour);
120       SET_RANGE(3);
121       break;
122     case TTCSPC_8LGS:
123       if (colour >= 8) goto inval;
124       if (acaps&TTACF_8LGS)
125         { *space_out = TTACF_8LGS; *colour_out = colour; return; }
126       r = g = b = colour + 1;
127       SET_RANGE(9);
128       break;
129     case TTCSPC_6LPC:
130       if (colour >= 216) goto inval;
131       if (acaps&TTACF_6LPC)
132         { *space_out = TTACF_6LPC; *colour_out = colour; return; }
133       r = TTCOL_6LR(colour); g = TTCOL_6LG(colour); b = TTCOL_6LB(colour);
134       SET_RANGE(5);
135       break;
136     case TTCSPC_24LGS:
137       if (colour >= 24) goto inval;
138       if (acaps&TTACF_24LGS)
139         { *space_out = TTACF_24LGS; *colour_out = colour; return; }
140       r = g = b = colour + 1;
141       SET_RANGE(25);
142       break;
143     case TTCSPC_8BPC:
144       if (colour >= 0x01000000) goto inval;
145       if (acaps&TTACF_8BPC)
146         { *space_out = TTACF_8BPC; *colour_out = colour; return; }
147       r = TTCOL_8BR(colour); g = TTCOL_8BG(colour); b = TTCOL_8BB(colour);
148       SET_RANGE(255);
149       break;
150       default:
151     goto inval;
152   }
153
154 #undef SET_RANGE
155
156   /* We didn't get an exact match, so we'll have to make do with what we've
157    * got.
158    */
159   best_error = -1;
160
161 #define RWT 2
162 #define GWT 4
163 #define BWT 1
164
165   t = COMMON_SCALE/range; r *= t; g *= t; b *= t;
166   y = (RWT*r + GWT*g + BWT*b + (RWT + GWT + BWT)/2)/(RWT + GWT + BWT);
167
168 #define TRY_APPROX(red, grn, blu, spc, clr) do {                        \
169   int r_err = (red) - r, g_err = (grn) - g, b_err = (blu) - b;          \
170   int err;                                                              \
171                                                                         \
172   if (r_err < 0) r_err = -r_err;                                        \
173   if (g_err < 0) g_err = -g_err;                                        \
174   if (b_err < 0) b_err = -b_err;                                        \
175                                                                         \
176   err = r_err + g_err + b_err;                                          \
177   if (best_error < 0 || err < best_error)                               \
178     { best_error = err; best_space = (spc); best_colour = (clr); }      \
179 } while (0)
180
181 #define TRY_RGB(spc, lvls) do {                                         \
182   CHECK_RANGE((lvls) - 1);                                              \
183   unsigned sc = COMMON_SCALE/((lvls) - 1);                              \
184   unsigned rr, gg, bb, clr;                                             \
185                                                                         \
186   rr = (r + sc/2)/sc; gg = (g + sc/2)/sc; bb = (b + sc/2)/sc;           \
187   TRY_APPROX(rr*sc, gg*sc, bb*sc, (spc), (rr*(lvls) + bb)*(lvls) + gg); \
188 } while (0)
189
190 #define TRY_GREY(spc, lvls) do {                                        \
191   CHECK_RANGE((lvls) + 1);                                              \
192   unsigned sc = COMMON_SCALE/((lvls) + 1);                              \
193   unsigned yy, grey;                                                    \
194                                                                         \
195   yy = (y + sc/2)/sc;                                                   \
196   if (yy >= 1 && yy <= (lvls))                                          \
197     { grey = yy*sc; TRY_APPROX(grey, grey, grey, (spc), grey - 1); }    \
198 } while (0)
199
200   if (acaps&TTACF_1BPC)
201     for (i = 0; i < 8; i++) {
202       t = basic_colour_map[i];
203       TRY_APPROX(TTCOL_8BR(t), TTCOL_8BG(t), TTCOL_8BB(t), TTCSPC_1BPC, i);
204     }
205   if (acaps&TTACF_1BPCBR)
206     for (i = 0; i < 8; i++) {
207       t = bright_colour_map[i];
208       TRY_APPROX(TTCOL_8BR(t), TTCOL_8BG(t), TTCOL_8BB(t), TTCSPC_1BPCBR, i);
209     }
210   if (acaps&TTACF_2BPC) TRY_RGB(TTCSPC_2BPC, 4);
211   if (acaps&TTACF_8LGS) TRY_GREY(TTCSPC_8LGS, 8);
212   if (acaps&TTACF_6LPC) TRY_RGB(TTCSPC_6LPC, 6);
213   if (acaps&TTACF_24LGS) TRY_GREY(TTCSPC_24LGS, 24);
214   if (acaps&TTACF_8BPC) TRY_RGB(TTCSPC_8BPC, 256);
215
216 #undef TRY_APPROX
217 #undef TRY_RGB
218 #undef TRY_GREY
219
220 #undef RWT
221 #undef GWT
222 #undef BWT
223
224   *space_out = best_space; *colour_out = best_colour;
225   return;
226
227 inval:
228   *space_out = TTCSPC_NONE; *colour_out = 0;
229 }
230
231 void tty_clampattr(struct tty_attr *a_out, const struct tty_attr *a,
232                    uint32 acaps)
233 {
234   uint32 ff = 0, f = a->f, t;
235
236   t = (f&TTAF_LNMASK) >> TTAF_LNSHIFT;
237   switch (t) {
238     case TTLN_NONE:
239       break;
240     case TTLN_ULINE:
241       if (!acaps&TTACF_ULINE) t = TTLN_NONE;
242       break;
243     case TTLN_UULINE:
244       if (acaps&TTACF_UULINE) ;
245       else if (acaps&TTACF_ULINE) t = TTLN_ULNE;
246       else t = TTLN_NONE;
247       break;
248     case TTLN_STRIKE:
249       if (!acaps&TTACF_STRIKE) t = TTLN_NONE;
250       break;
251     default:
252       t = TTLN_NONE;
253   }
254   ff |= t << TTAF_LNSHIFT;
255
256   t = (f&TTAF_WTMASK) >> TTAF_WTSHIFT;
257   switch (t) {
258     case TTWT_MED: break;
259     case TTWT_BOLD: if (!(acaps&TTACF_BOLD)) t = TTWT_MED; break;
260     case TTWT_DIM: if (!(acaps&TTACF_DIM)) t = TTWT_MED; break;
261     default: t = TTWD_MED; break;
262   }
263   ff |= t << TTAF_WTSHIFT;
264
265   if (acaps&TTACF_ITAL) ff |= f&TTAF_ITAL;
266   if (acaps&TTACF_INVV) ff |= f&TTAF_INVV;
267
268   if (acaps&TTACF_FG) {
269     clamp_colours(&t, &a_out->fg,
270                   (f&TTAF_FGSPCMASK) >> TTAF_FGSPCSHIFT, a->fg);
271     ff |= t << TTAF_FGSPCSHIFT;
272   }
273   if (acaps&TTACF_BG) {
274     clamp_colours(&t, &a_out->bg,
275                   (f&TTAF_BGSPCMASK) >> TTAF_BGSPCSHIFT, a->bg);
276     ff |= t << TTAF_BGSPCSHIFT;
277   }
278
279   a_out->f = ff; a_out->_res = 0;
280 }
281
282 /*----- Common machinery for `termcap' and `terminfo' ---------------------*/
283
284 #if defined(HAVE_TERMINFO) ||                                           \
285     defined(HAVE_TERMCAP) ||                                            \
286     defined(HAVE_UNIBILIUM)
287
288 #if defined(HAVE_TERMINFO) || defined(HAVE_TERMCAP)
289
290 static const struct gprintf_ops *global_gops;
291 static void *global_gout;
292
293 static int term_putch(int ch)
294   { return (global_gops->putch(global_gout, ch)); }
295
296 #endif
297
298 #ifdef HAVE_UNIBILIUM
299 #  define UNIBI_(x) unibi_##x
300 #else
301 #  define UNIBI_(x) 0
302 #endif
303
304 #define BASICCAPS(_bool, _int, _str)                                    \
305   _str(carriage_return, cr, cr)                                         \
306   _int(lines, lines, li) _int(columns, cols, co)
307
308 #define ATTRCAPS(_bool, _int, _str)                                     \
309   _str(exit_attribute_mode, sgr0, me)                                   \
310   _str(enter_underline_mode, smul, us) _str(exit_underline_mode, rmul, ue) \
311   _str(enter_italics_mode, sitm, ZH) _str(exit_italics_mode, ritm, ZR)  \
312   _str(enter_bold_mode, bold, md) _str(enter_dim_mode, dim, mh)         \
313   _str(enter_reverse_mode, rev, mr)                                     \
314   COLOURCAPS(_bool, _int, _str)
315
316 #define COLOURCAPS(_bool, _int, _str)                                   \
317   _str(set_a_foreground, setaf, AF) _str(set_a_background, setab, AB)   \
318   _str(orig_pair, op, op) _int(max_colors, colors, Co)
319
320 #define MODECAPS(_bool, _int, _str)                                     \
321   _str(enter_am_mode, smam, SA) _str(exit_am_mode, rmam, RA)            \
322   _str(enter_ca_mode, smcup, ti) _str(exit_ca_mode, rmcup, te)          \
323   _str(enter_insert_mode, smir, im) _str(exit_insert_mode, rmir, ei)
324
325 #define MOVECAPS(_bool, _int, _str)                                     \
326   _str(carriage_return, cr, cr)                                         \
327   _str(cursor_home, home, ho)                                           \
328   _str(cusor_address, cup, cm)                                          \
329   _str(row_address, vpa, cv) _str(column_address, hpa, ch)              \
330   _str(cursor_left, cub1, le) _(parm_left_cursor, cub, LE)              \
331   _str(cursor_right, cuf1, nd) _(parm_right_cursor, cuf, RI)            \
332   _str(cursor_up, cuu1, up) _str(parm_up_cursor, cuu, UP)               \
333   _str(cursor_down, cud1, do) _str(parm_down_cursor, cud, DO)
334
335 #define SCROLLCAPS(_bool, _int, _str)                                   \
336   _str(change_scroll_region, csr, cs)                                   \
337   _str(scroll_forward, ind, sf) _str(parm_index, indn, SF)              \
338   _str(scroll_reverse, ri, sr) _str(parm_rindex, rin, SR)
339
340 #define ERASECAPS(_bool, _int, _str)                                    \
341   _str(clr_bol, el1, cb)                                                \
342   _str(clr_eol, el, ce)                                                 \
343   _str(clr_eos, ed, cd)
344
345 #define INSDELCAPS(_bool, _int, _str)                                   \
346   _str(insert_character, ich1, ic) _str(parm_ich, ich, IC)              \
347   _str(insert_line, il1, al) _str(parm_insert_line, il, AL)             \
348   _str(delete_character, dch1, dc) _str(parm_dch, dch, DC)              \
349   _str(delete_line, dl1, dl) _str(parm_delete_line, dl, DL)
350
351 #define STORECAPS(_bool, _int, _str)                                    \
352   ATTRCAPS(_bool, _int, _str)                                           \
353   MODECAPS(_bool, _int, _str)                                           \
354   MOVECAPS(_bool, _int, _str)                                           \
355   SCROLLCAPS(_bool, _int, _str)                                         \
356   ERASECAPS(_bool, _int, _str)                                          \
357   INSDELCAPS(_bool, _int, _str)
358
359 #define CAP_XMC magic_cookie_glitch, xmc, sg
360 #define CAP_BCE back_color_erase, bce, ut
361 #define CAP_XHPA row_addr_glitch, xvpa, YD
362 #define CAP_XVPA col_addr_glitch, xhpa, YA
363 #define CAP_MIR move_insert_mode, mir, mi
364 #define CAP_MSGR move_standout_mode, msgr, ms
365 #define CAP_NPC no_pad_char, npc, NP
366 #define CAP_AM auto_left_margin, am, am
367 #define CAP_XENL eat_newline_glitch, xenl, xn
368
369 #define CAPREF(var, info, cap) UNIBI_(var), #info, #cap
370
371 struct ttycaps {
372 #define DEF_STRCAP(info, cap) const char *cap;
373 #define DEF_INTCAP(info, cap) int cap;
374   STORECAPS(DEF_STRCAP, DEF_INTCAP)
375 #undef DEF_STRCAP
376 #undef DEF_INTCAP
377 };
378
379 typedef int boolcapfn(int uix, const char *info, const char *cap, void *arg);
380 typedef int intcapfn(int uix, const char *info, const char *cap, void *arg);
381 typedef const char *strcapfn(int uix, const char *info, const char *cap,
382                              void *arg);
383
384 #define DEFINE_CAPISH(PRE, pre)
385
386 static int init_caps(struct tty *tty, struct tty_caps *caps,
387                      boolcapfn *boolcap, intcapfn *intcap, strcapfn *strcap,
388                      void *arg)
389 {
390   tty->acaps = tty->ocaps = 0;
391   tty->st.modes = 0;
392   tty->st.attr.f = 0;
393
394   /* Inhale all of the interesting terminal capabilities. */
395 #define GETBOOL(var, info, cap)                                         \
396         caps->info = boolcap(CAPREF(var, info, cap), arg);
397 #define GETINT(var, info, cap)                                          \
398         caps->info = intcap(CAPREF(var, info, cap), arg);
399 #define GETSTR(var, info, cap)                                          \
400         caps->info = strcap(CAPREF(var, info, cap), arg);
401   STORECAPS(GETBOOL, GETINT, GETSTR)
402 #undef GETBOOL
403 #undef GETINT
404 #undef GETSTR
405
406 #define CLEARCAP(var, info, cap) caps->info = 0;
407 #define CLEAR_CAPS(caplist) do { caplist(CLEARCAP) } while (0)
408
409   /* Basic capabilities. */
410   if (!caps->cr) caps->cr = "\r";
411
412   /* Attribute capabilities. */
413   if (intcap(CAPREF(CAP_XMC)) || !caps->sgr0)
414     CLEAR_CAPS(ATTRCAPS);
415   else {
416     if (caps->smul) tty->acaps |= TTACF_ULINE;
417     if (caps->bold) tty->acaps |= TTACF_BOLD;
418     if (caps->dim) tty->acaps |= TTACF_DIM;
419     if (caps->sitm) tty->acaps |= TTACF_ITAL;
420     if (caps->rev) tty->acaps |= TTACF_INVV;
421
422     if (!caps->colors >= 8 || (!caps->setaf && !caps->setbf))
423       CLEAR_CAPS(COLOURCAPS);
424     else {
425       if (caps->setaf) tty->acaps |= TTACF_FG;
426       if (caps->setab) tty->acaps |= TTACF_BG;
427       tty->acaps |= TTACF_1BPC;
428       if (caps->colors >= 16) tty->acaps |= TTACF_1BPCBR;
429       if (caps->colors == 88) tty->acaps |= TTACF_2BPC | TTACF_8LGS;
430       else if (caps->colors >= 256) tty->acaps |= TTACF_6LPC | TTACF_24LGS;
431       if (caps->colors >= 16777216) tty->acaps |= TTACF_8BPC;
432       if (boolcap(CAPREF(CAP_BCE))) tty->ocaps |= TTCF_BGER;
433     }
434   }
435
436   /* Motion capabilities. */
437   if (boolcap(CAPREF(CAP_XVPA))) caps->vpa = 0;
438   if (boolcap(CAPREF(CAP_XHPA))) caps->hpa = 0;
439   if (!caps->cub1) caps->cub1 = "\b";
440   if (!caps->cud1) caps->cud1 = "\n";
441   if ((caps->cuf || caps->cuf1) && (caps->cuu || caps->cuu1)) {
442     tty->ocaps |= TTCF_RELMV;
443     if (caps->vpa) tty->ocaps |= TTCF_MIXMV;
444   }
445   if (caps->cup || (caps->hpa && caps->vpa)) tty->ocaps |= TTCF_ABSMV;
446
447   /* Mode capabilities. */
448   if (caps->smam && caps->rmam) tty->ocaps |= TTMF_AUTOM;
449   if (caps->smir && caps->rmir) tty->ocaps |= TTMF_INS;
450   if (boolcap(CAPREF(CAP_AM))) {
451     tty->st.modes |= TTMF_AUTOM;
452     if (boolcap(CAPREF(CAP_XENL))) tty->ocaps |= TTCF_MMARG;
453   }
454
455   /* Scrolling. */
456   if (caps->csr) tty->ocaps |= TTCF_SCRGN;
457   if ((caps->ind || caps->indn) && (caps->ri || caps->rin))
458     tty->ocaps |= TTCF_SCROLL;
459
460   /* Erasure. */
461   if (caps->ech) tty->ocaps |= TTCF_ERCH;
462   if (caps->el1) tty->ocaps |= TTCF_ERBOL;
463   if (caps->el) tty->ocaps |= TTCF_EREOL;
464   if (caps->ed) tty->ocaps |= TTCF_EREOD;
465
466   /* Insertion and deletion. */
467   if (caps->ich || caps->ich1) tty->ocaps |= TTCF_INSCH;
468   if (caps->il || caps->il1) tty->ocaps |= TTCF_INSLN;
469   if (caps->dch || caps->dch1) tty->ocaps |= TTCF_DELCH;
470   if (caps->dl || caps->dl1) tty->ocaps |= TTCF_DELLN;
471 }
472
473 static int caps_setattr(struct tty *tty, const struct tty_caps *caps,
474                         const struct gprintf_ops *gops, void *go,
475                         const struct tty_attr *a)
476 {
477   struct tty_attr aa;
478
479   tty_clampattr(&aa, a, tty->acaps);
480   
481 }
482
483 static int caps_setmodes(struct tty *tty,
484                          const struct gprintf_ops *gops, void *go,
485                          uint32 modes_bic, uint32 modes_xor);
486 static int caps_move(struct tty *tty,
487                      const struct gprintf_ops *gops, void *go,
488                      unsigned orig, int y, int x);
489 static int caps_repeat(struct tty *tty,
490                        const struct gprintf_ops *gops, void *go,
491                        int ch, unsigned n);
492 static int caps_erase(struct tty *tty,
493                       const struct gprintf_ops *gops, void *go,
494                       unsigned f);
495 static int caps_erch(struct tty *tty,
496                      const struct gprintf_ops *gops, void *go,
497                      unsigned n);
498 static int caps_ins(struct tty *tty,
499                     const struct gprintf_ops *gops, void *go,
500                     unsigned f, unsigned n);
501 static int caps_del(struct tty *tty,
502                     const struct gprintf_ops *gops, void *go,
503                     unsigned f, unsigned n);
504 static int caps_setscrgn(struct tty *tty,
505                          const struct gprintf_ops *gops, void *go,
506                          unsigned y0, unsigned y1);
507 static int caps_scroll(struct tty *tty,
508                        const struct gprintf_ops *gops, void *go,
509                        int n);
510
511
512 #endif
513
514 /*----- That's all, folks -------------------------------------------------*/