From 7b45ee638b3097fd7956b4fb2bb98210cf5ea347 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 14 Oct 2026 15:28:52 +0200 Subject: [PATCH] End a SIXEL string at an Escape that is not followed by a backslash. An unterminated SIXEL image kept the pane in the DCS state, so everything the program printed afterwards was swallowed until the timeout, and the image itself was never drawn. Treat the Escape as the end of the image, as other terminals do, and handle the sequence after it normally. Other DCS strings are unchanged. --- input.c | 28 ++++++++++++++++++++++++++-- regress/image-support.sh | 9 +++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/input.c b/input.c index 2be86ec03..1d0458c4e 100644 --- a/input.c +++ b/input.c @@ -209,6 +209,7 @@ static void input_csi_dispatch_sgr_256(struct input_ctx *, int, u_int *); static void input_csi_dispatch_sgr_rgb(struct input_ctx *, int, u_int *); static void input_csi_dispatch_sgr(struct input_ctx *); static int input_dcs_dispatch(struct input_ctx *); +static int input_dcs_escape(struct input_ctx *); static int input_top_bit_set(struct input_ctx *); static int input_end_bel(struct input_ctx *); @@ -699,9 +700,9 @@ static const struct input_transition input_state_dcs_handler_table[] = { static const struct input_transition input_state_dcs_escape_table[] = { /* No INPUT_STATE_ANYWHERE */ - { 0x00, 0x5b, input_input, &input_state_dcs_handler }, + { 0x00, 0x5b, input_dcs_escape, &input_state_dcs_handler }, { 0x5c, 0x5c, input_dcs_dispatch, &input_state_ground }, - { 0x5d, 0xff, input_input, &input_state_dcs_handler }, + { 0x5d, 0xff, input_dcs_escape, &input_state_dcs_handler }, { -1, -1, NULL, NULL } }; @@ -2626,6 +2627,29 @@ not_recognized: return (0); } +/* Escape in a DCS string followed by anything other than a backslash. */ +static int +input_dcs_escape(struct input_ctx *ictx) +{ + static const struct input_transition to_ground = { + 0, 0, NULL, &input_state_ground + }; + u_char buf[2] = { '\033', ictx->ch }; + + /* + * Other strings carry the byte as data, but a SIXEL image ends at the + * Escape, which then starts a new sequence as usual. + */ + if (ictx->input_len == 0 || + ictx->input_buf[0] != 'q' || + ictx->interm_len != 0) + return (input_input(ictx)); + input_dcs_dispatch(ictx); + input_set_state(ictx, &to_ground); + input_parse(ictx, buf, sizeof buf); + return (1); +} + /* DCS terminator (ST) received. */ static int input_dcs_dispatch(struct input_ctx *ictx) diff --git a/regress/image-support.sh b/regress/image-support.sh index 41e97b26a..1d15aac9c 100755 --- a/regress/image-support.sh +++ b/regress/image-support.sh @@ -76,6 +76,15 @@ sleep 1 [ "$($TMUX display-message -pt:sixel-stray '#{cursor_y}')" -gt 0 ] || exit 1 $TMUX kill-window -t:sixel-stray || exit 1 +# A SIXEL string that is not terminated ends at the next Escape, and the +# sequence after it is still processed. +$TMUX new-window -d -n sixel-esc " + printf '\033Pq#0;2;100;0;0#0!8~\033[HXY' + sleep 10" +sleep 1 +[ "$($TMUX capture-pane -pt:sixel-esc -S0 -E0)" = "XY" ] || exit 1 +$TMUX kill-window -t:sixel-esc || exit 1 + # The protocol default is transmit-only; a later placement uses the retained # immutable image and advances by the same canonical one-cell footprint. $TMUX new-window -d "