Skip to content

Commit 5768c54

Browse files
committed
Add support for pending mode via SM/RM 2026
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.
1 parent 6d413e2 commit 5768c54

File tree

4 files changed

+83
-31
lines changed

4 files changed

+83
-31
lines changed

kitty/modes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,6 @@
8080
#define BRACKETED_PASTE (2004 << 5)
8181
#define BRACKETED_PASTE_START "200~"
8282
#define BRACKETED_PASTE_END "201~"
83+
84+
// Pending updates mode
85+
#define PENDING_UPDATE (2026 << 5)

kitty/parser.c

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,7 +1244,7 @@ END_ALLOW_CASE_RANGE
12441244
handle_esc_mode_char(screen, codepoint, dump_callback); \
12451245
break; \
12461246
case CSI: \
1247-
if (accumulate_csi(screen, codepoint, dump_callback)) { dispatch_csi(screen, dump_callback); SET_STATE(0); } \
1247+
if (accumulate_csi(screen, codepoint, dump_callback)) { dispatch_csi(screen, dump_callback); SET_STATE(0); watch_for_pending; } \
12481248
break; \
12491249
case OSC: \
12501250
if (accumulate_osc(screen, codepoint, dump_callback)) { dispatch_osc(screen, dump_callback); SET_STATE(0); } \
@@ -1316,78 +1316,110 @@ FLUSH_DRAW;
13161316
}
13171317

13181318

1319-
static inline size_t
1319+
static size_t
13201320
_queue_pending_bytes(Screen *screen, const uint8_t *buf, size_t len, PyObject *dump_callback DUMP_UNUSED) {
13211321
size_t pos = 0;
1322-
enum STATE { NORMAL, MAYBE_DCS, IN_DCS, EXPECTING_DATA, EXPECTING_SLASH };
1323-
enum STATE state = screen->pending_mode.state;
1324-
#define COPY(what) screen->pending_mode.buf[screen->pending_mode.used++] = what
1325-
#define COPY_STOP_BUF { \
1326-
COPY(0x1b); COPY('P'); COPY(PENDING_MODE_CHAR); \
1327-
for (size_t i = 0; i < screen->pending_mode.stop_buf_pos; i++) { \
1328-
COPY(screen->pending_mode.stop_buf[i]); \
1322+
enum STATE { NORMAL, MAYBE_DCS_OR_CSI, IN_DCS, IN_CSI, EXPECTING_DATA, EXPECTING_CSI_DATA, EXPECTING_SLASH };
1323+
#define pm screen->pending_mode
1324+
enum STATE state = pm.state;
1325+
#define stop_pending_mode \
1326+
if (state == EXPECTING_CSI_DATA) { REPORT_COMMAND(screen_reset_mode, 2026, 1); } \
1327+
else { REPORT_COMMAND(screen_stop_pending_mode); } \
1328+
pm.activated_at = 0; \
1329+
goto end;
1330+
#define start_pending_mode \
1331+
if (state == EXPECTING_CSI_DATA) { REPORT_COMMAND(screen_set_mode, 2026, 1); } \
1332+
else { REPORT_COMMAND(screen_start_pending_mode); } \
1333+
pm.activated_at = monotonic();
1334+
#define COPY(what) pm.buf[pm.used++] = what
1335+
#define COPY_STOP_BUF(for_dcs) { \
1336+
COPY(0x1b); \
1337+
if (for_dcs) { COPY('P'); COPY(PENDING_MODE_CHAR); } \
1338+
else { COPY('['); COPY('?'); } \
1339+
for (size_t i = 0; i < pm.stop_buf_pos; i++) { \
1340+
COPY(pm.stop_buf[i]); \
13291341
} \
1330-
screen->pending_mode.stop_buf_pos = 0;}
1342+
pm.stop_buf_pos = 0;}
13311343

