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 ? <slot index> ; <palette count> # 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
This commit is contained in:
Daniel Eklöf 2024-07-01 17:40:45 +02:00
parent 5d4a002413
commit dd6fc99ae1
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
3 changed files with 105 additions and 10 deletions

76
csi.c
View file

@ -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': {

View file

@ -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;

View file

@ -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 {