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