From 309119f659472c9e18ae20026cf9c444db1e341f Mon Sep 17 00:00:00 2001 From: Thomas Adam Date: Mon, 13 Jul 2026 10:54:27 +0100 Subject: [PATCH 1/8] Remove worflow from master --- .../workflows/update-openbsd-and-portable.yml | 174 ------------------ 1 file changed, 174 deletions(-) delete mode 100644 .github/workflows/update-openbsd-and-portable.yml diff --git a/.github/workflows/update-openbsd-and-portable.yml b/.github/workflows/update-openbsd-and-portable.yml deleted file mode 100644 index c914da332..000000000 --- a/.github/workflows/update-openbsd-and-portable.yml +++ /dev/null @@ -1,174 +0,0 @@ -name: Update OpenBSD tmux and portable - -on: - workflow_dispatch: - schedule: - - cron: "*/15 * * * *" - -permissions: - contents: write - -concurrency: - group: update-openbsd-and-portable - cancel-in-progress: false - -env: - OPENBSD_SRC_URL: https://github.com/openbsd/src.git - OPENBSD_SRC_DIR: openbsd-src - CUTOVER_DIR: tmux-openbsd-cutover - PORTABLE_DIR: tmux-portable - PORTABLE_REPO: ${{ vars.TMUX_PORTABLE_REPO }} - -jobs: - update: - runs-on: ubuntu-latest - steps: - - name: Configure Git identity - run: | - git config --global user.name "tmux update bot" - git config --global user.email "actions@github.com" - - - name: Install git-filter-repo - run: python3 -m pip install --user git-filter-repo - - - name: Checkout cutover repository - uses: actions/checkout@v4 - with: - path: ${{ env.CUTOVER_DIR }} - fetch-depth: 0 - token: ${{ secrets.UPDATE_TOKEN || github.token }} - - - name: Check portable repo setting - run: | - if [ -z "$PORTABLE_REPO" ]; then - echo "Set repository variable TMUX_PORTABLE_REPO, for example owner/tmux-portable." >&2 - exit 1 - fi - - - name: Checkout portable repository - uses: actions/checkout@v4 - with: - repository: ${{ env.PORTABLE_REPO }} - path: ${{ env.PORTABLE_DIR }} - fetch-depth: 0 - token: ${{ secrets.UPDATE_TOKEN }} - - - name: Restore OpenBSD source clone cache - id: openbsd-cache - uses: actions/cache/restore@v4 - with: - path: ${{ env.OPENBSD_SRC_DIR }} - key: openbsd-src-blobless-${{ runner.os }}-${{ github.run_id }} - restore-keys: | - openbsd-src-blobless-${{ runner.os }}- - - - name: Refresh OpenBSD source clone - run: | - set -eu - - if [ ! -d "$OPENBSD_SRC_DIR/.git" ]; then - git clone --filter=blob:none --no-tags --single-branch --branch master \ - "$OPENBSD_SRC_URL" "$OPENBSD_SRC_DIR" - fi - - cd "$OPENBSD_SRC_DIR" - git remote set-url origin "$OPENBSD_SRC_URL" - git fetch --filter=blob:none --no-tags origin master - git switch -C master origin/master - - - name: Capture published OpenBSD tmux base - id: openbsd-base - run: | - set -eu - - cd "$CUTOVER_DIR" - if ! git rev-parse --verify -q origin/openbsd-git >/dev/null; then - echo "origin/openbsd-git is missing; publish the openbsd-git branch before running updates." >&2 - exit 1 - fi - echo "sha=$(git rev-parse origin/openbsd-git)" >> "$GITHUB_OUTPUT" - - - name: Filter OpenBSD tmux history - run: | - set -eu - - cd "$OPENBSD_SRC_DIR" - git switch -C tmux-openbsd origin/master - git filter-repo \ - --force \ - --refs tmux-openbsd \ - --path usr.bin/tmux/ \ - --path-rename usr.bin/tmux/: \ - --replace-refs delete-no-add - - git push "../$CUTOVER_DIR" tmux-openbsd:refs/heads/openbsd-git - - - name: Update cutover master - id: cutover - run: | - set -eu - - cd "$CUTOVER_DIR" - git switch master - - old_base="${{ steps.openbsd-base.outputs.sha }}" - new_base=$(git rev-parse openbsd-git) - - if [ "$old_base" = "$new_base" ]; then - echo "changed=false" >> "$GITHUB_OUTPUT" - echo "No new OpenBSD tmux commits to import; continuing to portable merge." - else - if ! git merge-base --is-ancestor "$old_base" "$new_base"; then - echo "new openbsd-git is not a descendant of origin/openbsd-git; aborting." >&2 - exit 1 - fi - - git cherry-pick "$old_base..$new_base" - echo "changed=true" >> "$GITHUB_OUTPUT" - fi - - - name: Push cutover repository - if: steps.cutover.outputs.changed == 'true' - run: | - set -eu - - cd "$CUTOVER_DIR" - git push origin master openbsd-git - - - name: Merge cutover into portable - id: portable - run: | - set -eu - - cd "$PORTABLE_DIR" - git config remote.tmux-openbsd.tagOpt --no-tags - - if git remote get-url tmux-openbsd >/dev/null 2>&1; then - git remote set-url tmux-openbsd "../$CUTOVER_DIR" - else - git remote add tmux-openbsd "../$CUTOVER_DIR" - fi - - git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master - git switch master - git merge --no-ff --log refs/remotes/tmux-openbsd/master - if git diff --quiet origin/master..master; then - echo "changed=false" >> "$GITHUB_OUTPUT" - else - echo "changed=true" >> "$GITHUB_OUTPUT" - fi - - - name: Push portable repository - if: steps.portable.outputs.changed == 'true' - run: | - set -eu - - cd "$PORTABLE_DIR" - git push origin master - - - name: Save OpenBSD source clone cache - if: always() - uses: actions/cache/save@v4 - with: - path: ${{ env.OPENBSD_SRC_DIR }} - key: openbsd-src-blobless-${{ runner.os }}-${{ github.run_id }} From 447d804a3a23f38b76c2014e270186cdb28cea33 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jul 2026 09:37:39 +0000 Subject: [PATCH 2/8] Size comparison should be >= not >, from Yayo Razo. --- cmd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd.c b/cmd.c index d2e98d466..01f11834b 100644 --- a/cmd.c +++ b/cmd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd.c,v 1.186 2026/07/12 20:35:52 nicm Exp $ */ +/* $OpenBSD: cmd.c,v 1.187 2026/07/13 09:37:39 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -872,7 +872,7 @@ cmd_template_replace(const char *template, const char *s, int idx) ptr++; slen = strlen(s); - if (slen > SIZE_MAX / 3 || len > SIZE_MAX - (slen * 3) - 1) + if (slen >= SIZE_MAX / 3 || len > SIZE_MAX - (slen * 3) - 1) fatalx("argument too long"); buf = xrealloc(buf, len + (slen * 3) + 1); for (cp = s; *cp != '\0'; cp++) { From 6d227a84791536449213f44cf0354b7bf9c86f25 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jul 2026 09:42:12 +0000 Subject: [PATCH 3/8] Set other cell when creating layout set, from Dane Jensen. --- layout-set.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/layout-set.c b/layout-set.c index 7e944d32f..65cb38918 100644 --- a/layout-set.c +++ b/layout-set.c @@ -1,4 +1,4 @@ -/* $OpenBSD: layout-set.c,v 1.39 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: layout-set.c,v 1.40 2026/07/13 09:42:12 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -135,6 +135,21 @@ layout_set_first_tiled(struct window *w) return (NULL); } +static void +layout_set_link_floating(struct window *w, struct layout_cell *lcroot) +{ + struct window_pane *wp; + struct layout_cell *lc; + + TAILQ_FOREACH(wp, &w->panes, entry) { + lc = wp->layout_cell; + if (!layout_cell_is_tiled(lc)) { + TAILQ_INSERT_TAIL(&lcroot->cells, lc, entry); + lc->parent = lcroot; + } + } +} + static void layout_set_even(struct window *w, enum layout_type type) { @@ -267,6 +282,8 @@ layout_set_main_h(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, sx, otherh, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_set_size(lcother, sx, otherh, 0, 0); @@ -364,6 +381,8 @@ layout_set_main_h_mirrored(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, sx, otherh, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_set_size(lcother, sx, otherh, 0, 0); @@ -461,6 +480,8 @@ layout_set_main_v(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, otherw, sy, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_make_node(lcother, LAYOUT_TOPBOTTOM); @@ -559,6 +580,8 @@ layout_set_main_v_mirrored(struct window *w) wp = TAILQ_NEXT(wp, entry); TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry); wp->layout_cell->parent = lcroot; + layout_set_size(wp->layout_cell, otherw, sy, 0, 0); + layout_set_link_floating(w, lcroot); } else { lcother = layout_create_cell(lcroot); layout_make_node(lcother, LAYOUT_TOPBOTTOM); @@ -700,6 +723,7 @@ layout_set_tiled(struct window *w) w->sy - used); } + layout_set_link_floating(w, lcroot); layout_fix_offsets(w); layout_fix_panes(w, NULL); From 2b091d91d38c88b9dc9bbfaea08b6f80b56f6573 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jul 2026 10:03:27 +0000 Subject: [PATCH 4/8] Make split-window inside a floating pane work more nicely and not immediately create an overlapping pane, from Dane Jensen. --- cmd-join-pane.c | 9 ++- cmd-split-window.c | 35 +++++----- layout.c | 158 ++++++++++++++++++++++++++++++++++++++++----- tmux.1 | 19 +++++- tmux.h | 9 ++- 5 files changed, 194 insertions(+), 36 deletions(-) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 1b290a01e..01fbfbe5b 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-join-pane.c,v 1.69 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-join-pane.c,v 1.70 2026/07/13 10:03:27 nicm Exp $ */ /* * Copyright (c) 2011 George Nachman @@ -459,6 +459,13 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } + if (args_has(args, 'h')) + flags |= SPAWN_HORIZONTAL; + if (args_has(args, 'b')) + flags |= SPAWN_BEFORE; + if (args_has(args, 'f')) + flags |= SPAWN_FULLSIZE; + lc = layout_get_tiled_cell(item, args, dst_w, dst_wp, flags, &cause); if (cause != NULL) { cmdq_error(item, "size or position %s", cause); diff --git a/cmd-split-window.c b/cmd-split-window.c index 64fa6b877..22c346c8b 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-split-window.c,v 1.141 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-split-window.c,v 1.142 2026/07/13 10:03:27 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -62,8 +62,8 @@ const struct cmd_entry cmd_split_window_entry = { .usage = "[-bdefhIklPvWZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " - "[-R inactive-border-style] [-T title] " CMD_TARGET_PANE_USAGE " " - "[shell-command [argument ...]]", + "[-R inactive-border-style] [-T title] " + CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -96,14 +96,23 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (cmd_get_entry(self) == &cmd_new_pane_entry) is_floating = !args_has(args, 'L'); - else + else { is_floating = window_pane_is_floating(wp); + flags |= SPAWN_SPLIT; + } - flags = is_floating ? SPAWN_FLOATING : 0; + if (is_floating) + flags |= SPAWN_FLOATING; + if (args_has(args, 'h')) + flags |= SPAWN_HORIZONTAL; if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) flags |= SPAWN_FULLSIZE; + if (args_has(args, 'd')) + flags |= SPAWN_DETACHED; + if (args_has(args, 'Z')) + flags |= SPAWN_ZOOM; input = args_has(args, 'I'); if (input || (count == 1 && *args_string(args, 0) == '\0')) @@ -131,9 +140,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) } } - if (is_floating) - lc = layout_get_floating_cell(item, args, lines, w, wp, &cause); - else + if (flags & SPAWN_FLOATING) { + lc = layout_get_floating_cell(item, args, lines, w, wp, flags, + &cause); + } else lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause); if (cause != NULL) { cmdq_error(item, "%s", cause); @@ -159,12 +169,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) sc.idx = -1; sc.cwd = args_get(args, 'c'); - sc.flags = flags; - if (args_has(args, 'd')) - sc.flags |= SPAWN_DETACHED; - if (args_has(args, 'Z')) - sc.flags |= SPAWN_ZOOM; if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { cmdq_error(item, "create pane failed: %s", cause); @@ -240,10 +245,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) break; } } - if (!args_has(args, 'd')) + if (~flags & SPAWN_DETACHED) cmd_find_from_winlink_pane(current, wl, new_wp, 0); - if (!is_floating) { + if (~flags & SPAWN_FLOATING) { window_pop_zoom(wp->window); server_redraw_window(wp->window); } diff --git a/layout.c b/layout.c index 177d2f4cf..bddad3ab8 100644 --- a/layout.c +++ b/layout.c @@ -1,4 +1,4 @@ -/* $OpenBSD: layout.c,v 1.93 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: layout.c,v 1.94 2026/07/13 10:03:27 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -1598,7 +1598,7 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, struct window *w, struct window_pane *wp, int flags, char **cause) { struct layout_cell *lc; - enum layout_type type; + enum layout_type type = LAYOUT_TOPBOTTOM; u_int curval; int size = -1; char *error = NULL; @@ -1608,12 +1608,11 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, return (NULL); } - type = LAYOUT_TOPBOTTOM; - if (args_has(args, 'h')) + if (flags & SPAWN_HORIZONTAL) type = LAYOUT_LEFTRIGHT; if (args_has(args, 'l') || args_has(args, 'p')) { - if (args_has(args, 'f')) { + if (flags & SPAWN_FULLSIZE) { if (type == LAYOUT_TOPBOTTOM) curval = w->sy; else @@ -1641,12 +1640,7 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, return (NULL); } - if (args_has(args, 'b')) - flags |= SPAWN_BEFORE; - if (args_has(args, 'f')) - flags |= SPAWN_FULLSIZE; - - window_push_zoom(wp->window, 1, args_has(args, 'Z')); + window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); lc = layout_split_pane(wp, type, size, flags); if (lc == NULL) *cause = xstrdup("no space for a new pane"); @@ -1656,17 +1650,24 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, struct layout_cell * layout_get_floating_cell(struct cmdq_item *item, struct args *args, - enum pane_lines lines, struct window *w, struct window_pane *wp, + enum pane_lines lines, struct window *w, struct window_pane *wp, int flags, char **cause) { - struct layout_cell *lcnew; + struct layout_cell *lcnew, *lc = wp->layout_cell; struct layout_geometry fg; layout_geometry_init(&fg); - if (layout_floating_args_parse(item, args, lines, w, &fg, cause) != 0) - return (NULL); + if (flags & SPAWN_SPLIT) { + if (layout_split_floating_cell(lc, w, &fg, lines, flags, cause) + != 0) + return (NULL); + } else { + if (layout_floating_args_parse(item, args, lines, w, &fg, cause) + != 0) + return (NULL); + } - window_push_zoom(wp->window, 1, args_has(args, 'Z')); + window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); lcnew = layout_floating_pane(w, wp, &fg); return (lcnew); } @@ -1766,6 +1767,131 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args, return (0); } +int +layout_split_floating_cell(struct layout_cell *lc, struct window *w, + struct layout_geometry *out, enum pane_lines lines, int flags, + char **cause) +{ + struct layout_geometry old, new; + int tborder = 1, bborder = w->sy - 1; + int lborder = 3, rborder = w->sx - 3; + int border = lines != PANE_LINES_NONE ? 1 : 0; + int size, space; + + /* First, move the target cell in-bounds. */ + memcpy(&old, &lc->g, sizeof old); + if (lborder > old.xoff - border) + old.xoff = lborder + border; + if (rborder < old.xoff + (int)old.sx + border) + old.xoff = rborder - (int)old.sx - border; + if (tborder > old.yoff - border) + old.yoff = tborder + border; + if (bborder < old.yoff + (int)old.sy + border) + old.yoff = bborder - (int)old.sy - border; + + /* Move the new cell to its ideal position. */ + memcpy(&new, &old, sizeof new); + if (flags & SPAWN_HORIZONTAL) { + if (flags & SPAWN_BEFORE) + new.xoff -= old.sx + 2 * border; + else + new.xoff += old.sx + 2 * border; + } else { + if (flags & SPAWN_BEFORE) + new.yoff -= old.sy + 2 * border; + else + new.yoff += old.sy + 2 * border; + } + + /* + * The position of the new cell is checked to see if it is in bounds. + * If it isn't, the availible space is split and equally given to both + * cells. Only one border is check because the target cell is in bounds + * already. + */ + if (lborder > new.xoff - border) { + /* + * The space for both panes is calculated. Since the offsets are + * associated to where pane contents start, we remove pane + * borders from the space. '1' is added in case the space is + * odd. + */ + space = old.xoff + old.sx - lborder - 3 * border + 1; + size = space / 2; + new.sx = size; + old.sx = size; + new.xoff = lborder + border; + old.xoff = new.xoff + new.sx + 2 * border; + /* + * If the original space was to be odd (now even), subtract 1 + * from the rightmost cell + */ + if (space % 2 == 0) + old.sx -= 1; + } else if (rborder < new.xoff + (int)new.sx + border) { + space = rborder - old.xoff - 3 * border + 1; + size = space / 2; + new.sx = size; + old.sx = size; + new.xoff = old.xoff + old.sx + 2 * border; + if (space % 2 == 0) + new.sx -= 1; + } else if (tborder > new.yoff - border) { + space = old.sy + old.yoff - tborder - 3 * border + 1; + size = space / 2; + new.sy = size; + old.sy = size; + new.yoff = tborder + border; + old.yoff = new.yoff + new.sy + 2 * border; + if (space % 2 == 0) + old.sy -= 1; + } else if (bborder < new.yoff + (int)new.sy + border) { + space = bborder - old.yoff - 3 * border + 1; + size = space / 2; + new.sy = size; + old.sy = size; + new.yoff = old.yoff + old.sy + 2 * border; + if (space % 2 == 0) + new.sy -= 1; + } + + /* + * Expand the cell to occupy the whole availible space where it was + * spawned. + */ + if (flags & SPAWN_FULLSIZE) { + if (flags & SPAWN_HORIZONTAL) { + new.yoff = tborder + border; + new.sy = bborder - tborder - 2 * border; + if (flags & SPAWN_BEFORE) { + new.xoff = lborder + border; + new.sx = old.xoff - new.xoff - 2 * border; + } else { + new.sx = rborder - new.xoff - border; + } + } else { + new.xoff = lborder + border; + new.sx = rborder - lborder - 2 * border; + if (flags & SPAWN_BEFORE) { + new.yoff = tborder + border; + new.sy = old.yoff - new.yoff - 2 * border; + } else { + new.sy = bborder - new.yoff - border; + } + } + } + + if (new.sx < PANE_MINIMUM || new.sy < PANE_MINIMUM || + old.sx < PANE_MINIMUM || old.sy < PANE_MINIMUM) { + *cause = xstrdup("no space for a new pane"); + return (-1); + } + + layout_set_size(lc, old.sx, old.sy, old.xoff, old.yoff); + memcpy(out, &new, sizeof *out); + return (0); +} + /* * Removes a cell from the tiled layout by giving the cell's space to the * nearest neighbour. diff --git a/tmux.1 b/tmux.1 index 262187889..01d654a25 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1132 2026/07/10 15:20:06 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1133 2026/07/13 10:03:27 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -14,7 +14,7 @@ .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: July 10 2026 $ +.Dd $Mdocdate: July 13 2026 $ .Dt TMUX 1 .Os .Sh NAME @@ -4075,6 +4075,21 @@ or full window width (with .Fl v ) , instead of splitting the active pane. .Pp +If +.Ar target\-pane +is floating, a new floating pane is created adjacent to +.Ar target\-pane +with equal sizes. +The position is determined by the +.Fl h , +.Fl v , +and +.Fl b +flags. +If any of the new pane would not be visible on the screen, +.Ar target\-pane +and the new pane are resized to fit. +.Pp .Fl k keeps the pane open after the optional .Ar shell\-command diff --git a/tmux.h b/tmux.h index 5031a581f..387911558 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1396 2026/07/10 15:45:11 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1397 2026/07/13 10:03:27 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -2455,6 +2455,8 @@ struct spawn_context { #define SPAWN_EMPTY 0x40 #define SPAWN_ZOOM 0x80 #define SPAWN_FLOATING 0x100 +#define SPAWN_HORIZONTAL 0x200 +#define SPAWN_SPLIT 0x400 }; /* Paste buffer. */ @@ -3790,10 +3792,13 @@ struct layout_cell *layout_get_tiled_cell(struct cmdq_item *, struct args *, struct window *, struct window_pane *, int, char **); struct layout_cell *layout_get_floating_cell(struct cmdq_item *, struct args *, enum pane_lines, struct window *, struct window_pane *, - char **); + int, char **); int layout_floating_args_parse(struct cmdq_item *, struct args *, enum pane_lines, struct window *, struct layout_geometry *, char **); +int layout_split_floating_cell(struct layout_cell *, + struct window *, struct layout_geometry *, enum pane_lines, + int, char **); int layout_remove_tile(struct window *, struct layout_cell *); int layout_insert_tile(struct window *, struct layout_cell *); From fe1b877410c9f4b5ad351427b9de9097d606e927 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jul 2026 10:22:52 +0000 Subject: [PATCH 5/8] Allow drag on a selection to modify it, from Michael Grant. --- window-copy.c | 159 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 151 insertions(+), 8 deletions(-) diff --git a/window-copy.c b/window-copy.c index 997cb8888..0e27af5c5 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-copy.c,v 1.413 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: window-copy.c,v 1.414 2026/07/13 10:22:52 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -103,8 +103,12 @@ static void window_copy_goto_line(struct window_mode_entry *, const char *); static void window_copy_update_cursor(struct window_mode_entry *, u_int, u_int); static void window_copy_start_selection(struct window_mode_entry *); +static int window_copy_mouse_in_selection(struct window_mode_entry *, + u_int, u_int, int *, int *); static int window_copy_adjust_selection(struct window_mode_entry *, u_int *, u_int *); +static int window_copy_update_selection_view(struct window_mode_entry *, + int, int); static int window_copy_set_selection(struct window_mode_entry *, int, int); static int window_copy_update_selection(struct window_mode_entry *, int, int); @@ -862,7 +866,7 @@ window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp, if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); - window_copy_update_selection(wme, 1, 0); + window_copy_update_selection_view(wme, 1, 0); window_pane_scrollbar_show(wp, 1); window_copy_redraw_screen(wme, 1); } @@ -1648,7 +1652,7 @@ window_copy_cmd_scroll_to(struct window_copy_cmd_state *cs, u_int to) data->cy += delta; } - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -2361,6 +2365,7 @@ window_copy_cmd_scroll_down(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_CANCEL); return (WINDOW_COPY_CMD_NOTHING); } + for (; np != 0; np--) window_copy_cursor_down(wme, 1); if (data->scroll_exit && data->oy == 0 && data->screen.sel == NULL) @@ -2374,6 +2379,17 @@ window_copy_cmd_scroll_down_and_cancel(struct window_copy_cmd_state *cs) struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; u_int np = wme->prefix; + int dragging; + + /* With a selection but no active drag, only scroll the view. */ + dragging = (cs->c != NULL && cs->c->tty.mouse_drag_flag != 0); + if (data->screen.sel != NULL && !dragging) { + data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; + /* Move down in the history. */ + window_copy_scroll_up(wme, np); + return (WINDOW_COPY_CMD_NOTHING); + } for (; np != 0; np--) window_copy_cursor_down(wme, 1); @@ -2388,6 +2404,7 @@ window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs) struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; u_int np = wme->prefix; + int dragging; /* * If at the top, nothing can change, so return WINDOW_COPY_CMD_NOTHING @@ -2395,6 +2412,17 @@ window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs) */ if (data->oy == screen_hsize(data->backing)) return (WINDOW_COPY_CMD_NOTHING); + + /* With a selection but no active drag, only scroll the view. */ + dragging = (cs->c != NULL && cs->c->tty.mouse_drag_flag != 0); + if (data->screen.sel != NULL && !dragging) { + data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; + /* Move up in the history. */ + window_copy_scroll_down(wme, np); + return (WINDOW_COPY_CMD_NOTHING); + } + for (; np != 0; np--) window_copy_cursor_up(wme, 1); return (WINDOW_COPY_CMD_MOVE); @@ -3117,7 +3145,7 @@ window_copy_cmd_recentre_top_bottom(struct window_copy_cmd_state *cs) data->cy = cy + (data->oy - oy); break; } - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -5504,6 +5532,76 @@ window_copy_start_selection(struct window_mode_entry *wme) window_copy_set_selection(wme, 1, 0); } +/* + * Return whether x,y is inside the selection and optionally identify the + * endpoint. + */ +static int +window_copy_mouse_in_selection(struct window_mode_entry *wme, u_int x, u_int y, + int *on_start, int *on_end) +{ + struct window_copy_mode_data *data = wme->data; + u_int sx = screen_size_x(&data->screen); + u_int sy = screen_size_y(&data->screen); + u_int hsize, screeny; + u_int mx, my, selx, sely, endselx, endsely; + u_int cursorx, cursory; + long long mpos, spos, epos, dstart, dend; + + if (on_start != NULL) + *on_start = 0; + if (on_end != NULL) + *on_end = 0; + if (data->screen.sel == NULL) + return (0); + + hsize = screen_hsize(data->backing); + screeny = hsize - data->oy; + + selx = window_copy_cursor_offset(wme, data->selx, sx); + sely = data->sely - screeny; + if (data->sely >= screeny && sely < sy && x == selx && y == sely) { + if (on_start != NULL) + *on_start = 1; + return (1); + } + + endselx = window_copy_cursor_offset(wme, data->endselx, sx); + endsely = data->endsely - screeny; + if (data->endsely >= screeny && + endsely < sy && + x == endselx && y == endsely) { + if (on_end != NULL) + *on_end = 1; + return (1); + } + + cursorx = window_copy_cursor_offset(wme, data->cx, + sx); cursory = data->cy; + if (x != cursorx || y != cursory) { + if (!screen_check_selection(&data->screen, x, y)) + return (0); + } + + if (on_start != NULL || on_end != NULL) { + mx = window_copy_cursor_unoffset(wme, x, sx); + my = screeny + y; + mpos = (long long)my * (sx + 1) + mx; + spos = (long long)data->sely * (sx + 1) + data->selx; + epos = (long long)data->endsely * (sx + 1) + data->endselx; + dstart = llabs(mpos - spos); + dend = llabs(mpos - epos); + if (dstart <= dend) { + if (on_start != NULL) + *on_start = 1; + } else { + if (on_end != NULL) + *on_end = 1; + } + } + return (1); +} + static int window_copy_adjust_selection(struct window_mode_entry *wme, u_int *selx, u_int *sely) @@ -5549,6 +5647,35 @@ window_copy_update_selection(struct window_mode_entry *wme, int may_redraw, return (window_copy_set_selection(wme, may_redraw, no_reset)); } +/* + * Update the visible selection after the view has changed. If the mouse is + * not dragging, keep the selection endpoints anchored to the same text. + */ +static int +window_copy_update_selection_view(struct window_mode_entry *wme, int may_redraw, + int no_reset) +{ + struct window_copy_mode_data *data = wme->data; + u_int selx, sely, endselx, endsely; + int changed; + + if (data->cursordrag != CURSORDRAG_NONE) + return (window_copy_update_selection(wme, may_redraw, no_reset)); + + selx = data->selx; + sely = data->sely; + endselx = data->endselx; + endsely = data->endsely; + + changed = window_copy_update_selection(wme, may_redraw, 1); + + data->selx = selx; + data->sely = sely; + data->endselx = endselx; + data->endsely = endsely; + return (changed); +} + static int window_copy_set_selection(struct window_mode_entry *wme, int may_redraw, int no_reset) @@ -6578,7 +6705,7 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); if (window_copy_line_numbers_active(wme)) { if (window_copy_line_number_mode(wme) != WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) { @@ -6649,7 +6776,7 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) if (data->searchmark != NULL && !data->timeout) window_copy_search_marks(wme, NULL, data->searchregex, 1); - window_copy_update_selection(wme, 0, 0); + window_copy_update_selection_view(wme, 0, 0); if (window_copy_line_numbers_active(wme)) { if (window_copy_line_number_mode(wme) != WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) { @@ -6738,6 +6865,7 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) struct window_mode_entry *wme; struct window_copy_mode_data *data; u_int x, y, yg; + int inside_selection, on_start, on_end; if (c == NULL) return; @@ -6758,10 +6886,16 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) c->tty.mouse_drag_release = window_copy_drag_release; data = wme->data; + on_start = on_end = 0; + inside_selection = window_copy_mouse_in_selection(wme, x, y, + &on_start, &on_end); x = window_copy_cursor_unoffset(wme, x, screen_size_x(&data->screen)); yg = screen_hsize(data->backing) + y - data->oy; - if (x < data->selrx || x > data->endselrx || yg != data->selry) + if (on_start || on_end || !inside_selection || + x < data->selrx || x > data->endselrx || yg != data->selry) { + data->lineflag = LINE_SEL_NONE; data->selflag = SEL_CHAR; + } switch (data->selflag) { case SEL_WORD: if (data->separators != NULL) { @@ -6777,7 +6911,15 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) break; case SEL_CHAR: window_copy_update_cursor(wme, x, y); - window_copy_start_selection(wme); + if (!inside_selection) + window_copy_start_selection(wme); + else { + if (on_start) + data->cursordrag = CURSORDRAG_SEL; + else if (on_end) + data->cursordrag = CURSORDRAG_ENDSEL; + window_copy_update_selection(wme, 1, 0); + } break; } @@ -6853,6 +6995,7 @@ window_copy_drag_release(struct client *c, struct mouse_event *m) data = wme->data; if (window_copy_line_numbers_active(wme)) window_copy_drag_update(c, m); + data->cursordrag = CURSORDRAG_NONE; evtimer_del(&data->dragtimer); } From 4a06a1f2960605536bf10ef834d8dbf80df4018e Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jul 2026 10:29:17 +0000 Subject: [PATCH 6/8] Work out a single layout for the entire prompt so it can all be drawn together, fixes centered prompts. GitHub issue 5328. --- prompt.c | 301 +++++++++++++++++++++++++++++++++++++++---------------- tmux.h | 4 +- 2 files changed, 218 insertions(+), 87 deletions(-) diff --git a/prompt.c b/prompt.c index 6187a7a12..c12a249a9 100644 --- a/prompt.c +++ b/prompt.c @@ -1,4 +1,4 @@ -/* $OpenBSD: prompt.c,v 1.4 2026/06/26 14:40:30 nicm Exp $ */ +/* $OpenBSD: prompt.c,v 1.5 2026/07/13 10:29:17 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -42,6 +42,8 @@ struct prompt { char *word_separators; struct grid_cell style; struct grid_cell command_style; + char *style_str; + char *command_style_str; enum screen_cursor_style cstyle; enum screen_cursor_style command_cstyle; int ccolour; @@ -61,11 +63,30 @@ struct prompt { char *complete_display; }; +struct prompt_layout { + u_int area_x; + u_int area_width; + + u_int content_x; + u_int content_width; + + u_int label_width; + u_int input_x; + u_int cursor_x; + + u_int input_offset; + u_int input_width; +}; + static char *prompt_complete(struct prompt *, const char *, u_int); static void prompt_clear_complete(struct prompt *); -static char *prompt_expand(struct prompt *); +static struct format_tree *prompt_format_tree(struct prompt *); +static char *prompt_expand1(struct prompt *, struct format_tree *); +static void prompt_effective_style(struct prompt *, struct style *, + struct format_tree *); +static void prompt_layout(struct prompt *, u_int, u_int, + struct prompt_layout *, char **, struct style *); static int prompt_replace_complete(struct prompt *, const char *); -static u_int prompt_width(struct prompt *, u_int); /* Get prompt flags as a string. */ static const char * @@ -120,6 +141,8 @@ prompt_set_options(struct prompt_create_data *pd, struct session *s) style_apply(&pd->style, oo, "message-style", NULL); style_apply(&pd->command_style, oo, "message-command-style", NULL); + pd->style_str = options_get_string(oo, "message-style"); + pd->command_style_str = options_get_string(oo, "message-command-style"); n = options_get_number(oo, "prompt-cursor-style"); screen_set_cursor_style(n, &pd->cstyle, &pd->cmode); n = options_get_number(oo, "prompt-command-cursor-style"); @@ -179,6 +202,8 @@ prompt_create(const struct prompt_create_data *pd) memcpy(&pr->style, &pd->style, sizeof pr->style); memcpy(&pr->command_style, &pd->command_style, sizeof pr->command_style); + pr->style_str = xstrdup(pd->style_str); + pr->command_style_str = xstrdup(pd->command_style_str); pr->cstyle = pd->cstyle; pr->command_cstyle = pd->command_cstyle; pr->ccolour = pd->ccolour; @@ -201,6 +226,8 @@ prompt_free(struct prompt *pr) if (pr->freecb != NULL && pr->data != NULL) pr->freecb(pr->data); free(pr->message_format); + free(pr->style_str); + free(pr->command_style_str); free(pr->word_separators); free(pr->last); free(pr->string); @@ -321,16 +348,17 @@ prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, * 0 otherwise. */ static int -prompt_redraw_quote(const struct prompt *pr, u_int pcursor, - struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, +prompt_redraw_quote(const struct prompt *pr, u_int pcursor, u_int input_x, + struct screen_write_ctx *ctx, u_int offset, u_int pw, u_int *w, struct grid_cell *gc) { struct utf8_data ud; - if (pr->flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) { + if (pr->flags & PROMPT_QUOTENEXT && + pcursor >= offset && + ctx->s->cx == input_x + pcursor - offset) { utf8_set(&ud, '^'); - return (prompt_redraw_character(ctx, offset, pwidth, - width, gc, &ud)); + return (prompt_redraw_character(ctx, offset, pw, w, gc, &ud)); } return (1); } @@ -368,12 +396,12 @@ prompt_draw_complete(struct prompt *pr, struct screen_write_ctx *ctx, u_int ax, free(ud); } -/* Expand prompt string using the current input. */ -static char * -prompt_expand(struct prompt *pr) +/* Create the prompt format tree using the current input. */ +static struct format_tree * +prompt_format_tree(struct prompt *pr) { struct format_tree *ft; - char *expanded, *prompt, *tmp; + char *tmp; if (cmd_find_valid_state(&pr->state)) ft = format_create_from_state(NULL, NULL, &pr->state); @@ -385,31 +413,145 @@ prompt_expand(struct prompt *pr) format_add(ft, "prompt_flags", "%s", prompt_flags_to_string(pr->flags)); format_add(ft, "prompt_type", "%s", prompt_type_string(pr->type)); - prompt = format_expand_time(ft, pr->string); - format_add(ft, "message", "%s", prompt); if (pr->flags & PROMPT_COMMANDMODE) format_add(ft, "command_prompt", "1"); else format_add(ft, "command_prompt", "0"); + return (ft); +} + +/* Expand prompt string using the current input. */ +static char * +prompt_expand1(struct prompt *pr, struct format_tree *ft) +{ + char *expanded, *prompt; + + prompt = format_expand_time(ft, pr->string); + format_add(ft, "message", "%s", prompt); expanded = format_expand_time(ft, pr->message_format); free(prompt); - format_free(ft); return (expanded); } -/* Work out the width used by the prompt string. */ -static u_int -prompt_width(struct prompt *pr, u_int aw) +/* Get the effective message style for the current prompt. */ +static void +prompt_effective_style(struct prompt *pr, struct style *sy, + struct format_tree *ft) { - char *expanded; - u_int start; + const char *s; + struct grid_cell *gc; + char *expanded; - expanded = prompt_expand(pr); - start = format_width(expanded); - if (start > aw) - start = aw; - free(expanded); - return (start); + if (pr->flags & PROMPT_COMMANDMODE) { + s = pr->command_style_str; + gc = &pr->command_style; + } else { + s = pr->style_str; + gc = &pr->style; + } + + style_set(sy, gc); + if (s != NULL) { + expanded = format_expand_time(ft, s); + if (style_parse(sy, &grid_default_cell, expanded) != 0) + style_set(sy, gc); + free(expanded); + } +} + +/* Work out where the editable prompt content appears. */ +static void +prompt_layout(struct prompt *pr, u_int ax, u_int aw, struct prompt_layout *pl, + char **expanded, struct style *sy) +{ + char *local = NULL; + struct format_tree *ft; + u_int pcursor, pwidth, end, width, offset, avail; + + memset(pl, 0, sizeof *pl); + pl->area_x = ax; + pl->area_width = aw; + + ft = prompt_format_tree(pr); + if (sy != NULL) + prompt_effective_style(pr, sy, ft); + if (expanded != NULL) + *expanded = prompt_expand1(pr, ft); + else { + local = prompt_expand1(pr, ft); + expanded = &local; + } + format_free(ft); + + if (aw == 0) { + free(local); + return; + } + + pl->label_width = format_width(*expanded); + if (pl->label_width > aw) + pl->label_width = aw; + + pcursor = utf8_strwidth(pr->buffer, pr->index); + pwidth = utf8_strwidth(pr->buffer, -1); + if (pr->flags & PROMPT_QUOTENEXT) + pwidth++; + + avail = aw - pl->label_width; + if (avail == 0) { + pl->input_offset = 0; + pl->input_width = 0; + pl->cursor_x = pl->label_width; + } else { + if (pcursor >= avail) { + offset = (pcursor - avail) + 1; + width = avail; + } else { + offset = 0; + width = pwidth; + } + if (width > avail) + width = avail; + + pl->input_offset = offset; + pl->input_width = width; + pl->cursor_x = pl->label_width + pcursor - offset; + } + + pl->content_width = pl->label_width + pl->input_width; + if (pr->complete_display != NULL && + pr->index == utf8_strlen(pr->buffer) && + pl->cursor_x < aw) { + avail = aw - pl->cursor_x; + width = utf8_cstrwidth(pr->complete_display); + if (width > avail) + width = avail; + end = pl->cursor_x + width; + if (end > pl->content_width) + pl->content_width = end; + } + if (pl->content_width > aw) + pl->content_width = aw; + + if (sy != NULL) { + switch (sy->align) { + case STYLE_ALIGN_CENTRE: + case STYLE_ALIGN_ABSOLUTE_CENTRE: + pl->content_x = ax + (aw - pl->content_width) / 2; + break; + case STYLE_ALIGN_RIGHT: + pl->content_x = ax + aw - pl->content_width; + break; + default: + pl->content_x = ax; + break; + } + } else + pl->content_x = ax; + + pl->input_x = pl->content_x + pl->label_width; + pl->cursor_x += pl->content_x; + free(local); } /* Choose a completion from a mouse position. */ @@ -460,70 +602,59 @@ prompt_draw(struct prompt *pr, struct prompt_draw_data *pd) { struct screen_write_ctx *ctx = pd->ctx; struct screen *s = ctx->s; - u_int ax = pd->area_x, py = pd->prompt_line; - u_int aw = pd->area_width, *cx = pd->cursor_x; + u_int ax = pd->area_x, py = pd->prompt_line, *cx; + u_int aw = pd->area_width; struct grid_cell gc; - u_int i, offset, left, start, width; - u_int pcursor, pwidth; + struct prompt_layout pl; + struct style sy; + u_int i, width, pcursor; char *expanded; - /* Choose the cursor colour and style for this prompt. */ if (pr->flags & PROMPT_COMMANDMODE) { - memcpy(&gc, &pr->command_style, sizeof gc); s->default_cstyle = pr->command_cstyle; s->default_mode = pr->command_cmode; s->default_ccolour = pr->command_ccolour; } else { - memcpy(&gc, &pr->style, sizeof gc); s->default_cstyle = pr->cstyle; s->default_mode = pr->cmode; s->default_ccolour = pr->ccolour; } - expanded = prompt_expand(pr); - start = format_width(expanded); - if (start > aw) - start = aw; - *cx = ax + start; + prompt_layout(pr, ax, aw, &pl, &expanded, &sy); + memcpy(&gc, &sy.gc, sizeof gc); + cx = pd->cursor_x; + *cx = pl.cursor_x; screen_write_cursormove(ctx, ax, py, 0); - format_draw(ctx, &gc, aw, expanded, NULL, 0); - screen_write_cursormove(ctx, ax + start, py, 0); - free(expanded); - - left = aw - start; - if (left == 0) - return; + if (sy.fill != 8) + screen_write_clearcharacter(ctx, aw, sy.fill); pcursor = utf8_strwidth(pr->buffer, pr->index); - pwidth = utf8_strwidth(pr->buffer, -1); - if (pr->flags & PROMPT_QUOTENEXT) - pwidth++; - if (pcursor >= left) { - /* - * The cursor would be outside the screen so start drawing - * with it on the right. - */ - offset = (pcursor - left) + 1; - pwidth = left; - } else - offset = 0; - if (pwidth > left) - pwidth = left; - *cx = ax + start + pcursor - offset; - width = 0; - for (i = 0; pr->buffer[i].size != 0; i++) { - if (!prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, - &width, &gc)) - break; - if (!prompt_redraw_character(ctx, offset, pwidth, &width, &gc, - &pr->buffer[i])) - break; + if (pl.content_width != 0) { + screen_write_cursormove(ctx, pl.content_x, py, 0); + if (pl.label_width != 0) + format_draw(ctx, &gc, pl.label_width, expanded, NULL, + 0); + + screen_write_cursormove(ctx, pl.input_x, py, 0); + width = 0; + for (i = 0; pr->buffer[i].size != 0; i++) { + if (!prompt_redraw_quote(pr, pcursor, pl.input_x, + ctx, pl.input_offset, pl.input_width, &width, + &gc)) + break; + if (!prompt_redraw_character(ctx, pl.input_offset, + pl.input_width, &width, &gc, &pr->buffer[i])) + break; + } + prompt_redraw_quote(pr, pcursor, pl.input_x, ctx, + pl.input_offset, pl.input_width, &width, &gc); + + prompt_draw_complete(pr, ctx, pl.content_x, pl.content_width, + pl.cursor_x, py, &gc); } - prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, &width, &gc); - - prompt_draw_complete(pr, ctx, ax, aw, *cx, py, &gc); + free(expanded); } /* Move cursor in prompt from a mouse position. */ @@ -532,36 +663,34 @@ prompt_mouse(struct prompt *pr, u_int x, u_int ax, u_int aw, int *redraw) { struct utf8_data *ud; enum prompt_key_result result; - u_int cx, start, left, pcursor, pwidth, offset, width; - u_int target; + struct prompt_layout pl; + struct style sy; + char *expanded; + u_int pwidth, width, target; size_t idx; if (x < ax || x >= ax + aw) return (PROMPT_KEY_NOT_HANDLED); - start = prompt_width(pr, aw); - left = aw - start; - if (left == 0) + prompt_layout(pr, ax, aw, &pl, &expanded, &sy); + free(expanded); + + if (pl.input_width == 0) return (PROMPT_KEY_HANDLED); - pcursor = utf8_strwidth(pr->buffer, pr->index); pwidth = utf8_strwidth(pr->buffer, -1); if (pr->flags & PROMPT_QUOTENEXT) pwidth++; - if (pcursor >= left) - offset = (pcursor - left) + 1; - else - offset = 0; - cx = ax + start + pcursor - offset; - result = prompt_mouse_complete(pr, x, cx, ax, aw, redraw); + result = prompt_mouse_complete(pr, x, pl.cursor_x, pl.content_x, + pl.content_width, redraw); if (result != PROMPT_KEY_NOT_HANDLED) return (result); - if (x <= ax + start) - target = offset; + if (x <= pl.input_x) + target = pl.input_offset; else - target = offset + x - (ax + start); + target = pl.input_offset + x - pl.input_x; if (target > pwidth) target = pwidth; diff --git a/tmux.h b/tmux.h index 387911558..ea357d08c 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1397 2026/07/13 10:03:27 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1398 2026/07/13 10:29:17 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -2086,6 +2086,8 @@ struct prompt_create_data { struct grid_cell style; struct grid_cell command_style; + const char *style_str; + const char *command_style_str; enum screen_cursor_style cstyle; enum screen_cursor_style command_cstyle; int ccolour; From 006cce5e4d7ab6f9c09b988b03b1e040231e3f5c Mon Sep 17 00:00:00 2001 From: jsg Date: Mon, 13 Jul 2026 12:53:56 +0000 Subject: [PATCH 7/8] fix window_copy_mouse_in_selection() indentation; ok nicm@ --- window-copy.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/window-copy.c b/window-copy.c index 0e27af5c5..6d0390fe9 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-copy.c,v 1.414 2026/07/13 10:22:52 nicm Exp $ */ +/* $OpenBSD: window-copy.c,v 1.415 2026/07/13 12:53:56 jsg Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -5576,8 +5576,8 @@ window_copy_mouse_in_selection(struct window_mode_entry *wme, u_int x, u_int y, return (1); } - cursorx = window_copy_cursor_offset(wme, data->cx, - sx); cursory = data->cy; + cursorx = window_copy_cursor_offset(wme, data->cx, sx); + cursory = data->cy; if (x != cursorx || y != cursory) { if (!screen_check_selection(&data->screen, x, y)) return (0); From e00a1a3e2656bdb1aa2b4bb10edf30a3c864adf1 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 13 Jul 2026 13:01:14 +0000 Subject: [PATCH 8/8] Add some additional simple events. --- cmd-break-pane.c | 9 ++++--- cmd-join-pane.c | 3 ++- cmd-swap-pane.c | 9 ++++++- input.c | 8 +++--- options-table.c | 10 ++++++- resize.c | 22 +++++++++++++-- server-client.c | 69 +++++++++++++++++++++++++++++++++++++++++++++--- session.c | 51 ++++++++++++++++++++++++++++++++--- spawn.c | 6 +++-- tmux.1 | 18 ++++++++++++- tmux.h | 4 ++- window.c | 30 +++++++++++++++++++-- 12 files changed, 216 insertions(+), 23 deletions(-) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 8d85c57b2..dcc5a6af4 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-break-pane.c,v 1.72 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-break-pane.c,v 1.73 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -98,9 +98,9 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) struct session *src_s = source->s; struct session *dst_s = target->s; struct window_pane *wp = source->wp; - struct window *w = wl->window; + struct window *w = wl->window, *old_w = w; char *cause, *cp; - int idx = target->idx, before; + int idx = target->idx, before, old_idx = wl->idx; const char *template, *name = args_get(args, 'n'); if (args_has(args, 'W')) @@ -137,6 +137,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) wl = winlink_find_by_window(&dst_s->windows, w); if (wl == NULL) return (CMD_RETURN_ERROR); + window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx); goto out; } if (idx != -1 && winlink_find_by_index(&dst_s->windows, idx) != NULL) { @@ -175,6 +176,8 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) idx = -1 - options_get_number(dst_s->options, "base-index"); wl = session_attach(dst_s, w, idx, &cause); /* can't fail */ window_remove_ref(w, __func__); + events_fire_window("window-created", w); + window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx); if (!args_has(args, 'd')) { session_select(dst_s, wl->idx); cmd_find_from_session(current, dst_s, 0); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 01fbfbe5b..927ccda43 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-join-pane.c,v 1.70 2026/07/13 10:03:27 nicm Exp $ */ +/* $OpenBSD: cmd-join-pane.c,v 1.71 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2011 George Nachman @@ -506,6 +506,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) } else server_status_session(dst_s); + window_fire_pane_moved(src_wp, src_w, src_wl->idx, dst_w, dst_idx); if (window_count_panes(src_w, 1) == 0) server_kill_window(src_w, 1); else diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index da7ea3706..b2e53f315 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-swap-pane.c,v 1.53 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-swap-pane.c,v 1.54 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -68,11 +68,14 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *tmp_wp, *src_wp, *dst_wp; struct layout_cell *src_lc, *dst_lc; u_int sx, sy, xoff, yoff; + int src_idx, dst_idx; dst_w = target->wl->window; dst_wp = target->wp; + dst_idx = target->wl->idx; src_w = source->wl->window; src_wp = source->wp; + src_idx = source->wl->idx; if (window_push_zoom(dst_w, 0, args_has(args, 'Z'))) server_redraw_window(dst_w); @@ -180,6 +183,10 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) redraw_invalidate_scene(dst_w); server_redraw_window(dst_w); + if (src_w != dst_w) { + window_fire_pane_moved(src_wp, src_w, src_idx, dst_w, dst_idx); + window_fire_pane_moved(dst_wp, dst_w, dst_idx, src_w, src_idx); + } events_fire_window("window-layout-changed", src_w); if (src_w != dst_w) events_fire_window("window-layout-changed", dst_w); diff --git a/input.c b/input.c index fe7297dae..46e7b91aa 100644 --- a/input.c +++ b/input.c @@ -1,4 +1,4 @@ -/* $OpenBSD: input.c,v 1.266 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: input.c,v 1.267 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1316,8 +1316,10 @@ input_c0_dispatch(struct input_ctx *ictx) case '\000': /* NUL */ break; case '\007': /* BEL */ - if (wp != NULL) + if (wp != NULL) { + events_fire_pane("pane-bell", wp); alerts_queue(wp->window, WINDOW_BELL); + } break; case '\010': /* BS */ screen_write_backspace(sctx); @@ -2180,7 +2182,7 @@ input_csi_dispatch_winops(struct input_ctx *ictx) screen_pop_title(sctx->s); if (wp == NULL) break; - events_fire_pane("pane-title-changed", wp); + input_fire_pane_title_changed(wp, sctx->s->title); server_redraw_window_borders(w); server_status_window(w); break; diff --git a/options-table.c b/options-table.c index 49cf9730f..3e8dd5b46 100644 --- a/options-table.c +++ b/options-table.c @@ -1,4 +1,4 @@ -/* $OpenBSD: options-table.c,v 1.233 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: options-table.c,v 1.234 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -1925,6 +1925,8 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_HOOK("alert-silence", ""), OPTIONS_TABLE_HOOK("client-active", ""), OPTIONS_TABLE_HOOK("client-attached", ""), + OPTIONS_TABLE_HOOK("client-created", ""), + OPTIONS_TABLE_HOOK("client-closed", ""), OPTIONS_TABLE_HOOK("client-detached", ""), OPTIONS_TABLE_HOOK("client-focus-in", ""), OPTIONS_TABLE_HOOK("client-focus-out", ""), @@ -1934,6 +1936,7 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_HOOK("client-dark-theme", ""), OPTIONS_TABLE_HOOK("command-error", ""), OPTIONS_TABLE_HOOK("marked-pane-changed", ""), + OPTIONS_TABLE_PANE_HOOK("pane-bell", ""), OPTIONS_TABLE_PANE_HOOK("pane-command-finished", ""), OPTIONS_TABLE_PANE_HOOK("pane-command-started", ""), OPTIONS_TABLE_PANE_HOOK("pane-created", ""), @@ -1944,6 +1947,7 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_PANE_HOOK("pane-mode-changed", ""), OPTIONS_TABLE_PANE_HOOK("pane-mode-entered", ""), OPTIONS_TABLE_PANE_HOOK("pane-mode-exited", ""), + OPTIONS_TABLE_PANE_HOOK("pane-moved", ""), OPTIONS_TABLE_PANE_HOOK("pane-prompt-closed", ""), OPTIONS_TABLE_PANE_HOOK("pane-prompt-opened", ""), OPTIONS_TABLE_PANE_HOOK("pane-resized", ""), @@ -1952,8 +1956,12 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_PANE_HOOK("pane-title-changed", ""), OPTIONS_TABLE_HOOK("session-closed", ""), OPTIONS_TABLE_HOOK("session-created", ""), + OPTIONS_TABLE_HOOK("session-added-to-group", ""), OPTIONS_TABLE_HOOK("session-renamed", ""), + OPTIONS_TABLE_HOOK("session-removed-from-group", ""), OPTIONS_TABLE_HOOK("session-window-changed", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-created", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-closed", ""), OPTIONS_TABLE_WINDOW_HOOK("window-layout-changed", ""), OPTIONS_TABLE_HOOK("window-linked", ""), OPTIONS_TABLE_WINDOW_HOOK("window-pane-changed", ""), diff --git a/resize.c b/resize.c index c8257cec0..1d83e4b70 100644 --- a/resize.c +++ b/resize.c @@ -1,4 +1,4 @@ -/* $OpenBSD: resize.c,v 1.56 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: resize.c,v 1.57 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -22,9 +22,27 @@ #include "tmux.h" +static void +resize_fire_window_resized(struct window *w, u_int old_sx, u_int old_sy) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_window(&fs, w, 0); + event_payload_set_target(ep, &fs); + event_payload_set_window(ep, "window", w); + event_payload_set_uint(ep, "width", w->sx); + event_payload_set_uint(ep, "height", w->sy); + event_payload_set_uint(ep, "old_width", old_sx); + event_payload_set_uint(ep, "old_height", old_sy); + events_fire("window-resized", ep); +} + void resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) { + u_int old_sx = w->sx, old_sy = w->sy; int zoomed; /* Check size limits. */ @@ -61,7 +79,7 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) tty_update_window_offset(w); server_redraw_window(w); events_fire_window("window-layout-changed", w); - events_fire_window("window-resized", w); + resize_fire_window_resized(w, old_sx, old_sy); w->flags &= ~WINDOW_RESIZE; } diff --git a/server-client.c b/server-client.c index 86360de20..971b8d1aa 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.486 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.487 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -407,6 +407,62 @@ server_client_attached_lost(struct client *c) } } +/* Fire client session changed. */ +static void +server_client_fire_session_changed(struct client *c, struct session *old) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_client(&fs, c, 0); + event_payload_set_target(ep, &fs); + event_payload_set_client(ep, "client", c); + if (fs.s != NULL) { + event_payload_set_session(ep, "session", fs.s); + event_payload_set_session(ep, "new_session", fs.s); + } + if (old != NULL) + event_payload_set_session(ep, "old_session", old); + if (fs.w != NULL) + event_payload_set_window(ep, "window", fs.w); + if (fs.wl != NULL) + event_payload_set_int(ep, "window_index", fs.wl->idx); + else if (fs.idx != -1) + event_payload_set_int(ep, "window_index", fs.idx); + if (fs.wp != NULL) + event_payload_set_pane(ep, "pane", fs.wp); + events_fire("client-session-changed", ep); +} + +/* Fire client resized. */ +static void +server_client_fire_resized(struct client *c, u_int old_sx, u_int old_sy) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_client(&fs, c, 0); + event_payload_set_target(ep, &fs); + event_payload_set_client(ep, "client", c); + if (fs.s != NULL) + event_payload_set_session(ep, "session", fs.s); + if (fs.w != NULL) + event_payload_set_window(ep, "window", fs.w); + if (fs.wl != NULL) + event_payload_set_int(ep, "window_index", fs.wl->idx); + else if (fs.idx != -1) + event_payload_set_int(ep, "window_index", fs.idx); + if (fs.wp != NULL) + event_payload_set_pane(ep, "pane", fs.wp); + event_payload_set_uint(ep, "width", c->tty.sx); + event_payload_set_uint(ep, "height", c->tty.sy); + event_payload_set_uint(ep, "old_width", old_sx); + event_payload_set_uint(ep, "old_height", old_sy); + events_fire("client-resized", ep); +} + /* Set client session. */ void server_client_set_session(struct client *c, struct session *s) @@ -433,7 +489,7 @@ server_client_set_session(struct client *c, struct session *s) alerts_check_session(s); tty_update_client_offset(c); status_timer_start(c); - events_fire_client("client-session-changed", c); + server_client_fire_session_changed(c, old); server_redraw_client(c); } @@ -470,6 +526,8 @@ server_client_lost(struct client *c) server_client_attached_lost(c); events_fire_client("client-detached", c); } + if (c->name != NULL && (c->flags & (CLIENT_CONTROL|CLIENT_TERMINAL))) + events_fire_client("client-closed", c); if (c->flags & CLIENT_CONTROL) control_stop(c); @@ -2425,6 +2483,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) struct client *c = arg; ssize_t datalen; struct session *s; + u_int old_sx, old_sy; if (c->flags & CLIENT_DEAD) return; @@ -2463,6 +2522,8 @@ server_client_dispatch(struct imsg *imsg, void *arg) if (c->flags & CLIENT_CONTROL) break; server_client_update_latest(c); + old_sx = c->tty.sx; + old_sy = c->tty.sy; tty_resize(&c->tty); tty_repeat_requests(&c->tty, 0); recalculate_sizes(); @@ -2472,7 +2533,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) c->overlay_resize(c, c->overlay_data); server_redraw_client(c); if (c->session != NULL) - events_fire_client("client-resized", c); + server_client_fire_resized(c, old_sx, old_sy); break; case MSG_EXITING: if (datalen != 0) @@ -2774,6 +2835,8 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) close(c->out_fd); c->out_fd = -1; } + if (c->flags & (CLIENT_CONTROL|CLIENT_TERMINAL)) + events_fire_client("client-created", c); /* If pasting has taken too long, turn it off. */ if (c->flags & (CLIENT_BRACKETPASTING|CLIENT_ASSUMEPASTING) && diff --git a/session.c b/session.c index 9c23c4b50..f51df3bbd 100644 --- a/session.c +++ b/session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: session.c,v 1.105 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: session.c,v 1.106 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -473,6 +473,29 @@ session_last(struct session *s) return (session_set_current(s, wl)); } +/* Fire session window changed. */ +static void +session_fire_window_changed(struct session *s, struct winlink *wl, + struct winlink *old) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_winlink(&fs, wl, 0); + event_payload_set_target(ep, &fs); + event_payload_set_session(ep, "session", s); + event_payload_set_window(ep, "window", wl->window); + event_payload_set_window(ep, "new_window", wl->window); + event_payload_set_int(ep, "window_index", wl->idx); + event_payload_set_int(ep, "new_window_index", wl->idx); + if (old != NULL) { + event_payload_set_window(ep, "old_window", old->window); + event_payload_set_int(ep, "old_window_index", old->idx); + } + events_fire("session-window-changed", ep); +} + /* Set current winlink to wl .*/ int session_set_current(struct session *s, struct winlink *wl) @@ -495,7 +518,7 @@ session_set_current(struct session *s, struct winlink *wl) winlink_clear_flags(wl); window_update_activity(wl->window); tty_update_window_offset(wl->window); - events_fire_session("session-window-changed", s); + session_fire_window_changed(s, wl, old); return (0); } @@ -542,12 +565,33 @@ session_group_new(const char *name) return (sg); } +/* Fire session group changed. */ +static void +session_group_fire(const char *name, struct session_group *sg, + struct session *s) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + if (session_alive(s)) { + cmd_find_from_session(&fs, s, 0); + event_payload_set_target(ep, &fs); + } + event_payload_set_session(ep, "session", s); + event_payload_set_string(ep, "group", "%s", sg->name); + event_payload_set_uint(ep, "group_size", session_group_count(sg)); + events_fire(name, ep); +} + /* Add a session to a session group. */ void session_group_add(struct session_group *sg, struct session *s) { - if (session_group_contains(s) == NULL) + if (session_group_contains(s) == NULL) { TAILQ_INSERT_TAIL(&sg->sessions, s, gentry); + session_group_fire("session-added-to-group", sg, s); + } } /* Remove a session from its group and destroy the group if empty. */ @@ -558,6 +602,7 @@ session_group_remove(struct session *s) if ((sg = session_group_contains(s)) == NULL) return; + session_group_fire("session-removed-from-group", sg, s); TAILQ_REMOVE(&sg->sessions, s, gentry); if (TAILQ_EMPTY(&sg->sessions)) { RB_REMOVE(session_groups, &session_groups, sg); diff --git a/spawn.c b/spawn.c index 21fc25937..5d020b297 100644 --- a/spawn.c +++ b/spawn.c @@ -1,4 +1,4 @@ -/* $OpenBSD: spawn.c,v 1.47 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: spawn.c,v 1.48 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -231,8 +231,10 @@ spawn_window(struct spawn_context *sc, char **cause) session_select(s, sc->wl->idx); /* Fire notification if new window. */ - if (~sc->flags & SPAWN_RESPAWN) + if (~sc->flags & SPAWN_RESPAWN) { + events_fire_window("window-created", w); events_fire_winlink("window-linked", sc->wl); + } session_group_synchronize_from(s); return (sc->wl); diff --git a/tmux.1 b/tmux.1 index 01d654a25..3b4b228f6 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1133 2026/07/13 10:03:27 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1134 2026/07/13 13:01:14 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -6356,6 +6356,10 @@ See Run when a client becomes the latest active client of its session. .It client\-attached Run when a client is attached. +.It client\-closed +Run when a client is closed. +.It client\-created +Run when a client is created. .It client\-detached Run when a client is detached. .It client\-focus\-in @@ -6374,6 +6378,8 @@ Run when a client switches to a dark theme. Run when a command fails. .It marked\-pane\-changed Run when the marked pane is set or cleared. +.It pane\-bell +Run when a pane receives a bell. .It pane\-command\-finished Run when an OSC 133 command finishes in a pane. The event payload includes @@ -6407,6 +6413,8 @@ Run when a pane changes mode. Run when a pane enters a mode. .It pane\-mode\-exited Run when a pane exits a mode. +.It pane\-moved +Run when a pane is moved to another window. .It pane\-prompt\-closed Run when a prompt in a pane is closed. .It pane\-prompt\-opened @@ -6425,10 +6433,18 @@ Run when a pane title is changed. Run when a session is closed. .It session\-created Run when a new session is created. +.It session\-added\-to\-group +Run when a session is added to a session group. +.It session\-removed\-from\-group +Run when a session is removed from a session group. .It session\-renamed Run when a session is renamed. .It session\-window\-changed Run when a session changes its active window. +.It window\-closed +Run when a window is closed. +.It window\-created +Run when a window is created. .It window\-layout\-changed Run when a window layout is changed. .It window\-linked diff --git a/tmux.h b/tmux.h index ea357d08c..db6d4cf4e 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1398 2026/07/13 10:29:17 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1399 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -3624,6 +3624,8 @@ int window_has_floating_panes(struct window *); int window_has_pane(struct window *, struct window_pane *); int window_set_active_pane(struct window *, struct window_pane *, int); +void window_fire_pane_moved(struct window_pane *, struct window *, + int, struct window *, int); void window_update_focus(struct window *); void window_pane_update_focus(struct window_pane *); void window_redraw_active_switch(struct window *, diff --git a/window.c b/window.c index f6e1a1f23..ca2eca389 100644 --- a/window.c +++ b/window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window.c,v 1.360 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: window.c,v 1.361 2026/07/13 13:01:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -130,6 +130,29 @@ window_fire_pane_changed(struct window *w, struct window_pane *wp, events_fire("window-pane-changed", ep); } +void +window_fire_pane_moved(struct window_pane *wp, struct window *old_w, + int old_idx, struct window *new_w, int new_idx) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_pane(&fs, wp, 0); + event_payload_set_target(ep, &fs); + event_payload_set_pane(ep, "pane", wp); + event_payload_set_window(ep, "window", new_w); + event_payload_set_window(ep, "old_window", old_w); + event_payload_set_window(ep, "new_window", new_w); + if (old_idx != -1) + event_payload_set_int(ep, "old_window_index", old_idx); + if (new_idx != -1) { + event_payload_set_int(ep, "window_index", new_idx); + event_payload_set_int(ep, "new_window_index", new_idx); + } + events_fire("pane-moved", ep); +} + static void window_fire_pane_mode_changed(const char *name, struct window_pane *wp, const char *previous, const char *current, int entered) @@ -490,6 +513,9 @@ window_add_ref(struct window *w, const char *from) void window_remove_ref(struct window *w, const char *from) { + if (w->references == 1) + events_fire_window("window-closed", w); + w->references--; log_debug("%s: @%u %s, now %d", __func__, w->id, from, w->references); @@ -972,7 +998,7 @@ window_lost_pane(struct window *w, struct window_pane *wp) if (w->active != NULL) { window_pane_stack_remove(&w->last_panes, w->active); w->active->flags |= PANE_CHANGED; - events_fire_window("window-pane-changed", w); + window_fire_pane_changed(w, w->active, wp); window_update_focus(w); } }