From fb8f266a2183d5a729c42f76eadc453c8dcab4ba Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 5 Jul 2026 14:03:54 +0100 Subject: [PATCH] Add control mode flag window-layout-v2 to return new list-windows layout format. --- cmd-list-panes.c | 3 +- cmd-list-sessions.c | 3 +- cmd-list-windows.c | 3 +- control-notify.c | 11 ++++--- format.c | 15 +++++++-- layout-custom.c | 79 +++++++++++++++++++++++++++++++++++++++++++++ server-client.c | 4 +++ tmux.1 | 30 ++++++++++++++--- tmux.h | 3 +- 9 files changed, 134 insertions(+), 17 deletions(-) diff --git a/cmd-list-panes.c b/cmd-list-panes.c index 094ca850c..77056c43b 100644 --- a/cmd-list-panes.c +++ b/cmd-list-panes.c @@ -97,6 +97,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl, struct cmdq_item *item, int type) { struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_client(item); struct window_pane *wp, **l; u_int i, n; struct format_tree *ft; @@ -147,7 +148,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl, wp = l[i]; ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); format_add(ft, "line", "%u", n); - format_defaults(ft, NULL, s, wl, wp); + format_defaults(ft, c, s, wl, wp); if (filter != NULL) { expanded = format_expand(ft, filter); diff --git a/cmd-list-sessions.c b/cmd-list-sessions.c index 79a1ab620..f8156a08b 100644 --- a/cmd-list-sessions.c +++ b/cmd-list-sessions.c @@ -53,6 +53,7 @@ static enum cmd_retval cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_client(item); struct session **l; u_int n, i; struct format_tree *ft; @@ -76,7 +77,7 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item) for (i = 0; i < n; i++) { ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); format_add(ft, "line", "%u", i); - format_defaults(ft, NULL, l[i], NULL, NULL); + format_defaults(ft, c, l[i], NULL, NULL); if (filter != NULL) { expanded = format_expand(ft, filter); diff --git a/cmd-list-windows.c b/cmd-list-windows.c index 927976398..ff28912ce 100644 --- a/cmd-list-windows.c +++ b/cmd-list-windows.c @@ -59,6 +59,7 @@ static enum cmd_retval cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_client(item); struct cmd_find_state *target = cmdq_get_target(item); struct winlink *wl, **l; struct session *s; @@ -94,7 +95,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item) s = wl->session; ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); format_add(ft, "line", "%u", n); - format_defaults(ft, NULL, s, wl, NULL); + format_defaults(ft, c, s, wl, NULL); if (filter != NULL) { expanded = format_expand(ft, filter); diff --git a/control-notify.c b/control-notify.c index 021f9884a..13f24ac38 100644 --- a/control-notify.c +++ b/control-notify.c @@ -57,19 +57,20 @@ control_notify_window_layout_changed(struct window *w) * and we don't need to inform the client about the layout change * because the whole window will go away soon. */ - wl = TAILQ_FIRST(&w->winlinks); - if (wl == NULL || w->layout_root == NULL) + if (TAILQ_FIRST(&w->winlinks) == NULL || w->layout_root == NULL) return; - cp = format_single(NULL, template, NULL, NULL, wl, NULL); TAILQ_FOREACH(c, &clients, entry) { if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL) continue; s = c->session; - if (winlink_find_by_window_id(&s->windows, w->id) != NULL) + wl = winlink_find_by_window_id(&s->windows, w->id); + if (wl != NULL) { + cp = format_single(NULL, template, c, s, wl, NULL); control_write(c, "%s", cp); + free(cp); + } } - free(cp); } void diff --git a/format.c b/format.c index 8d8dfec94..4bff41e9e 100644 --- a/format.c +++ b/format.c @@ -839,6 +839,15 @@ format_cb_window_active_clients_list(struct format_tree *ft) } /* Callback for window_layout. */ +static char * +format_window_layout(struct format_tree *ft, struct layout_cell *root) +{ + if (ft->c != NULL && (ft->c->flags & CLIENT_CONTROL) && + (ft->c->flags & CLIENT_CONTROL_WINDOWLAYOUTV2) == 0) + return (layout_dump_legacy(root)); + return (layout_dump(ft->w, root)); +} + static void * format_cb_window_layout(struct format_tree *ft) { @@ -848,8 +857,8 @@ format_cb_window_layout(struct format_tree *ft) return (NULL); if (w->saved_layout_root != NULL) - return (layout_dump(w, w->saved_layout_root)); - return (layout_dump(w, w->layout_root)); + return (format_window_layout(ft, w->saved_layout_root)); + return (format_window_layout(ft, w->layout_root)); } /* Callback for window_visible_layout. */ @@ -861,7 +870,7 @@ format_cb_window_visible_layout(struct format_tree *ft) if (w == NULL) return (NULL); - return (layout_dump(w, w->layout_root)); + return (format_window_layout(ft, w->layout_root)); } /* Callback for pane_start_command. */ diff --git a/layout-custom.c b/layout-custom.c index fc044560e..08b3a6d9d 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -54,6 +54,8 @@ static struct layout_cell *layout_find_bottomright(struct layout_cell *); static u_short layout_checksum(const char *, const char *); static int layout_append(struct window *, struct layout_cell *, char *, size_t); +static int layout_append_legacy(struct layout_cell *, char *, + size_t); static int layout_append_geometry(char *, size_t, u_int, u_int, int, int); static int layout_check(struct layout_cell *); @@ -144,6 +146,83 @@ layout_dump(struct window *w, struct layout_cell *root) return (out); } +/* Dump layout using the legacy syntax. */ +char * +layout_dump_legacy(struct layout_cell *root) +{ + char layout[LAYOUT_STRING_MAX], *out; + + *layout = '\0'; + if (layout_append_legacy(root, layout, sizeof layout) != 0) + return (NULL); + + xasprintf(&out, "%04hx,%s", + layout_checksum(layout, layout + strlen(layout)), layout); + return (out); +} + +/* Append information for a single cell using the legacy syntax. */ +static int +layout_append_legacy(struct layout_cell *lc, char *buf, size_t len) +{ + struct layout_cell *lcchild; + char tmp[96]; + const char *brackets = "]["; + size_t tmplen; + u_int sx, sy; + int xoff, yoff; + + if (len == 0) + return (-1); + if (lc == NULL) + return (0); + + sx = lc->sx; + sy = lc->sy; + xoff = lc->xoff; + yoff = lc->yoff; + if (lc->flags & LAYOUT_CELL_HIDDEN) { + if (lc->saved_sx != UINT_MAX) + sx = lc->saved_sx; + if (lc->saved_sy != UINT_MAX) + sy = lc->saved_sy; + if (lc->saved_xoff != INT_MAX) + xoff = lc->saved_xoff; + if (lc->saved_yoff != INT_MAX) + yoff = lc->saved_yoff; + } + + if (lc->wp != NULL) { + tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%d,%d,%u", sx, sy, + xoff, yoff, lc->wp->id); + } else { + tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%d,%d", sx, sy, + xoff, yoff); + } + if (tmplen > (sizeof tmp) - 1 || strlcat(buf, tmp, len) >= len) + return (-1); + + switch (lc->type) { + case LAYOUT_LEFTRIGHT: + brackets = "}{"; + /* FALLTHROUGH */ + case LAYOUT_TOPBOTTOM: + if (strlcat(buf, &brackets[1], len) >= len) + return (-1); + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (layout_append_legacy(lcchild, buf, len) != 0) + return (-1); + if (strlcat(buf, ",", len) >= len) + return (-1); + } + buf[strlen(buf) - 1] = brackets[0]; + break; + case LAYOUT_WINDOWPANE: + break; + } + return (0); +} + /* Append information for a single cell. */ static int layout_append(struct window *w, struct layout_cell *lc, char *buf, size_t len) diff --git a/server-client.c b/server-client.c index 4af0457bc..0a304713a 100644 --- a/server-client.c +++ b/server-client.c @@ -2851,6 +2851,8 @@ server_client_control_flags(struct client *c, const char *next) return (CLIENT_CONTROL_NOOUTPUT); if (strcmp(next, "wait-exit") == 0) return (CLIENT_CONTROL_WAITEXIT); + if (strcmp(next, "window-layout-v2") == 0) + return (CLIENT_CONTROL_WINDOWLAYOUTV2); return (0); } @@ -2919,6 +2921,8 @@ server_client_get_flags(struct client *c) strlcat(s, "no-output,", sizeof s); if (c->flags & CLIENT_CONTROL_WAITEXIT) strlcat(s, "wait-exit,", sizeof s); + if (c->flags & CLIENT_CONTROL_WINDOWLAYOUTV2) + strlcat(s, "window-layout-v2,", sizeof s); if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { xsnprintf(tmp, sizeof tmp, "pause-after=%u,", c->pause_age / 1000); diff --git a/tmux.1 b/tmux.1 index a0ab51020..03047cd13 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1113,6 +1113,9 @@ The flags are: the client has an independent active pane .It ignore\-size the client does not affect the size of other clients +.It window\-layout\-v2 +the client receives the current layout serialization in control mode rather +than the legacy serialization .It no\-detach\-on\-destroy do not detach the client when the session it is attached to is destroyed if there are any other sessions @@ -3387,6 +3390,9 @@ format variable. It is repeated for each pane from the same window. Its format is described under .Ic select\-layout . +In control mode, it uses the legacy format unless the client has the +.Ql window\-layout\-v2 +flag. .Tg lsw .It Xo Ic list\-windows .Op Fl ar @@ -3428,6 +3434,9 @@ The default format includes the layout of each window, also available with the format variable. The format is described under .Ic select\-layout . +In control mode, the legacy format is used unless the client has the +.Ql window\-layout\-v2 +flag. .Pp Layouts for multiple windows may be produced in a form suitable for .Ic select\-layout @@ -4069,10 +4078,18 @@ accepted and checked but are not generated by .Ic list\-windows . A layout must use one form consistently. In a legacy layout, panes are treated as tiled, visible and unzoomed. -.Ic list\-windows -and the +Outside control mode, .Ql window_layout -format variable generate the current format with a checksum. +and +.Ql window_visible_layout +generate the current format with a checksum. +In control mode, they generate the legacy format by default; a client with the +.Ql window\-layout\-v2 +flag receives the current format instead. +.Ic list\-windows +uses +.Ql window_layout +for the layout in its default output. .Pp A serialized layout is a layout snapshot, not a complete session restoration. It rearranges existing panes and windows but does not create panes or windows. @@ -7323,7 +7340,7 @@ The following variables are available, where appropriate: .It Li "window_id" Ta "" Ta "Unique window ID" .It Li "window_index" Ta "#I" Ta "Index of window" .It Li "window_last_flag" Ta "" Ta "1 if window is the last used" -.It Li "window_layout" Ta "" Ta "Window layout description, including zoom and pane flags" +.It Li "window_layout" Ta "" Ta "Window layout description (legacy by default in control mode)" .It Li "window_linked" Ta "" Ta "1 if window is linked across sessions" .It Li "window_linked_sessions" Ta "" Ta "Number of sessions this window is linked to" .It Li "window_linked_sessions_list" Ta "" Ta "List of sessions this window is linked to" @@ -7336,7 +7353,7 @@ The following variables are available, where appropriate: .It Li "window_silence_flag" Ta "" Ta "1 if window has silence alert" .It Li "window_stack_index" Ta "" Ta "Index in session most recent stack" .It Li "window_start_flag" Ta "" Ta "1 if window has the lowest index" -.It Li "window_visible_layout" Ta "" Ta "Window layout description, respecting zoomed window panes" +.It Li "window_visible_layout" Ta "" Ta "Visible window layout description (legacy by default in control mode)" .It Li "window_width" Ta "" Ta "Width of window" .It Li "window_zoomed_flag" Ta "" Ta "1 if window is zoomed" .It Li "wrap_flag" Ta "" Ta "Pane wrap flag" @@ -8861,6 +8878,9 @@ The window's visible layout is .Ar window\-visible\-layout and the window flags are .Ar window\-flags . +The layouts use the legacy serialization unless the client has the +.Ql window\-layout\-v2 +flag, in which case they use the current serialization. .It Ic %message Ar message A message sent with the .Ic display\-message diff --git a/tmux.h b/tmux.h index 86989c060..9bdf205fb 100644 --- a/tmux.h +++ b/tmux.h @@ -2245,7 +2245,7 @@ struct client { #define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL #define CLIENT_CONTROL_WAITEXIT 0x200000000ULL #define CLIENT_WINDOWSIZECHANGED 0x400000000ULL -/* 0x800000000ULL unused */ +#define CLIENT_CONTROL_WINDOWLAYOUTV2 0x800000000ULL #define CLIENT_BRACKETPASTING 0x1000000000ULL #define CLIENT_ASSUMEPASTING 0x2000000000ULL /* 0x4000000000ULL unused */ @@ -3740,6 +3740,7 @@ int layout_insert_tile(struct window *, struct layout_cell *); /* layout-custom.c */ struct layout_prepared; char *layout_dump(struct window *, struct layout_cell *); +char *layout_dump_legacy(struct layout_cell *); struct layout_prepared *layout_prepare(struct window *, const char *, char **); void layout_free_prepared(struct layout_prepared *); void layout_apply_prepared(struct window *, struct layout_prepared *);