diff --git a/src/tsm/tsm-vte.c b/src/tsm/tsm-vte.c index cdbdc5e..44bfd4a 100644 --- a/src/tsm/tsm-vte.c +++ b/src/tsm/tsm-vte.c @@ -1220,9 +1220,45 @@ static void do_esc(struct tsm_vte *vte, uint32_t data) } } -static void csi_attribute(struct tsm_vte *vte) +static void palette_rgb(struct tsm_vte *vte, int code, uint8_t *cr, + uint8_t *cg, uint8_t *cb) +{ + *cr = vte->palette[code][0]; + *cg = vte->palette[code][1]; + *cb = vte->palette[code][2]; +} + +static void cube_rgb(int code, uint8_t *cr, uint8_t *cg, uint8_t *cb) { static const uint8_t bval[6] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff }; + code -= 16; + *cb = bval[code % 6]; + code /= 6; + *cg = bval[code % 6]; + code /= 6; + *cr = bval[code % 6]; +} + +static void greyscale_rgb(int code, uint8_t *cr, uint8_t *cg, uint8_t *cb) +{ + uint8_t lightness = (code - 232) * 10 + 8; + *cr = *cg = *cb = lightness; +} + +static void lookup_color(struct tsm_vte *vte, int color, uint8_t *cr, + uint8_t *cg, uint8_t *cb) +{ + if (color < 16) { + palette_rgb(vte, color, cr, cg, cb); + } else if (color < 232) { + cube_rgb(color, cr, cg, cb); + } else { + greyscale_rgb(color, cr, cg, cb); + } +} + +static void csi_attribute(struct tsm_vte *vte) +{ unsigned int i, code, val; uint8_t cr, cg, cb; @@ -1390,19 +1426,8 @@ static void csi_attribute(struct tsm_vte *vte) if (code < 16) { //no change cb = cg = cr = 0; - } else if (code < 232) { - code -= 16; - cb = bval[code % 6]; - code /= 6; - cg = bval[code % 6]; - code /= 6; - cr = bval[code % 6]; - code = -1; } else { - code = (code - 232) * 10 + 8; - cr = code; - cg = code; - cb = code; + lookup_color(vte, code, &cr, &cg, &cb); code = -1; } i += 2; @@ -2059,12 +2084,97 @@ static void do_osc_collect(struct tsm_vte *vte, uint32_t val) { vte->osc_len += len; } -static void do_osc_end(struct tsm_vte *vte) { - if (!vte->osc_cb) { - return; +static void vte_write_xcolor(struct tsm_vte *vte, char *code, + const char *end_seq, + uint8_t r, uint8_t g, uint8_t b) +{ + char buf[32]; + snprintf(buf, sizeof(buf), "\e]%s;rgb:%02x%02x/%02x%02x/%02x%02x%s", + code, r, r, g, g, b, b, end_seq); + vte_write(vte, buf, strlen(buf)); +} + +static void do_osc_4(struct tsm_vte *vte, const char *data, const char *end_seq) +{ + char buf[32]; + uint8_t cr, cg, cb; + const char *c = data; + + while (*c) { + // parse color code + unsigned int code = 0; + while (*c >= '0' && *c <= '9') { + code = code * 10 + (*c++ - '0'); + } + if (*c++ != ';') { + break; + } + + // parse value field + if (*c == '?') { + c++; + // handle rgb value query + snprintf(buf, sizeof(buf), "4;%u", code); + lookup_color(vte, code, &cr, &cg, &cb); + vte_write_xcolor(vte, buf, end_seq, cr, cg, cb); + } else { + // parsing of new rgb value to assign to code would go + // here. For now, skip past field contents. + while (*c && *c != ';') { + c++; + } + } + if (*c++ != ';') { + break; + } } +} +static bool do_osc_internal(struct tsm_vte *vte, const char *end_seq) +{ + // OSC 4, 10, and 11 query or change the RGB values of color numbers in + // the foreground/background (SGR 38 & 48), 3-bit (SGR 30-37 & 40-47), + // 4-bit (SGR 90-97 & 100-107), and 256-color (SGR 38;5 & 48;5) indexed + // color tables. We don't support changing the tables, but we do support + // querying them. + if (!strncmp(vte->osc_arg, "4;", 2)) { + do_osc_4(vte, vte->osc_arg + 2, end_seq); + return true; + } + if (!strncmp(vte->osc_arg, "10;?", 4)) { + vte_write_xcolor(vte, "10", end_seq, + vte->def_attr.fr, vte->def_attr.fg, + vte->def_attr.fb); + return true; + } + if (!strncmp(vte->osc_arg, "11;?", 4)) { + vte_write_xcolor(vte, "11", end_seq, + vte->def_attr.br, vte->def_attr.bg, + vte->def_attr.bb); + return true; + } + return false; +} + +/* XTerm OSC responses use the same sequence terminator used in the query */ +static const char *osc_end_seq(uint32_t end) +{ + if (end == 0x07) { + return "\x07"; + } + return "\x1b\x5c"; +} + +static void do_osc_end(struct tsm_vte *vte, uint32_t val) { + const char *end_seq = osc_end_seq(val); vte->osc_arg[vte->osc_len] = 0; + + if (do_osc_internal(vte, end_seq)) { + return; + } + if (!vte->osc_cb) { + return; + } vte->osc_cb(vte, vte->osc_arg, vte->osc_len, vte->osc_data); } @@ -2115,7 +2225,7 @@ static void do_action(struct tsm_vte *vte, uint32_t data, int action) do_osc_collect(vte, data); break; case ACTION_OSC_END: - do_osc_end(vte); + do_osc_end(vte, data); break; default: llog_warning(vte, "invalid action %d", action); diff --git a/test/test_vte.c b/test/test_vte.c index a109f13..9aae9f6 100644 --- a/test/test_vte.c +++ b/test/test_vte.c @@ -164,8 +164,12 @@ START_TEST(test_vte_custom_palette) } END_TEST +static bool write_cb_called = false; + static void checking_write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data) { + write_cb_called = true; + ck_assert_ptr_ne(vte, NULL); ck_assert_ptr_ne(u8, NULL); ck_assert_uint_gt(len, 0); @@ -173,6 +177,127 @@ static void checking_write_cb(struct tsm_vte *vte, const char *u8, size_t len, v ck_assert_mem_eq(u8, data, len); } +static void checked_vte_input(struct tsm_vte *vte, const char *u8, size_t len) +{ + write_cb_called = false; + tsm_vte_input(vte, u8, len); + ck_assert(write_cb_called); +} + +START_TEST(test_vte_osc_query) +{ + struct tsm_screen *screen; + struct tsm_vte *vte; + int r; + char expected_output[32]; + const char *input; + + r = tsm_screen_new(&screen, log_cb, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_vte_new(&vte, screen, checking_write_cb, expected_output, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_vte_set_custom_palette(vte, test_palette); + ck_assert_int_eq(r, 0); + + r = tsm_vte_set_palette(vte, "custom"); + ck_assert_int_eq(r, 0); + + // query foreground, end_seq = BEL + input = "\e]10;?\x07"; + strcpy(expected_output, "\e]10;rgb:1010/2222/3434\x07"); + checked_vte_input(vte, input, strlen(input)); + + // query foreground, end_seq = ST + input = "\e]10;?\e\\"; + strcpy(expected_output, "\e]10;rgb:1010/2222/3434\e\\"); + checked_vte_input(vte, input, strlen(input)); + + // ignore additional parameters after foreground query + input = "\e]10;?;11;?\x07"; + strcpy(expected_output, "\e]10;rgb:1010/2222/3434\x07"); + checked_vte_input(vte, input, strlen(input)); + + // query background + input = "\e]11;?\x07"; + strcpy(expected_output, "\e]11;rgb:1111/2323/3535\x07"); + checked_vte_input(vte, input, strlen(input)); + + // ignore additional parameters after background query + input = "\e]11;?;12;?\x07"; + strcpy(expected_output, "\e]11;rgb:1111/2323/3535\x07"); + checked_vte_input(vte, input, strlen(input)); +} +END_TEST + +unsigned char write_buffer[512]; +unsigned char *write_buffer_p = write_buffer; + +static void storing_write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data) +{ + ck_assert_ptr_ne(vte, NULL); + ck_assert_ptr_ne(u8, NULL); + + memcpy(write_buffer_p, u8, len); + write_buffer_p[len] = '\0'; + write_buffer_p += len; +} + +static void storing_write_cb_reset(void) +{ + memset(write_buffer, 0, sizeof(write_buffer)); + write_buffer_p = write_buffer; +} + +START_TEST(test_vte_osc4) +{ + struct tsm_screen *screen; + struct tsm_vte *vte; + int r; + const char *input; + + r = tsm_screen_new(&screen, log_cb, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_vte_new(&vte, screen, storing_write_cb, NULL, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_vte_set_custom_palette(vte, test_palette); + ck_assert_int_eq(r, 0); + + r = tsm_vte_set_palette(vte, "custom"); + ck_assert_int_eq(r, 0); + + // query palette entries + storing_write_cb_reset(); + input = "\e]4;1;?;13;?;3;?\x07"; + const char *expected = + "\e]4;1;rgb:0101/1313/2525\x07" + "\e]4;13;rgb:0d0d/1f1f/3131\x07" + "\e]4;3;rgb:0303/1515/2727\x07"; + tsm_vte_input(vte, input, strlen(input)); + ck_assert_mem_eq(write_buffer, expected, strlen(expected)); + ck_assert_int_eq(write_buffer_p - write_buffer, strlen(expected)); + + // query cube & grayscale entries + storing_write_cb_reset(); + input = "\e]4;110;?;254;?;\x07"; + expected = + "\e]4;110;rgb:8787/afaf/d7d7\x07" + "\e]4;254;rgb:e4e4/e4e4/e4e4\x07"; + tsm_vte_input(vte, input, strlen(input)); + ck_assert_mem_eq(write_buffer, expected, strlen(expected)); + + // ignore color change requests, incomplete messages + storing_write_cb_reset(); + input = "\e]4;1;rgb:1111/2222/3333;2\x07"; + tsm_vte_input(vte, input, strlen(input)); + ck_assert_ptr_eq(write_buffer, write_buffer_p); + storing_write_cb_reset(); +} +END_TEST + START_TEST(test_vte_backspace_key) { struct tsm_screen *screen; @@ -337,6 +462,8 @@ TEST_DEFINE_CASE(misc) TEST(test_vte_init) TEST(test_vte_null) TEST(test_vte_custom_palette) + TEST(test_vte_osc_query) + TEST(test_vte_osc4) TEST(test_vte_backspace_key) TEST(test_vte_get_flags) TEST(test_vte_decrqm_no_reset)