chiark / gitweb /
@@@ more mess
[mLib] / ui / ttycolour.c
1 /* -*-c-*-
2  *
3  * Configurable terminal colour support
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 <ctype.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include "macros.h"
36 #include "report.h"
37 #include "ttycolour.h"
38
39 /*----- Main code ---------------------------------------------------------*/
40
41 int ttycolour_config(ttycolour_attr *attr, const char *user, unsigned f,
42                      const struct ttycolour_style *tab)
43 {
44   const char *p, *q;
45   size_t n;
46   unsigned i, arg, a;
47   int rc = 0;
48
49   for (i = 0; tab[i].tok; i++) attr[i] = tab[i].dflt;
50
51   if (f&TCIF_GETENV) p = getenv(user);
52   else p = user;
53   if (p)
54     while (*p) {
55       n = strcspn(p, "=:"); q = p + n;
56       if (!*q || *q == ':') {
57         if (f&TCIF_REPORT) moan("missing colour token in `%s'", user);
58         rc = -1; p = *q ? q + 1 : q; continue;
59       }
60       for (i = 0; tab[i].tok; i++)
61         if (STRNCMP(p, ==, tab[i].tok, n) && !tab[i].tok[n])
62           goto found_tok;
63       if (f&TCIF_REPORT)
64         moan("unknown colour token `%.*s' in `%s'", (int)n, p, user);
65       rc = -1; q = p + strcspn(p, ":"); p = *q ? q + 1 : q; continue;
66
67     found_tok:
68       a = 0; arg = 0; q++;
69       for (;;) {
70
71         if (ISDIGIT(*q))
72           { arg = 10*arg + (*q++ - '0'); continue; }
73
74         switch (arg) {
75           case 0: a = 0; break;
76           case 1: a |= TCAF_BOLD; break;
77           case 39: a &= ~(TCAF_FG | TCAF_FGMASK); break;
78           case 49: a &= ~(TCAF_BG | TCAF_BGMASK); break;
79           case 30: case 31: case 32: case 33:
80           case 34: case 35: case 36: case 37:
81             a = (a&~TCAF_FGMASK) | TCAF_FG | ((arg - 30) << TCAF_FGSHIFT);
82             break;
83           case 40: case 41: case 42: case 43:
84           case 44: case 45: case 46: case 47:
85             a = (a&~TCAF_BGMASK) | TCAF_BG | ((arg - 40) << TCAF_BGSHIFT);
86             break;
87           case 90: case 91: case 92: case 93:
88           case 94: case 95: case 96: case 97:
89             a = (a&~TCAF_FGMASK) | TCAF_FG |
90                 (((arg - 90) | TCCF_BRIGHT) << TCAF_FGSHIFT);
91             break;
92           case 100: case 101: case 102: case 103:
93           case 104: case 105: case 106: case 107:
94             a = (a&~TCAF_BGMASK) | TCAF_BG |
95                 (((arg - 100) | TCCF_BRIGHT) << TCAF_BGSHIFT);
96             break;
97           default:
98             if (f&TCIF_REPORT)
99               moan("unknown colour code %u in `%.*s' string in `%s'",
100                    arg, (int)n, p, user);
101             rc = -1; break;
102         }
103         arg = 0;
104         if (!*q || *q == ':') break;
105         else if (*q != ';') {
106           if (f&TCIF_REPORT)
107             moan("expected `;' but found `%c' in `%.*s' string in `%s'",
108                  *q, (int)n, p, user);
109           rc = -1; q += strcspn(q, ":;"); if (!*q || *q == ':') break;
110         }
111         q++;
112       }
113       attr[i] = a; p = *q ? q + 1 : q;
114     }
115
116   return (rc);
117 }
118
119 void ttycolour_init(struct ttycolour_state *tc)
120   { tc->attr = 0; }
121
122 /* --- @ttycolour_setattr@ --- *
123  *
124  * Arguments:   @const struct gprintf_ops *ops@ = formatting operations
125  *              @void *go@ = output sink
126  *              @struct ttycolour_state *tc
127  *              @ttycolour_attr attr@ = attribute code to set
128  *
129  * Returns:     Zero on success, %$-1$% on error.
130  *
131  * Use:         Send a control sequence to the output stream so that
132  *              subsequent text is printed with the given attributes.
133  *
134  *              Some effort is taken to avoid unnecessary control sequences.
135  *              In particular, if @attr@ matches the current terminal
136  *              settings already, then nothing is written.
137  */
138
139 int ttycolour_setattr(const struct gprintf_ops *gops, void *go,
140                       struct ttycolour_state *tc, ttycolour_attr attr)
141 {
142   unsigned diff = tc->attr ^ attr;
143   unsigned f = 0;
144
145 #define f_semi 1u
146
147 #define PUT_SEMI do {                                                   \
148   if (!(f&f_semi)) f |= f_semi;                                         \
149   else if (gops->putch(go, ';') < 0) return (-1);                       \
150 } while (0)
151
152 #define SET_COLOUR(norm, bright, colour) do {                           \
153   unsigned char _col = (colour);                                        \
154                                                                         \
155   PUT_SEMI;                                                             \
156   gprintf(gops, go, "%d",                                               \
157           (_col&TCCF_BRIGHT ? (bright) : (norm)) + (_col&TCCF_RGBMASK)); \
158 } while (0)
159
160   /* If there's nothing to do, we might as well stop now. */
161   if (!diff) return (0);
162
163   /* Start on the control command. */
164   if (gops->putm(go, "\x1b[", 2) < 0) return (-1);
165
166   /* Change the boldness if necessary. */
167   if (diff&TCAF_BOLD) {
168     PUT_SEMI;
169     if (attr&TCAF_BOLD) { if (gops->putch(go, '1') < 0) return (-1); }
170     else { diff = tc->attr; if (gops->putch(go, '0') < 0) return (-1); }
171   }
172
173   /* Change the foreground colour if necessary. */
174   if (diff&(TCAF_FG | TCAF_FGMASK)) {
175     if (attr&TCAF_FG)
176       SET_COLOUR(30, 90, (attr&TCAF_FGMASK) >> TCAF_FGSHIFT);
177     else
178       { PUT_SEMI; if (gops->putm(go, "39", 2) < 0) return (-1); }
179   }
180
181   /* Change the background colour if necessary. */
182   if (diff&(TCAF_BG | TCAF_BGMASK)) {
183     if (attr&TCAF_BG)
184       SET_COLOUR(40, 100, (attr&TCAF_BGMASK) >> TCAF_BGSHIFT);
185     else
186       { PUT_SEMI; if (gops->putm(go, "49", 2) < 0) return (-1); }
187   }
188
189   /* Terminate the control command and save the new attributes. */
190   if (gops->putch(go, 'm') < 0) return (-1);
191   tc->attr = attr; return (0);
192
193 #undef f_semi
194 #undef PUT_SEMI
195 #undef SET_COLOUR
196 }
197
198 /*----- That's all, folks -------------------------------------------------*/