diff --git a/kitty/cell_defines.glsl b/kitty/cell_defines.glsl index 04f882c31..7bc0fe619 100644 --- a/kitty/cell_defines.glsl +++ b/kitty/cell_defines.glsl @@ -7,6 +7,7 @@ #define REVERSE_SHIFT {REVERSE_SHIFT} #define STRIKE_SHIFT {STRIKE_SHIFT} #define DIM_SHIFT {DIM_SHIFT} +#define BLINK_SHIFT {BLINK_SHIFT} #define MARK_SHIFT {MARK_SHIFT} #define MARK_MASK {MARK_MASK} #define USE_SELECTION_FG diff --git a/kitty/cursor.c b/kitty/cursor.c index cd81819ad..b68086318 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -34,9 +34,9 @@ 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, dim=%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, text_blink=%R)", self->x, self->y, (self->shape < NUM_OF_CURSOR_SHAPES ? cursor_names[self->shape] : "INVALID"), - BOOL(!self->non_blinking), self->sgr.fg, self->sgr.bg, BOOL(self->sgr.bold), BOOL(self->sgr.italic), BOOL(self->sgr.reverse), BOOL(self->sgr.strikethrough), BOOL(self->sgr.dim), self->sgr.decoration, self->sgr.decoration_fg + BOOL(!self->non_blinking), self->sgr.fg, self->sgr.bg, BOOL(self->sgr.bold), BOOL(self->sgr.italic), BOOL(self->sgr.reverse), BOOL(self->sgr.strikethrough), BOOL(self->sgr.dim), self->sgr.decoration, self->sgr.decoration_fg, BOOL(self->sgr.blink) ); } @@ -93,6 +93,8 @@ START_ALLOW_CASE_RANGE if (is_group && i < count) { self->sgr.decoration = MIN(5, params[i]); i++; } else self->sgr.decoration = 1; break; + case 5: + self->sgr.blink = true; break; case 7: self->sgr.reverse = true; break; case 9: @@ -109,6 +111,8 @@ START_ALLOW_CASE_RANGE self->sgr.italic = false; break; case 24: self->sgr.decoration = 0; break; + case 25: + self->sgr.blink = false; break; case 27: self->sgr.reverse = false; break; case 29: @@ -169,6 +173,8 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un if (is_group && i < count) { val = MIN(5, params[i]); i++; } S(decoration, val); } + case 5: + S(blink, true); case 7: S(reverse, true); case 9: @@ -185,6 +191,8 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un S(italic, false); case 24: S(decoration, 0); + case 25: + S(blink, false); case 27: S(reverse, false); case 29: @@ -272,6 +280,11 @@ static PyObject* blink_get(Cursor *self, void UNUSED *closure) { PyObject *ans = static int blink_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->non_blinking = PyObject_IsTrue(value) ? false : true; return 0; } +static PyObject* text_blink_get(Cursor *self, void UNUSED *closure) { return Py_NewRef(self->sgr.blink ? Py_True : Py_False); } + +static int text_blink_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->sgr.blink = PyObject_IsTrue(value) ? false : true; return 0; } + + #define SGR_UINT_GETSET(x) \ static PyObject* x##_get(Cursor *self, void UNUSED *closure) { return PyLong_FromUnsignedLong((unsigned long)self->sgr.x); } \ static int x##_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } if (!PyLong_Check(value)) { PyErr_SetString(PyExc_TypeError, "value must be an int"); return -1; } self->sgr.x = PyLong_AsUnsignedLong(value); return 0; } @@ -295,6 +308,7 @@ static PyGetSetDef getseters[] = { GETSET(strikethrough) GETSET(dim) GETSET(blink) + GETSET(text_blink) GETSET(decoration) GETSET(fg) GETSET(bg) diff --git a/kitty/data-types.c b/kitty/data-types.c index d299fe263..999e61d0d 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -836,7 +836,7 @@ PyInit_fast_data_types(void) { CellAttrs a; #define s(name, attr) { a.val = 0; a.attr = 1; PyModule_AddIntConstant(m, #name, shift_to_first_set_bit(a)); } s(BOLD, bold); s(ITALIC, italic); s(REVERSE, reverse); s(MARK, mark); - s(STRIKETHROUGH, strike); s(DIM, dim); s(DECORATION, decoration); + s(STRIKETHROUGH, strike); s(DIM, dim); s(DECORATION, decoration); s(BLINK, blink); #undef s PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK); PyModule_AddIntConstant(m, "DECORATION_MASK", DECORATION_MASK); diff --git a/kitty/data-types.h b/kitty/data-types.h index 0c0716171..4867f9927 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -217,7 +217,7 @@ typedef struct { CursorShape shape; struct { - bool bold, italic, reverse, strikethrough, dim; + bool bold, italic, reverse, strikethrough, dim, blink; uint8_t decoration; color_type fg, bg, decoration_fg; } sgr; diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index e7a0e98d8..de5687029 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -277,6 +277,7 @@ CELL_BG_PROGRAM: int BLIT_PROGRAM: int ROUNDED_RECT_PROGRAM: int DECORATION: int +BLINK: int DIM: int GRAPHICS_ALPHA_MASK_PROGRAM: int GRAPHICS_PROGRAM: int @@ -1195,6 +1196,7 @@ class Cursor: bold: bool italic: bool blink: bool + text_blink: bool shape: int diff --git a/kitty/line.c b/kitty/line.c index 87f946eb5..44aa36997 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -920,6 +920,7 @@ cell_as_sgr(const GPUCell *cell, const GPUCell *prev) { if (CA.italic != PA.italic) P(CA.italic ? "3;" : "23;"); if (CA.reverse != PA.reverse) P(CA.reverse ? "7;" : "27;"); if (CA.strike != PA.strike) P(CA.strike ? "9;" : "29;"); + if (CA.blink != PA.blink) P(CA.blink ? "5;" : "25;"); if (cell->fg != prev->fg) p += color_as_sgr(p, SZ, cell->fg, 30, 90, 38); if (cell->bg != prev->bg) p += color_as_sgr(p, SZ, cell->bg, 40, 100, 48); if (cell->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, cell->decoration_fg, 0, 0, DECORATION_FG_CODE); diff --git a/kitty/line.h b/kitty/line.h index 7a4186f2b..f655ccf2d 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -17,8 +17,9 @@ typedef union CellAttrs { uint16_t reverse : 1; uint16_t strike : 1; uint16_t dim : 1; + uint16_t blink: 1; uint16_t mark : 2; - uint32_t : 22; + uint32_t : 21; }; uint32_t val; } CellAttrs; @@ -180,7 +181,7 @@ static inline CellAttrs cursor_to_attrs(const Cursor *c) { CellAttrs ans = { .decoration=c->sgr.decoration, .bold=c->sgr.bold, .italic=c->sgr.italic, .reverse=c->sgr.reverse, - .strike=c->sgr.strikethrough, .dim=c->sgr.dim}; + .strike=c->sgr.strikethrough, .dim=c->sgr.dim, .blink=c->sgr.blink}; return ans; } @@ -188,6 +189,7 @@ static inline void attrs_to_cursor(const CellAttrs attrs, Cursor *c) { c->sgr.decoration = attrs.decoration; c->sgr.bold = attrs.bold; c->sgr.italic = attrs.italic; c->sgr.reverse = attrs.reverse; c->sgr.strikethrough = attrs.strike; c->sgr.dim = attrs.dim; + c->sgr.blink = attrs.blink; } #define cursor_as_gpu_cell(cursor) {.attrs=cursor_to_attrs(cursor), .fg=(cursor->sgr.fg & COL_MASK), .bg=(cursor->sgr.bg & COL_MASK), .decoration_fg=cursor->sgr.decoration_fg & COL_MASK} diff --git a/kitty/shaders.py b/kitty/shaders.py index dd9db27ca..c8dcb875a 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -10,6 +10,7 @@ from typing import Any, Literal, NamedTuple, Optional from .constants import read_kitty_resource from .fast_data_types import ( BGIMAGE_PROGRAM, + BLINK, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, @@ -172,6 +173,7 @@ class LoadShaderPrograms: REVERSE_SHIFT=REVERSE, STRIKE_SHIFT=STRIKETHROUGH, DIM_SHIFT=DIM, + BLINK_SHIFT=BLINK, DECORATION_SHIFT=DECORATION, MARK_SHIFT=MARK, MARK_MASK=MARK_MASK, diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 1c201d55d..5dd28c42d 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -112,7 +112,6 @@ class TestScreen(BaseTest): ln = s.line(0) self.ae(txt, ln.as_ansi()) - def test_rep(self): s = self.create_screen() s.draw('a') @@ -618,11 +617,13 @@ class TestScreen(BaseTest): def test_sgr(self): s = self.create_screen() - s.select_graphic_rendition(0, 1, 37, 42) + s.select_graphic_rendition(0, 1, 5, 37, 42) s.draw('a') c = s.line(0).cursor_from(0) self.assertTrue(c.bold) + self.assertTrue(c.blink) self.ae(c.bg, (2 << 8) | 1) + self.ae('\x1b[22;1;5;37;42ma', s.line(0).as_ansi()) s.cursor_position(2, 1) s.select_graphic_rendition(0, 35) s.draw('b')