diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index c08953536..59e875e54 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -441,9 +441,8 @@ do_parse(ChildMonitor *self, Screen *screen, monotonic_t now, bool flush) { if (pd.input_read) { if (pd.write_space_created) wakeup_io_loop(self, false); } - if (pd.input_read && pd.pending_activated_at) { - monotonic_t time_since_pending = MAX(0, now - pd.pending_activated_at); - set_maximum_wait(pd.pending_wait_time - time_since_pending); + if (pd.input_read && screen->paused_rendering.expires_at) { + set_maximum_wait(MAX(0, screen->paused_rendering.expires_at - now)); } else set_maximum_wait(OPT(input_delay) - pd.time_since_new_input); return pd.input_read; } @@ -652,7 +651,7 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) { static bool cursor_needs_render(Window *w) { - return w->cursor_visible_at_last_render != w->render_data.screen->cursor_render_info.is_visible || w->render_data.screen->cursor_render_info.last.x != w->render_data.screen->cursor_render_info.x || w->render_data.screen->cursor_render_info.last.y != w->render_data.screen->cursor_render_info.y || w->last_cursor_shape != w->render_data.screen->cursor_render_info.shape; + return w->cursor_visible_at_last_render != w->render_data.screen->cursor_render_info.is_visible || w->render_data.screen->last_rendered.cursor_x != w->render_data.screen->cursor_render_info.x || w->render_data.screen->last_rendered.cursor_y != w->render_data.screen->cursor_render_info.y || w->last_cursor_shape != w->render_data.screen->cursor_render_info.shape; } static bool @@ -665,7 +664,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow ans->x = rd->screen->overlay_line.cursor_x; ans->y = rd->screen->overlay_line.ynum; } else { - cursor = rd->screen->cursor; + cursor = rd->screen->paused_rendering.expires_at ? &rd->screen->paused_rendering.cursor : rd->screen->cursor; ans->x = cursor->x; ans->y = cursor->y; } ans->is_visible = false; @@ -799,7 +798,7 @@ render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, co if (WD.screen->start_visual_bell_at != 0) { set_maximum_wait(OPT(repaint_delay)); } - w->cursor_visible_at_last_render = WD.screen->cursor_render_info.is_visible; WD.screen->cursor_render_info.last.x = WD.screen->cursor_render_info.x; WD.screen->cursor_render_info.last.y = WD.screen->cursor_render_info.y; w->last_cursor_shape = WD.screen->cursor_render_info.shape; + w->cursor_visible_at_last_render = WD.screen->cursor_render_info.is_visible; w->last_cursor_shape = WD.screen->cursor_render_info.shape; } } if (os_window->live_resize.in_progress) draw_resizing_text(os_window); diff --git a/kitty/control-codes.h b/kitty/control-codes.h index 55ea0cde9..75e17824c 100644 --- a/kitty/control-codes.h +++ b/kitty/control-codes.h @@ -230,4 +230,5 @@ // File transfer OSC number #define FILE_TRANSFER_CODE 5113 +// Pending mode CSI code #define PENDING_MODE 2026 diff --git a/kitty/data-types.h b/kitty/data-types.h index a2f0a8545..782d9f115 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -303,7 +303,6 @@ typedef struct { bool is_visible, is_focused, render_even_when_unfocused; CursorShape shape; unsigned int x, y; - struct { unsigned int x, y; } last; } CursorRenderInfo; typedef enum DynamicColorType { diff --git a/kitty/screen.c b/kitty/screen.c index 0975c7ec6..a569e6ab1 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1067,6 +1067,11 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) { case 7727 << 5: log_error("Application escape mode is not supported, the extended keyboard protocol should be used instead"); break; + case PENDING_MODE << 5: + if (!screen_pause_rendering(self, val, 0)) { + log_error("Pending mode change to already current mode (%d) requested. Either pending mode expired or there is an application bug.", val); + } + break; default: private = mode >= 1 << 5; if (private) mode >>= 5; @@ -2111,7 +2116,7 @@ report_mode_status(Screen *self, unsigned int which, bool private) { case MOUSE_SGR_PIXEL_MODE: ans = self->modes.mouse_tracking_protocol == SGR_PIXEL_PROTOCOL ? 1 : 2; break; case PENDING_UPDATE: - ans = vt_parser_pending_activated_at(self->vt_parser) ? 1 : 2; break; + ans = self->paused_rendering.expires_at ? 1 : 2; break; } int sz = snprintf(buf, sizeof(buf) - 1, "%s%u;%u$y", (private ? "?" : ""), which, ans); if (sz > 0) write_escape_code_to_child(self, ESC_CSI, buf); @@ -2350,6 +2355,21 @@ screen_request_capabilities(Screen *self, char c, const char *query) { // }}} // Rendering {{{ + +bool +screen_pause_rendering(Screen *self, bool pause, int for_in_ms) { + if (!pause) { + if (!self->paused_rendering.expires_at) return false; + self->paused_rendering.expires_at = 0; + return true; + } + if (self->paused_rendering.expires_at) return false; + if (for_in_ms <= 0) for_in_ms = 2000; + self->paused_rendering.expires_at = monotonic() + ms_to_monotonic_t(for_in_ms); + memcpy(&self->paused_rendering.cursor, self->cursor, sizeof(self->paused_rendering.cursor)); + return true; +} + static color_type effective_cell_edge_color(char_type ch, color_type fg, color_type bg, bool is_left_edge) { START_ALLOW_CASE_RANGE @@ -3251,22 +3271,6 @@ hyperlink_for_id(Screen *self, PyObject *val) { return Py_BuildValue("s", get_hyperlink_for_id(self->hyperlink_pool, id, true)); } -static PyObject* -set_pending_timeout(Screen *self, PyObject *val) { - if (!PyFloat_Check(val)) { PyErr_SetString(PyExc_TypeError, "timeout must be a float"); return NULL; } - PyObject *ans = PyFloat_FromDouble(monotonic_t_to_s_double(vt_parser_pending_wait_time(self->vt_parser))); - vt_parser_set_pending_wait_time(self->vt_parser, s_double_to_monotonic_t(PyFloat_AS_DOUBLE(val))); - return ans; -} - -static PyObject* -set_pending_activated_at(Screen *self, PyObject *val) { - if (!PyFloat_Check(val)) { PyErr_SetString(PyExc_TypeError, "timeout must be a float"); return NULL; } - PyObject *ans = PyFloat_FromDouble(monotonic_t_to_s_double(vt_parser_pending_activated_at(self->vt_parser))); - vt_parser_set_pending_activated_at(self->vt_parser, s_double_to_monotonic_t(PyFloat_AS_DOUBLE(val))); - return ans; -} - static Line* get_visual_line(void *x, int y) { return visual_line_(x, y); } static Line* get_range_line(void *x, int y) { return range_line_(x, y); } @@ -4600,8 +4604,6 @@ static PyMethodDef methods[] = { MND(cursor_forward, METH_VARARGS) {"index", (PyCFunction)xxx_index, METH_VARARGS, ""}, {"has_selection", (PyCFunction)has_selection, METH_VARARGS, ""}, - MND(set_pending_timeout, METH_O) - MND(set_pending_activated_at, METH_O) MND(as_text, METH_VARARGS) MND(as_text_non_visual, METH_VARARGS) MND(as_text_for_history_buf, METH_VARARGS) diff --git a/kitty/screen.h b/kitty/screen.h index 416b3a4c0..3e7416718 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -150,6 +150,10 @@ typedef struct { uint8_t stack[16], count; } main_pointer_shape_stack, alternate_pointer_shape_stack; Parser *vt_parser; + struct { + monotonic_t expires_at; + Cursor cursor; + } paused_rendering; } Screen; @@ -261,6 +265,7 @@ bool screen_fake_move_cursor_to_position(Screen *, index_type x, index_type y); bool screen_send_signal_for_key(Screen *, char key); bool get_line_edge_colors(Screen *self, color_type *left, color_type *right); bool parse_sgr(Screen *screen, const uint8_t *buf, unsigned int num, const char *report_name, bool is_deccara); +bool screen_pause_rendering(Screen *self, bool pause, int for_in_ms); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) diff --git a/kitty/shaders.c b/kitty/shaders.c index d291deb0a..336e18412 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -381,9 +381,10 @@ cell_prepare_to_render(ssize_t vao_idx, Screen *screen, GLfloat xstart, GLfloat bool changed = false; ensure_sprite_map(fonts_data); + const Cursor *cursor = screen->paused_rendering.expires_at ? &screen->paused_rendering.cursor : screen->cursor; - bool cursor_pos_changed = screen->cursor->x != screen->last_rendered.cursor_x - || screen->cursor->y != screen->last_rendered.cursor_y; + bool cursor_pos_changed = cursor->x != screen->last_rendered.cursor_x + || cursor->y != screen->last_rendered.cursor_y; bool disable_ligatures = screen->disable_ligatures == DISABLE_LIGATURES_CURSOR; bool screen_resized = screen->last_rendered.columns != screen->columns || screen->last_rendered.lines != screen->lines; @@ -396,8 +397,8 @@ cell_prepare_to_render(ssize_t vao_idx, Screen *screen, GLfloat xstart, GLfloat } if (cursor_pos_changed) { - screen->last_rendered.cursor_x = screen->cursor->x; - screen->last_rendered.cursor_y = screen->cursor->y; + screen->last_rendered.cursor_x = cursor->x; + screen->last_rendered.cursor_y = cursor->y; } if (screen->reload_all_gpu_data || screen_resized || screen_is_selection_dirty(screen)) { diff --git a/kitty/vt-parser.c b/kitty/vt-parser.c index 40575adc2..73f0138dc 100644 --- a/kitty/vt-parser.c +++ b/kitty/vt-parser.c @@ -12,7 +12,6 @@ #include "screen.h" #include "control-codes.h" #include "state.h" -#include "modes.h" #include "simd-string.h" #include @@ -21,7 +20,6 @@ #define BUF_EXTRA (512u/8u) #define MAX_ESCAPE_CODE_LENGTH (BUF_SZ / 4u) #define MAX_CSI_PARAMS 256u -#define DEFAULT_PENDING_WAIT_TIME s_double_to_monotonic_t(2.0) // Macros {{{ @@ -178,7 +176,6 @@ vte_state_name(VTEState s) { } typedef enum { CSI_START, CSI_BODY, CSI_POST_SECONDARY } CSIState; -typedef enum { PENDING_NORMAL, PENDING_ESC, PENDING_CSI, PENDING_DCS, PENDING_ST } PendingState; typedef struct ParsedCSI { char primary, secondary, trailer; @@ -197,13 +194,6 @@ typedef struct PS { VTEState vte_state; ParsedCSI csi; - struct { - monotonic_t activated_at, wait_time; - size_t esc_code_start; - bool draining; - PendingState state; - } pending_mode; - // these are temporary variables set only for duration of a parse call PyObject *dump_callback; Screen *screen; @@ -589,12 +579,6 @@ startswith(const uint8_t *string, ssize_t sz, const char *prefix, ssize_t l) { return true; } -static void -activate_pending_mode(PS *self) { - self->pending_mode.activated_at = self->now; - self->pending_mode.state = PENDING_NORMAL; -} - static bool parse_kitty_dcs(PS *self, uint8_t *buf, size_t bufsz) { #define starts_with(x) startswith(buf, bufsz, x, literal_strlen(x)) @@ -649,15 +633,13 @@ dispatch_dcs(PS *self, uint8_t *buf, size_t bufsz, bool is_extended UNUSED) { case '=': if (bufsz > 2 && (buf[1] == '1' || buf[1] == '2') && buf[2] == 's') { if (buf[1] == '1') { - if (!self->pending_mode.draining) { - REPORT_COMMAND(screen_start_pending_mode) - activate_pending_mode(self); + REPORT_COMMAND(screen_start_pending_mode) + if (!screen_pause_rendering(self->screen, true, 0)) { + REPORT_ERROR("Pending mode start requested while already in pending mode. This is most likely an application error."); } } else { - if (self->pending_mode.draining) { - self->pending_mode.draining = false; - REPORT_COMMAND(screen_stop_pending_mode); - } else { + REPORT_COMMAND(screen_stop_pending_mode); + if (!screen_pause_rendering(self->screen, false, 0)) { REPORT_ERROR("Pending mode stop command issued while not in pending mode, this can" " be either a bug in the terminal application or caused by a timeout with no data" " received for too long or by too much data in pending mode"); @@ -1016,25 +998,6 @@ handle_mode(PS *self) { int p = self->csi.params[i]; if (p >= 0) { unsigned int sp = p << shift; - if (sp == PENDING_UPDATE) { - if (self->csi.trailer == SM) { - if (!self->pending_mode.draining) { - activate_pending_mode(self); - REPORT_COMMAND(screen_start_pending_mode); - } - } else if (self->csi.trailer == RM) { - if (self->pending_mode.draining) { - REPORT_COMMAND(screen_stop_pending_mode); - self->pending_mode.draining = false; - } else { - REPORT_ERROR( - "Pending mode stop command issued while not in pending mode, this can" - " be either a bug in the terminal application or caused by a timeout with no data" - " received for too long or by too much data in pending mode"); - } - } - return; - } switch (self->csi.trailer) { case SM: screen_set_mode(self->screen, sp); @@ -1407,113 +1370,6 @@ consume_input(PS *self) { #undef consume } -#define pending_mode_sentinel "\x1b[?2026l" -#if PENDING_MODE != 2026 -#error "PENDING_MODE != 2026" -#endif -#define pmslen (literal_strlen(pending_mode_sentinel)) - -static bool -find_pending_stop_csi(PS *self) { - const size_t sz = self->read.sz - self->read.pos; - const uint8_t *q = find_either_of_two_bytes(self->buf + self->read.pos, sz, ESC, 'l'); - if (q == NULL) { - self->read.pos += sz; - return false; - } - switch(*q) { - case 'l': - self->pending_mode.state = PENDING_NORMAL; - self->read.pos = q - self->buf + 1; - return self->read.pos > self->read.consumed + pmslen && memcmp(self->buf + self->read.pos - pmslen, pending_mode_sentinel, pmslen) == 0; - case ESC: - self->pending_mode.state = PENDING_ESC; - self->read.pos = q - self->buf + 1; - break; - } - return false; -} - -static bool -find_pending_stop_dcs(PS *self, size_t end_pos) { -#define sentinel "=2s" - return end_pos - self->pending_mode.esc_code_start >= literal_strlen(sentinel) && memcmp(self->buf +self->pending_mode.esc_code_start, sentinel, literal_strlen(sentinel)) == 0; -#undef sentinel -} - -static bool -search_for_pending_stop(PS *self) { - while (self->read.pos < self->read.sz) { - switch(self->pending_mode.state) { - case PENDING_NORMAL: { - uint8_t *pos = memchr(self->buf + self->read.pos, ESC, self->read.sz - self->read.pos); - if (!pos) { - self->read.pos = self->read.sz; - return false; - } - self->read.pos = (pos - self->buf) + 1; - self->pending_mode.state = PENDING_ESC; - } break; - case PENDING_ESC: { - switch(self->buf[self->read.pos++]) { - default: - self->pending_mode.state = PENDING_NORMAL; break; - case ESC_OSC: case ESC_APC: case ESC_PM: - self->pending_mode.state = PENDING_ST; break; - case ESC_DCS: - self->pending_mode.state = PENDING_DCS; self->pending_mode.esc_code_start = self->read.pos; break; - case ESC_CSI: - self->pending_mode.state = PENDING_CSI; self->pending_mode.esc_code_start = self->read.pos; break; - } - } break; - case PENDING_ST: { - size_t end_pos; - if (find_st_terminator(self, &end_pos)) self->pending_mode.state = PENDING_NORMAL; - } break; - case PENDING_DCS: { - size_t end_pos; - if (find_st_terminator(self, &end_pos)) { - self->pending_mode.state = PENDING_NORMAL; - if (find_pending_stop_dcs(self, end_pos)) return true; - } - } break; - case PENDING_CSI: { - if (find_pending_stop_csi(self)) { - self->pending_mode.state = PENDING_NORMAL; - return true; - } - break; - } - } - } - return false; -} - -static void -do_parse_vt(PS *self) { -#define LOG(prefix) \ - log_error(#prefix " state: %s pos: %zu consumed: %zu sz: %zu %.*s", vte_state_name(self->vte_state), self->read.pos, self->read.consumed, self->read.sz, (int)MIN(64u, (self->read.sz-self->read.pos)), self->buf + self->read.pos); - - self->pending_mode.draining = false; - /* LOG(START); */ - do { - if (self->pending_mode.activated_at) { - if ( - self->pending_mode.activated_at + self->pending_mode.wait_time < self->now || - search_for_pending_stop(self) || - self->read.pos - self->read.consumed >= BUF_SZ - ) { - self->pending_mode.activated_at = 0; - self->pending_mode.draining = true; - self->pending_mode.state = PENDING_NORMAL; - self->read.pos = self->read.consumed; - } else continue; - } - consume_input(self); - } while (self->read.pos < self->read.sz); - /* LOG(END); */ -#undef LOG -} // }}} // API {{{ @@ -1543,13 +1399,11 @@ run_worker(void *p, ParseData *pd, bool flush) { self->screen = screen; do { end_with_lock; { - do_parse_vt(self); + consume_input(self); } with_lock; self->read.sz += self->write.pending; self->write.pending = 0; } while (self->read.pos < self->read.sz); self->new_input_at = 0; - pd->pending_activated_at = self->pending_mode.activated_at; - pd->pending_wait_time = self->pending_mode.wait_time; if (self->read.consumed) { pd->write_space_created = self->read.sz >= BUF_SZ; self->read.pos -= MIN(self->read.pos, self->read.consumed); @@ -1636,8 +1490,6 @@ reset(PS *self) { SET_STATE(NORMAL); reset_csi(&self->csi); utf8_decoder_reset(&self->utf8_decoder); - zero_at_ptr(&self->pending_mode); - self->pending_mode.wait_time = DEFAULT_PENDING_WAIT_TIME; } void @@ -1688,21 +1540,12 @@ alloc_vt_parser(id_type window_id) { return NULL; } state->window_id = window_id; - state->pending_mode.wait_time = DEFAULT_PENDING_WAIT_TIME; utf8_decoder_reset(&state->utf8_decoder); reset_csi(&state->csi); } return self; } -monotonic_t vt_parser_pending_activated_at(Parser*p) { return ((PS*)p->state)->pending_mode.activated_at; } -monotonic_t vt_parser_pending_wait_time(Parser*p) { return ((PS*)p->state)->pending_mode.wait_time; } -void vt_parser_set_pending_wait_time(Parser*p, monotonic_t n) { ((PS*)p->state)->pending_mode.wait_time = n; } -void vt_parser_set_pending_activated_at(Parser*p, monotonic_t n) { - PS *state = (PS*)p->state; - state->now = n; - activate_pending_mode(state); -} #undef EXTRA_INIT #define EXTRA_INIT \ if (0 != PyModule_AddIntConstant(module, "VT_PARSER_BUFFER_SIZE", BUF_SZ)) return 0; \ diff --git a/kitty/vt-parser.h b/kitty/vt-parser.h index 0eb3bea2e..7d0a15d36 100644 --- a/kitty/vt-parser.h +++ b/kitty/vt-parser.h @@ -21,17 +21,13 @@ typedef struct ParseData { monotonic_t now; bool input_read, write_space_created; - monotonic_t pending_activated_at, pending_wait_time, time_since_new_input; + monotonic_t time_since_new_input; } ParseData; // The must only be called on the main thread Parser* alloc_vt_parser(id_type window_id); void free_vt_parser(Parser*); void reset_vt_parser(Parser*); -monotonic_t vt_parser_pending_activated_at(Parser*); -monotonic_t vt_parser_pending_wait_time(Parser*); -void vt_parser_set_pending_activated_at(Parser*, monotonic_t); -void vt_parser_set_pending_wait_time(Parser*, monotonic_t); // The following are thread safe, using an internal lock diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 8f8a420b2..14fef5628 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal -import time from base64 import standard_b64encode from binascii import hexlify from functools import partial @@ -115,18 +114,6 @@ class TestParser(BaseTest): self.assertFalse(self.write_bytes(s, self.create_write_buffer(s), '23mx')) self.parse_written_data(s, ('select_graphic_rendition', '23'), 'x') - # now test with pending - for start in range(len('\x1b[?2026h')): - prefix = '\x1b[?2026h'[:start] - suffix = '\x1b[?2026h'[start:] - self.assertFalse(self.write_bytes(s, self.create_write_buffer(s), prefix)) - b = self.create_write_buffer(s) - self.parse_written_data(s) - self.assertFalse(self.write_bytes(s, b, suffix + 'mouse' + '\x1b[?2026l'[:start])) - b = self.create_write_buffer(s) - self.parse_written_data(s, ('screen_start_pending_mode',)) - self.assertFalse(self.write_bytes(s, b, '\x1b[?2026l'[start:] + ' cheese')) - self.parse_written_data(s, 'mouse', ('screen_stop_pending_mode',), ' cheese') # test full write sz = VT_PARSER_BUFFER_SIZE // 3 + 7 self.assertFalse(self.write_bytes(s, self.create_write_buffer(s), b'a' * sz)) @@ -470,73 +457,6 @@ class TestParser(BaseTest): pb(f'\033P@kitty-print|{p}\033\\', ('handle_remote_print', p)) self.ae(['abcd'], s.callbacks.printbuf) - def test_pending(self): - s = self.create_screen() - timeout = 0.1 - s.set_pending_timeout(timeout) - pb = partial(self.parse_bytes_dump, s) - - pb('\033P=1s\033\\', ('screen_start_pending_mode',)) - pb('a') - self.ae(str(s.line(0)), '') - pb('\033P=2s\033\\', ('draw', 'a'), ('screen_stop_pending_mode',)) - self.ae(str(s.line(0)), 'a') - pb('\033P=1s\033\\', ('screen_start_pending_mode',)) - pb('b') - self.ae(str(s.line(0)), 'a') - time.sleep(timeout) - pb('c', ('draw', 'bc')) - self.ae(str(s.line(0)), 'abc') - pb('\033P=1s\033\\d', ('screen_start_pending_mode',)) - pb('\033P=2s\033\\', ('draw', 'd'), ('screen_stop_pending_mode',)) - pb('\033P=1s\033\\e', ('screen_start_pending_mode',)) - pb('\033P'), pb('='), pb('2s') - pb('\033\\', ('draw', 'e'), ('screen_stop_pending_mode',)) - pb('\033P=1sxyz;.;\033\\''\033P=2skjf".,>