Add support for the color settings stack that XTerm copied from us without acknowledgement and decided to use incompatible escape codes for.
Completely in keeping with that project's past behavior. See https://github.com/kovidgoyal/kitty/issues/879 XTerm announcement: https://www.mail-archive.com/xorg@lists.x.org/msg06419.html
This commit is contained in:
parent
e97f1a4310
commit
5f8dee8384
10 changed files with 120 additions and 29 deletions
|
|
@ -4,6 +4,13 @@ Changelog
|
|||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
0.20.0 [future]
|
||||
----------------------
|
||||
|
||||
- Add support for the color settings stack that XTerm copied from us without
|
||||
acknowledgement and decided to use incompatible escape codes for.
|
||||
|
||||
|
||||
0.19.3 [2020-12-19]
|
||||
-------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -199,6 +199,14 @@ These escape codes save/restore the colors, default
|
|||
background, default foreground, selection background, selection foreground and
|
||||
cursor color and the 256 colors of the ANSI color table.
|
||||
|
||||
.. note:: In July 2020, after several years, XTerm copied this protocol
|
||||
extension, without acknowledgement, and using incompatible escape codes
|
||||
(XTPUSHCOLORS, XTPOPCOLORS, XTREPORTCOLORS). And they decided to save not
|
||||
just the dynamic colors but the entire ANSI color table. In the interests of
|
||||
promoting interoperability, kitty added support for XTerm's escape codes as
|
||||
well, and changed this extension to also save/restore the entire ANSI color
|
||||
table.
|
||||
|
||||
|
||||
Pasting to clipboard
|
||||
----------------------
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@
|
|||
RESTORE_CURSOR = '\0338'
|
||||
SAVE_PRIVATE_MODE_VALUES = '\033[?s'
|
||||
RESTORE_PRIVATE_MODE_VALUES = '\033[?r'
|
||||
|
||||
SAVE_COLORS = '\033[#P'
|
||||
RESTORE_COLORS = '\033[#Q'
|
||||
MODES = dict(
|
||||
LNM=(20, ''),
|
||||
IRM=(4, ''),
|
||||
|
|
@ -270,7 +271,7 @@ def init_state(alternate_screen: bool = True) -> str:
|
|||
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
|
||||
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
|
||||
set_mode('BRACKETED_PASTE') + set_mode('EXTENDED_KEYBOARD') +
|
||||
'\033]30001\033\\' +
|
||||
SAVE_COLORS +
|
||||
'\033[*x' # reset DECSACE to default region select
|
||||
)
|
||||
if alternate_screen:
|
||||
|
|
@ -285,7 +286,7 @@ def reset_state(normal_screen: bool = True) -> str:
|
|||
ans += reset_mode('ALTERNATE_SCREEN')
|
||||
ans += RESTORE_PRIVATE_MODE_VALUES
|
||||
ans += RESTORE_CURSOR
|
||||
ans += '\033]30101\033\\'
|
||||
ans += RESTORE_COLORS
|
||||
return ans
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
import sys
|
||||
from contextlib import suppress
|
||||
from typing import Any
|
||||
from functools import partial
|
||||
|
||||
|
||||
CSI = '\033['
|
||||
|
|
@ -109,6 +108,18 @@ def screen_delete_characters(count: int) -> None:
|
|||
write(CSI + '%dP' % count)
|
||||
|
||||
|
||||
def screen_push_colors(which: int) -> None:
|
||||
write(CSI + '%d#P' % which)
|
||||
|
||||
|
||||
def screen_pop_colors(which: int) -> None:
|
||||
write(CSI + '%d#Q' % which)
|
||||
|
||||
|
||||
def screen_report_colors() -> None:
|
||||
write(CSI + '#R')
|
||||
|
||||
|
||||
def screen_insert_characters(count: int) -> None:
|
||||
write(CSI + '%d@' % count)
|
||||
|
||||
|
|
@ -193,8 +204,6 @@ def write_osc(code: int, string: str = '') -> None:
|
|||
|
||||
|
||||
set_dynamic_color = set_color_table_color = write_osc
|
||||
screen_push_dynamic_colors = partial(write_osc, 30001)
|
||||
screen_pop_dynamic_colors = partial(write_osc, 30101)
|
||||
|
||||
|
||||
def replay(raw: str) -> None:
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
|
|||
|
||||
static void
|
||||
dealloc(ColorProfile* self) {
|
||||
if (self->color_stack) free(self->color_stack);
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
}
|
||||
|
||||
|
|
@ -321,7 +322,6 @@ copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size
|
|||
static void
|
||||
push_onto_color_stack_at(ColorProfile *self, unsigned int i) {
|
||||
self->color_stack[i].dynamic_colors = self->overridden;
|
||||
self->color_stack[i].valid = true;
|
||||
memcpy(self->color_stack[i].color_table, self->color_table, sizeof(self->color_stack->color_table));
|
||||
}
|
||||
|
||||
|
|
@ -333,18 +333,25 @@ copy_from_color_stack_at(ColorProfile *self, unsigned int i) {
|
|||
|
||||
bool
|
||||
colorprofile_push_colors(ColorProfile *self, unsigned int idx) {
|
||||
if (idx > 10) return false;
|
||||
size_t sz = idx ? idx : self->color_stack_idx + 1;
|
||||
sz = MIN(10u, sz);
|
||||
if (self->color_stack_sz < sz) {
|
||||
self->color_stack = realloc(self->color_stack, sz * sizeof(self->color_stack[0]));
|
||||
if (self->color_stack == NULL) fatal("Out of memory while ensuring space for %zu elements in color stack", sz);
|
||||
memset(self->color_stack + self->color_stack_sz, 0, (sz - self->color_stack_sz) * sizeof(self->color_stack[0]));
|
||||
self->color_stack_sz = sz;
|
||||
}
|
||||
if (idx == 0) {
|
||||
for (unsigned i = 0; i < arraysz(self->color_stack); i++) {
|
||||
if (!self->color_stack[i].valid) {
|
||||
push_onto_color_stack_at(self, i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
memmove(self->color_stack, self->color_stack + 1, sizeof(self->color_stack) - sizeof(self->color_stack[0]));
|
||||
push_onto_color_stack_at(self, arraysz(self->color_stack) - 1);
|
||||
if (self->color_stack_idx >= self->color_stack_sz) {
|
||||
memmove(self->color_stack, self->color_stack + 1, (self->color_stack_sz - 1) * sizeof(self->color_stack[0]));
|
||||
idx = self->color_stack_sz - 1;
|
||||
} else idx = self->color_stack_idx++;
|
||||
push_onto_color_stack_at(self, idx);
|
||||
return true;
|
||||
}
|
||||
if (idx < arraysz(self->color_stack)) {
|
||||
idx -= 1;
|
||||
if (idx < self->color_stack_sz) {
|
||||
push_onto_color_stack_at(self, idx);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -354,22 +361,25 @@ colorprofile_push_colors(ColorProfile *self, unsigned int idx) {
|
|||
bool
|
||||
colorprofile_pop_colors(ColorProfile *self, unsigned int idx) {
|
||||
if (idx == 0) {
|
||||
for (unsigned i = arraysz(self->color_stack) - 1; i-- > 0; ) {
|
||||
if (self->color_stack[i].valid) {
|
||||
copy_from_color_stack_at(self, i);
|
||||
self->color_stack[i].valid = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (!self->color_stack_idx) return false;
|
||||
copy_from_color_stack_at(self, --self->color_stack_idx);
|
||||
memset(self->color_stack + self->color_stack_idx, 0, sizeof(self->color_stack[0]));
|
||||
return true;
|
||||
}
|
||||
if (idx < arraysz(self->color_stack)) {
|
||||
idx -= 1;
|
||||
if (idx < self->color_stack_sz) {
|
||||
copy_from_color_stack_at(self, idx);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
colorprofile_report_stack(ColorProfile *self, unsigned int *idx, unsigned int *count) {
|
||||
*count = self->color_stack_idx;
|
||||
*idx = self->color_stack_idx ? self->color_stack_idx - 1 : 0;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
color_table_address(ColorProfile *self, PyObject *a UNUSED) {
|
||||
#define color_table_address_doc "Pointer address to start of color table"
|
||||
|
|
|
|||
|
|
@ -241,7 +241,6 @@ typedef struct {
|
|||
typedef struct {
|
||||
DynamicColor dynamic_colors;
|
||||
uint32_t color_table[256];
|
||||
bool valid;
|
||||
} ColorStackEntry;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -250,7 +249,8 @@ typedef struct {
|
|||
bool dirty;
|
||||
uint32_t color_table[256];
|
||||
uint32_t orig_color_table[256];
|
||||
ColorStackEntry color_stack[16];
|
||||
ColorStackEntry *color_stack;
|
||||
unsigned int color_stack_idx, color_stack_sz;
|
||||
DynamicColor configured, overridden;
|
||||
color_type mark_foregrounds[MARK_MASK+1], mark_backgrounds[MARK_MASK+1];
|
||||
} ColorProfile;
|
||||
|
|
@ -312,6 +312,7 @@ float cursor_text_as_bg(ColorProfile *self);
|
|||
void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride);
|
||||
bool colorprofile_push_colors(ColorProfile*, unsigned int);
|
||||
bool colorprofile_pop_colors(ColorProfile*, unsigned int);
|
||||
void colorprofile_report_stack(ColorProfile*, unsigned int*, unsigned int*);
|
||||
|
||||
void set_mouse_cursor(MouseShape);
|
||||
void enter_event(void);
|
||||
|
|
|
|||
|
|
@ -439,7 +439,8 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
|||
case '*': \
|
||||
case '\'': \
|
||||
case ' ': \
|
||||
case '$':
|
||||
case '$': \
|
||||
case '#':
|
||||
|
||||
|
||||
static inline void
|
||||
|
|
@ -759,7 +760,23 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
|||
case DL:
|
||||
CALL_CSI_HANDLER1(screen_delete_lines, 1);
|
||||
case DCH:
|
||||
CALL_CSI_HANDLER1(screen_delete_characters, 1);
|
||||
if (end_modifier == '#' && !start_modifier) {
|
||||
CALL_CSI_HANDLER1(screen_push_colors, 0);
|
||||
} else {
|
||||
CALL_CSI_HANDLER1(screen_delete_characters, 1);
|
||||
}
|
||||
case 'Q':
|
||||
if (end_modifier == '#' && !start_modifier) { CALL_CSI_HANDLER1(screen_pop_colors, 0); }
|
||||
REPORT_ERROR("Unknown CSI Q sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
|
||||
break;
|
||||
case 'R':
|
||||
if (end_modifier == '#' && !start_modifier) {
|
||||
REPORT_COMMAND(screen_report_color_stack);
|
||||
screen_report_color_stack(screen);
|
||||
break;
|
||||
}
|
||||
REPORT_ERROR("Unknown CSI R sequence with start and end modifiers: '%c' '%c' and %u parameters", start_modifier, end_modifier, num_params);
|
||||
break;
|
||||
case ECH:
|
||||
CALL_CSI_HANDLER1(screen_erase_characters, 1);
|
||||
case DA:
|
||||
|
|
|
|||
|
|
@ -1603,6 +1603,15 @@ screen_pop_colors(Screen *self, unsigned int idx) {
|
|||
colorprofile_pop_colors(self->color_profile, idx);
|
||||
}
|
||||
|
||||
void
|
||||
screen_report_color_stack(Screen *self) {
|
||||
unsigned int idx, count;
|
||||
colorprofile_report_stack(self->color_profile, &idx, &count);
|
||||
char buf[128] = {0};
|
||||
snprintf(buf, arraysz(buf), "%u;%u#Q", idx, count);
|
||||
write_escape_code_to_child(self, CSI, buf);
|
||||
}
|
||||
|
||||
void
|
||||
screen_handle_print(Screen *self, PyObject *msg) {
|
||||
CALLBACK("handle_remote_print", "O", msg);
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ void screen_change_charset(Screen *, uint32_t to);
|
|||
void screen_handle_cmd(Screen *, PyObject *cmd);
|
||||
void screen_push_colors(Screen *, unsigned int);
|
||||
void screen_pop_colors(Screen *, unsigned int);
|
||||
void screen_report_color_stack(Screen *);
|
||||
void screen_handle_print(Screen *, PyObject *cmd);
|
||||
void screen_designate_charset(Screen *, uint32_t which, uint32_t as);
|
||||
void screen_use_latin1(Screen *, bool);
|
||||
|
|
|
|||
|
|
@ -716,3 +716,31 @@ def test_top_and_bottom_margin(self):
|
|||
|
||||
self.ae(str(s.linebuf), '0\n5\n6\n7\n\n')
|
||||
self.ae(str(s.historybuf), '')
|
||||
|
||||
def test_color_stack(self):
|
||||
s = self.create_screen()
|
||||
c = s.callbacks
|
||||
|
||||
def w(code):
|
||||
return parse_bytes(s, ('\033[' + code).encode('ascii'))
|
||||
|
||||
def ac(idx, count):
|
||||
self.ae(c.wtcbuf, f'\033[{idx};{count}#Q'.encode('ascii'))
|
||||
c.clear()
|
||||
|
||||
w('#R')
|
||||
ac(0, 0)
|
||||
|
||||
w('#P')
|
||||
w('#R')
|
||||
ac(0, 1)
|
||||
w('#10P')
|
||||
w('#R')
|
||||
ac(0, 1)
|
||||
w('#Q')
|
||||
w('#R')
|
||||
ac(0, 0)
|
||||
for i in range(20):
|
||||
w('#P')
|
||||
w('#R')
|
||||
ac(9, 10)
|
||||
|
|
|
|||
Loading…
Reference in a new issue