diff --git a/CHANGELOG.md b/CHANGELOG.md index a504f77a..39c95231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,10 +63,14 @@ * Support for high-res mouse wheel scroll events ([#1738][1738]). * Styled and colored underlines ([#828][828]). * Support for SGR 21 (double underline). +* Support for `XTPUSHCOLORS`, `XTPOPCOLORS` and `XTREPORTCOLORS`, + i.e. color palette stack ([#856][856]). [1707]: https://codeberg.org/dnkl/foot/issues/1707 [1738]: https://codeberg.org/dnkl/foot/issues/1738 [828]: https://codeberg.org/dnkl/foot/issues/828 +[856]: https://codeberg.org/dnkl/foot/issues/856 + ### Changed diff --git a/csi.c b/csi.c index 2d0f3c62..86117c7e 100644 --- a/csi.c +++ b/csi.c @@ -2048,6 +2048,83 @@ csi_dispatch(struct terminal *term, uint8_t final) break; /* private[0] == ‘$’ */ } + case '#': { + switch (final) { + case 'P': { /* XTPUSHCOLORS */ + int slot = vt_param_get(term, 0, 0); + + /* Pm == 0, "push" (what xterm does is take take the + *current* slot + 1, even if that's in the middle of the + stack, and overwrites whatever is already in that + slot) */ + if (slot == 0) + slot = term->color_stack.idx + 1; + + if (term->color_stack.size < slot) { + const size_t new_size = slot; + term->color_stack.stack = xrealloc( + term->color_stack.stack, + new_size * sizeof(term->color_stack.stack[0])); + + /* Initialize new slots (except the selected slot, + which is done below) */ + xassert(new_size > 0); + for (size_t i = term->color_stack.size; i < new_size - 1; i++) { + memcpy(&term->color_stack.stack[i], &term->colors, + sizeof(term->colors)); + } + term->color_stack.size = new_size; + } + + xassert(slot > 0); + xassert(slot <= term->color_stack.size); + term->color_stack.idx = slot; + memcpy(&term->color_stack.stack[slot - 1], &term->colors, + sizeof(term->colors)); + break; + } + + case 'Q': { /* XTPOPCOLORS */ + int slot = vt_param_get(term, 0, 0); + + /* Pm == 0, "pop" (what xterm does is copy colors from the + *current* slot, *and* decrease the current slot index, + even if that's in the middle of the stack) */ + if (slot == 0) + slot = term->color_stack.idx; + + if (slot > 0 && slot <= term->color_stack.size) { + memcpy(&term->colors, &term->color_stack.stack[slot - 1], + sizeof(term->colors)); + term->color_stack.idx = slot - 1; + + /* TODO: we _could_ iterate all cells and only dirty + those that are affected by the palette change... */ + term_damage_view(term); + term_damage_margins(term); + } else if (slot == 0) { + LOG_ERR("XTPOPCOLORS: cannot pop beyond the first element"); + } else { + LOG_ERR( + "XTPOPCOLORS: invalid color slot: %d " + "(stack has %zu slots, current slot is %zu)", + vt_param_get(term, 0, 0), + term->color_stack.size, term->color_stack.idx); + } + break; + } + + case 'R': { /* XTREPORTCOLORS */ + char reply[64]; + int n = xsnprintf(reply, sizeof(reply), "\033[?%zu;%zu#Q", + term->color_stack.idx, term->color_stack.size); + term_to_slave(term, reply, n); + break; + } + } + break; /* private[0] == '#' */ + } + case 0x243f: /* ?$ */ switch (final) { case 'p': { diff --git a/doc/foot-ctlseqs.7.scd b/doc/foot-ctlseqs.7.scd index 3ed26b11..f73b5793 100644 --- a/doc/foot-ctlseqs.7.scd +++ b/doc/foot-ctlseqs.7.scd @@ -632,6 +632,19 @@ manipulation sequences. The generic format is: : : kitty : Update current Kitty keyboard flags, according to _mode_. +| \\E[ # P +: XTPUSHCOLORS +: xterm +: Push current color palette onto stack +| \\E[ # Q +: XTPOPCOLORS +: xterm +: Pop color palette from stack +| \\E[ # R +: XTREPORTCOLORS +: xterm +: Report the current entry on the palette stack, and the number of + palettes stored on the stack. # OSC diff --git a/osc.c b/osc.c index 546421f5..d9e952c0 100644 --- a/osc.c +++ b/osc.c @@ -712,15 +712,25 @@ osc_dispatch(struct terminal *term) osc_notify(term, string); break; - case 10: - case 11: - case 17: - case 19: { + case 10: /* fg */ + case 11: /* bg */ + case 12: /* cursor */ + case 17: /* highlight (selection) fg */ + case 19: { /* highlight (selection) bg */ /* Set default foreground/background/highlight-bg/highlight-fg color */ /* Client queried for current value */ if (string[0] == '?' && string[1] == '\0') { - uint32_t color = param == 10 ? term->colors.fg : term->colors.bg; + uint32_t color = param == 10 + ? term->colors.fg + : param == 11 + ? term->colors.bg + : param == 12 + ? term->colors.cursor_bg + : param == 17 + ? term->colors.selection_bg + : term->colors.selection_fg; + uint8_t r = (color >> 16) & 0xff; uint8_t g = (color >> 8) & 0xff; uint8_t b = (color >> 0) & 0xff; @@ -754,6 +764,7 @@ osc_dispatch(struct terminal *term) LOG_DBG("change color definition for %s to %06x", param == 10 ? "foreground" : param == 11 ? "background" : + param == 12 ? "cursor" : param == 17 ? "selection background" : "selection foreground", color); @@ -761,6 +772,7 @@ osc_dispatch(struct terminal *term) switch (param) { case 10: term->colors.fg = color; + term_damage_view(term); break; case 11: @@ -774,61 +786,31 @@ osc_dispatch(struct terminal *term) term_font_subpixel_changed(term); } } + term_damage_view(term); + term_damage_margins(term); + break; + + case 12: + term->colors.cursor_bg = 1u << 31 | color; + term_damage_cursor(term); break; case 17: term->colors.selection_bg = color; term->colors.use_custom_selection = true; + term_damage_view(term); break; case 19: term->colors.selection_fg = color; term->colors.use_custom_selection = true; + term_damage_view(term); break; } - term_damage_view(term); - term_damage_margins(term); break; } - case 12: /* Set cursor color */ - - /* Client queried for current value */ - if (string[0] == '?' && string[1] == '\0') { - uint8_t r = (term->cursor_color.cursor >> 16) & 0xff; - uint8_t g = (term->cursor_color.cursor >> 8) & 0xff; - uint8_t b = (term->cursor_color.cursor >> 0) & 0xff; - const char *terminator = term->vt.osc.bel ? "\a" : "\033\\"; - - char reply[32]; - size_t n = xsnprintf( - reply, sizeof(reply), "\033]12;rgb:%02x/%02x/%02x%s", - r, g, b, terminator); - - term_to_slave(term, reply, n); - break; - } - - uint32_t color; - - if (string[0] == '#' || string[0] == '[' - ? !parse_legacy_color(string, &color, NULL, NULL) - : !parse_rgb(string, &color, NULL, NULL)) - { - break; - } - - LOG_DBG("change cursor color to %06x", color); - - if (color == 0) - term->cursor_color.cursor = 0; /* Invert fg/bg */ - else - term->cursor_color.cursor = 1u << 31 | color; - - term_damage_cursor(term); - break; - case 22: /* Set mouse cursor */ term_set_user_mouse_cursor(term, string); break; @@ -895,8 +877,8 @@ osc_dispatch(struct terminal *term) case 112: LOG_DBG("resetting cursor color"); - term->cursor_color.text = term->conf->cursor.color.text; - term->cursor_color.cursor = term->conf->cursor.color.cursor; + term->colors.cursor_fg = term->conf->cursor.color.text; + term->colors.cursor_bg = term->conf->cursor.color.cursor; term_damage_cursor(term); break; diff --git a/render.c b/render.c index 4103be5c..2f18ff88 100644 --- a/render.c +++ b/render.c @@ -557,13 +557,13 @@ cursor_colors_for_cell(const struct terminal *term, const struct cell *cell, const pixman_color_t *fg, const pixman_color_t *bg, pixman_color_t *cursor_color, pixman_color_t *text_color) { - if (term->cursor_color.cursor >> 31) - *cursor_color = color_hex_to_pixman(term->cursor_color.cursor); + if (term->colors.cursor_bg >> 31) + *cursor_color = color_hex_to_pixman(term->colors.cursor_bg); else *cursor_color = *fg; - if (term->cursor_color.text >> 31) - *text_color = color_hex_to_pixman(term->cursor_color.text); + if (term->colors.cursor_fg >> 31) + *text_color = color_hex_to_pixman(term->colors.cursor_fg); else { *text_color = *bg; diff --git a/terminal.c b/terminal.c index 0518e57d..ca3d2bc1 100644 --- a/terminal.c +++ b/terminal.c @@ -1221,10 +1221,17 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .fg = conf->colors.fg, .bg = conf->colors.bg, .alpha = conf->colors.alpha, + .cursor_fg = conf->cursor.color.text, + .cursor_bg = conf->cursor.color.cursor, .selection_fg = conf->colors.selection_fg, .selection_bg = conf->colors.selection_bg, .use_custom_selection = conf->colors.use_custom.selection, }, + .color_stack = { + .stack = NULL, + .size = 0, + .idx = 0, + }, .origin = ORIGIN_ABSOLUTE, .cursor_style = conf->cursor.style, .cursor_blink = { @@ -1233,10 +1240,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .state = CURSOR_BLINK_ON, .fd = -1, }, - .cursor_color = { - .text = conf->cursor.color.text, - .cursor = conf->cursor.color.cursor, - }, .selection = { .coords = { .start = {-1, -1}, @@ -1825,6 +1828,7 @@ term_destroy(struct terminal *term) free(term->foot_exe); free(term->cwd); free(term->mouse_user_cursor); + free(term->color_stack.stack); int ret = EXIT_SUCCESS; @@ -2035,11 +2039,17 @@ term_reset(struct terminal *term, bool hard) term->colors.fg = term->conf->colors.fg; term->colors.bg = term->conf->colors.bg; term->colors.alpha = term->conf->colors.alpha; + term->colors.cursor_fg = term->conf->cursor.color.text; + term->colors.cursor_bg = term->conf->cursor.color.cursor; term->colors.selection_fg = term->conf->colors.selection_fg; term->colors.selection_bg = term->conf->colors.selection_bg; term->colors.use_custom_selection = term->conf->colors.use_custom.selection; memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table)); + free(term->color_stack.stack); + term->color_stack.stack = NULL; + term->color_stack.size = 0; + term->color_stack.idx = 0; term->origin = ORIGIN_ABSOLUTE; term->normal.cursor.lcf = false; term->alt.cursor.lcf = false; @@ -2051,8 +2061,6 @@ term_reset(struct terminal *term, bool hard) term->cursor_blink.decset = false; term->cursor_blink.deccsusr = term->conf->cursor.blink.enabled; term_cursor_blink_update(term); - term->cursor_color.text = term->conf->cursor.color.text; - term->cursor_color.cursor = term->conf->cursor.color.cursor; selection_cancel(term); term->normal.offset = term->normal.view = 0; term->alt.offset = term->alt.view = 0; diff --git a/terminal.h b/terminal.h index a148e528..4d628661 100644 --- a/terminal.h +++ b/terminal.h @@ -393,6 +393,19 @@ struct url { }; typedef tll(struct url) url_list_t; + +struct colors { + uint32_t fg; + uint32_t bg; + uint32_t table[256]; + uint16_t alpha; + uint32_t cursor_fg; /* Text color */ + uint32_t cursor_bg; /* cursor color */ + uint32_t selection_fg; + uint32_t selection_bg; + bool use_custom_selection; +}; + struct terminal { struct fdm *fdm; struct reaper *reaper; @@ -563,15 +576,13 @@ struct terminal { int cell_width; /* pixels per cell, x-wise */ int cell_height; /* pixels per cell, y-wise */ + struct colors colors; + struct { - uint32_t fg; - uint32_t bg; - uint32_t table[256]; - uint16_t alpha; - uint32_t selection_fg; - uint32_t selection_bg; - bool use_custom_selection; - } colors; + struct colors *stack; + size_t idx; + size_t size; + } color_stack; enum cursor_style cursor_style; struct { @@ -580,10 +591,6 @@ struct terminal { int fd; enum { CURSOR_BLINK_ON, CURSOR_BLINK_OFF } state; } cursor_blink; - struct { - uint32_t text; - uint32_t cursor; - } cursor_color; struct { enum selection_kind kind;