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 single
301       state->pen.underline = 1;
302       setpenattr_int(state, VTERM_ATTR_UNDERLINE, 1);
303       break;
304 
305     case 5: // Blink
306       state->pen.blink = 1;
307       setpenattr_bool(state, VTERM_ATTR_BLINK, 1);
308       break;
309 
310     case 7: // Reverse on
311       state->pen.reverse = 1;
312       setpenattr_bool(state, VTERM_ATTR_REVERSE, 1);
313       break;
314 
315     case 9: // Strikethrough on
316       state->pen.strike = 1;
317       setpenattr_bool(state, VTERM_ATTR_STRIKE, 1);
318       break;
319 
320     case 10: case 11: case 12: case 13: case 14:
321     case 15: case 16: case 17: case 18: case 19: // Select font
322       state->pen.font = CSI_ARG(args[argi]) - 10;
323       setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
324       break;
325 
326     case 21: // Underline double
327       state->pen.underline = 2;
328       setpenattr_int(state, VTERM_ATTR_UNDERLINE, 2);
329       break;
330 
331     case 22: // Bold off
332       state->pen.bold = 0;
333       setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
334       break;
335 
336     case 23: // Italic and Gothic (currently unsupported) off
337       state->pen.italic = 0;
338       setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
339       break;
340 
341     case 24: // Underline off
342       state->pen.underline = 0;
343       setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);
344       break;
345 
346     case 25: // Blink off
347       state->pen.blink = 0;
348       setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
349       break;
350 
351     case 27: // Reverse off
352       state->pen.reverse = 0;
353       setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
354       break;
355 
356     case 29: // Strikethrough off
357       state->pen.strike = 0;
358       setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
359       break;
360 
361     case 30: case 31: case 32: case 33:
362     case 34: case 35: case 36: case 37: // Foreground colour palette
363       value = CSI_ARG(args[argi]) - 30;
364       if(state->pen.bold && state->bold_is_highbright)
365         value += 8;
366       set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
367       break;
368 
369     case 38: // Foreground colour alternative palette
370       if(argcount - argi < 1)
371         return;
372       argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg);
373       setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
374       break;
375 
376     case 39: // Foreground colour default
377       state->pen.fg = state->default_fg;
378       setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
379       break;
380 
381     case 40: case 41: case 42: case 43:
382     case 44: case 45: case 46: case 47: // Background colour palette
383       value = CSI_ARG(args[argi]) - 40;
384       set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
385       break;
386 
387     case 48: // Background colour alternative palette
388       if(argcount - argi < 1)
389         return;
390       argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg);
391       setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
392       break;
393 
394     case 49: // Default background
395       state->pen.bg = state->default_bg;
396       setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
397       break;
398 
399     case 90: case 91: case 92: case 93:
400     case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette
401       value = CSI_ARG(args[argi]) - 90 + 8;
402       set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
403       break;
404 
405     case 100: case 101: case 102: case 103:
406     case 104: case 105: case 106: case 107: // Background colour high-intensity palette
407       value = CSI_ARG(args[argi]) - 100 + 8;
408       set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
409       break;
410 
411     default:
412       done = 0;
413       break;
414     }
415 
416     if(!done)
417       DEBUG_LOG("libvterm: Unhandled CSI SGR %lu\n", arg);
418 
419     while(CSI_ARG_HAS_MORE(args[argi++]));
420   }
421 }
422 
vterm_state_getpen_color(const VTermColor * col,int argi,long args[],int fg)423 static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg)
424 {
425     /* Do nothing if the given color is the default color */
426     if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) ||
427         (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) {
428         return argi;
429     }
430 
431     /* Decide whether to send an indexed color or an RGB color */
432     if (VTERM_COLOR_IS_INDEXED(col)) {
433         const uint8_t idx = col->indexed.idx;
434         if (idx < 8) {
435             args[argi++] = (idx + (fg ? 30 : 40));
436         }
437         else if (idx < 16) {
438             args[argi++] = (idx - 8 + (fg ? 90 : 100));
439         }
440         else {
441             args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
442             args[argi++] = CSI_ARG_FLAG_MORE | 5;
443             args[argi++] = idx;
444         }
445     }
446     else if (VTERM_COLOR_IS_RGB(col)) {
447         args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
448         args[argi++] = CSI_ARG_FLAG_MORE | 2;
449         args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red;
450         args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green;
451         args[argi++] = col->rgb.blue;
452     }
453     return argi;
454 }
455 
vterm_state_getpen(VTermState * state,long args[],int argcount)456 INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount)
457 {
458   int argi = 0;
459 
460   if(state->pen.bold)
461     args[argi++] = 1;
462 
463   if(state->pen.italic)
464     args[argi++] = 3;
465 
466   if(state->pen.underline == 1)
467     args[argi++] = 4;
468 
469   if(state->pen.blink)
470     args[argi++] = 5;
471 
472   if(state->pen.reverse)
473     args[argi++] = 7;
474 
475   if(state->pen.strike)
476     args[argi++] = 9;
477 
478   if(state->pen.font)
479     args[argi++] = 10 + state->pen.font;
480 
481   if(state->pen.underline == 2)
482     args[argi++] = 21;
483 
484   argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true);
485 
486   argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false);
487 
488   return argi;
489 }
490 
vterm_state_get_penattr(const VTermState * state,VTermAttr attr,VTermValue * val)491 int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)
492 {
493   switch(attr) {
494   case VTERM_ATTR_BOLD:
495     val->boolean = state->pen.bold;
496     return 1;
497 
498   case VTERM_ATTR_UNDERLINE:
499     val->number = state->pen.underline;
500     return 1;
501 
502   case VTERM_ATTR_ITALIC:
503     val->boolean = state->pen.italic;
504     return 1;
505 
506   case VTERM_ATTR_BLINK:
507     val->boolean = state->pen.blink;
508     return 1;
509 
510   case VTERM_ATTR_REVERSE:
511     val->boolean = state->pen.reverse;
512     return 1;
513 
514   case VTERM_ATTR_STRIKE:
515     val->boolean = state->pen.strike;
516     return 1;
517 
518   case VTERM_ATTR_FONT:
519     val->number = state->pen.font;
520     return 1;
521 
522   case VTERM_ATTR_FOREGROUND:
523     val->color = state->pen.fg;
524     return 1;
525 
526   case VTERM_ATTR_BACKGROUND:
527     val->color = state->pen.bg;
528     return 1;
529 
530   case VTERM_N_ATTRS:
531     return 0;
532   }
533 
534   return 0;
535 }
536