From 67dbf01eeb3cb76c1003229c589d69b100367638 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 1 Jun 2026 13:28:49 +0000 Subject: [PATCH 1/5] Add some more DECRQM responses, from Ayman Bagabas in GitHub issue 5118. --- input.c | 82 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/input.c b/input.c index d9c9344a..0681b88e 100644 --- a/input.c +++ b/input.c @@ -64,7 +64,7 @@ struct input_request { enum input_request_type type; uint64_t t; - enum input_end_type end; + enum input_end_type end; int idx; void *data; @@ -149,7 +149,7 @@ struct input_ctx { /* Helper functions. */ struct input_transition; -static void input_request_timer_callback(int, short, void *); +static void input_request_timer_callback(int, short, void *); static void input_start_request_timer(struct input_ctx *); static struct input_request *input_make_request(struct input_ctx *, enum input_request_type); @@ -281,6 +281,7 @@ enum input_csi_type { INPUT_CSI_IL, INPUT_CSI_MODOFF, INPUT_CSI_MODSET, + INPUT_CSI_QUERY, INPUT_CSI_QUERY_PRIVATE, INPUT_CSI_RCP, INPUT_CSI_REP, @@ -336,6 +337,7 @@ static const struct input_table_entry input_csi_table[] = { { 'n', "", INPUT_CSI_DSR }, { 'n', ">", INPUT_CSI_MODOFF }, { 'n', "?", INPUT_CSI_DSR_PRIVATE }, + { 'p', "$", INPUT_CSI_QUERY }, { 'p', "?$", INPUT_CSI_QUERY_PRIVATE }, { 'q', " ", INPUT_CSI_DECSCUSR }, { 'q', ">", INPUT_CSI_XDA }, @@ -1604,8 +1606,34 @@ input_csi_dispatch(struct input_ctx *ictx) break; } break; + case INPUT_CSI_QUERY: + m = input_get(ictx, 0, 0, 0); + switch (m) { + case 4: /* IRM */ + n = (s->mode & MODE_INSERT) ? 1 : 2; + break; + default: + n = 0; + break; + } + if (m > 0) + input_reply(ictx, 1, "\033[%d;%d$y", m, n); + break; case INPUT_CSI_QUERY_PRIVATE: - switch (input_get(ictx, 0, 0, 0)) { + m = input_get(ictx, 0, 0, 0); + switch (m) { + case 1: /* DECCKM */ + n = (s->mode & MODE_KCURSOR) ? 1 : 2; + break; + case 3: /* DECCOLM: always reset */ + n = 4; + break; + case 6: /* DECOM */ + n = (s->mode & MODE_ORIGIN) ? 1 : 2; + break; + case 7: /* DECAWM */ + n = (s->mode & MODE_WRAP) ? 1 : 2; + break; case 12: /* cursor blink: 1 = blink, 2 = steady */ if (s->cstyle != SCREEN_CURSOR_DEFAULT || s->mode & MODE_CURSOR_BLINKING_SET) @@ -1618,30 +1646,50 @@ input_csi_dispatch(struct input_ctx *ictx) p = options_get_number(oo, "cursor-style"); /* blink for 1,3,5; steady for 0,2,4,6 */ - n = (p == 1 || p == 3 || p == 5) ? 1 : 2; + n = (p == 1 || p == 3 || p == 5) ? 1 : 2; } - input_reply(ictx, 1, "\033[?12;%d$y", n); break; - case 1004: /* focus reporting */ + case 25: /* DECTCEM */ + n = (s->mode & MODE_CURSOR) ? 1 : 2; + break; + case 47: + case 1047: + case 1049: /* alternate screen */ + n = SCREEN_IS_ALTERNATE(s) ? 1 : 2; + break; + case 1000: /* mouse: normal tracking */ + n = (s->mode & MODE_MOUSE_STANDARD) ? 1 : 2; + break; + case 1002: /* mouse: button-event tracking */ + n = (s->mode & MODE_MOUSE_BUTTON) ? 1 : 2; + break; + case 1003: /* mouse: any-event tracking */ + n = (s->mode & MODE_MOUSE_ALL) ? 1 : 2; + break; + case 1004: /* focus reporting */ n = (s->mode & MODE_FOCUSON) ? 1 : 2; - input_reply(ictx, 1, "\033[?1004;%d$y", n); break; - case 1006: /* SGR mouse */ + case 1005: /* mouse: UTF-8 */ + n = (s->mode & MODE_MOUSE_UTF8) ? 1 : 2; + break; + case 1006: /* mouse: SGR */ n = (s->mode & MODE_MOUSE_SGR) ? 1 : 2; - input_reply(ictx, 1, "\033[?1006;%d$y", n); break; - case 2004: /* bracketed paste */ + case 2004: /* bracketed paste */ n = (s->mode & MODE_BRACKETPASTE) ? 1 : 2; - input_reply(ictx, 1, "\033[?2004;%d$y", n); break; - case 2026: /* synchronized output */ + case 2026: /* synchronized output */ n = (s->mode & MODE_SYNC) ? 1 : 2; - input_reply(ictx, 1, "\033[?2026;%d$y", n); break; - case 2031: - input_reply(ictx, 1, "\033[?2031;2$y"); + case 2031: /* theme update notifications */ + n = (s->mode & MODE_THEME_UPDATES) ? 1 : 2; + break; + default: + n = 0; break; } + if (m > 0) + input_reply(ictx, 1, "\033[?%d;%d$y", m, n); break; case INPUT_CSI_DSR: switch (input_get(ictx, 0, 0, 0)) { @@ -2911,7 +2959,7 @@ input_set_progress_bar(struct input_ctx *ictx, enum progress_bar_state state, int p) { screen_set_progress_bar(ictx->ctx.s, state, p); - if (ictx->wp != NULL) { + if (ictx->wp != NULL) { server_redraw_window_borders(ictx->wp->window); server_status_window(ictx->wp->window); } @@ -3185,7 +3233,7 @@ static void input_osc_52(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; - struct screen_write_ctx ctx; + struct screen_write_ctx ctx; u_char *out; int outlen; char clip[sizeof "cpqs01234567"] = ""; From f6398ba51c61e420c10b33f0c5ad515e6d981235 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 1 Jun 2026 14:30:02 +0100 Subject: [PATCH 2/5] Add some more DECRQM responses, from Ayman Bagabas in GitHub issue 5118. --- regress/decrqm-sync.sh | 75 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/regress/decrqm-sync.sh b/regress/decrqm-sync.sh index 9620faf5..bca57dca 100644 --- a/regress/decrqm-sync.sh +++ b/regress/decrqm-sync.sh @@ -15,7 +15,10 @@ sleep 1 TMP=$(mktemp) TMP2=$(mktemp) -trap "rm -f $TMP $TMP2; $TMUX kill-server 2>/dev/null" 0 1 15 +TMP3=$(mktemp) +TMP4=$(mktemp) +TMP5=$(mktemp) +trap "rm -f $TMP $TMP2 $TMP3 $TMP4 $TMP5; $TMUX kill-server 2>/dev/null" 0 1 15 $TMUX -f/dev/null new -d -x80 -y24 || exit 1 sleep 1 @@ -25,18 +28,19 @@ $TMUX set -g remain-on-exit on exit_status=0 -# query_decrpm [setup_seq] +# query_decrpm [setup_seq] # Spawn a pane that optionally sends setup_seq, then sends DECRQM for # mode 2026 and captures the response into outfile in cat -v form. -query_decrpm () { +query_decrpm() { _outfile=$1 - _setup=$2 + _mode=$2 + _setup=$3 $TMUX respawnw -k -t:0 -- sh -c " exec 2>/dev/null stty raw -echo ${_setup:+printf '$_setup'; sleep 0.2} - printf '\033[?2026\$p' + printf '\033[%s\$p' "$_mode" dd bs=1 count=11 2>/dev/null | cat -v > $_outfile sleep 0.2 " || exit 1 @@ -46,7 +50,7 @@ query_decrpm () { # ------------------------------------------------------------------ # Test 1: mode 2026 should be reset by default (Ps=2) # ------------------------------------------------------------------ -query_decrpm "$TMP" +query_decrpm "$TMP" "?2026" actual=$(cat "$TMP") expected='^[[?2026;2$y' @@ -63,7 +67,7 @@ fi # ------------------------------------------------------------------ # Test 2: set mode 2026 (SM ?2026), then query (expect Ps=1) # ------------------------------------------------------------------ -query_decrpm "$TMP2" '\033[?2026h' +query_decrpm "$TMP2" "?2026" '\033[?2026h' actual=$(cat "$TMP2") expected='^[[?2026;1$y' @@ -77,6 +81,61 @@ else exit_status=1 fi +# ------------------------------------------------------------------ +# Test 3: mode 25 should return current value +# ------------------------------------------------------------------ +query_decrpm "$TMP3" "?25" '\033[?25l' + +actual=$(cat "$TMP3") +expected='^[[?25;2$y' + +if [ "$actual" = "$expected" ]; then + if [ -n "$VERBOSE" ]; then + echo "[PASS] DECTCEM 25 (reset) -> $actual" + fi +else + echo "[FAIL] DECTCEM 25 (reset): expected '$expected', got '$actual'" + exit_status=1 +fi + +# ------------------------------------------------------------------ +# Test 4: mode ?9999 should return not recognized +# ------------------------------------------------------------------ +query_decrpm "$TMP4" "?9999" '\033[?9999h' + +actual=$(cat "$TMP4") +expected='^[[?9999;0$y' + +if [ "$actual" = "$expected" ]; then + if [ -n "$VERBOSE" ]; then + echo "[PASS] DECSM 9999 (not recognized) -> $actual" + fi +else + echo "[FAIL] DECSM 9999 (not recognized): expected '$expected', got '$actual'" + exit_status=1 +fi + $TMUX kill-server 2>/dev/null -exit $exit_status \ No newline at end of file +exit $exit_status + +# ------------------------------------------------------------------ +# Test 5: mode 4 is reset by default +# ------------------------------------------------------------------ +query_decrpm "$TMP5" "4" '\033[4h' + +actual=$(cat "$TMP5") +expected='^[[4;1$y' + +if [ "$actual" = "$expected" ]; then + if [ -n "$VERBOSE" ]; then + echo "[PASS] SM 4 (is set) -> $actual" + fi +else + echo "[FAIL] SM 4 (is set): expected '$expected', got '$actual'" + exit_status=1 +fi + +$TMUX kill-server 2>/dev/null + +exit $exit_status From b68ab3a4b48c37da3a45f447efa5c9a5fca4509f Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 1 Jun 2026 14:01:09 +0000 Subject: [PATCH 3/5] Add pane_x, y, z format variables and show in list-panes. --- cmd-list-panes.c | 14 ++++++++++---- format.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ tmux.1 | 3 +++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/cmd-list-panes.c b/cmd-list-panes.c index 6a670b29..094ca850 100644 --- a/cmd-list-panes.c +++ b/cmd-list-panes.c @@ -110,22 +110,28 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl, switch (type) { case 0: template = "#{pane_index}: " - "[#{pane_width}x#{pane_height}] [history " + "[#{pane_width}x#{pane_height}" + "#{?pane_floating_flag, " + "#{pane_x}#,#{pane_y}#,#{pane_z}}] [history " "#{history_size}/#{history_limit}, " "#{history_bytes} bytes] #{pane_id}" "#{?pane_active, (active),}#{?pane_dead, (dead),}"; break; case 1: template = "#{window_index}.#{pane_index}: " - "[#{pane_width}x#{pane_height}] [history " + "[#{pane_width}x#{pane_height}" + "#{?pane_floating_flag, " + "#{pane_x}#,#{pane_y}#,#{pane_z}}] [history " "#{history_size}/#{history_limit}, " "#{history_bytes} bytes] #{pane_id}" "#{?pane_active, (active),}#{?pane_dead, (dead),}"; break; case 2: template = "#{session_name}:#{window_index}." - "#{pane_index}: [#{pane_width}x#{pane_height}] " - "[history #{history_size}/#{history_limit}, " + "#{pane_index}: [#{pane_width}x#{pane_height}" + "#{?pane_floating_flag, " + "#{pane_x}#,#{pane_y}#,#{pane_z}}] [history " + "#{history_size}/#{history_limit}, " "#{history_bytes} bytes] #{pane_id}" "#{?pane_active, (active),}#{?pane_dead, (dead),}"; break; diff --git a/format.c b/format.c index f81d807d..92b1dd3a 100644 --- a/format.c +++ b/format.c @@ -2397,6 +2397,45 @@ format_cb_pane_width(struct format_tree *ft) return (NULL); } +/* Callback for pane_x. */ +static void * +format_cb_pane_x(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%d", ft->wp->xoff)); + return (NULL); +} + +/* Callback for pane_y. */ +static void * +format_cb_pane_y(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%d", ft->wp->yoff)); + return (NULL); +} + +/* Callback for pane_z. */ +static void * +format_cb_pane_z(struct format_tree *ft) +{ + struct window_pane *wp; + u_int n = 0; + + if (ft->wp != NULL) { + if (~ft->wp->flags & PANE_FLOATING) + return (xstrdup("0")); + TAILQ_FOREACH(wp, &ft->wp->window->z_index, zentry) { + if (wp->flags & PANE_FLOATING) + n++; + if (wp == ft->wp) + return (format_printf("%u", n)); + } + return (xstrdup("0")); + } + return (NULL); +} + /* Callback for pane_zoomed_flag. */ static void * format_cb_pane_zoomed_flag(struct format_tree *ft) @@ -3482,6 +3521,15 @@ static const struct format_table_entry format_table[] = { { "pane_width", FORMAT_TABLE_STRING, format_cb_pane_width }, + { "pane_x", FORMAT_TABLE_STRING, + format_cb_pane_x + }, + { "pane_y", FORMAT_TABLE_STRING, + format_cb_pane_y + }, + { "pane_z", FORMAT_TABLE_STRING, + format_cb_pane_z + }, { "pane_zoomed_flag", FORMAT_TABLE_STRING, format_cb_pane_zoomed_flag }, diff --git a/tmux.1 b/tmux.1 index decd945b..aaf811dd 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6569,6 +6569,9 @@ The following variables are available, where appropriate: .It Li "pane_tty" Ta "" Ta "Pseudo terminal of pane" .It Li "pane_unseen_changes" Ta "" Ta "1 if there were changes in pane while in mode" .It Li "pane_width" Ta "" Ta "Width of pane" +.It Li "pane_x" Ta "" Ta "X position of pane" +.It Li "pane_y" Ta "" Ta "Y position of pane" +.It Li "pane_z" Ta "" Ta "Z position of pane" .It Li "pane_zoomed_flag" Ta "" Ta "1 if pane is zoomed" .It Li "pid" Ta "" Ta "Server PID" .It Li "prev_window_active" Ta "" Ta "1 if previous window in W: loop is active" From cd6c01e42ba455256fc9feda07c681630ff85db5 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 1 Jun 2026 18:00:20 +0000 Subject: [PATCH 4/5] Mark floating panes in tree mode. --- window-tree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/window-tree.c b/window-tree.c index 9b5269b7..79404dd9 100644 --- a/window-tree.c +++ b/window-tree.c @@ -38,8 +38,8 @@ static void window_tree_key(struct window_mode_entry *, #define WINDOW_TREE_DEFAULT_FORMAT \ "#{?pane_format," \ - "#{?pane_marked,#[reverse],}" \ - "#{pane_current_command}#{?pane_active,*,}#{?pane_marked,M,}" \ + "#{?pane_marked,#[reverse],}#{?pane_floating_flag,#[italics],}" \ + "#{pane_current_command}#{pane_flags}" \ "#{?#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}},: \"#{pane_title}\",}" \ ",window_format," \ "#{?window_marked_flag,#[reverse],}" \ From f6557d1ea26577f75947afdebfec3b184b02a78b Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 1 Jun 2026 18:19:51 +0000 Subject: [PATCH 5/5] Add a Z sort order in tree mode. --- format.c | 16 +++------------- sort.c | 14 ++++++++++++++ tmux.1 | 5 +++-- tmux.h | 2 ++ window-tree.c | 1 + window.c | 22 +++++++++++++++++++++- 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/format.c b/format.c index 92b1dd3a..96344f27 100644 --- a/format.c +++ b/format.c @@ -2419,20 +2419,10 @@ format_cb_pane_y(struct format_tree *ft) static void * format_cb_pane_z(struct format_tree *ft) { - struct window_pane *wp; - u_int n = 0; + u_int idx; - if (ft->wp != NULL) { - if (~ft->wp->flags & PANE_FLOATING) - return (xstrdup("0")); - TAILQ_FOREACH(wp, &ft->wp->window->z_index, zentry) { - if (wp->flags & PANE_FLOATING) - n++; - if (wp == ft->wp) - return (format_printf("%u", n)); - } - return (xstrdup("0")); - } + if (ft->wp != NULL && window_pane_zindex(ft->wp, &idx) == 0) + return (format_printf("%u", idx)); return (NULL); } diff --git a/sort.c b/sort.c index a9e5da5c..fda6a90b 100644 --- a/sort.c +++ b/sort.c @@ -74,6 +74,7 @@ sort_buffer_cmp(const void *a0, const void *b0) case SORT_INDEX: case SORT_MODIFIER: case SORT_ORDER: + case SORT_Z: case SORT_END: break; } @@ -120,6 +121,7 @@ sort_client_cmp(const void *a0, const void *b0) case SORT_INDEX: case SORT_MODIFIER: case SORT_ORDER: + case SORT_Z: case SORT_END: break; } @@ -172,6 +174,7 @@ sort_session_cmp(const void *a0, const void *b0) case SORT_MODIFIER: case SORT_ORDER: case SORT_SIZE: + case SORT_Z: case SORT_END: break; } @@ -211,6 +214,11 @@ sort_pane_cmp(const void *a0, const void *b0) case SORT_NAME: result = strcmp(a->screen->title, b->screen->title); break; + case SORT_Z: + window_pane_zindex(a, &ai); + window_pane_zindex(b, &bi); + result = ai - bi; + break; case SORT_MODIFIER: case SORT_ORDER: case SORT_END: @@ -269,6 +277,7 @@ sort_winlink_cmp(const void *a0, const void *b0) break; case SORT_MODIFIER: case SORT_ORDER: + case SORT_Z: case SORT_END: break; } @@ -304,6 +313,7 @@ sort_key_binding_cmp(const void *a0, const void *b0) case SORT_CREATION: case SORT_ORDER: case SORT_SIZE: + case SORT_Z: case SORT_END: break; } @@ -358,6 +368,8 @@ sort_order_from_string(const char* order) return (SORT_ORDER); if (strcasecmp(order, "size") == 0) return (SORT_SIZE); + if (strcasecmp(order, "z") == 0) + return (SORT_Z); } return (SORT_END); } @@ -379,6 +391,8 @@ sort_order_to_string(enum sort_order order) return "order"; if (order == SORT_SIZE) return "size"; + if (order == SORT_Z) + return "z"; return (NULL); } diff --git a/tmux.1 b/tmux.1 index aaf811dd..c8c7dd41 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2862,9 +2862,10 @@ is not given, "switch\-client \-t \[aq]%%\[aq]" is used. specifies the initial sort order: one of .Ql index , .Ql name , -or .Ql activity -(time). +(time) +or +.Ql z . .Fl r reverses the sort order. .Fl f diff --git a/tmux.h b/tmux.h index fed51792..456c1666 100644 --- a/tmux.h +++ b/tmux.h @@ -2334,6 +2334,7 @@ enum sort_order { SORT_NAME, SORT_ORDER, SORT_SIZE, + SORT_Z, SORT_END, }; @@ -3391,6 +3392,7 @@ struct window_pane *window_pane_next_by_number(struct window *, struct window_pane *window_pane_previous_by_number(struct window *, struct window_pane *, u_int); int window_pane_index(struct window_pane *, u_int *); +int window_pane_zindex(struct window_pane *, u_int *); u_int window_count_panes(struct window *, int); void window_destroy_panes(struct window *); struct window_pane *window_pane_find_by_id_str(const char *); diff --git a/window-tree.c b/window-tree.c index 79404dd9..2f4dccd4 100644 --- a/window-tree.c +++ b/window-tree.c @@ -136,6 +136,7 @@ static enum sort_order window_tree_order_seq[] = { SORT_INDEX, SORT_NAME, SORT_ACTIVITY, + SORT_Z, SORT_END, }; diff --git a/window.c b/window.c index 5af4f823..4b4af783 100644 --- a/window.c +++ b/window.c @@ -880,8 +880,8 @@ window_pane_previous_by_number(struct window *w, struct window_pane *wp, int window_pane_index(struct window_pane *wp, u_int *i) { - struct window_pane *wq; struct window *w = wp->window; + struct window_pane *wq; *i = options_get_number(w->options, "pane-base-index"); TAILQ_FOREACH(wq, &w->panes, entry) { @@ -894,6 +894,26 @@ window_pane_index(struct window_pane *wp, u_int *i) return (-1); } +int +window_pane_zindex(struct window_pane *wp, u_int *i) +{ + struct window *w = wp->window; + struct window_pane *wq; + + *i = 0; + TAILQ_FOREACH(wq, &w->z_index, zentry) { + if (wq == wp) { + if (~wp->flags & PANE_FLOATING) + (*i)++; + return (0); + } + if (wq->flags & PANE_FLOATING) + (*i)++; + } + + return (-1); +} + u_int window_count_panes(struct window *w, int with_floating) {