From 6f2d63eb876c4d684e90cb2ef64213ef6dd303a6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 20 May 2018 10:43:26 +0530 Subject: [PATCH] Implement SGR dim kitty now supports the SGR DIM escape code, which makes text fade into the background. It works by alpha blending the text color into the background color. Fixes #446 --- kitty/cell_fragment.glsl | 4 ++-- kitty/cell_vertex.glsl | 14 ++++++++------ kitty/cursor.c | 28 ++++++++++++++++++++-------- kitty/data-types.c | 1 + kitty/data-types.h | 8 +++++--- kitty/line-buf.c | 2 +- kitty/line.c | 2 +- kitty/window.py | 11 ++++++++--- kitty_tests/__init__.py | 2 +- kitty_tests/datatypes.py | 8 ++++---- kitty_tests/parser.py | 6 +++--- 11 files changed, 54 insertions(+), 32 deletions(-) diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 7cf8f7cec..b5de428e3 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -19,7 +19,7 @@ in float bg_alpha; #ifdef NEEDS_FOREGROUND uniform sampler2DArray sprites; -uniform float inactive_text_alpha; +in float effective_text_alpha; in vec3 sprite_pos; in vec3 underline_pos; in vec3 strike_pos; @@ -61,7 +61,7 @@ vec4 calculate_foreground() { // Since strike and text are the same color, we simply add the alpha values float combined_alpha = min(text_alpha + strike_alpha, 1.0f); // Underline color might be different, so alpha blend - return alpha_blend(decoration_fg, underline_alpha * inactive_text_alpha, fg, combined_alpha * inactive_text_alpha); + return alpha_blend(decoration_fg, underline_alpha * effective_text_alpha, fg, combined_alpha * effective_text_alpha); } #endif diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index 4cd5a1ea9..5b69c5fc7 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -1,6 +1,7 @@ #version GLSL_VERSION #define WHICH_PROGRAM #define NOT_TRANSPARENT +#define SHIFTS // Inputs {{{ layout(std140) uniform CellRenderData { @@ -45,12 +46,14 @@ out float bg_alpha; #endif #ifdef NEEDS_FOREGROUND +uniform float inactive_text_alpha; out vec3 sprite_pos; out vec3 underline_pos; out vec3 strike_pos; out vec3 foreground; out vec3 decoration_fg; out float colored_sprite; +out float effective_text_alpha; #endif @@ -63,9 +66,6 @@ const uint ONE = uint(1); const uint TWO = uint(2); const uint THREE = uint(3); const uint FOUR = uint(4); -const uint DECORATION_MASK = uint(3); -const uint STRIKE_MASK = uint(1); -const uint REVERSE_MASK = uint(1); vec3 color_to_vec(uint c) { uint r, g, b; @@ -134,7 +134,7 @@ void main() { // set cell color indices {{{ uvec2 default_colors = uvec2(default_fg, default_bg); uint text_attrs = sprite_coords[3]; - uint is_inverted = ((text_attrs >> 6) & REVERSE_MASK) + inverted; + uint is_inverted = ((text_attrs >> REVERSE_SHIFT) & ONE) + inverted; int fg_index = fg_index_map[is_inverted]; int bg_index = 1 - fg_index; float cursor = is_cursor(c, r); @@ -151,13 +151,15 @@ void main() { // Foreground uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]); foreground = color_to_vec(resolved_fg); + float has_dim = float((text_attrs >> DIM_SHIFT) & ONE); + effective_text_alpha = inactive_text_alpha * mix(1.0, 0.75, has_dim); // Selection foreground = choose_color(float(is_selected & ONE), color_to_vec(highlight_fg), foreground); // Underline and strike through (rendered via sprites) float in_url = float((is_selected & TWO) >> 1); decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], resolved_fg)); - underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> 2) & DECORATION_MASK, ZERO, ZERO)); - strike_pos = to_sprite_pos(pos, ((text_attrs >> 7) & STRIKE_MASK) * FOUR, ZERO, ZERO); + underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> DECORATION_SHIFT) & THREE, ZERO, ZERO)); + strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * FOUR, ZERO, ZERO); // Cursor foreground = choose_color(cursor, bg, foreground); diff --git a/kitty/cursor.c b/kitty/cursor.c index 630d2ee33..15848a5b6 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -24,7 +24,7 @@ dealloc(Cursor* self) { #define EQ(x) (a->x == b->x) static int __eq__(Cursor *a, Cursor *b) { - return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(blink); + return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(dim) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(blink); } static const char* cursor_names[NUM_OF_CURSOR_SHAPES] = { "NO_SHAPE", "BLOCK", "BEAM", "UNDERLINE" }; @@ -33,16 +33,16 @@ static const char* cursor_names[NUM_OF_CURSOR_SHAPES] = { "NO_SHAPE", "BLOCK", " static PyObject * repr(Cursor *self) { return PyUnicode_FromFormat( - "Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, decoration=%d, decoration_fg=#%08x)", + "Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, dim=%R, decoration=%d, decoration_fg=#%08x)", self->x, self->y, (self->shape < NUM_OF_CURSOR_SHAPES ? cursor_names[self->shape] : "INVALID"), - BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), self->decoration, self->decoration_fg + BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), BOOL(self->dim), self->decoration, self->decoration_fg ); } void cursor_reset_display_attrs(Cursor *self) { self->bg = 0; self->fg = 0; self->decoration_fg = 0; - self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false; + self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false; self->dim = false; } @@ -85,6 +85,8 @@ START_ALLOW_CASE_RANGE cursor_reset_display_attrs(self); break; case 1: self->bold = true; break; + case 2: + self->dim = true; break; case 3: self->italic = true; break; case 4: @@ -98,7 +100,7 @@ START_ALLOW_CASE_RANGE case 21: self->decoration = 2; break; case 22: - self->bold = false; break; + self->bold = false; self->dim = false; break; case 23: self->italic = false; break; case 24: @@ -138,6 +140,7 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para #define RANGE for(unsigned c = 0; c < cell_count; c++, cell++) #define SET(shift) RANGE { cell->attrs |= (1 << shift); } break; #define RESET(shift) RANGE { cell->attrs &= ~(1 << shift); } break; +#define RESET2(shift1, shift2) RANGE { cell->attrs &= ~((1 << shift1) | (1 << shift2)); } break; #define SETM(val, mask, shift) { RANGE { cell->attrs &= ~(mask << shift); cell->attrs |= ((val) << shift); } break; } #define SET_COLOR(which) { color_type color = 0; parse_color(params, &i, count, &color); if (color) { RANGE { cell->which = color; }} } break; #define SIMPLE(which, val) RANGE { cell->which = (val); } break; @@ -153,6 +156,8 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para break; case 1: SET(BOLD_SHIFT); + case 2: + SET(DIM_SHIFT); case 3: SET(ITALIC_SHIFT); case 4: @@ -165,7 +170,7 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para case 21: SETM(2, DECORATION_MASK, DECORATION_SHIFT); case 22: - RESET(BOLD_SHIFT); + RESET2(DIM_SHIFT, BOLD_SHIFT); case 23: RESET(ITALIC_SHIFT); case 24: @@ -199,6 +204,7 @@ END_ALLOW_CASE_RANGE } } #undef RESET +#undef RESET2 #undef SET_COLOR #undef SET #undef SETM @@ -237,7 +243,11 @@ cursor_as_sgr(Cursor *self, Cursor *prev) { #define SZ sizeof(buf) - (p - buf) - 2 #define P(fmt, ...) { p += snprintf(p, SZ, fmt ";", __VA_ARGS__); } char *p = buf; - if (self->bold != prev->bold) P("%d", self->bold ? 1 : 22); + bool intensity_differs = self->bold != prev->bold || self->dim != prev->dim; + if (intensity_differs) { + if (!self->bold || !self->dim) { P("%d", 22); } + else P("%d;%d", 1, 2); + } if (self->italic != prev->italic) P("%d", self->italic ? 3 : 23); if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27); if (self->strikethrough != prev->strikethrough) P("%d", self->strikethrough ? 9 : 29); @@ -268,7 +278,7 @@ void cursor_reset(Cursor *self) { void cursor_copy_to(Cursor *src, Cursor *dest) { #define CCY(x) dest->x = src->x; CCY(x); CCY(y); CCY(shape); CCY(blink); - CCY(bold); CCY(italic); CCY(strikethrough); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg); + CCY(bold); CCY(italic); CCY(strikethrough); CCY(dim); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg); } static PyObject* @@ -281,6 +291,7 @@ BOOL_GETSET(Cursor, bold) BOOL_GETSET(Cursor, italic) BOOL_GETSET(Cursor, reverse) BOOL_GETSET(Cursor, strikethrough) +BOOL_GETSET(Cursor, dim) BOOL_GETSET(Cursor, blink) static PyMemberDef members[] = { @@ -299,6 +310,7 @@ static PyGetSetDef getseters[] = { GETSET(italic) GETSET(reverse) GETSET(strikethrough) + GETSET(dim) GETSET(blink) {NULL} /* Sentinel */ }; diff --git a/kitty/data-types.c b/kitty/data-types.c index 3de790170..37ff33c1f 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -197,6 +197,7 @@ PyInit_fast_data_types(void) { PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT); PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT); PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT); + PyModule_AddIntConstant(m, "DIM", DIM_SHIFT); PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT); PyModule_AddStringMacro(m, ERROR_PREFIX); #ifdef KITTY_VCS_REV diff --git a/kitty/data-types.h b/kitty/data-types.h index 3fcf9d3e7..e189257ba 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -58,6 +58,7 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape; #define BI_VAL(attrs) ((attrs >> 4) & 3) #define REVERSE_SHIFT 6 #define STRIKE_SHIFT 7 +#define DIM_SHIFT 8 #define COL_MASK 0xFFFFFFFF #define UTF8_ACCEPT 0 #define UTF8_REJECT 1 @@ -71,11 +72,12 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape; #define CURSOR_TO_ATTRS(c, w) \ ((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \ - ((c->italic & 1) << ITALIC_SHIFT) | ((c->reverse & 1) << REVERSE_SHIFT) | ((c->strikethrough & 1) << STRIKE_SHIFT))) + ((c->italic & 1) << ITALIC_SHIFT) | ((c->reverse & 1) << REVERSE_SHIFT) | \ + ((c->strikethrough & 1) << STRIKE_SHIFT) | ((c->dim & 1) << DIM_SHIFT))) #define ATTRS_TO_CURSOR(a, c) \ (c)->decoration = (a >> DECORATION_SHIFT) & 3; (c)->bold = (a >> BOLD_SHIFT) & 1; (c)->italic = (a >> ITALIC_SHIFT) & 1; \ - (c)->reverse = (a >> REVERSE_SHIFT) & 1; (c)->strikethrough = (a >> STRIKE_SHIFT) & 1; + (c)->reverse = (a >> REVERSE_SHIFT) & 1; (c)->strikethrough = (a >> STRIKE_SHIFT) & 1; (c)->dim = (a >> DIM_SHIFT) & 1; #define COPY_CELL(src, s, dest, d) \ (dest)->cells[d] = (src)->cells[s]; @@ -178,7 +180,7 @@ typedef struct { typedef struct { PyObject_HEAD - bool bold, italic, reverse, strikethrough, blink; + bool bold, italic, reverse, strikethrough, blink, dim; unsigned int x, y; uint8_t decoration; CursorShape shape; diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 38f4940a8..2b298faa2 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -149,7 +149,7 @@ set_attribute(LineBuf *self, PyObject *args) { #define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line." unsigned int shift, val; if (!PyArg_ParseTuple(args, "II", &shift, &val)) return NULL; - if (shift < DECORATION_SHIFT || shift > STRIKE_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; } + if (shift < DECORATION_SHIFT || shift > DIM_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; } linebuf_set_attribute(self, shift, val); Py_RETURN_NONE; } diff --git a/kitty/line.c b/kitty/line.c index 1f35936f5..0af7ac390 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -515,7 +515,7 @@ set_attribute(Line *self, PyObject *args) { #define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line." unsigned int shift, val; if (!PyArg_ParseTuple(args, "II", &shift, &val)) return NULL; - if (shift < DECORATION_SHIFT || shift > STRIKE_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; } + if (shift < DECORATION_SHIFT || shift > DIM_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; } set_attribute_on_line(self->cells, shift, val, self->xnum); Py_RETURN_NONE; } diff --git a/kitty/window.py b/kitty/window.py index a6a00995e..91a3f20b6 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -15,9 +15,10 @@ from .constants import ( ) from .fast_data_types import ( BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM, - CELL_SPECIAL_PROGRAM, CSI, CURSOR_PROGRAM, DCS, GRAPHICS_PREMULT_PROGRAM, - GRAPHICS_PROGRAM, OSC, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen, - add_window, compile_program, get_clipboard_string, glfw_post_empty_event, + CELL_SPECIAL_PROGRAM, CSI, CURSOR_PROGRAM, DCS, DECORATION, DIM, + GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, OSC, REVERSE, SCROLL_FULL, + SCROLL_LINE, SCROLL_PAGE, STRIKETHROUGH, Screen, add_window, + compile_program, get_clipboard_string, glfw_post_empty_event, init_cell_program, init_cursor_program, set_clipboard_string, set_titlebar_color, set_window_render_data, update_window_title, update_window_visibility, viewport_for_window @@ -64,6 +65,10 @@ def load_shader_programs(semi_transparent=0): 'FOREGROUND': CELL_FG_PROGRAM, }.items(): vv, ff = v.replace('WHICH_PROGRAM', which), f.replace('WHICH_PROGRAM', which) + shifts = '\n'.join('#define {} {}'.format(name, val) for name, val in ( + ('DECORATION_SHIFT', DECORATION), ('REVERSE_SHIFT', REVERSE), ('STRIKE_SHIFT', STRIKETHROUGH), ('DIM_SHIFT', DIM), + )) + vv = vv.replace('#define SHIFTS', shifts) if semi_transparent: vv = vv.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') ff = ff.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index 306e51aee..a53fea0e5 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -52,7 +52,7 @@ def filled_line_buf(ynum=5, xnum=5, cursor=Cursor()): def filled_cursor(): ans = Cursor() - ans.bold = ans.italic = ans.reverse = ans.strikethrough = True + ans.bold = ans.italic = ans.reverse = ans.strikethrough = ans.dim = True ans.fg = 0x101 ans.bg = 0x201 ans.decoration_fg = 0x301 diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index fe27cb953..d1c66b5f0 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -179,7 +179,7 @@ class TestDataTypes(BaseTest): self.assertEqual(str(l0), 'a' + t[1:]) c = C(3, 5) - c.bold = c.italic = c.reverse = c.strikethrough = True + c.bold = c.italic = c.reverse = c.strikethrough = c.dim = True c.fg = c.bg = c.decoration_fg = 0x0101 self.ae(c, c) c2, c3 = c.copy(), c.copy() @@ -219,7 +219,7 @@ class TestDataTypes(BaseTest): l3.set_text(t, 0, len(t), C()) q = C() - q.bold = q.italic = q.reverse = q.strikethrough = True + q.bold = q.italic = q.reverse = q.strikethrough = c.dim = True q.decoration = 2 c = C() c.x = 3 @@ -450,12 +450,12 @@ class TestDataTypes(BaseTest): self.ae(a, ['\x1b[0m' + str(lb.line(i)) + '\n' for i in range(lb.ynum)]) l2 = lb.line(0) c = C() - c.bold = c.italic = c.reverse = c.strikethrough = True + c.bold = c.italic = c.reverse = c.strikethrough = c.dim = True c.fg = (4 << 8) | 1 c.bg = (1 << 24) | (2 << 16) | (3 << 8) | 2 c.decoration_fg = (5 << 8) | 1 l2.set_text('1', 0, 1, c) - self.ae(l2.as_ansi(), '\x1b[0m\x1b[1;3;7;9;34;48:2:1:2:3;58:5:5m' '1' + self.ae(l2.as_ansi(), '\x1b[0m\x1b[1;2;3;7;9;34;48:2:1:2:3;58:5:5m' '1' '\x1b[22;23;27;29;39;49;59m' '0000') lb = filled_line_buf() for i in range(lb.ynum): diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 1643cd7e4..3f79ffcde 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -118,8 +118,8 @@ class TestParser(BaseTest): def sgr(params): return (('select_graphic_rendition', '{} '.format(x)) for x in params.split()) - pb('\033[1;3;4;7;9;34;44m', *sgr('1 3 4 7 9 34 44')) - for attr in 'bold italic reverse strikethrough'.split(): + pb('\033[1;2;3;4;7;9;34;44m', *sgr('1 2 3 4 7 9 34 44')) + for attr in 'bold italic reverse strikethrough dim'.split(): self.assertTrue(getattr(s.cursor, attr)) self.ae(s.cursor.decoration, 1) self.ae(s.cursor.fg, 4 << 8 | 1) @@ -202,7 +202,7 @@ class TestParser(BaseTest): c.clear() pb('\033P$qm\033\\', ('screen_request_capabilities', ord('$'), 'm')) self.ae(c.wtcbuf, b'\033P1$rm\033\\') - for sgr in '0;34;102;1;3;4 0;38:5:200;58:2:10:11:12'.split(): + for sgr in '0;34;102;1;2;3;4 0;38:5:200;58:2:10:11:12'.split(): expected = set(sgr.split(';')) - {'0'} c.clear() parse_bytes(s, '\033[{}m\033P$qm\033\\'.format(sgr).encode('ascii'))