Skip to content

Commit

Permalink
Add support for pending mode via SM/RM 2026
Browse files Browse the repository at this point in the history
Because, why the hell not, it's not like I have an actual life.
More seriously, terminal-wg (aka Bikeshedder's Anonymous) is
pushing for it so it's likely at least one poor application writer
will fall for their propaganda.
  • Loading branch information
kovidgoyal committed Jun 29, 2021
1 parent 6d413e2 commit 5768c54
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 31 deletions.
3 changes: 3 additions & 0 deletions kitty/modes.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,6 @@
#define BRACKETED_PASTE (2004 << 5)
#define BRACKETED_PASTE_START "200~"
#define BRACKETED_PASTE_END "201~"

// Pending updates mode
#define PENDING_UPDATE (2026 << 5)
92 changes: 62 additions & 30 deletions kitty/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1244,7 +1244,7 @@ END_ALLOW_CASE_RANGE
handle_esc_mode_char(screen, codepoint, dump_callback); \
break; \
case CSI: \
if (accumulate_csi(screen, codepoint, dump_callback)) { dispatch_csi(screen, dump_callback); SET_STATE(0); } \
if (accumulate_csi(screen, codepoint, dump_callback)) { dispatch_csi(screen, dump_callback); SET_STATE(0); watch_for_pending; } \
break; \
case OSC: \
if (accumulate_osc(screen, codepoint, dump_callback)) { dispatch_osc(screen, dump_callback); SET_STATE(0); } \
Expand Down Expand Up @@ -1316,78 +1316,110 @@ FLUSH_DRAW;
}


static inline size_t
static size_t
_queue_pending_bytes(Screen *screen, const uint8_t *buf, size_t len, PyObject *dump_callback DUMP_UNUSED) {
size_t pos = 0;
enum STATE { NORMAL, MAYBE_DCS, IN_DCS, EXPECTING_DATA, EXPECTING_SLASH };
enum STATE state = screen->pending_mode.state;
#define COPY(what) screen->pending_mode.buf[screen->pending_mode.used++] = what
#define COPY_STOP_BUF { \
COPY(0x1b); COPY('P'); COPY(PENDING_MODE_CHAR); \
for (size_t i = 0; i < screen->pending_mode.stop_buf_pos; i++) { \
COPY(screen->pending_mode.stop_buf[i]); \
enum STATE { NORMAL, MAYBE_DCS_OR_CSI, IN_DCS, IN_CSI, EXPECTING_DATA, EXPECTING_CSI_DATA, EXPECTING_SLASH };
#define pm screen->pending_mode
enum STATE state = pm.state;
#define stop_pending_mode \
if (state == EXPECTING_CSI_DATA) { REPORT_COMMAND(screen_reset_mode, 2026, 1); } \
else { REPORT_COMMAND(screen_stop_pending_mode); } \
pm.activated_at = 0; \
goto end;
#define start_pending_mode \
if (state == EXPECTING_CSI_DATA) { REPORT_COMMAND(screen_set_mode, 2026, 1); } \
else { REPORT_COMMAND(screen_start_pending_mode); } \
pm.activated_at = monotonic();
#define COPY(what) pm.buf[pm.used++] = what
#define COPY_STOP_BUF(for_dcs) { \
COPY(0x1b); \
if (for_dcs) { COPY('P'); COPY(PENDING_MODE_CHAR); } \
else { COPY('['); COPY('?'); } \
for (size_t i = 0; i < pm.stop_buf_pos; i++) { \
COPY(pm.stop_buf[i]); \
} \
screen->pending_mode.stop_buf_pos = 0;}
pm.stop_buf_pos = 0;}

