3 * Configurable terminal colour support
5 * (c) 2024 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
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.
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.
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,
28 /*----- Header files ------------------------------------------------------*/
35 #include "ttycolour.h"
37 /*----- Main code ---------------------------------------------------------*/
39 int ttycolour_config(ttycolour_attr *attr, const char *user, unsigned f,
40 const struct ttycolour_style *tab)
47 for (i = 0; tab[i].tok; i++) attr[i] = tab[i].dflt;
49 if (f&TCIF_GETENV) p = getenv(user);
53 n = strcspn(p, "=:"); q = p + n;
54 if (!*q || *q == ':') {
55 if (f&TCIF_REPORT) moan("missing colour token in `%s'", user);
56 rc = -1; p = *q ? q + 1 : q; continue;
58 for (i = 0; tab[i].tok; i++)
59 if (STRNCMP(p, ==, tab[i].tok, n) && !tab[i].tok[n])
62 moan("unknown colour token `%.*s' in `%s'", (int)n, p, user);
63 rc = -1; q = p + strcspn(p, ":"); p = *q ? q + 1 : q; continue;
70 { arg = 10*arg + (*q++ - '0'); continue; }
74 case 1: a |= TCAF_BOLD; break;
75 case 39: a &= ~(TCAF_FG | TCAF_FGMASK); break;
76 case 49: a &= ~(TCAF_BG | TCAF_BGMASK); break;
77 case 30: case 31: case 32: case 33:
78 case 34: case 35: case 36: case 37:
79 a = (a&~TCAF_FGMASK) | TCAF_FG | ((arg - 30) << TCAF_FGSHIFT);
81 case 40: case 41: case 42: case 43:
82 case 44: case 45: case 46: case 47:
83 a = (a&~TCAF_BGMASK) | TCAF_BG | ((arg - 40) << TCAF_BGSHIFT);
85 case 90: case 91: case 92: case 93:
86 case 94: case 95: case 96: case 97:
87 a = (a&~TCAF_FGMASK) | TCAF_FG |
88 (((arg - 90) | TCCF_BRIGHT) << TCAF_FGSHIFT);
90 case 100: case 101: case 102: case 103:
91 case 104: case 105: case 106: case 107:
92 a = (a&~TCAF_BGMASK) | TCAF_BG |
93 (((arg - 100) | TCCF_BRIGHT) << TCAF_BGSHIFT);
97 moan("unknown colour code %u in `%.*s' string in `%s'",
98 arg, (int)n, p, user);
102 if (!*q || *q == ':') break;
103 else if (*q != ';') {
105 moan("expected `;' but found `%c' in `%.*s' string in `%s'",
106 *q, (int)n, p, user);
107 rc = -1; q += strcspn(q, ":;"); if (!*q || *q == ':') break;
111 attr[i] = a; p = *q ? q + 1 : q;
117 void ttycolour_init(struct ttycolour_state *tc)
120 /* --- @ttycolour_setattr@ --- *
122 * Arguments: @const struct gprintf_ops *ops@ = formatting operations
123 * @void *go@ = output sink
124 * @struct ttycolour_state *tc
125 * @ttycolour_attr attr@ = attribute code to set
127 * Returns: Zero on success, %$-1$% on error.
129 * Use: Send a control sequence to the output stream so that
130 * subsequent text is printed with the given attributes.
132 * Some effort is taken to avoid unnecessary control sequences.
133 * In particular, if @attr@ matches the current terminal
134 * settings already, then nothing is written.
137 int ttycolour_setattr(const struct gprintf_ops *gops, void *go,
138 struct ttycolour_state *tc, ttycolour_attr attr)
140 unsigned diff = tc->attr ^ attr;
145 #define PUT_SEMI do { \
146 if (!(f&f_semi)) f |= f_semi; \
147 else if (gops->putch(go, ';') < 0) return (-1); \
150 #define SET_COLOUR(norm, bright, colour) do { \
151 unsigned char _col = (colour); \
154 gprintf(gops, go, "%d", \
155 (_col&TCCF_BRIGHT ? (bright) : (norm)) + (_col&TCCF_RGBMASK)); \
158 /* If there's nothing to do, we might as well stop now. */
159 if (!diff) return (0);
161 /* Start on the control command. */
162 if (gops->putm(go, "\x1b[", 2) < 0) return (-1);
164 /* Change the boldness if necessary. */
165 if (diff&TCAF_BOLD) {
167 if (attr&TCAF_BOLD) { if (gops->putch(go, '1') < 0) return (-1); }
168 else { diff = tc->attr; if (gops->putch(go, '0') < 0) return (-1); }
171 /* Change the foreground colour if necessary. */
172 if (diff&(TCAF_FG | TCAF_FGMASK)) {
174 SET_COLOUR(30, 90, (attr&TCAF_FGMASK) >> TCAF_FGSHIFT);
176 { PUT_SEMI; if (gops->putm(go, "39", 2) < 0) return (-1); }
179 /* Change the background colour if necessary. */
180 if (diff&(TCAF_BG | TCAF_BGMASK)) {
182 SET_COLOUR(40, 100, (attr&TCAF_BGMASK) >> TCAF_BGSHIFT);
184 { PUT_SEMI; if (gops->putm(go, "49", 2) < 0) return (-1); }
187 /* Terminate the control command and save the new attributes. */
188 if (gops->putch(go, 'm') < 0) return (-1);
189 tc->attr = attr; return (0);
196 /*----- That's all, folks -------------------------------------------------*/