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.
This commit is contained in:
parent
6d413e2492
commit
5768c54c5b
@ -80,3 +80,6 @@
|
|||||||
#define BRACKETED_PASTE (2004 << 5)
|
#define BRACKETED_PASTE (2004 << 5)
|
||||||
#define BRACKETED_PASTE_START "200~"
|
#define BRACKETED_PASTE_START "200~"
|
||||||
#define BRACKETED_PASTE_END "201~"
|
#define BRACKETED_PASTE_END "201~"
|
||||||
|
|
||||||
|
// Pending updates mode
|
||||||
|
#define PENDING_UPDATE (2026 << 5)
|
||||||
|
|||||||
@ -1244,7 +1244,7 @@ END_ALLOW_CASE_RANGE
|
|||||||
handle_esc_mode_char(screen, codepoint, dump_callback); \
|
handle_esc_mode_char(screen, codepoint, dump_callback); \
|
||||||
break; \
|
break; \
|
||||||
case CSI: \
|
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; \
|
break; \
|
||||||
case OSC: \
|
case OSC: \
|
||||||
if (accumulate_osc(screen, codepoint, dump_callback)) { dispatch_osc(screen, dump_callback); SET_STATE(0); } \
|
if (accumulate_osc(screen, codepoint, dump_callback)) { dispatch_osc(screen, dump_callback); SET_STATE(0); } \
|
||||||
@ -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) {
|
_queue_pending_bytes(Screen *screen, const uint8_t *buf, size_t len, PyObject *dump_callback DUMP_UNUSED) {
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
enum STATE { NORMAL, MAYBE_DCS, IN_DCS, EXPECTING_DATA, EXPECTING_SLASH };
|
enum STATE { NORMAL, MAYBE_DCS_OR_CSI, IN_DCS, IN_CSI, EXPECTING_DATA, EXPECTING_CSI_DATA, EXPECTING_SLASH };
|
||||||
enum STATE state = screen->pending_mode.state;
|
#define pm screen->pending_mode
|
||||||
#define COPY(what) screen->pending_mode.buf[screen->pending_mode.used++] = what
|
enum STATE state = pm.state;
|
||||||
#define COPY_STOP_BUF { \
|
#define stop_pending_mode \
|
||||||
COPY(0x1b); COPY('P'); COPY(PENDING_MODE_CHAR); \
|
if (state == EXPECTING_CSI_DATA) { REPORT_COMMAND(screen_reset_mode, 2026, 1); } \
|
||||||
for (size_t i = 0; i < screen->pending_mode.stop_buf_pos; i++) { \
|
else { REPORT_COMMAND(screen_stop_pending_mode); } \
|
||||||
COPY(screen->pending_mode.stop_buf[i]); \
|
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) {
|
while (pos < len) {
|
||||||
uint8_t ch = buf[pos++];
|
uint8_t ch = buf[pos++];
|
||||||
switch(state) {
|
switch(state) {
|
||||||
case NORMAL:
|
case NORMAL:
|
||||||
if (ch == ESC) state = MAYBE_DCS;
|
if (ch == ESC) state = MAYBE_DCS_OR_CSI;
|
||||||
else COPY(ch);
|
else COPY(ch);
|
||||||
break;
|
break;
|
||||||
case MAYBE_DCS:
|
case MAYBE_DCS_OR_CSI:
|
||||||
if (ch == 'P') state = IN_DCS;
|
if (ch == 'P') state = IN_DCS;
|
||||||
|
else if (ch == '[') state = IN_CSI;
|
||||||
else {
|
else {
|
||||||
state = NORMAL;
|
state = NORMAL;
|
||||||
COPY(0x1b); COPY(ch);
|
COPY(0x1b); COPY(ch);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case IN_DCS:
|
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 {
|
else {
|
||||||
state = NORMAL;
|
state = NORMAL;
|
||||||
COPY(0x1b); COPY('P'); COPY(ch);
|
COPY(0x1b); COPY('P'); COPY(ch);
|
||||||
}
|
}
|
||||||
break;
|
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:
|
case EXPECTING_DATA:
|
||||||
|
pm.stop_buf[pm.stop_buf_pos++] = ch;
|
||||||
if (ch == 0x1b) state = EXPECTING_SLASH;
|
if (ch == 0x1b) state = EXPECTING_SLASH;
|
||||||
else {
|
else {
|
||||||
screen->pending_mode.stop_buf[screen->pending_mode.stop_buf_pos++] = ch;
|
if (pm.stop_buf_pos >= sizeof(pm.stop_buf)) {
|
||||||
if (screen->pending_mode.stop_buf_pos >= sizeof(screen->pending_mode.stop_buf)) {
|
|
||||||
state = NORMAL;
|
state = NORMAL;
|
||||||
COPY_STOP_BUF;
|
COPY_STOP_BUF(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EXPECTING_SLASH:
|
case EXPECTING_SLASH:
|
||||||
if (
|
if (
|
||||||
ch == '\\' &&
|
ch == '\\' &&
|
||||||
screen->pending_mode.stop_buf_pos >= 2 &&
|
pm.stop_buf_pos >= 2 &&
|
||||||
(screen->pending_mode.stop_buf[0] == '1' || screen->pending_mode.stop_buf[0] == '2') &&
|
(pm.stop_buf[0] == '1' || pm.stop_buf[0] == '2') &&
|
||||||
screen->pending_mode.stop_buf[1] == 's'
|
pm.stop_buf[1] == 's'
|
||||||
) {
|
) {
|
||||||
// We found a pending mode sequence
|
// We found a pending mode sequence
|
||||||
if (screen->pending_mode.stop_buf[0] == '2') {
|
if (pm.stop_buf[0] == '2') { stop_pending_mode } else { start_pending_mode }
|
||||||
REPORT_COMMAND(screen_stop_pending_mode);
|
|
||||||
screen->pending_mode.activated_at = 0;
|
|
||||||
goto end;
|
|
||||||
} else {
|
} else {
|
||||||
REPORT_COMMAND(screen_start_pending_mode);
|
COPY_STOP_BUF(true); COPY(ch);
|
||||||
screen->pending_mode.activated_at = monotonic();
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
state = NORMAL;
|
state = NORMAL;
|
||||||
COPY_STOP_BUF; COPY(ch);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end:
|
end:
|
||||||
screen->pending_mode.state = state;
|
pm.state = state;
|
||||||
return pos;
|
return pos;
|
||||||
#undef COPY
|
#undef COPY
|
||||||
#undef COPY_STOP_BUF
|
#undef COPY_STOP_BUF
|
||||||
|
#undef stop_pending_mode
|
||||||
|
#undef start_pending_mode
|
||||||
|
#undef pm
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
|
|||||||
@ -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_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
|
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) {
|
set_mode_from_const(Screen *self, unsigned int mode, bool val) {
|
||||||
#define SIMPLE_MODE(name) \
|
#define SIMPLE_MODE(name) \
|
||||||
case name: \
|
case name: \
|
||||||
@ -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);
|
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);
|
else if (!val && self->linebuf != self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN);
|
||||||
break;
|
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:
|
default:
|
||||||
private = mode >= 1 << 5;
|
private = mode >= 1 << 5;
|
||||||
if (private) mode >>= 5;
|
if (private) mode >>= 5;
|
||||||
@ -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;
|
ans = self->modes.mouse_tracking_mode == ANY_MODE ? 1 : 2; break;
|
||||||
case MOUSE_SGR_MODE:
|
case MOUSE_SGR_MODE:
|
||||||
ans = self->modes.mouse_tracking_protocol == SGR_PROTOCOL ? 1 : 2; break;
|
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);
|
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);
|
if (sz > 0) write_escape_code_to_child(self, CSI, buf);
|
||||||
|
|||||||
@ -343,6 +343,7 @@ class TestParser(BaseTest):
|
|||||||
timeout = 0.1
|
timeout = 0.1
|
||||||
s.set_pending_timeout(timeout)
|
s.set_pending_timeout(timeout)
|
||||||
pb = partial(self.parse_bytes_dump, s)
|
pb = partial(self.parse_bytes_dump, s)
|
||||||
|
|
||||||
pb('\033P=1s\033\\', ('screen_start_pending_mode',))
|
pb('\033P=1s\033\\', ('screen_start_pending_mode',))
|
||||||
pb('a')
|
pb('a')
|
||||||
self.ae(str(s.line(0)), '')
|
self.ae(str(s.line(0)), '')
|
||||||
@ -361,6 +362,12 @@ class TestParser(BaseTest):
|
|||||||
pb('\033\\', ('screen_stop_pending_mode',), ('draw', 'e'))
|
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=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=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):
|
def test_oth_codes(self):
|
||||||
s = self.create_screen()
|
s = self.create_screen()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user