13321344
while (pos < len) {
13331345
uint8_t ch = buf[pos++];
13341346
switch(state) {
13351347
case NORMAL:
1336-
if (ch == ESC) state = MAYBE_DCS;
1348+
if (ch == ESC) state = MAYBE_DCS_OR_CSI;
13371349
else COPY(ch);
13381350
break;
1339-
case MAYBE_DCS:
1351+
case MAYBE_DCS_OR_CSI:
13401352
if (ch == 'P') state = IN_DCS;
1353+
else if (ch == '[') state = IN_CSI;
13411354
else {
13421355
state = NORMAL;
13431356
COPY(0x1b); COPY(ch);
13441357
}
13451358
break;
13461359
case IN_DCS:
1347-
if (ch == PENDING_MODE_CHAR) { state = EXPECTING_DATA; screen->pending_mode.stop_buf_pos = 0; }
1360+
if (ch == PENDING_MODE_CHAR) { state = EXPECTING_DATA; pm.stop_buf_pos = 0; }
13481361
else {
13491362
state = NORMAL;
13501363
COPY(0x1b); COPY('P'); COPY(ch);
13511364
}
13521365
break;
1366+
case IN_CSI:
1367+
if (ch == '?') { state = EXPECTING_CSI_DATA; pm.stop_buf_pos = 0; }
1368+
else {
1369+
state = NORMAL;
1370+
COPY(0x1b); COPY('['); COPY(ch);
1371+
}
1372+
break;
1373+
case EXPECTING_CSI_DATA:
1374+
if (ch == 'h' || ch == 'l') {
1375+
if (pm.stop_buf_pos == 4 && memcmp(pm.stop_buf, "2026", 4) == 0) {
1376+
if (ch == 'h') { start_pending_mode } else { stop_pending_mode }
1377+
} else {
1378+
COPY_STOP_BUF(false); COPY(ch);
1379+
}
1380+
state = NORMAL;
1381+
} else {
1382+
pm.stop_buf[pm.stop_buf_pos++] = ch;
1383+
if (pm.stop_buf_pos >= sizeof(pm.stop_buf)) {
1384+
state = NORMAL;
1385+
COPY_STOP_BUF(false);
1386+
}
1387+
}
1388+
break;
13531389
case EXPECTING_DATA:
1390+
pm.stop_buf[pm.stop_buf_pos++] = ch;
13541391
if (ch == 0x1b) state = EXPECTING_SLASH;
13551392
else {
1356-
screen->pending_mode.stop_buf[screen->pending_mode.stop_buf_pos++] = ch;
1357-
if (screen->pending_mode.stop_buf_pos >= sizeof(screen->pending_mode.stop_buf)) {
1393+
if (pm.stop_buf_pos >= sizeof(pm.stop_buf)) {
13581394
state = NORMAL;
1359-
COPY_STOP_BUF;
1395+
COPY_STOP_BUF(true);
13601396
}
13611397
}
13621398
break;
13631399
case EXPECTING_SLASH:
13641400
if (
13651401
ch == '\\' &&
1366-
screen->pending_mode.stop_buf_pos >= 2 &&
1367-
(screen->pending_mode.stop_buf[0] == '1' || screen->pending_mode.stop_buf[0] == '2') &&
1368-
screen->pending_mode.stop_buf[1] == 's'
1402+
pm.stop_buf_pos >= 2 &&
1403+
(pm.stop_buf[0] == '1' || pm.stop_buf[0] == '2') &&
1404+
pm.stop_buf[1] == 's'
13691405
) {
13701406
// We found a pending mode sequence
1371-
if (screen->pending_mode.stop_buf[0] == '2') {
1372-
REPORT_COMMAND(screen_stop_pending_mode);
1373-
screen->pending_mode.activated_at = 0;
1374-
goto end;
1375-
} else {
1376-
REPORT_COMMAND(screen_start_pending_mode);
1377-
screen->pending_mode.activated_at = monotonic();
1378-
}
1407+
if (pm.stop_buf[0] == '2') { stop_pending_mode } else { start_pending_mode }
13791408
} else {
1380-
state = NORMAL;
1381-
COPY_STOP_BUF; COPY(ch);
1409+
COPY_STOP_BUF(true); COPY(ch);
13821410
}
1411+
state = NORMAL;
13831412
break;
13841413
}
13851414
}
13861415
end:
1387-
screen->pending_mode.state = state;
1416+
pm.state = state;
13881417
return pos;
13891418
#undef COPY
13901419
#undef COPY_STOP_BUF
1420+
#undef stop_pending_mode
1421+
#undef start_pending_mode
1422+
#undef pm
13911423
}
13921424

