Home | History | Annotate | Download | only in src
      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 
     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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    230 void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col)
    231 {
    232   lookup_colour_palette(state, index, col);
    233 }
    234 
    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 
    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 
    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 
    262 void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)
    263 {
    264   state->bold_is_highbright = bold_is_highbright;
    265 }
    266 
    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 
    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 
    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 
    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