From 5d4a002413cdf09aa471e29b0a7ae29f7cf7b89c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 1 Jul 2024 17:24:50 +0200 Subject: [PATCH 1/6] osc: merge OSC 10/11/12/17/19 handling 10/11/17/19 were already merged, so this patch just stops special casing 12 (cursor color). In preparation for XTPUSHCOLORS/XTPOPCOLORS, the cursor colors are moved from their own struct, into the 'colors' struct. Also fix a bug where OSC 17/19 queries returned OSC-11 data. --- osc.c | 67 +++++++++++++++++++----------------------------------- render.c | 8 +++---- terminal.c | 10 ++++---- terminal.h | 6 ++--- 4 files changed, 33 insertions(+), 58 deletions(-) diff --git a/osc.c b/osc.c index 546421f5..ff52fbca 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); @@ -776,6 +787,11 @@ osc_dispatch(struct terminal *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; @@ -792,43 +808,6 @@ osc_dispatch(struct terminal *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 +874,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..caa8aa5a 100644 --- a/terminal.c +++ b/terminal.c @@ -1221,6 +1221,8 @@ 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, @@ -1233,10 +1235,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}, @@ -2035,6 +2033,8 @@ 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; @@ -2051,8 +2051,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..9fce2396 100644 --- a/terminal.h +++ b/terminal.h @@ -568,6 +568,8 @@ struct terminal { 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; @@ -580,10 +582,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; From dd6fc99ae1badb7963271ed171beea8ef0e39601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 1 Jul 2024 17:40:45 +0200 Subject: [PATCH 2/6] csi: implement XTPUSHCOLORS+XTPOPCOLORS+XTREPORTCOLORS The documentation of these sequences are vague and lacking, as is often the case with XTerm invented control sequences. I've tried to replicate what XTerm does (as of xterm-392). The stack represents *stashed/stored* palettes. The currently active palette is *not* stored on the stack. The stack is dynamically allocated, and starts out with zero elements. Now, XTerm has a somewhat weird definition of "pushing" and "popping" in this context, and the documentation is somewhat misleading. What a push does is this: it stores the current palette to the stack at the specified slot. If the specified slot number (Pm) is 0, the slot used is the current slot index incremented by 1. The "current" slot index is then set to the specified slot (which is current slot + 1 if Pm == 0). Thus, "push" (i.e. when Pm == 0 is used) means store to the "next" slot. This is true even if the current slot index points into the middle of stack. Pop works in a similar way. The palette is restored from the specified slot index. If the specified slot number is 0, we use the current slot index. The "current" slot index is then set to the specified slot - 1 (current slot - 1 if Pm == 0). XTREPORTCOLORS return the current slot index, and the number of palettes stored on the stack, on the format CSI ? ; # Q When XTPUSHCOLORS grows the stack with more than one element (i.e. via a 'CSI N # P' sequence), make sure *all* new slots are initialized (to the current color palette). This avoids uninitialized slots, that could then be popped with XTPOPCOLORS. Closes #856 --- csi.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ terminal.c | 10 +++++++ terminal.h | 29 ++++++++++++++------- 3 files changed, 105 insertions(+), 10 deletions(-) diff --git a/csi.c b/csi.c index 2d0f3c62..42f19a91 100644 --- a/csi.c +++ b/csi.c @@ -2048,6 +2048,82 @@ 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); + } 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/terminal.c b/terminal.c index caa8aa5a..ca3d2bc1 100644 --- a/terminal.c +++ b/terminal.c @@ -1227,6 +1227,11 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .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 = { @@ -1823,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; @@ -2040,6 +2046,10 @@ term_reset(struct terminal *term, bool hard) 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; diff --git a/terminal.h b/terminal.h index 9fce2396..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,17 +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 cursor_fg; /* Text color */ - uint32_t cursor_bg; /* cursor color */ - 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 { From bebd5ce4150a9d43fed8e19f793ade13347c247a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 1 Jul 2024 19:10:50 +0200 Subject: [PATCH 3/6] doc: foot-ctlseq: document XTPUSHCOLORS, XTPOPCOLORS and XTREPORTCOLORS --- doc/foot-ctlseqs.7.scd | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 From 5edb0deffe293689274c3e39c1fdbfc06fb7c663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 1 Jul 2024 19:11:05 +0200 Subject: [PATCH 4/6] changelog: XTPUSHCOLORS, XTPOPCOLORS and XTREPORTCOLORS --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) 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 From d440d5aa2c31e673e0b4fe5c8db41486ae706391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 1 Jul 2024 19:29:28 +0200 Subject: [PATCH 5/6] osc: 10/11/12/17/19: don't apply overly much damage --- osc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osc.c b/osc.c index ff52fbca..d9e952c0 100644 --- a/osc.c +++ b/osc.c @@ -772,6 +772,7 @@ osc_dispatch(struct terminal *term) switch (param) { case 10: term->colors.fg = color; + term_damage_view(term); break; case 11: @@ -785,6 +786,8 @@ osc_dispatch(struct terminal *term) term_font_subpixel_changed(term); } } + term_damage_view(term); + term_damage_margins(term); break; case 12: @@ -795,16 +798,16 @@ osc_dispatch(struct terminal *term) 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; } From dd58dad15ac0b298a25a7a3412240bfaaebe9998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 1 Jul 2024 19:29:54 +0200 Subject: [PATCH 6/6] csi: redraw margins after restoring the palette --- csi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/csi.c b/csi.c index 42f19a91..86117c7e 100644 --- a/csi.c +++ b/csi.c @@ -2101,6 +2101,7 @@ csi_dispatch(struct terminal *term, uint8_t final) /* 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 {