13931425
static inline void

kitty/screen.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_scree
815815
void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI
816816
void screen_alternate_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI
817817

818-
static inline void
818+
static void
819819
set_mode_from_const(Screen *self, unsigned int mode, bool val) {
820820
#define SIMPLE_MODE(name) \
821821
case name: \
@@ -884,6 +884,14 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
884884
if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
885885
else if (!val && self->linebuf != self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
886886
break;
887+
case PENDING_UPDATE:
888+
if (val) {
889+
self->pending_mode.activated_at = monotonic();
890+
} else {
891+
if (!self->pending_mode.activated_at) log_error("Pending mode stop command issued while not in pending mode");
892+
self->pending_mode.activated_at = 0;
893+
}
894+
break;
887895
default:
888896
private = mode >= 1 << 5;
889897
if (private) mode >>= 5;
@@ -1626,6 +1634,8 @@ report_mode_status(Screen *self, unsigned int which, bool private) {
16261634
ans = self->modes.mouse_tracking_mode == ANY_MODE ? 1 : 2; break;
16271635
case MOUSE_SGR_MODE:
16281636
ans = self->modes.mouse_tracking_protocol == SGR_PROTOCOL ? 1 : 2; break;
1637+
case PENDING_UPDATE:
1638+
ans = self->pending_mode.activated_at ? 1 : 2; break;
16291639
}
16301640
int sz = snprintf(buf, sizeof(buf) - 1, "%s%u;%u$y", (private ? "?" : ""), which, ans);
16311641
if (sz > 0) write_escape_code_to_child(self, CSI, buf);

kitty_tests/parser.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ def test_pending(self):
343343
timeout = 0.1
344344
s.set_pending_timeout(timeout)
345345
pb = partial(self.parse_bytes_dump, s)
346+
346347
pb('\033P=1s\033\\', ('screen_start_pending_mode',))
347348
pb('a')
348349
self.ae(str(s.line(0)), '')
@@ -361,6 +362,12 @@ def test_pending(self):
361362
pb('\033\\', ('screen_stop_pending_mode',), ('draw', 'e'))
362363
pb('\033P=1sxyz;.;\033\\''\033P=2skjf".,><?_+)98\033\\', ('screen_start_pending_mode',), ('screen_stop_pending_mode',))
363364
pb('\033P=1s\033\\f\033P=1s\033\\', ('screen_start_pending_mode',), ('screen_start_pending_mode',))
365+
pb('\033P=2s\033\\', ('screen_stop_pending_mode',), ('draw', 'f'))
366+
367+
pb('\033[?2026hXXX\033[?2026l', ('screen_set_mode', 2026, 1), ('screen_reset_mode', 2026, 1), ('draw', 'XXX'))
368+
pb('\033[?2026h\033[32ma\033[?2026l', ('screen_set_mode', 2026, 1), ('screen_reset_mode', 2026, 1), ('select_graphic_rendition', '32 '), ('draw', 'a'))
369+
pb('\033[?2026h\033P+q544e\033\\ama\033P=2s\033\\',
370+
('screen_set_mode', 2026, 1), ('screen_stop_pending_mode',), ('screen_request_capabilities', 43, '544e'), ('draw', 'ama'))
364371

365372
def test_oth_codes(self):
366373
s = self.create_screen()

0 commit comments

Comments
 (0)