From 34e2035badecb6dbbfa343129c9e64db1d62ac24 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 19 May 2026 09:48:14 +0000 Subject: [PATCH 1/2] More bits for pane Z index tracking from floating panes, mostly by Michael Grant. --- cmd-break-pane.c | 2 ++ cmd-join-pane.c | 8 ++++++-- format.c | 18 +++++++++++------- layout-custom.c | 9 +++++++++ layout.c | 24 ++++++++++++++++++++++++ tmux.h | 1 + window.c | 49 ++++++++++++++++++++++++++++++++++++++---------- 7 files changed, 92 insertions(+), 19 deletions(-) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 790b8df2..aad23334 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -96,6 +96,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) } TAILQ_REMOVE(&w->panes, wp, entry); + TAILQ_REMOVE(&w->z_index, wp, zentry); server_client_remove_pane(wp); window_lost_pane(w, wp); layout_close_pane(wp); @@ -104,6 +105,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) options_set_parent(wp->options, w->options); wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); TAILQ_INSERT_HEAD(&w->panes, wp, entry); + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); w->active = wp; w->latest = tc; diff --git a/cmd-join-pane.c b/cmd-join-pane.c index f8975a67..7ff8859f 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -147,14 +147,18 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) server_client_remove_pane(src_wp); window_lost_pane(src_w, src_wp); TAILQ_REMOVE(&src_w->panes, src_wp, entry); + TAILQ_REMOVE(&src_w->z_index, src_wp, zentry); src_wp->window = dst_w; options_set_parent(src_wp->options, dst_w->options); src_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); - if (flags & SPAWN_BEFORE) + if (flags & SPAWN_BEFORE) { TAILQ_INSERT_BEFORE(dst_wp, src_wp, entry); - else + TAILQ_INSERT_BEFORE(dst_wp, src_wp, zentry); + } else { TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry); + TAILQ_INSERT_AFTER(&dst_w->z_index, dst_wp, src_wp, zentry); + } layout_assign_pane(lc, src_wp, 0); colour_palette_from_option(&src_wp->palette, src_wp->options); diff --git a/format.c b/format.c index 472316ec..6766427f 100644 --- a/format.c +++ b/format.c @@ -1170,9 +1170,9 @@ format_cb_pane_at_bottom(struct format_tree *ft) status = options_get_number(w->options, "pane-border-status"); if (status == PANE_STATUS_BOTTOM) - flag = (wp->yoff + wp->sy == w->sy - 1); + flag = (wp->yoff + (int)wp->sy == (int)w->sy - 1); else - flag = (wp->yoff + wp->sy == w->sy); + flag = (wp->yoff + (int)wp->sy == (int)w->sy); xasprintf(&value, "%d", flag); return (value); } @@ -2034,7 +2034,7 @@ static void * format_cb_pane_at_right(struct format_tree *ft) { if (ft->wp != NULL) { - if (ft->wp->xoff + ft->wp->sx == ft->wp->window->sx) + if (ft->wp->xoff + (int)ft->wp->sx == (int)ft->wp->window->sx) return (xstrdup("1")); return (xstrdup("0")); } @@ -2045,8 +2045,10 @@ format_cb_pane_at_right(struct format_tree *ft) static void * format_cb_pane_bottom(struct format_tree *ft) { - if (ft->wp != NULL) - return (format_printf("%d", ft->wp->yoff + ft->wp->sy - 1)); + struct window_pane *wp = ft->wp; + + if (wp != NULL) + return (format_printf("%d", wp->yoff + (int)wp->sy - 1)); return (NULL); } @@ -2328,8 +2330,10 @@ format_cb_pane_pb_state(struct format_tree *ft) static void * format_cb_pane_right(struct format_tree *ft) { - if (ft->wp != NULL) - return (format_printf("%d", ft->wp->xoff + ft->wp->sx - 1)); + struct window_pane *wp = ft->wp; + + if (wp != NULL) + return (format_printf("%d", wp->xoff + (int)wp->sx - 1)); return (NULL); } diff --git a/layout-custom.c b/layout-custom.c index aa36d2c4..054be77f 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -288,6 +288,15 @@ layout_parse(struct window *w, const char *layout, char **cause) if (floating_lc != NULL) layout_assign(&wp, floating_lc, PANE_FLOATING); + /* Fix pane Z indexes. */ + while (!TAILQ_EMPTY(&w->z_index)) { + wp = TAILQ_FIRST(&w->z_index); + TAILQ_REMOVE(&w->z_index, wp, zentry); + } + if (floating_lc != NULL) + layout_fix_zindexes(w, floating_lc); + layout_fix_zindexes(w, tiled_lc); + /* Update pane offsets and sizes. */ layout_fix_offsets(w); layout_fix_panes(w, NULL); diff --git a/layout.c b/layout.c index 5afd697b..407c5a05 100644 --- a/layout.c +++ b/layout.c @@ -233,6 +233,30 @@ layout_make_node(struct layout_cell *lc, enum layout_type type) lc->wp = NULL; } +/* Fix Z indexes. */ +void +layout_fix_zindexes(struct window *w, struct layout_cell *lc) +{ + struct layout_cell *lcchild; + + if (lc == NULL) + return; + + switch (lc->type) { + case LAYOUT_WINDOWPANE: + TAILQ_INSERT_TAIL(&w->z_index, lc->wp, zentry); + break; + case LAYOUT_LEFTRIGHT: + case LAYOUT_TOPBOTTOM: + case LAYOUT_FLOATING: + TAILQ_FOREACH(lcchild, &lc->cells, entry) + layout_fix_zindexes(w, lcchild); + return; + default: + fatalx("bad layout type"); + } +} + /* Fix cell offsets for a child cell. */ static void layout_fix_offsets1(struct layout_cell *lc) diff --git a/tmux.h b/tmux.h index 2209732e..5ac1d4f7 100644 --- a/tmux.h +++ b/tmux.h @@ -3438,6 +3438,7 @@ struct layout_cell *layout_search_by_border(struct layout_cell *, u_int, u_int); void layout_set_size(struct layout_cell *, u_int, u_int, int, int); void layout_make_leaf(struct layout_cell *, struct window_pane *); void layout_make_node(struct layout_cell *, enum layout_type); +void layout_fix_zindexes(struct window *, struct layout_cell *); void layout_fix_offsets(struct window *); void layout_fix_panes(struct window *, struct window_pane *); void layout_resize_adjust(struct window *, struct layout_cell *, diff --git a/window.c b/window.c index 72ccb7cc..ac48246f 100644 --- a/window.c +++ b/window.c @@ -585,6 +585,14 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) wp->flags |= PANE_REDRAW; } } + + /* If the pane is floating, move to the front. */ + if (wp->flags & PANE_FLOATING) { + TAILQ_REMOVE(&w->z_index, wp, zentry); + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); + wp->flags |= PANE_REDRAW; + } + if (wp == w->active) break; wp = w->active; @@ -600,18 +608,32 @@ window_get_active_at(struct window *w, u_int x, u_int y) pane_status = options_get_number(w->options, "pane-border-status"); - TAILQ_FOREACH(wp, &w->panes, entry) { + TAILQ_FOREACH(wp, &w->z_index, zentry) { if (!window_pane_visible(wp)) continue; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); - if ((int)x < xoff || x > xoff + sx) - continue; - if (pane_status == PANE_STATUS_TOP) { - if ((int)y <= yoff - 2 || y > yoff + sy - 1) + if (~wp->flags & PANE_FLOATING) { + /* Tiled - to and including bottom or right border. */ + if ((int)x < xoff || x > xoff + sx) continue; + if (pane_status == PANE_STATUS_TOP) { + if ((int)y < yoff - 1 || y > yoff + sy) + continue; + } else { + if ((int)y < yoff || y > yoff + sy) + continue; + } } else { - if ((int)y < yoff || y > yoff + sy) + /* Floating - include top or or left border. */ + if ((int)x < xoff - 1 || x > xoff + sx) continue; + if (pane_status == PANE_STATUS_TOP) { + if ((int)y <= yoff - 2 || y > yoff + sy - 1) + continue; + } else { + if ((int)y < yoff - 1 || y > yoff + sy) + continue; + } } return (wp); } @@ -762,6 +784,12 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, else TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); } + if (~flags & SPAWN_FLOATING) + TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); + else { + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); + wp->flags |= PANE_FLOATING; + } return (wp); } @@ -794,8 +822,8 @@ void window_remove_pane(struct window *w, struct window_pane *wp) { window_lost_pane(w, wp); - TAILQ_REMOVE(&w->panes, wp, entry); + TAILQ_REMOVE(&w->z_index, wp, zentry); window_pane_destroy(wp); } @@ -861,7 +889,7 @@ window_count_panes(struct window *w, int with_floating) u_int n = 0; TAILQ_FOREACH(wp, &w->panes, entry) { - if (with_floating || ~wp->flags & PANE_FLOATING) + if (with_floating || ~wp->flags & PANE_FLOATING) n++; } return (n); @@ -880,6 +908,7 @@ window_destroy_panes(struct window *w) while (!TAILQ_EMPTY(&w->panes)) { wp = TAILQ_FIRST(&w->panes); TAILQ_REMOVE(&w->panes, wp, entry); + TAILQ_REMOVE(&w->z_index, wp, zentry); window_pane_destroy(wp); } } @@ -1214,7 +1243,7 @@ window_pane_reset_mode_all(struct window_pane *wp) static void window_pane_copy_paste(struct window_pane *wp, char *buf, size_t len) { - struct window_pane *loop; + struct window_pane *loop; TAILQ_FOREACH(loop, &wp->window->panes, entry) { if (loop != wp && @@ -1232,7 +1261,7 @@ window_pane_copy_paste(struct window_pane *wp, char *buf, size_t len) static void window_pane_copy_key(struct window_pane *wp, key_code key) { - struct window_pane *loop; + struct window_pane *loop; TAILQ_FOREACH(loop, &wp->window->panes, entry) { if (loop != wp && From a46cdb8bbc06a344c2466af82bb02405372ad3b9 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 19 May 2026 10:26:03 +0000 Subject: [PATCH 2/2] More (currently disabled) bits for creating floating panes, from Michael Grant and Dane Jensen. --- cmd-split-window.c | 72 +++++++++++++++++++++++++++++++++-------- spawn.c | 9 +++++- tmux.h | 8 ++++- window.c | 80 ++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 145 insertions(+), 24 deletions(-) diff --git a/cmd-split-window.c b/cmd-split-window.c index ed3f47fc..c95700dc 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -39,11 +39,12 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:fF:hIkl:m:p:PR:s:S:t:vZ", 0, -1, NULL }, + .args = { "bc:de:fF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL }, .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " - "[-R inactive-border-style] " CMD_TARGET_PANE_USAGE " " + "[-R inactive-border-style] [-x width] [-y height] " + "[-X x-position] [-Y y-position] " CMD_TARGET_PANE_USAGE " " "[shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -70,15 +71,48 @@ const struct cmd_entry cmd_split_window_entry = { }; static struct layout_cell * -cmd_split_window_get_tiled_layout_cell(struct cmdq_item *item, - struct args *args, struct window *w, struct window_pane *wp, int flags) +cmd_split_window_get_floating_cell(struct cmdq_item *item, struct args *args, + struct window *w, struct window_pane *wp) +{ + struct layout_cell *lc = NULL; + char *cause = NULL; + u_int x, y, sx, sy; + + if (window_pane_floating_geometry(w, wp, &x, &y, &sx, &sy, item, args, + &cause) != 0) { + cmdq_error(item, "invalid floating pane geometry %s", cause); + free(cause); + return (NULL); + } + + /* + * Floating panes sit in layout cells which are not in the layout_root + * tree so we call it with parent == NULL. + */ + lc = layout_create_cell(NULL); + lc->xoff = x; + lc->yoff = y; + lc->sx = sx; + lc->sy = sy; + + return (lc); +} + +static struct layout_cell * +cmd_split_window_get_tiled_cell(struct cmdq_item *item, struct args *args, + struct window *w, struct window_pane *wp, int flags) { enum layout_type type; struct layout_cell *lc = NULL; char *cause = NULL; int size; - if (window_pane_tile_geometry(w, wp, &size, &flags, &type, item, args, + if (wp->flags & PANE_FLOATING) { + cmdq_error(item, "can't split a floating pane"); + return (NULL); + } + + if (window_pane_tiled_geometry(w, wp, &size, &flags, &type, item, args, &cause) != 0) { cmdq_error(item, "invalid tiled geometry %s", cause); free(cause); @@ -89,6 +123,7 @@ cmd_split_window_get_tiled_layout_cell(struct cmdq_item *item, lc = layout_split_pane(wp, type, size, flags); if (lc == NULL) cmdq_error(item, "no space for new pane"); + return (lc); } @@ -106,15 +141,19 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *wp = target->wp, *new_wp; struct layout_cell *lc = NULL; struct cmd_find_state fs; - int flags, input; + int input, is_floating, flags = 0; const char *template, *style; char *cause = NULL, *cp; struct args_value *av; u_int count = args_count(args); + if (cmd_get_entry(self) == &cmd_new_pane_entry) + is_floating = 0; /* !args_has(args, 'L'); */ + else + is_floating = 0; input = (args_has(args, 'I') && count == 0); - flags = 0; + flags = is_floating ? SPAWN_FLOATING : 0; if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) @@ -122,7 +161,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (input || (count == 1 && *args_string(args, 0) == '\0')) flags |= SPAWN_EMPTY; - lc = cmd_split_window_get_tiled_layout_cell(item, args, w, wp, flags); + if (is_floating) + lc = cmd_split_window_get_floating_cell(item, args, w, wp); + else + lc = cmd_split_window_get_tiled_cell(item, args, w, wp, flags); if (lc == NULL) return (CMD_RETURN_ERROR); @@ -169,8 +211,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) } options_set_string(new_wp->options, "window-active-style", 0, "%s", style); - new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED - |PANE_THEMECHANGED); + new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED| + PANE_THEMECHANGED); } style = args_get(args, 'S'); if (style != NULL) { @@ -201,7 +243,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) switch (window_pane_start_input(new_wp, item, &cause)) { case -1: server_client_remove_pane(new_wp); - layout_close_pane(new_wp); + if (!is_floating) + layout_close_pane(new_wp); window_remove_pane(wp->window, new_wp); cmdq_error(item, "%s", cause); free(cause); @@ -216,8 +259,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) } if (!args_has(args, 'd')) cmd_find_from_winlink_pane(current, wl, new_wp, 0); - window_pop_zoom(wp->window); - server_redraw_window(wp->window); + + if (!is_floating) { + window_pop_zoom(wp->window); + server_redraw_window(wp->window); + } server_status_session(s); if (args_has(args, 'P')) { diff --git a/spawn.c b/spawn.c index 1d284144..dddb3ce1 100644 --- a/spawn.c +++ b/spawn.c @@ -282,6 +282,13 @@ spawn_pane(struct spawn_context *sc, char **cause) layout_assign_pane(sc->lc, new_wp, 0); } + /* + * If window currently zoomed, window_set_active_pane calls + * window_unzoom which it copies back the saved_layout_cell. + */ + if (w->flags & WINDOW_ZOOMED) + new_wp->saved_layout_cell = new_wp->layout_cell; + /* * Now we have a pane with nothing running in it ready for the new * process. Work out the command and arguments and store the working @@ -374,7 +381,7 @@ spawn_pane(struct spawn_context *sc, char **cause) goto complete; } - /* Store current working directory and change to new one. */ + /* Store current working directory and change to new one. */ if (getcwd(path, sizeof path) != NULL) { if (chdir(new_wp->cwd) == 0) actual_cwd = new_wp->cwd; diff --git a/tmux.h b/tmux.h index 5ac1d4f7..097ba090 100644 --- a/tmux.h +++ b/tmux.h @@ -1346,6 +1346,9 @@ struct window { u_int new_xpixel; u_int new_ypixel; + u_int last_new_pane_x; + u_int last_new_pane_y; + struct utf8_data *fill_character; int flags; #define WINDOW_BELL 0x1 @@ -3421,9 +3424,12 @@ enum client_theme window_pane_get_theme(struct window_pane *); void window_pane_send_theme_update(struct window_pane *); struct style_range *window_pane_border_status_get_range(struct window_pane *, u_int, u_int); -int window_pane_tile_geometry(struct window *, +int window_pane_tiled_geometry(struct window *, struct window_pane *, int *, int *, enum layout_type *, struct cmdq_item *, struct args *, char **); +int window_pane_floating_geometry(struct window *, + struct window_pane *, u_int *, u_int *, u_int *, u_int *, + struct cmdq_item *, struct args *, char **); /* layout.c */ u_int layout_count_cells(struct layout_cell *); diff --git a/window.c b/window.c index ac48246f..e6e6eba7 100644 --- a/window.c +++ b/window.c @@ -541,6 +541,7 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) } tty_update_window_offset(w); + server_redraw_window(w); if (notify) notify_window("window-pane-changed", w); @@ -585,6 +586,8 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) wp->flags |= PANE_REDRAW; } } + if (wp == w->active) + break; /* If the pane is floating, move to the front. */ if (wp->flags & PANE_FLOATING) { @@ -593,9 +596,9 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) wp->flags |= PANE_REDRAW; } - if (wp == w->active) - break; wp = w->active; + if (wp == NULL) + break; } } @@ -613,7 +616,7 @@ window_get_active_at(struct window *w, u_int x, u_int y) continue; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); if (~wp->flags & PANE_FLOATING) { - /* Tiled - to and including bottom or right border. */ + /* Tiled - to and including bottom or right border. */ if ((int)x < xoff || x > xoff + sx) continue; if (pane_status == PANE_STATUS_TOP) { @@ -689,7 +692,6 @@ window_zoom(struct window_pane *wp) if (w->flags & WINDOW_ZOOMED) return (-1); - if (window_count_panes(w, 1) == 1) return (-1); @@ -726,7 +728,7 @@ window_unzoom(struct window *w, int notify) TAILQ_FOREACH(wp, &w->panes, entry) { wp->layout_cell = wp->saved_layout_cell; wp->saved_layout_cell = NULL; - wp->flags ^= PANE_ZOOMED; + wp->flags &= ~PANE_ZOOMED; } layout_fix_panes(w, NULL); @@ -779,7 +781,7 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, TAILQ_INSERT_BEFORE(other, wp, entry); } else { log_debug("%s: @%u after %%%u", __func__, w->id, wp->id); - if (flags & SPAWN_FULLSIZE) + if (flags & (SPAWN_FULLSIZE|SPAWN_FLOATING)) TAILQ_INSERT_TAIL(&w->panes, wp, entry); else TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); @@ -787,8 +789,8 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, if (~flags & SPAWN_FLOATING) TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); else { - TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); wp->flags |= PANE_FLOATING; + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); } return (wp); } @@ -946,7 +948,7 @@ window_pane_printable_flags(struct window_pane *wp) { struct window *w = wp->window; static char flags[32]; - u_int pos = 0; + int pos = 0; if (wp == w->active) flags[pos++] = '*'; @@ -2057,8 +2059,9 @@ window_pane_border_status_get_range(struct window_pane *wp, u_int x, u_int y) return (style_ranges_get_range(srs, x - wp->xoff - 2)); } +/* Work out geometry for tiled panes. */ int -window_pane_tile_geometry(struct window *w, struct window_pane *wp, +window_pane_tiled_geometry(struct window *w, struct window_pane *wp, int *out_size, int *out_flags, enum layout_type *out_type, struct cmdq_item *item, struct args *args, char **cause) { @@ -2106,3 +2109,62 @@ window_pane_tile_geometry(struct window *w, struct window_pane *wp, *out_type = type; return (0); } + +/* Work out geometry for floating panes. */ +int +window_pane_floating_geometry(struct window *w, __unused struct window_pane *wp, + u_int *out_x, u_int *out_y, u_int *out_sx, u_int *out_sy, + struct cmdq_item *item, struct args *args, char **cause) +{ + u_int x, y, sx = w->sx / 2, sy = w->sy / 2; + + if (args_has(args, 'x')) { + sx = args_percentage_and_expand(args, 'x', 0, USHRT_MAX, w->sx, + item, cause); + if (*cause != NULL) + return (-1); + } + if (args_has(args, 'y')) { + sy = args_percentage_and_expand(args, 'y', 0, USHRT_MAX, w->sy, + item, cause); + if (*cause != NULL) + return (-1); + } + + if (args_has(args, 'X')) { + x = args_percentage_and_expand(args, 'X', 0, USHRT_MAX, w->sx, + item, cause); + if (*cause != NULL) + return (-1); + } else { + if (w->last_new_pane_x == 0) + x = 4; + else { + x = w->last_new_pane_x + 4; + if (w->last_new_pane_x > w->sx) + x = 4; + } + w->last_new_pane_x = x; + } + if (args_has(args, 'Y')) { + y = args_percentage_and_expand(args, 'Y', 0, USHRT_MAX, w->sy, + item, cause); + if (*cause != NULL) + return (-1); + } else { + if (w->last_new_pane_y == 0) + y = 2; + else { + y = w->last_new_pane_y + 2; + if (w->last_new_pane_y > w->sy) + y = 2; + } + w->last_new_pane_y = y; + } + + *out_x = x; + *out_y = y; + *out_sx = sx; + *out_sy = sy; + return (0); +}