From 8a3620e438c51b10f4c25cbd54fc45a1f1b9e73a Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 30 Mar 2026 17:47:28 -0400 Subject: [PATCH] Initial commit. --- cmd-tile-float-pane.c | 341 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 cmd-tile-float-pane.c diff --git a/cmd-tile-float-pane.c b/cmd-tile-float-pane.c new file mode 100644 index 00000000..58129289 --- /dev/null +++ b/cmd-tile-float-pane.c @@ -0,0 +1,341 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Michael Grant + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include "tmux.h" + +/* + * float-pane: lift a tiled pane out of the layout tree into a floating pane. + * tile-pane: insert a floating pane back into the tiled layout. + * + * saved_layout_cell is reused to remember the pane's tiled slot while it is + * floating, using the same mechanism as minimise-pane. The cell's wp pointer + * is cleared while the pane is floating so that layout helpers treat the slot + * as empty. + */ + +static enum cmd_retval cmd_float_pane_exec(struct cmd *, struct cmdq_item *); +static enum cmd_retval cmd_tile_pane_exec(struct cmd *, struct cmdq_item *); + +static enum cmd_retval do_float_pane(struct window *, struct window_pane *, + int, int, u_int, u_int); +static enum cmd_retval do_tile_pane(struct window *, struct window_pane *, + struct cmdq_item *); + +const struct cmd_entry cmd_float_pane_entry = { + .name = "float-pane", + .alias = NULL, + + .args = { "t:x:y:w:h:", 0, 0, NULL }, + .usage = "[-h height] [-w width] [-x x] [-y y] " + CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_float_pane_exec +}; + +const struct cmd_entry cmd_tile_pane_entry = { + .name = "tile-pane", + .alias = NULL, + + .args = { "t:", 0, 0, NULL }, + .usage = CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_tile_pane_exec +}; + +/* + * Parse geometry arguments for float-pane. + * Returns 0 on success, -1 on error (error message already set on item). + * x/y/sx/sy are set to parsed values or cascade defaults. + * last_x/last_y are the static cascade counters; pass the address of the + * caller's statics. + */ +static int +parse_float_geometry(struct args *args, struct cmdq_item *item, + struct window *w, int *out_x, int *out_y, u_int *out_sx, u_int *out_sy, + int *last_x, int *last_y) +{ + char *cause = NULL; + int x, y; + u_int sx, sy; + + /* Default size: half the window. */ + sx = w->sx / 2; + sy = w->sy / 2; + + if (args_has(args, 'w')) { + sx = args_strtonum_and_expand(args, 'w', 1, USHRT_MAX, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "width %s", cause); + free(cause); + return (-1); + } + } + if (args_has(args, 'h')) { + sy = args_strtonum_and_expand(args, 'h', 1, USHRT_MAX, item, + &cause); + if (cause != NULL) { + cmdq_error(item, "height %s", cause); + free(cause); + return (-1); + } + } + + /* Default position: cascade from (5,5), step +5, wrap at window edge. */ + if (args_has(args, 'x')) { + x = args_strtonum_and_expand(args, 'x', SHRT_MIN, SHRT_MAX, + item, &cause); + if (cause != NULL) { + cmdq_error(item, "x %s", cause); + free(cause); + return (-1); + } + } else { + if (*last_x == 0) { + x = 5; + } else { + x = (*last_x += 5); + if (*last_x > (int)w->sx) + x = *last_x = 5; + } + } + if (args_has(args, 'y')) { + y = args_strtonum_and_expand(args, 'y', SHRT_MIN, SHRT_MAX, + item, &cause); + if (cause != NULL) { + cmdq_error(item, "y %s", cause); + free(cause); + return (-1); + } + } else { + if (*last_y == 0) { + y = 5; + } else { + y = (*last_y += 5); + if (*last_y > (int)w->sy) + y = *last_y = 5; + } + } + + *last_x = x; + *last_y = y; + *out_x = x; + *out_y = y; + *out_sx = sx; + *out_sy = sy; + return (0); +} + +static enum cmd_retval +cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct window *w = target->wl->window; + struct window_pane *wp = target->wp; + static int last_x = 0, last_y = 0; + int x, y; + u_int sx, sy; + + if (wp->flags & PANE_FLOATING) { + cmdq_error(item, "pane is already floating"); + return (CMD_RETURN_ERROR); + } + if (wp->flags & PANE_MINIMISED) { + cmdq_error(item, "can't float a minimised pane"); + return (CMD_RETURN_ERROR); + } + if (w->flags & WINDOW_ZOOMED) { + cmdq_error(item, "can't float a pane while window is zoomed"); + return (CMD_RETURN_ERROR); + } + + /* + * If no geometry was given explicitly and we have a saved floating + * position from a previous tile-pane, restore it. + */ + if ((wp->flags & PANE_SAVED_FLOAT) && + !args_has(args, 'x') && !args_has(args, 'y') && + !args_has(args, 'w') && !args_has(args, 'h')) { + x = wp->saved_float_xoff; + y = wp->saved_float_yoff; + sx = wp->saved_float_sx; + sy = wp->saved_float_sy; + } else { + if (parse_float_geometry(args, item, w, &x, &y, &sx, &sy, + &last_x, &last_y) != 0) + return (CMD_RETURN_ERROR); + } + + return (do_float_pane(w, wp, x, y, sx, sy)); +} + +static enum cmd_retval +cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item) +{ + __attribute((unused)) struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct window *w = target->wl->window; + struct window_pane *wp = target->wp; + + if (!(wp->flags & PANE_FLOATING)) { + cmdq_error(item, "pane is not floating"); + return (CMD_RETURN_ERROR); + } + if (w->flags & WINDOW_ZOOMED) { + cmdq_error(item, "can't tile a pane while window is zoomed"); + return (CMD_RETURN_ERROR); + } + + return (do_tile_pane(w, wp, item)); +} + +static enum cmd_retval +do_float_pane(struct window *w, struct window_pane *wp, int x, int y, + u_int sx, u_int sy) +{ + struct layout_cell *lc; + + /* + * Remove the pane from the tiled layout tree so neighbours reclaim + * the space. layout_close_pane calls layout_destroy_cell which frees + * the tiled layout_cell and sets wp->layout_cell = NULL via + * layout_free_cell. It also calls layout_fix_offsets/fix_panes and + * notify_window, which is fine to do here before we set up the + * floating cell. + */ + layout_close_pane(wp); /* wp->layout_cell is NULL afterwards */ + + /* Create a detached floating cell with the requested geometry. */ + lc = layout_create_cell(NULL); + lc->xoff = x; + lc->yoff = y; + lc->sx = sx; + lc->sy = sy; + layout_make_leaf(lc, wp); /* sets wp->layout_cell = lc, lc->wp = wp */ + + wp->flags |= PANE_FLOATING; + TAILQ_REMOVE(&w->z_index, wp, zentry); + TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); + + if (w->layout_root != NULL) + layout_fix_offsets(w); + layout_fix_panes(w, NULL); + notify_window("window-layout-changed", w); + server_redraw_window(w); + + return (CMD_RETURN_NORMAL); +} + +static enum cmd_retval +do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item) +{ + struct window_pane *target_wp; + struct layout_cell *float_lc, *lc; + + /* + * Save the floating geometry so we can restore it next time this pane + * is floated without an explicit position/size. + */ + float_lc = wp->layout_cell; + wp->saved_float_xoff = float_lc->xoff; + wp->saved_float_yoff = float_lc->yoff; + wp->saved_float_sx = float_lc->sx; + wp->saved_float_sy = float_lc->sy; + wp->flags |= PANE_SAVED_FLOAT; + + /* + * Free the detached floating cell. Clear its wp pointer first so + * layout_free_cell's WINDOWPANE case does not corrupt wp->layout_cell. + */ + float_lc->wp = NULL; + layout_free_cell(float_lc); /* wp->layout_cell already NULL */ + wp->layout_cell = NULL; + + /* + * Find the best tiled pane to split after: prefer the active pane + * (if tiled), then the most-recently-visited tiled pane, then any + * visible tiled pane. + */ + target_wp = NULL; + if (w->active != NULL && !(w->active->flags & PANE_FLOATING)) + target_wp = w->active; + if (target_wp == NULL) { + TAILQ_FOREACH(target_wp, &w->last_panes, sentry) { + if (!(target_wp->flags & PANE_FLOATING) && + window_pane_visible(target_wp)) + break; + } + } + if (target_wp == NULL) { + TAILQ_FOREACH(target_wp, &w->panes, entry) { + if (!(target_wp->flags & PANE_FLOATING) && + window_pane_visible(target_wp)) + break; + } + } + + if (target_wp != NULL) { + lc = layout_split_pane(target_wp, LAYOUT_TOPBOTTOM, -1, 0); + if (lc == NULL) + lc = layout_split_pane(target_wp, LAYOUT_LEFTRIGHT, + -1, 0); + if (lc == NULL) { + cmdq_error(item, "not enough space to tile pane"); + return (CMD_RETURN_ERROR); + } + layout_assign_pane(lc, wp, 0); + } else { + /* + * No tiled panes at all: make this pane the sole tiled pane + * (new layout root). + */ + lc = layout_create_cell(NULL); + lc->sx = w->sx; + lc->sy = w->sy; + lc->xoff = 0; + lc->yoff = 0; + w->layout_root = lc; + layout_make_leaf(lc, wp); + } + + wp->flags &= ~PANE_FLOATING; + TAILQ_REMOVE(&w->z_index, wp, zentry); + TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); + + window_set_active_pane(w, wp, 1); + + if (w->layout_root != NULL) + layout_fix_offsets(w); + layout_fix_panes(w, NULL); + notify_window("window-layout-changed", w); + server_redraw_window(w); + + return (CMD_RETURN_NORMAL); +}