while (pos < len) {
uint8_t ch = buf[pos++];
switch(state) {
case NORMAL:
if (ch == ESC) state = MAYBE_DCS;
if (ch == ESC) state = MAYBE_DCS_OR_CSI;
else COPY(ch);
break;
case MAYBE_DCS:
case MAYBE_DCS_OR_CSI:
if (ch == 'P') state = IN_DCS;
else if (ch == '[') state = IN_CSI;
else {
state = NORMAL;
COPY(0x1b); COPY(ch);
}
break;
case IN_DCS:
if (ch == PENDING_MODE_CHAR) { state = EXPECTING_DATA; screen->pending_mode.stop_buf_pos = 0; }
if (ch == PENDING_MODE_CHAR) { state = EXPECTING_DATA; pm.stop_buf_pos = 0; }
else {
state = NORMAL;
COPY(0x1b); COPY('P'); COPY(ch);
}
break;
case IN_CSI:
if (ch == '?') { state = EXPECTING_CSI_DATA; pm.stop_buf_pos = 0; }
else {
state = NORMAL;
COPY(0x1b); COPY('['); COPY(ch);
}
break;
case EXPECTING_CSI_DATA:
if (ch == 'h' || ch == 'l') {
if (pm.stop_buf_pos == 4 && memcmp(pm.stop_buf, "2026", 4) == 0) {
if (ch == 'h') { start_pending_mode } else { stop_pending_mode }
} else {
COPY_STOP_BUF(false); COPY(ch);
}
state = NORMAL;
} else {
pm.stop_buf[pm.stop_buf_pos++] = ch;
if (pm.stop_buf_pos >= sizeof(pm.stop_buf)) {
state = NORMAL;
COPY_STOP_BUF(false);
}
}
break;
case EXPECTING_DATA:
pm.stop_buf[pm.stop_buf_pos++] = ch;
if (ch == 0x1b) state = EXPECTING_SLASH;
else {
screen->pending_mode.stop_buf[screen->pending_mode.stop_buf_pos++] = ch;
if (screen->pending_mode.stop_buf_pos >= sizeof(screen->pending_mode.stop_buf)) {
if (pm.stop_buf_pos >= sizeof(pm.stop_buf)) {
state = NORMAL;
COPY_STOP_BUF;
COPY_STOP_BUF(true);
}
}
break;
case EXPECTING_SLASH:
if (
ch == '\\' &&
screen->pending_mode.stop_buf_pos >= 2 &&
(screen->pending_mode.stop_buf[0] == '1' || screen->pending_mode.stop_buf[0] == '2') &&
screen->pending_mode.stop_buf[1] == 's'
pm.stop_buf_pos >= 2 &&
(pm.stop_buf[0] == '1' || pm.stop_buf[0] == '2') &&
pm.stop_buf[1] == 's'
) {
// We found a pending mode sequence
if (screen->pending_mode.stop_buf[0] == '2') {
REPORT_COMMAND(screen_stop_pending_mode);
screen->pending_mode.activated_at = 0;
goto end;
} else {
REPORT_COMMAND(screen_start_pending_mode);
screen->pending_mode.activated_at = monotonic();
}
if (pm.stop_buf[0] == '2') { stop_pending_mode } else { start_pending_mode }
} else {
state = NORMAL;
COPY_STOP_BUF; COPY(ch);
COPY_STOP_BUF(true); COPY(ch);
}
state = NORMAL;
break;
}
}
end:
screen->pending_mode.state = state;
pm.state = state;
return pos;
#undef COPY
#undef COPY_STOP_BUF
#undef stop_pending_mode
#undef start_pending_mode
#undef pm
}

static inline void
Expand Down
12 changes: 11 additions & 1 deletion kitty/screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_scree
void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI
void screen_alternate_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI

static inline void
static void
set_mode_from_const(Screen *self, unsigned int mode, bool val) {
#define SIMPLE_MODE(name) \
case name: \
Expand Down Expand Up @@ -884,6 +884,14 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
else if (!val && self->linebuf != self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
break;
case PENDING_UPDATE:
if (val) {
self->pending_mode.activated_at = monotonic();
} else {
if (!self->pending_mode.activated_at) log_error("Pending mode stop command issued while not in pending mode");
self->pending_mode.activated_at = 0;
}
break;
default:
private = mode >= 1 << 5;
if (private) mode >>= 5;
Expand Down Expand Up @@ -1626,6 +1634,8 @@ report_mode_status(Screen *self, unsigned int which, bool private) {
ans = self->modes.mouse_tracking_mode == ANY_MODE ? 1 : 2; break;
case MOUSE_SGR_MODE:
ans = self->modes.mouse_tracking_protocol == SGR_PROTOCOL ? 1 : 2; break;
case PENDING_UPDATE:
ans = self->pending_mode.activated_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, CSI, buf);
Expand Down
7 changes: 7 additions & 0 deletions kitty_tests/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ def test_pending(self):
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)), '')
Expand All @@ -361,6 +362,12 @@ def test_pending(self):
pb('\033\\', ('screen_stop_pending_mode',), ('draw', 'e'))
pb('\033P=1sxyz;.;\033\\''\033P=2skjf".,><?_+)98\033\\', ('screen_start_pending_mode',), ('screen_stop_pending_mode',))
pb('\033P=1s\033\\f\033P=1s\033\\', ('screen_start_pending_mode',), ('screen_start_pending_mode',))
pb('\033P=2s\033\\', ('screen_stop_pending_mode',), ('draw', 'f'))

pb('\033[?2026hXXX\033[?2026l', ('screen_set_mode', 2026, 1), ('screen_reset_mode', 2026, 1), ('draw', 'XXX'))
pb('\033[?2026h\033[32ma\033[?2026l', ('screen_set_mode', 2026, 1), ('screen_reset_mode', 2026, 1), ('select_graphic_rendition', '32 '), ('draw', 'a'))
pb('\033[?2026h\033P+q544e\033\\ama\033P=2s\033\\',
('screen_set_mode', 2026, 1), ('screen_stop_pending_mode',), ('screen_request_capabilities', 43, '544e'), ('draw', 'ama'))

def test_oth_codes(self):
s = self.create_screen()
Expand Down

0 comments on commit 5768c54

Please sign in to comment.