1 #include "vterm_internal.h"
2
3 #include <stdio.h>
4
5 /**
6 * Structure used to store RGB triples without the additional metadata stored in
7 * VTermColor.
8 */
9 typedef struct {
10 uint8_t red, green, blue;
11 } VTermRGB;
12
13 static const VTermRGB ansi_colors[] = {
14 /* R G B */
15 { 0, 0, 0 }, // black
16 { 224, 0, 0 }, // red
17 { 0, 224, 0 }, // green
18 { 224, 224, 0 }, // yellow
19 { 0, 0, 224 }, // blue
20 { 224, 0, 224 }, // magenta
21 { 0, 224, 224 }, // cyan
22 { 224, 224, 224 }, // white == light grey
23
24 // high intensity
25 { 128, 128, 128 }, // black
26 { 255, 64, 64 }, // red
27 { 64, 255, 64 }, // green
28 { 255, 255, 64 }, // yellow
29 { 64, 64, 255 }, // blue
30 { 255, 64, 255 }, // magenta
31 { 64, 255, 255 }, // cyan
32 { 255, 255, 255 }, // white for real
33 };
34
35 static int ramp6[] = {
36 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF,
37 };
38
39 static int ramp24[] = {
40 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79,
41 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF,
42 };
43
lookup_default_colour_ansi(long idx,VTermColor * col)44 static void lookup_default_colour_ansi(long idx, VTermColor *col)
45 {
46 if (idx >= 0 && idx < 16) {
47 vterm_color_rgb(
48 col,
49 ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue);
50 }
51 }
52
lookup_colour_ansi(const VTermState * state,long index,VTermColor * col)53 static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col)
54 {
55 if(index >= 0 && index < 16) {
56 *col = state->colors[index];
57 return true;
58 }
59
60 return false;
61 }
62
lookup_colour_palette(const VTermState * state,long index,VTermColor * col)63 static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col)
64 {
65 if(index >= 0 && index < 16) {
66 // Normal 8 colours or high intensity - parse as palette 0
67 return lookup_colour_ansi(state, index, col);
68 }
69 else if(index >= 16 && index < 232) {
70 // 216-colour cube
71 index -= 16;
72
73 vterm_color_rgb(col, ramp6[index/6/6 % 6],
74 ramp6[index/6 % 6],
75 ramp6[index % 6]);
76
77 return true;
78 }
79 else if(index >= 232 && index < 256) {
80 // 24 greyscales
81 index -= 232;
82
83 vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]);
84
85 return true;
86 }
87
88 return false;
89 }
90
lookup_colour(const VTermState * state,int palette,const long args[],int argcount,VTermColor * col)91 static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col)
92 {
93 switch(palette) {
94 case 2: // RGB mode - 3 args contain colour values directly
95 if(argcount < 3)
96 return argcount;
97
98 vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2]));
99
100 return 3;
101
102 case 5: // XTerm 256-colour mode
103 if (!argcount || CSI_ARG_IS_MISSING(args[0])) {
104 return argcount ? 1 : 0;
105 }
106
107 vterm_color_indexed(col, args[0]);
108
109 return argcount ? 1 : 0;
110
111 default:
112 DEBUG_LOG("Unrecognised colour palette %d\n", palette);
113 return 0;
114 }
115 }
116
117 // Some conveniences
118
setpenattr(VTermState * state,VTermAttr attr,VTermValueType type,VTermValue * val)119 static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val)
120 {
121 #ifdef DEBUG
122 if(type != vterm_get_attr_type(attr)) {
123 DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n",
124 attr, vterm_get_attr_type(attr), type);
125 return;
126 }
127 #endif
128 if(state->callbacks && state->callbacks->setpenattr)
129 (*state->callbacks->setpenattr)(attr, val, state->cbdata);
130 }
131
setpenattr_bool(VTermState * state,VTermAttr attr,int boolean)132 static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean)
133 {
134 VTermValue val = { .boolean = boolean };
135 setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val);
136 }
137
setpenattr_int(VTermState * state,VTermAttr attr,int number)138 static void setpenattr_int(VTermState *state, VTermAttr attr, int number)
139 {
140 VTermValue val = { .number = number };
141 setpenattr(state, attr, VTERM_VALUETYPE_INT, &val);
142 }
143
setpenattr_col(VTermState * state,VTermAttr attr,VTermColor color)144 static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color)
145 {
146 VTermValue val = { .color = color };
147 setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val);
148 }
149
set_pen_col_ansi(VTermState * state,VTermAttr attr,long col)150 static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col)
151 {
152 VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg;
153
154 vterm_color_indexed(colp, col);
155
156 setpenattr_col(state, attr, *colp);
157 }
158
vterm_state_newpen(VTermState * state)159 INTERNAL void vterm_state_newpen(VTermState *state)
160 {
161 // 90% grey so that pure white is brighter
162 vterm_color_rgb(&state->default_fg, 240, 240, 240);
163 vterm_color_rgb(&state->default_bg, 0, 0, 0);
164 vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg);
165
166 for(int col = 0; col < 16; col++)
167 lookup_default_colour_ansi(col, &state->colors[col]);
168 }
169
vterm_state_resetpen(VTermState * state)170 INTERNAL void vterm_state_resetpen(VTermState *state)
171 {
172 state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
173 state->pen.underline = 0; setpenattr_int( state, VTERM_ATTR_UNDERLINE, 0);
174 state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
175 state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
176 state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
177 state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
178 state->pen.font = 0; setpenattr_int( state, VTERM_ATTR_FONT, 0);
179
180 state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg);
181 state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg);
182 }
183
vterm_state_savepen(VTermState * state,int save)184 INTERNAL void vterm_state_savepen(VTermState *state, int save)
185 {
186 if(save) {
187 state->saved.pen = state->pen;
188 }
189 else {
190 state->pen = state->saved.pen;
191
192 setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold);
193 setpenattr_int( state, VTERM_ATTR_UNDERLINE, state->pen.underline);
194 setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic);
195 setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink);
196 setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse);
197 setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike);
198 setpenattr_int( state, VTERM_ATTR_FONT, state->pen.font);
199 setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg);
200 setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg);
201 }
202 }
203
vterm_color_is_equal(const VTermColor * a,const VTermColor * b)204 int vterm_color_is_equal(const VTermColor *a, const VTermColor *b)
205 {
206 /* First make sure that the two colours are of the same type (RGB/Indexed) */
207 if (a->type != b->type) {
208 return false;
209 }
210
211 /* Depending on the type inspect the corresponding members */
212 if (VTERM_COLOR_IS_INDEXED(a)) {
213 return a->indexed.idx == b->indexed.idx;
214 }
215 else if (VTERM_COLOR_IS_RGB(a)) {
216 return (a->rgb.red == b->rgb.red)
217 && (a->rgb.green == b->rgb.green)
218 && (a->rgb.blue == b->rgb.blue);
219 }
220
221 return 0;
222 }
223
vterm_state_get_default_colors(const VTermState * state,VTermColor * default_fg,VTermColor * default_bg)224 void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg)
225 {
226 *default_fg = state->default_fg;
227 *default_bg = state->default_bg;
228 }
229
vterm_state_get_palette_color(const VTermState * state,int index,VTermColor * col)230 void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col)
231 {
232 lookup_colour_palette(state, index, col);
233 }
234
vterm_state_set_default_colors(VTermState * state,const VTermColor * default_fg,const VTermColor * default_bg)235 void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg)
236 {
237 /* Copy the given colors */
238 state->default_fg = *default_fg;
239 state->default_bg = *default_bg;
240
241 /* Make sure the correct type flags are set */
242 state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK)
243 | VTERM_COLOR_DEFAULT_FG;
244 state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK)
245 | VTERM_COLOR_DEFAULT_BG;
246 }
247
vterm_state_set_palette_color(VTermState * state,int index,const VTermColor * col)248 void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col)
249 {
250 if(index >= 0 && index < 16)
251 state->colors[index] = *col;
252 }
253
vterm_state_convert_color_to_rgb(const VTermState * state,VTermColor * col)254 void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col)
255 {
256 if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */
257 lookup_colour_palette(state, col->indexed.idx, col);
258 }
259 col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */
260 }
261
vterm_state_set_bold_highbright(VTermState * state,int bold_is_highbright)262 void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)
263 {
264 state->bold_is_highbright = bold_is_highbright;
265 }
266
vterm_state_setpen(VTermState * state,const long args[],int argcount)267 INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount)
268 {
269 // SGR - ECMA-48 8.3.117
270
271 int argi = 0;
272 int value;
273
274 while(argi < argcount) {
275 // This logic is easier to do 'done' backwards; set it true, and make it
276 // false again in the 'default' case
277 int done = 1;
278
279 long arg;
280 switch(arg = CSI_ARG(args[argi])) {
281 case CSI_ARG_MISSING:
282 case 0: // Reset
283 vterm_state_resetpen(state);
284 break;
285
286 case 1: { // Bold on
287 const VTermColor *fg = &state->pen.fg;
288 state->pen.bold = 1;
289 setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
290 if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright)
291 set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0));
292 break;
293 }
294
295 case 3: // Italic on
296 state->pen.italic = 1;
297 setpenattr_bool(state, VTERM_ATTR_ITALIC, 1);
298 break;
299
300 case 4: // Underline
301 state->pen.underline = VTERM_UNDERLINE_SINGLE;
302 if(CSI_ARG_HAS_MORE(args[argi])) {
303 argi++;
304 switch(CSI_ARG(args[argi])) {
305 case 0:
306 state->pen.underline = 0;
307 break;
308 case 1:
309 state->pen.underline = VTERM_UNDERLINE_SINGLE;
310 break;
311 case 2:
312 state->pen.underline = VTERM_UNDERLINE_DOUBLE;
313 break;
314 case 3:
315 state->pen.underline = VTERM_UNDERLINE_CURLY;
316 break;
317 }
318 }
319 setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
320 break;
321
322 case 5: // Blink
323 state->pen.blink = 1;
324 setpenattr_bool(state, VTERM_ATTR_BLINK, 1);
325 break;
326
327 case 7: // Reverse on
328 state->pen.reverse = 1;
329 setpenattr_bool(state, VTERM_ATTR_REVERSE, 1);
330 break;
331
332 case 9: // Strikethrough on
333 state->pen.strike = 1;
334 setpenattr_bool(state, VTERM_ATTR_STRIKE, 1);
335 break;
336
337 case 10: case 11: case 12: case 13: case 14:
338 case 15: case 16: case 17: case 18: case 19: // Select font
339 state->pen.font = CSI_ARG(args[argi]) - 10;
340 setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
341 break;
342
343 case 21: // Underline double
344 state->pen.underline = VTERM_UNDERLINE_DOUBLE;
345 setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
346 break;
347
348 case 22: // Bold off
349 state->pen.bold = 0;
350 setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
351 break;
352
353 case 23: // Italic and Gothic (currently unsupported) off
354 state->pen.italic = 0;
355 setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
356 break;
357
358 case 24: // Underline off
359 state->pen.underline = 0;
360 setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);
361 break;
362
363 case 25: // Blink off
364 state->pen.blink = 0;
365 setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
366 break;
367
368 case 27: // Reverse off
369 state->pen.reverse = 0;
370 setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
371 break;
372
373 case 29: // Strikethrough off
374 state->pen.strike = 0;
375 setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
376 break;
377
378 case 30: case 31: case 32: case 33:
379 case 34: case 35: case 36: case 37: // Foreground colour palette
380 value = CSI_ARG(args[argi]) - 30;
381 if(state->pen.bold && state->bold_is_highbright)
382 value += 8;
383 set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
384 break;
385
386 case 38: // Foreground colour alternative palette
387 if(argcount - argi < 1)
388 return;
389 argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg);
390 setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
391 break;
392
393 case 39: // Foreground colour default
394 state->pen.fg = state->default_fg;
395 setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
396 break;
397
398 case 40: case 41: case 42: case 43:
399 case 44: case 45: case 46: case 47: // Background colour palette
400 value = CSI_ARG(args[argi]) - 40;
401 set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
402 break;
403
404 case 48: // Background colour alternative palette
405 if(argcount - argi < 1)
406 return;
407 argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg);
408 setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
409 break;
410
411 case 49: // Default background
412 state->pen.bg = state->default_bg;
413 setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
414 break;
415
416 case 90: case 91: case 92: case 93:
417 case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette
418 value = CSI_ARG(args[argi]) - 90 + 8;
419 set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
420 break;
421
422 case 100: case 101: case 102: case 103:
423 case 104: case 105: case 106: case 107: // Background colour high-intensity palette
424 value = CSI_ARG(args[argi]) - 100 + 8;
425 set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
426 break;
427
428 default:
429 done = 0;
430 break;
431 }
432
433 if(!done)
434 DEBUG_LOG("libvterm: Unhandled CSI SGR %lu\n", arg);
435
436 while(CSI_ARG_HAS_MORE(args[argi++]));
437 }
438 }
439
vterm_state_getpen_color(const VTermColor * col,int argi,long args[],int fg)440 static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg)
441 {
442 /* Do nothing if the given color is the default color */
443 if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) ||
444 (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) {
445 return argi;
446 }
447
448 /* Decide whether to send an indexed color or an RGB color */
449 if (VTERM_COLOR_IS_INDEXED(col)) {
450 const uint8_t idx = col->indexed.idx;
451 if (idx < 8) {
452 args[argi++] = (idx + (fg ? 30 : 40));
453 }
454 else if (idx < 16) {
455 args[argi++] = (idx - 8 + (fg ? 90 : 100));
456 }
457 else {
458 args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
459 args[argi++] = CSI_ARG_FLAG_MORE | 5;
460 args[argi++] = idx;
461 }
462 }
463 else if (VTERM_COLOR_IS_RGB(col)) {
464 args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
465 args[argi++] = CSI_ARG_FLAG_MORE | 2;
466 args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red;
467 args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green;
468 args[argi++] = col->rgb.blue;
469 }
470 return argi;
471 }
472
vterm_state_getpen(VTermState * state,long args[],int argcount)473 INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount)
474 {
475 int argi = 0;
476
477 if(state->pen.bold)
478 args[argi++] = 1;
479
480 if(state->pen.italic)
481 args[argi++] = 3;
482
483 if(state->pen.underline == VTERM_UNDERLINE_SINGLE)
484 args[argi++] = 4;
485 if(state->pen.underline == VTERM_UNDERLINE_CURLY)
486 args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3;
487
488 if(state->pen.blink)
489 args[argi++] = 5;
490
491 if(state->pen.reverse)
492 args[argi++] = 7;
493
494 if(state->pen.strike)
495 args[argi++] = 9;
496
497 if(state->pen.font)
498 args[argi++] = 10 + state->pen.font;
499
500 if(state->pen.underline == VTERM_UNDERLINE_DOUBLE)
501 args[argi++] = 21;
502
503 argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true);
504
505 argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false);
506
507 return argi;
508 }
509
vterm_state_get_penattr(const VTermState * state,VTermAttr attr,VTermValue * val)510 int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)
511 {
512 switch(attr) {
513 case VTERM_ATTR_BOLD:
514 val->boolean = state->pen.bold;
515 return 1;
516
517 case VTERM_ATTR_UNDERLINE:
518 val->number = state->pen.underline;
519 return 1;
520
521 case VTERM_ATTR_ITALIC:
522 val->boolean = state->pen.italic;
523 return 1;
524
525 case VTERM_ATTR_BLINK:
526 val->boolean = state->pen.blink;
527 return 1;
528
529 case VTERM_ATTR_REVERSE:
530 val->boolean = state->pen.reverse;
531 return 1;
532
533 case VTERM_ATTR_STRIKE:
534 val->boolean = state->pen.strike;
535 return 1;
536
537 case VTERM_ATTR_FONT:
538 val->number = state->pen.font;
539 return 1;
540
541 case VTERM_ATTR_FOREGROUND:
542 val->color = state->pen.fg;
543 return 1;
544
545 case VTERM_ATTR_BACKGROUND:
546 val->color = state->pen.bg;
547 return 1;
548
549 case VTERM_N_ATTRS:
550 return 0;
551 }
552
553 return 0;
554 }
555