Compare commits

..

17 Commits

Author SHA1 Message Date
nicm
a34d2b6b21 Do not trim all lines to make a zero line grid which reflow does not like, from
Kaixuan Li.
2026-09-21 21:06:07 +01:00
nicm
550231e83a Do not do anything in session_detach and rely on the caller destroying
the session, otherwise grouped sessions can reuse a session with no
windows. From Jeong, Heon in GitHub issue 5620.
2026-09-21 21:05:52 +01:00
nicm
102d5185c1 Do not unzoom when resizing a floating pane that was created with -A.
Similarly, skip hidden floating panes when changing Z order. Reported by Clark
Wang.
2026-09-21 21:05:19 +01:00
Nicholas Marriott
1d9029c8d7 Update tests in 3.8 branch. 2026-09-21 08:38:11 +01:00
nicm
a23ff880a2 If no floating panes, reset default starting position, and reset if any part of
the pane goes outside the window.
2026-09-21 08:30:53 +01:00
nicm
32dc7d5095 Empty string for invalid old-style formats causes old iTerm2 versions to crash,
so emit "0000," instead.
2026-09-21 08:30:29 +01:00
nicm
06ef15955c Fix session_*_flag format variables which loop over the windows (they should
only be false if all windows do not have the flag, not the first one). GitHub
issue 5599.
2026-09-21 08:30:17 +01:00
nicm
ce9bdf6734 Do not loop forever if someone tries to give WCHAR_MAX a width, GitHub issue
5602.
2026-09-21 08:30:14 +01:00
nicm
757b15f2ca Expand -c for run-shell like the other -c flags, reported by Saúl Nogueras. 2026-09-21 08:30:11 +01:00
nicm
fcb4e9760a Reset layout manually instead of calling window_unzoom which can go down the
notification path and end up double freeing the pane (this was previously
removed in 2015 but added back to fix a problem with late destroy - this is a
better fix). GitHub issue 5591 from Romain Francoise.
2026-09-21 08:30:08 +01:00
Nicholas Marriott
03436d273a Bump version. 2026-09-11 09:06:39 +01:00
Nicholas Marriott
7fdb1c90b7 Fix tests. 2026-09-11 09:06:20 +01:00
nicm
55012ded8d Add a nesting limit for v1 layouts, reported by M Khalilov, fix based on
issue 5572 from Afonso Januário. Also tweak some language while here.
2026-09-11 09:05:55 +01:00
nicm
de04ae2c65 Add remain-on-exit failed-key and a -D flag to new-pane to have a modal
pane wait for Escape/C-c. Both to allow better compatibility with
popups.
2026-09-11 09:05:41 +01:00
Nicholas Marriott
3b931d9c20 Note changes from Heon Jeong. 2026-09-11 09:05:39 +01:00
Nicholas Marriott
6f387eec2b Tweak changes. 2026-09-09 13:50:14 +01:00
Nicholas Marriott
80f522f577 Bump version. 2026-09-09 13:27:07 +01:00
28 changed files with 291 additions and 303 deletions

View File

@@ -115,15 +115,11 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
/* /*
* -c is intended to be the client where the message should be * -c is intended to be the client where the message should be
* displayed if -p is not given, but it makes sense to use it for the * displayed if -p is not given. But it makes sense to use it for the
* client formats too. If it was given explicitly, use it even when it * formats too, assuming it matches the session. If it doesn't, use the
* is not in the target session, since the formats should describe the * best client for the session.
* client which was asked for; the session, window and pane formats
* still come from the target. Otherwise it is just the current client,
* so only use it if it matches the session and fall back to the best
* client for the session if not.
*/ */
if (tc != NULL && (args_has(args, 'c') || tc->session == s)) if (tc != NULL && tc->session == s)
c = tc; c = tc;
else if (s != NULL) else if (s != NULL)
c = cmd_find_best_client(s); c = cmd_find_best_client(s);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-join-pane.c,v 1.74 2026/08/03 20:29:52 nicm Exp $ */ /* $OpenBSD: cmd-join-pane.c,v 1.75 2026/09/21 10:33:16 nicm Exp $ */
/* /*
* Copyright (c) 2011 George Nachman <tmux@georgester.com> * Copyright (c) 2011 George Nachman <tmux@georgester.com>
@@ -133,7 +133,7 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
} else if (strcmp(position, "back") == 0) { } else if (strcmp(position, "back") == 0) {
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_FOREACH(owp, &w->z_index, zentry) { TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp)) if (!window_pane_is_floating_with_hidden(owp))
break; break;
} }
if (owp != NULL) if (owp != NULL)
@@ -142,24 +142,30 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
} else if (strcmp(position, "forward") == 0) { } else if (strcmp(position, "forward") == 0) {
owp = TAILQ_PREV(wp, window_panes_zindex, zentry); owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
while (owp != NULL && owp->layout_cell == NULL)
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
if (owp != NULL) { if (owp != NULL) {
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_BEFORE(owp, wp, zentry); TAILQ_INSERT_BEFORE(owp, wp, zentry);
} }
} else if (strcmp(position, "backward") == 0) { } else if (strcmp(position, "backward") == 0) {
owp = TAILQ_NEXT(wp, zentry); owp = TAILQ_NEXT(wp, zentry);
while (owp != NULL && owp->layout_cell == NULL)
owp = TAILQ_NEXT(owp, zentry);
if (owp != NULL && window_pane_is_floating(owp)) { if (owp != NULL && window_pane_is_floating(owp)) {
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry); TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
} }
} else if (strcmp(position, "forward-loop") == 0) { } else if (strcmp(position, "forward-loop") == 0) {
owp = TAILQ_PREV(wp, window_panes_zindex, zentry); owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
while (owp != NULL && owp->layout_cell == NULL)
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
if (owp != NULL) if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry); TAILQ_INSERT_BEFORE(owp, wp, zentry);
else { else {
TAILQ_FOREACH(owp, &w->z_index, zentry) { TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp)) if (!window_pane_is_floating_with_hidden(owp))
break; break;
} }
if (owp != NULL) if (owp != NULL)
@@ -169,6 +175,8 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
} }
} else if (strcmp(position, "backward-loop") == 0) { } else if (strcmp(position, "backward-loop") == 0) {
owp = TAILQ_NEXT(wp, zentry); owp = TAILQ_NEXT(wp, zentry);
while (owp != NULL && owp->layout_cell == NULL)
owp = TAILQ_NEXT(owp, zentry);
if (owp != NULL && window_pane_is_floating(owp)) { if (owp != NULL && window_pane_is_floating(owp)) {
TAILQ_REMOVE(&w->z_index, wp, zentry); TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry); TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
@@ -347,8 +355,10 @@ cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl,
n = 0; n = 0;
TAILQ_FOREACH(owp, &w->z_index, zentry) { TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp)) if (!window_pane_is_floating_with_hidden(owp))
break; break;
if (owp->layout_cell == NULL)
continue;
if (n >= z) if (n >= z)
break; break;
n++; n++;
@@ -443,7 +453,6 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
cmdq_error(item, "pane is not floating"); cmdq_error(item, "pane is not floating");
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
server_unzoom_window(dst_w);
if ((s = args_get(args, 'P')) != NULL) if ((s = args_get(args, 'P')) != NULL)
return (cmd_join_pane_place(item, dst_wl, dst_wp, s)); return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
if ((s = args_get(args, 'z')) != NULL) if ((s = args_get(args, 'z')) != NULL)

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-resize-pane.c,v 1.68 2026/08/31 07:44:39 nicm Exp $ */ /* $OpenBSD: cmd-resize-pane.c,v 1.69 2026/09/21 10:33:16 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -91,7 +91,8 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
server_redraw_window(w); server_redraw_window(w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
server_unzoom_window(w); if (!window_pane_is_floating(wp))
server_unzoom_window(w);
lc = wp->layout_cell; /* may have been replaced by unzoom */ lc = wp->layout_cell; /* may have been replaced by unzoom */
if (args_has(args, 'x')) { if (args_has(args, 'x')) {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-run-shell.c,v 1.94 2026/08/25 06:04:33 nicm Exp $ */ /* $OpenBSD: cmd-run-shell.c,v 1.95 2026/09/20 07:59:55 nicm Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -164,7 +164,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
if (cdata->client != NULL) if (cdata->client != NULL)
cdata->client->references++; cdata->client->references++;
if (args_has(args, 'c')) if (args_has(args, 'c'))
cdata->cwd = xstrdup(args_get(args, 'c')); cdata->cwd = format_single_from_target(item, args_get(args, 'c'));
else else
cdata->cwd = xstrdup(server_client_get_cwd(c, s)); cdata->cwd = xstrdup(server_client_get_cwd(c, s));

View File

@@ -1,6 +1,6 @@
# configure.ac # configure.ac
AC_INIT([tmux], next-3.9) AC_INIT([tmux], 3.8-rc2)
AC_PREREQ([2.60]) AC_PREREQ([2.60])
AC_CONFIG_AUX_DIR(etc) AC_CONFIG_AUX_DIR(etc)

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: format.c,v 1.417 2026/09/08 15:42:26 nicm Exp $ */ /* $OpenBSD: format.c,v 1.418 2026/09/20 08:19:31 nicm Exp $ */
/* /*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -2876,10 +2876,10 @@ format_cb_session_activity_flag(struct format_tree *ft)
if (ft->s != NULL) { if (ft->s != NULL) {
RB_FOREACH(wl, winlinks, &ft->s->windows) { RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (ft->wl->flags & WINLINK_ACTIVITY) if (wl->flags & WINLINK_ACTIVITY)
return (xstrdup("1")); return (xstrdup("1"));
return (xstrdup("0"));
} }
return (xstrdup("0"));
} }
return (NULL); return (NULL);
} }
@@ -2894,8 +2894,8 @@ format_cb_session_bell_flag(struct format_tree *ft)
RB_FOREACH(wl, winlinks, &ft->s->windows) { RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (wl->flags & WINLINK_BELL) if (wl->flags & WINLINK_BELL)
return (xstrdup("1")); return (xstrdup("1"));
return (xstrdup("0"));
} }
return (xstrdup("0"));
} }
return (NULL); return (NULL);
} }
@@ -2908,10 +2908,10 @@ format_cb_session_silence_flag(struct format_tree *ft)
if (ft->s != NULL) { if (ft->s != NULL) {
RB_FOREACH(wl, winlinks, &ft->s->windows) { RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (ft->wl->flags & WINLINK_SILENCE) if (wl->flags & WINLINK_SILENCE)
return (xstrdup("1")); return (xstrdup("1"));
return (xstrdup("0"));
} }
return (xstrdup("0"));
} }
return (NULL); return (NULL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: layout-custom.c,v 1.41 2026/09/09 09:01:19 nicm Exp $ */ /* $OpenBSD: layout-custom.c,v 1.42 2026/09/20 08:37:47 nicm Exp $ */
/* /*
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -283,26 +283,28 @@ layout_checksum(const char *layout)
char * char *
layout_dump(__unused struct window *w, struct layout_cell *lcroot, int flags) layout_dump(__unused struct window *w, struct layout_cell *lcroot, int flags)
{ {
struct layout_string layout_string; struct layout_string layout_string = { 0 };
char *out = NULL; char *out;
if (lcroot == NULL) if (lcroot == NULL)
return NULL; goto bad;
layout_string_init(&layout_string); layout_string_init(&layout_string);
if (layout_append(lcroot, &layout_string, flags) != 0)
if (layout_append(lcroot, &layout_string, flags) == 0) { goto bad;
if (flags & LAYOUT_CUSTOM_OLD_FORMAT) if (~flags & LAYOUT_CUSTOM_OLD_FORMAT)
xasprintf(&out, "%04hx,%s", xasprintf(&out, "{\"V\":2,\"L\":%s}", layout_string.dat);
layout_checksum(layout_string.dat), else {
layout_string.dat); xasprintf(&out, "%04hx,%s", layout_checksum(layout_string.dat),
else layout_string.dat);
xasprintf(&out, "{\"V\":2,\"L\":%s}",
layout_string.dat);
} }
layout_string_free(&layout_string); layout_string_free(&layout_string);
return (out); return (out);
bad:
layout_string_free(&layout_string);
if (~flags & LAYOUT_CUSTOM_OLD_FORMAT)
return (NULL);
return (xstrdup("0000,"));
} }
/* Append information for a single cell in a JSON (v2) format. */ /* Append information for a single cell in a JSON (v2) format. */

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: layout.c,v 1.100 2026/09/11 08:16:14 nicm Exp $ */ /* $OpenBSD: layout.c,v 1.101 2026/09/20 08:42:46 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -768,49 +768,6 @@ layout_free(struct window *w, int only_nodes)
layout_free_cell(w->layout_root, only_nodes); layout_free_cell(w->layout_root, only_nodes);
} }
/* Move and resize floating panes so they stay inside the window. */
static void
layout_clamp_floating_panes(struct window *w, u_int sx, u_int sy)
{
struct window_pane *wp;
struct layout_cell *lc;
u_int pad, avail, csx, csy;
TAILQ_FOREACH(wp, &w->z_index, zentry) {
lc = wp->layout_cell;
if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING))
continue;
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
pad = 0;
else
pad = 1;
csx = lc->g.sx;
avail = (sx > 2 * pad) ? sx - 2 * pad : 0;
if (csx > avail)
csx = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
csy = lc->g.sy;
avail = (sy > 2 * pad) ? sy - 2 * pad : 0;
if (csy > avail)
csy = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
if (csx != lc->g.sx || csy != lc->g.sy)
layout_set_size(lc, csx, csy, lc->g.xoff, lc->g.yoff);
if (lc->g.xoff + lc->g.sx + pad > sx) {
if (lc->g.sx + 2 * pad >= sx)
lc->g.xoff = pad;
else
lc->g.xoff = sx - lc->g.sx - pad;
}
if (lc->g.yoff + lc->g.sy + pad > sy) {
if (lc->g.sy + 2 * pad >= sy)
lc->g.yoff = pad;
else
lc->g.yoff = sy - lc->g.sy - pad;
}
}
}
/* Resize the entire layout after window resize. */ /* Resize the entire layout after window resize. */
void void
layout_resize(struct window *w, u_int sx, u_int sy) layout_resize(struct window *w, u_int sx, u_int sy)
@@ -831,11 +788,8 @@ layout_resize(struct window *w, u_int sx, u_int sy)
* out proportionately - this should leave the layout fitting the new * out proportionately - this should leave the layout fitting the new
* window size. * window size.
*/ */
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) { if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING))
layout_clamp_floating_panes(w, sx, sy);
layout_fix_panes(w, NULL);
return; return;
}
xchange = sx - lc->g.sx; xchange = sx - lc->g.sx;
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT); xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
if (xchange < 0 && xchange < -xlimit) if (xchange < 0 && xchange < -xlimit)
@@ -865,7 +819,6 @@ layout_resize(struct window *w, u_int sx, u_int sy)
/* Fix cell offsets. */ /* Fix cell offsets. */
layout_fix_offsets(w); layout_fix_offsets(w);
layout_clamp_floating_panes(w, sx, sy);
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);
} }
@@ -1728,7 +1681,7 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
enum pane_lines lines, struct window *w, struct layout_geometry *lg, enum pane_lines lines, struct window *w, struct layout_geometry *lg,
char **cause) char **cause)
{ {
int sx, sy, ox, oy; int sx, sy, ox, oy, pad;
char *error = NULL; char *error = NULL;
sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx; sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx;
@@ -1777,12 +1730,20 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
} }
} }
if (!window_has_floating_panes(w)) {
w->last_new_pane_x = 0;
w->last_new_pane_y = 0;
}
if (ox == INT_MAX) { if (ox == INT_MAX) {
if (w->last_new_pane_x == 0) if (w->last_new_pane_x == 0)
ox = 4; ox = 4;
else { else {
if (lines != PANE_LINES_NONE)
pad = 1;
else
pad = 0;
ox = w->last_new_pane_x + 4; ox = w->last_new_pane_x + 4;
if (w->last_new_pane_x > w->sx) if (ox + sx + pad > (int)w->sx)
ox = 4; ox = 4;
} }
w->last_new_pane_x = ox; w->last_new_pane_x = ox;
@@ -1793,8 +1754,12 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
if (w->last_new_pane_y == 0) if (w->last_new_pane_y == 0)
oy = 2; oy = 2;
else { else {
if (lines != PANE_LINES_NONE)
pad = 1;
else
pad = 0;
oy = w->last_new_pane_y + 2; oy = w->last_new_pane_y + 2;
if (w->last_new_pane_y > w->sy) if (oy + sy + pad > (int)w->sy)
oy = 2; oy = 2;
} }
w->last_new_pane_y = oy; w->last_new_pane_y = oy;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: options-table.c,v 1.246 2026/09/11 10:17:16 nicm Exp $ */ /* $OpenBSD: options-table.c,v 1.245 2026/09/10 11:02:18 nicm Exp $ */
/* /*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -76,8 +76,7 @@ static const char *options_table_pane_border_indicators_list[] = {
"off", "colour", "arrows", "both", NULL "off", "colour", "arrows", "both", NULL
}; };
static const char *options_table_pane_border_lines_list[] = { static const char *options_table_pane_border_lines_list[] = {
"single", "double", "heavy", "simple", "number", "spaces", "none", "single", "double", "heavy", "simple", "number", "spaces", "none", NULL
"rounded", NULL
}; };
static const char *options_table_popup_border_lines_list[] = { static const char *options_table_popup_border_lines_list[] = {
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL "single", "double", "heavy", "simple", "rounded", "padded", "none", NULL

View File

@@ -2,8 +2,9 @@
# monitor-activity, monitor-bell and monitor-silence: both the # monitor-activity, monitor-bell and monitor-silence: both the
# alert-activity, alert-bell and alert-silence hooks and the winlink alert # alert-activity, alert-bell and alert-silence hooks and the winlink alert
# flags (window_activity_flag, window_bell_flag, window_silence_flag and # flags (window_activity_flag, window_bell_flag, window_silence_flag,
# the #, !, ~ characters in window_flags). The sessions are detached so # their session equivalents and the #, !, ~ characters in window_flags).
# The sessions are detached so
# alert flags are set even on the current window; the *-action options # alert flags are set even on the current window; the *-action options
# still decide whether the hooks fire. Panes run cat: activity is # still decide whether the hooks fire. Panes run cat: activity is
# generated by the tty echo of send-keys and a bell by sending a BEL and # generated by the tty echo of send-keys and a bell by sending a BEL and
@@ -111,6 +112,24 @@ flags_have()
esac esac
} }
assert_session_flag()
{
flag="#{session_${1}_flag}"
expected=$2
# Only mon exists here. Check both window contexts and list-sessions.
for target in mon:w0 "mon:$3"; do
value=$($TMUX display -pt "$target" "$flag") ||
fail "display $flag failed"
[ "$value" = "$expected" ] ||
fail "expected $flag for $target to be '$expected' but got '$value'"
done
value=$($TMUX list-sessions -F "$flag") ||
fail "list-sessions $flag failed"
[ "$value" = "$expected" ] ||
fail "expected list-sessions $flag to be '$expected' but got '$value'"
}
flags_lack() flags_lack()
{ {
target=$1 target=$1
@@ -150,10 +169,12 @@ $TMUX set-hook -g alert-silence \
# alert-bell and sets the bell flag, shown as ! in window_flags. # alert-bell and sets the bell flag, shown as ! in window_flags.
$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed" $TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed"
assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0 assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0
assert_session_flag bell 0 bellw
flags_lack mon:bellw '!' flags_lack mon:bellw '!'
bell mon:bellw bell mon:bellw
wait_for @log '|alert-bell:mon:bellw' wait_for @log '|alert-bell:mon:bellw'
wait_for_fmt mon:bellw '#{window_bell_flag}' 1 wait_for_fmt mon:bellw '#{window_bell_flag}' 1
assert_session_flag bell 1 bellw
flags_have mon:bellw '!' flags_have mon:bellw '!'
# Bells are not deduplicated: a second bell fires the hook again even # Bells are not deduplicated: a second bell fires the hook again even
@@ -164,6 +185,7 @@ wait_for @log '|alert-bell:mon:bellw|alert-bell:mon:bellw'
# Selecting the window clears the alert flags. # Selecting the window clears the alert flags.
$TMUX selectw -t mon:bellw || fail "select-window bellw failed" $TMUX selectw -t mon:bellw || fail "select-window bellw failed"
wait_for_fmt mon:bellw '#{window_bell_flag}' 0 wait_for_fmt mon:bellw '#{window_bell_flag}' 0
assert_session_flag bell 0 bellw
flags_lack mon:bellw '!' flags_lack mon:bellw '!'
$TMUX selectw -t mon:w0 || fail "select-window w0 failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
@@ -173,6 +195,7 @@ $TMUX set -g @log '' || fail "reset @log failed"
bell mon:w0 bell mon:w0
wait_for @log '|alert-bell:mon:w0' wait_for @log '|alert-bell:mon:w0'
wait_for_fmt mon:w0 '#{window_bell_flag}' 1 wait_for_fmt mon:w0 '#{window_bell_flag}' 1
assert_session_flag bell 1 bellw
$TMUX selectw -t mon:bellw || fail "select-window bellw failed" $TMUX selectw -t mon:bellw || fail "select-window bellw failed"
$TMUX selectw -t mon:w0 || fail "select-window w0 failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
wait_for_fmt mon:w0 '#{window_bell_flag}' 0 wait_for_fmt mon:w0 '#{window_bell_flag}' 0
@@ -199,6 +222,7 @@ $TMUX neww -d -t mon: -n actw cat || fail "new-window actw failed"
$TMUX set -g @log '' || fail "reset @log failed" $TMUX set -g @log '' || fail "reset @log failed"
activity mon:actw activity mon:actw
assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0 assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0
assert_session_flag activity 0 actw
assert_unchanged @log '' assert_unchanged @log ''
# With monitor-activity on, output fires alert-activity and sets the # With monitor-activity on, output fires alert-activity and sets the
@@ -208,6 +232,7 @@ $TMUX set -wt mon:actw monitor-activity on ||
activity mon:actw activity mon:actw
wait_for @log '|alert-activity:mon:actw' wait_for @log '|alert-activity:mon:actw'
wait_for_fmt mon:actw '#{window_activity_flag}' 1 wait_for_fmt mon:actw '#{window_activity_flag}' 1
assert_session_flag activity 1 actw
flags_have mon:actw '#' flags_have mon:actw '#'
# While the flag is set further activity does not fire the hook again. # While the flag is set further activity does not fire the hook again.
@@ -234,6 +259,7 @@ exec 3>"$OUT/fifo"
wait_for_fmt mon: '#{session_attached}' 1 wait_for_fmt mon: '#{session_attached}' 1
$TMUX selectw -t mon:actw || fail "select-window actw failed" $TMUX selectw -t mon:actw || fail "select-window actw failed"
wait_for_fmt mon:actw '#{window_activity_flag}' 0 wait_for_fmt mon:actw '#{window_activity_flag}' 0
assert_session_flag activity 0 actw
flags_lack mon:actw '#' flags_lack mon:actw '#'
$TMUX selectw -t mon:w0 || fail "select-window w0 failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
exec 3>&- exec 3>&-
@@ -250,6 +276,7 @@ $TMUX set -wt mon:w0 monitor-activity on ||
$TMUX set -g @log '' || fail "reset @log failed" $TMUX set -g @log '' || fail "reset @log failed"
activity mon:w0 activity mon:w0
wait_for_fmt mon:w0 '#{window_activity_flag}' 1 wait_for_fmt mon:w0 '#{window_activity_flag}' 1
assert_session_flag activity 1 actw
assert_unchanged @log '' assert_unchanged @log ''
$TMUX set -wut mon:w0 monitor-activity || $TMUX set -wut mon:w0 monitor-activity ||
fail "unset monitor-activity w0 failed" fail "unset monitor-activity w0 failed"
@@ -273,16 +300,19 @@ $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
# running. # running.
$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed" $TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed"
assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0 assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0
assert_session_flag silence 0 silw
$TMUX set -g @log '' || fail "reset @log failed" $TMUX set -g @log '' || fail "reset @log failed"
$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed" $TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed"
wait_for @log '|alert-silence:mon:silw' wait_for @log '|alert-silence:mon:silw'
wait_for_fmt mon:silw '#{window_silence_flag}' 1 wait_for_fmt mon:silw '#{window_silence_flag}' 1
assert_session_flag silence 1 silw
flags_have mon:silw '~' flags_have mon:silw '~'
assert_unchanged @log '|alert-silence:mon:silw' assert_unchanged @log '|alert-silence:mon:silw'
$TMUX set -wt mon:silw monitor-silence 0 || $TMUX set -wt mon:silw monitor-silence 0 ||
fail "reset monitor-silence failed" fail "reset monitor-silence failed"
$TMUX selectw -t mon:silw || fail "select-window silw failed" $TMUX selectw -t mon:silw || fail "select-window silw failed"
wait_for_fmt mon:silw '#{window_silence_flag}' 0 wait_for_fmt mon:silw '#{window_silence_flag}' 0
assert_session_flag silence 0 silw
flags_lack mon:silw '~' flags_lack mon:silw '~'
$TMUX selectw -t mon:w0 || fail "select-window w0 failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed"

View File

@@ -0,0 +1,53 @@
#!/bin/sh
# Width cache ranges must include their endpoint without overflowing wchar_t.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
SERVER=
WATCHDOG=
cleanup()
{
if [ -n "$WATCHDOG" ]; then
kill "$WATCHDOG" 2>/dev/null
wait "$WATCHDOG" 2>/dev/null
fi
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
}
trap cleanup 0
trap 'exit 1' 1 2 15
$TMUX new-session -d 'exec sleep 60' || exit 1
SERVER=$($TMUX display-message -p '#{pid}') || exit 1
# kill-server cannot stop a server stuck rebuilding the width cache.
(
sleep 5
kill -9 "$SERVER" 2>/dev/null
) &
WATCHDOG=$!
$TMUX set -g codepoint-widths 'U+1F600=2' || exit 1
# Cover both signed and unsigned 32-bit wchar_t. Values above WCHAR_MAX
# are ignored by the parser on platforms where they cannot be represented.
for value in U+7FFFFFFF=1 U+7FFFFFFE-U+7FFFFFFF=2 \
U+FFFFFFFF=1 U+FFFFFFFE-U+FFFFFFFF=2; do
$TMUX set -g codepoint-widths "$value" || exit 1
[ "$($TMUX display-message -p alive)" = alive ] || exit 1
done
# Check that an ordinary range still includes both endpoints and stops there.
$TMUX set -g codepoint-widths 'U+03B1-U+03B3=2' || exit 1
$TMUX set -g @text 'αβγδ' || exit 1
[ "$($TMUX display-message -p '#{w:@text}')" = 7 ] || exit 1
$TMUX set -gu codepoint-widths || exit 1
[ "$($TMUX display-message -p '#{w:@text}')" = 4 ] || exit 1
$TMUX kill-server || exit 1
SERVER=
exit 0

View File

@@ -268,149 +268,5 @@ $TMUX kill-pane -t "$floating" || exit 1
$TMUX kill-pane -t "$right" || exit 1 $TMUX kill-pane -t "$right" || exit 1
$TMUX kill-pane -t "$lower" || exit 1 $TMUX kill-pane -t "$lower" || exit 1
# --- Floating panes clamped when the window shrinks (issue #5581, PR #5582) ---
#
# layout_resize clamps floating panes back inside the window when it shrinks:
# move them and, only if they cannot fit, shrink them (never below
# PANE_MINIMUM). A floating cell is the pane's content, so with the default
# single-line border the clamp counts 1 cell of border per side, exactly as
# layout_floating_args_parse does on creation; with no border it counts 0.
# Each case below gets its own window, since resize-window fixes a window at
# a manual size for the rest of its life.
# Case 1: a lone floating pane -- break-pane -W on a window's only pane --
# takes the early-return path in layout_resize, added during PR #5582's
# review round, since there is no tiled tree to walk.
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for lone float failed"
# -x 20 -y 6 -> pane 18x4; -X 60 -Y 18 -> pane_left 61, pane_top 19: with the
# border, the footprint is columns 60-79, rows 18-23, flush with the 80x24
# window's right and bottom edges, so the float fits before it is shrunk.
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
fail "break-pane -W for lone float failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
# Shrink to 40x16. The float still fits at its own size (18 <= 40-2, 4 <=
# 16-2) so only its position moves, flush to the new right/bottom edges:
# xoff = 40 - 18 - 1 = 21; yoff = 16 - 4 - 1 = 11.
$TMUX resize-window -t "$win" -x 40 -y 16 ||
fail "resize-window (lone float) failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
# Case 5: grow the window back. The clamp must not chase the window back
# outward -- it only ever pulls a float in, never restores where it was.
$TMUX resize-window -t "$win" -x 80 -y 24 ||
fail "resize-window grow (lone float) failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
$TMUX kill-window -t "$win" || exit 1
# Case 2: the same float, but with a tiled sibling surviving alongside it, so
# the window's root cell stays tiled and layout_resize takes the normal path
# (the clamp call after layout_fix_offsets) instead of the early return
# above. Same geometry as case 1, so the same numbers should come out.
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for tiled sibling failed"
$TMUX split-window -t "$win" 'sleep 100' ||
fail "split-window for tiled sibling failed"
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
fail "break-pane -W for tiled sibling failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
$TMUX resize-window -t "$win" -x 40 -y 16 ||
fail "resize-window (tiled sibling) failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
$TMUX kill-window -t "$win" || exit 1
# Case 3: new-pane float, the original repro from issue #5581 and the PR
# body -- also the normal path, via the tiled base pane new-window creates.
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for new-pane repro failed"
$TMUX resize-window -t "$win" -x 120 -y 40 ||
fail "resize-window to 120x40 failed"
# -x 30 -y 10 -> pane 28x8; -X 85 -Y 25 -> pane_left 86, pane_top 26.
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
-x 30 -y 10 -X 85 -Y 25 'sleep 100') ||
fail "new-pane for new-pane repro failed"
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 86
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 26
# Shrink to 60x20: xoff = 60 - 28 - 1 = 31; yoff = 20 - 8 - 1 = 11.
$TMUX resize-window -t "$win" -x 60 -y 20 ||
fail "resize-window (new-pane repro) failed"
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 31
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 11
$TMUX kill-window -t "$win" || exit 1
# Case 4: a float that already fits inside the shrunk window is left alone --
# assert position and size are both unchanged, not just one of them.
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for untouched float failed"
# -x 20 -y 6 -> pane 18x4; -X 8 -Y 3 -> pane_left 9, pane_top 4 (as at the top
# of this file). Footprint columns 8-27, rows 3-8: well inside 60x20 too.
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
-x 20 -y 6 -X 8 -Y 3 'sleep 100') ||
fail "new-pane for untouched float failed"
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
$TMUX resize-window -t "$win" -x 60 -y 20 ||
fail "resize-window (untouched float) failed"
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
$TMUX kill-window -t "$win" || exit 1
# Case 6: pad = 0, via the pane-border-lines window option set to none. The
# border arithmetic differs here (no -2/+1 adjustment either on creation or
# in the clamp).
win=$($TMUX new-window -dPF '#{window_id}') ||
fail "new-window for pad=0 float failed"
$TMUX set-option -w -t "$win" pane-border-lines none ||
fail "set pane-border-lines none failed"
# No border: -x 20 -y 6 -> pane 20x6 directly; -X 60 -Y 18 -> pane_left 60,
# pane_top 18 directly. Footprint (== content, no border) is columns 60-79,
# rows 18-23: flush right/bottom of the 80x24 window, same as case 1.
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
fail "break-pane -W for pad=0 float failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 60
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 18
# Shrink to 40x16 with pad=0: xoff = 40 - 20 - 0 = 20; yoff = 16 - 6 - 0 = 10.
$TMUX resize-window -t "$win" -x 40 -y 16 ||
fail "resize-window (pad=0 float) failed"
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 20
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 10
$TMUX kill-window -t "$win" || exit 1
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
exit 0 exit 0

28
regress/kill-session-zoomed.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
# Killing a session whose window is zoomed must not crash the server:
# window_destroy used to unzoom the window, which resized the panes and
# fired pane-resized with the window in the payload, and releasing that
# reference destroyed the window a second time.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
$TMUX kill-server 2>/dev/null
$TMUX -f/dev/null new -d -sfoo || exit 1
$TMUX split-window -d -h -tfoo:0 || exit 1
$TMUX resize-pane -Z -tfoo:0 || exit 1
$TMUX new -d -sbar || exit 1
$TMUX kill-session -tfoo || exit 1
$TMUX has-session -tbar || exit 1
# The same with the zoomed window in a session killed by kill-server.
$TMUX new -d -sbaz || exit 1
$TMUX split-window -d -v -tbaz:0 || exit 1
$TMUX resize-pane -Z -tbaz:0 || exit 1
$TMUX kill-server || exit 1
exit 0

View File

@@ -50,7 +50,8 @@
# layout it names is a single cell or a split; # layout it names is a single cell or a split;
# - a window whose only tiled pane has been killed, which leaves it with a # - a window whose only tiled pane has been killed, which leaves it with a
# floating cell as its layout root or with a root node holding nothing but # floating cell as its layout root or with a root node holding nothing but
# floating cells, producing no v1 dump at all, and being parsed as v1; # floating cells, producing an empty v1 body with a checksum, and being
# parsed as v1;
# - the %layout-change notification, in both formats at once: two control # - the %layout-change notification, in both formats at once: two control
# clients watching one layout change, only one of which has asked for new # clients watching one layout change, only one of which has asked for new
# layouts, and the number of notifications a change produces in each format; # layouts, and the number of notifications a change produces in each format;
@@ -838,12 +839,12 @@ must_equal 'Panes left with one floating pane' \
"$($TMUX display-message -p -t L:gone1 '#{window_panes}')" '1' "$($TMUX display-message -p -t L:gone1 '#{window_panes}')" '1'
# The floating cell is the root and there is nothing tiled under it, so there is # The floating cell is the root and there is nothing tiled under it, so there is
# no v1 dump to make. In particular the floating cell must not be written out on # only an empty v1 body to dump. The floating cell must not be written out on
# its own, which would be a layout claiming the window is the size and position # its own, which would be a layout claiming the window is the size and position
# of the floating pane with no pane in it at all. # of the floating pane with no pane in it at all.
got=$(v1_layout L:gone1) got=$(v1_layout L:gone1)
check_ok display-message -p alive check_ok display-message -p alive
must_equal 'v1 dump with one floating pane and no tiled panes' "$got" '' must_equal 'v1 dump with one floating pane and no tiled panes' "$got" '0000,'
# Two floating panes left, so the node keeps two children, does not collapse, # Two floating panes left, so the node keeps two children, does not collapse,
# and stays the root with nothing but floating cells in it. # and stays the root with nothing but floating cells in it.
@@ -856,11 +857,11 @@ must_equal 'Panes left with two floating panes' \
"$($TMUX display-message -p -t L:gone2 '#{window_panes}')" '2' "$($TMUX display-message -p -t L:gone2 '#{window_panes}')" '2'
# The node is the root this time rather than the floating cell, but it has no # The node is the root this time rather than the floating cell, but it has no
# tiled cell anywhere under it either, so there is still no v1 dump to make - # tiled cell anywhere under it either, so the v1 body is still empty -
# and making one must not take the server with it. # and making one must not take the server with it.
got=$(v1_layout L:gone2) got=$(v1_layout L:gone2)
check_ok display-message -p alive check_ok display-message -p alive
must_equal 'v1 dump with two floating panes and no tiled panes' "$got" '' must_equal 'v1 dump with two floating panes and no tiled panes' "$got" '0000,'
# Nor must parsing a v1 layout against it. There is no tiled pane for the # Nor must parsing a v1 layout against it. There is no tiled pane for the
# layout to name, so whether it is applied or rejected is the format's business; # layout to name, so whether it is applied or rejected is the format's business;

View File

@@ -86,11 +86,6 @@ check_value "-gv status-keys" "vi"
check_fail "unknown value: bogus" set -g status-keys bogus check_fail "unknown value: bogus" set -g status-keys bogus
check_value "-gv status-keys" "vi" check_value "-gv status-keys" "vi"
# pane-border-lines accepts rounded as a pane border style.
check_ok set -gw pane-border-lines rounded
check_value "-gwv pane-border-lines" "rounded"
check_ok set -gw pane-border-lines single
# --- flag options --------------------------------------------------------- # --- flag options ---------------------------------------------------------
# #
# focus-events is an on/off flag. Setting with no value toggles it; explicit # focus-events is an on/off flag. Setting with no value toggles it; explicit

43
regress/run-shell-cwd.sh Normal file
View File

@@ -0,0 +1,43 @@
#!/bin/sh
# run-shell -c should expand formats using the target pane.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMP=$(mktemp -d)
trap '$TMUX kill-server 2>/dev/null; rm -rf "$TMP"' 0 1 15
TMP=$(cd "$TMP" && pwd -P)
mkdir "$TMP/first" "$TMP/second dir" || exit 1
check_directory()
{
actual=$(cat "$TMP/out")
if [ "$actual" != "$1" ]; then
echo "Expected directory '$1', got '$actual'"
exit 1
fi
}
# Expand the current pane's directory in the same command queue as creation.
$TMUX new-session -d -s test -c "$TMP/first" 'sleep 60' \; \
run-shell -c '#{pane_current_path}' "pwd >'$TMP/out'" || exit 1
check_directory "$TMP/first"
# An explicit target must supply the format context, including with a delay.
pane=$($TMUX new-window -d -P -F '#{pane_id}' -t test \
-c "$TMP/second dir" 'sleep 60') || exit 1
$TMUX run-shell -t "$pane" -d 0.1 -c '#{pane_current_path}' \
"pwd >'$TMP/out'" || exit 1
check_directory "$TMP/second dir"
# Literal directories and the default client directory still work.
$TMUX run-shell -c "$TMP/second dir" "pwd >'$TMP/out'" || exit 1
check_directory "$TMP/second dir"
(cd "$TMP/first" && $TMUX run-shell "pwd >'$TMP/out'") || exit 1
check_directory "$TMP/first"
exit 0

View File

@@ -107,12 +107,6 @@ $TMUX2 new-pane -x28 -y8 -X4 -Y1 -B double \
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1 "sh -c 'printf FLOAT; exec sleep 100'" || exit 1
compare floating-border-double compare floating-border-double
# Larger floating pane with rounded border lines.
new_scene 40 12
$TMUX2 new-pane -x28 -y8 -X4 -Y1 -B rounded \
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
compare floating-border-rounded
# Floating pane with no border lines: redraw_mark_pane_borders returns early so # Floating pane with no border lines: redraw_mark_pane_borders returns early so
# the float has no border at all, only its (clipped) content over the base pane. # the float has no border at all, only its (clipped) content over the base pane.
new_scene 40 12 new_scene 40 12

View File

@@ -1,12 +0,0 @@
base
╭──────────────────────────╮
│FLOAT │
│ │
│ │
│ │
│ │
│ │
╰──────────────────────────╯

View File

@@ -293,6 +293,16 @@ assert_key 'Escape [24@' 'C-S-F12'
assert_key 'Escape [I' 'FocusIn' assert_key 'Escape [I' 'FocusIn'
assert_key 'Escape [O' 'FocusOut' assert_key 'Escape [O' 'FocusOut'
# Focus events do not leave the prefix table.
$TMUX2 bind C-c set -g @focus-prefix yes
$TMUX2 set -g focus-events on
$TMUX send-keys C-b Escape '[O' Escape '[I' C-c
sleep 0.05
if [ "$($TMUX2 show -gv @focus-prefix)" != yes ]; then
echo "[FAIL] FocusIn and FocusOut left the prefix table"
exit_status=1
fi
# Paste keys # Paste keys
assert_key 'Escape [200~' 'PasteStart' assert_key 'Escape [200~' 'PasteStart'
assert_key 'Escape [201~' 'PasteEnd' assert_key 'Escape [201~' 'PasteEnd'

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server-fn.c,v 1.153 2026/09/10 11:02:18 nicm Exp $ */ /* $OpenBSD: server-fn.c,v 1.154 2026/09/21 12:43:36 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -310,8 +310,8 @@ server_link_window(struct session *src, struct winlink *srcwl,
} }
if (killflag) { if (killflag) {
/* /*
* Can't use session_detach as it will destroy session * Can't use session_detach as it won't detach the only
* if this makes it empty. * window.
*/ */
events_fire_winlink("window-unlinked", dstwl); events_fire_winlink("window-unlinked", dstwl);
dstwl->flags &= ~WINLINK_ALERTFLAGS; dstwl->flags &= ~WINLINK_ALERTFLAGS;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: session.c,v 1.107 2026/08/05 07:35:35 nicm Exp $ */ /* $OpenBSD: session.c,v 1.108 2026/09/21 12:43:36 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -336,10 +336,17 @@ session_attach(struct session *s, struct window *w, int idx, char **cause)
return (wl); return (wl);
} }
/* Detach a window from a session. */ /*
* Detach a window from a session. Returns 1 if the window has not been
* detached - the caller must destroy the session.
*/
int int
session_detach(struct session *s, struct winlink *wl) session_detach(struct session *s, struct winlink *wl)
{ {
if (RB_MIN(winlinks, &s->windows) == wl &&
RB_MAX(winlinks, &s->windows) == wl)
return (1);
if (s->curw == wl && if (s->curw == wl &&
session_last(s) != 0 && session_last(s) != 0 &&
session_previous(s, 0) != 0) session_previous(s, 0) != 0)
@@ -352,8 +359,6 @@ session_detach(struct session *s, struct winlink *wl)
session_group_synchronize_from(s); session_group_synchronize_from(s);
if (RB_EMPTY(&s->windows))
return (1);
return (0); return (0);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: spawn.c,v 1.52 2026/08/20 09:19:24 nicm Exp $ */ /* $OpenBSD: spawn.c,v 1.53 2026/09/21 12:43:36 nicm Exp $ */
/* /*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -166,8 +166,8 @@ spawn_window(struct spawn_context *sc, char **cause)
} }
if (wl != NULL) { if (wl != NULL) {
/* /*
* Can't use session_detach as it will destroy session * Can't use session_detach as it won't detach the only
* if this makes it empty. * window.
*/ */
wl->flags &= ~WINLINK_ALERTFLAGS; wl->flags &= ~WINLINK_ALERTFLAGS;
events_fire_winlink("window-unlinked", wl); events_fire_winlink("window-unlinked", wl);

14
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1170 2026/09/11 10:17:16 nicm Exp $ .\" $OpenBSD: tmux.1,v 1.1169 2026/09/10 11:02:18 nicm Exp $
.\" .\"
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> .\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
.\" .\"
@@ -14,7 +14,7 @@
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\" .\"
.Dd $Mdocdate: September 11 2026 $ .Dd $Mdocdate: September 10 2026 $
.Dt TMUX 1 .Dt TMUX 1
.Os .Os
.Sh NAME .Sh NAME
@@ -5980,8 +5980,6 @@ single lines using ACS or UTF\-8 characters
double lines using UTF\-8 characters double lines using UTF\-8 characters
.It heavy .It heavy
heavy lines using UTF\-8 characters heavy lines using UTF\-8 characters
.It rounded
single lines with rounded corners using UTF\-8 characters
.It simple .It simple
simple ASCII characters simple ASCII characters
.It number .It number
@@ -8328,17 +8326,11 @@ Otherwise, the format of
.Ar message .Ar message
is described in the is described in the
.Sx FORMATS .Sx FORMATS
section; session, window and pane information is taken from section; information is taken from
.Ar target\-pane .Ar target\-pane
if if
.Fl t .Fl t
is given, otherwise the active pane. is given, otherwise the active pane.
If
.Fl c
is given, the
.Ql #{client_*}
variables are taken from
.Ar target\-client .
.Pp .Pp
If If
.Fl j .Fl j

6
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1440 2026/09/11 10:17:16 nicm Exp $ */ /* $OpenBSD: tmux.h,v 1.1443 2026/09/21 10:33:16 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1150,8 +1150,7 @@ enum pane_lines {
PANE_LINES_SIMPLE, PANE_LINES_SIMPLE,
PANE_LINES_NUMBER, PANE_LINES_NUMBER,
PANE_LINES_SPACES, PANE_LINES_SPACES,
PANE_LINES_NONE, PANE_LINES_NONE
PANE_LINES_ROUNDED
}; };
/* Pane border indicator option. */ /* Pane border indicator option. */
@@ -3834,6 +3833,7 @@ int window_pane_get_pane_status(struct window_pane *);
struct style_range *window_pane_status_get_range(struct window_pane *, u_int, struct style_range *window_pane_status_get_range(struct window_pane *, u_int,
u_int); u_int);
int window_pane_is_floating(struct window_pane *); int window_pane_is_floating(struct window_pane *);
int window_pane_is_floating_with_hidden(struct window_pane *);
/* window-border.c */ /* window-border.c */
void window_set_fill_cells(struct window *); void window_set_fill_cells(struct window *);

9
utf8.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: utf8.c,v 1.72 2026/09/01 12:49:49 nicm Exp $ */ /* $OpenBSD: utf8.c,v 1.73 2026/09/20 08:11:00 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -374,8 +374,13 @@ utf8_add_to_width_cache(const char *s)
wc_end = wc_start; wc_end = wc_start;
} }
for (wc = wc_start; wc <= wc_end; wc++) wc = wc_start;
for (;;) {
utf8_insert_width_cache(wc, width); utf8_insert_width_cache(wc, width);
if (wc == wc_end)
break;
wc++;
}
} else { } else {
utf8_no_width = 1; utf8_no_width = 1;
ud = utf8_fromcstr(copy); ud = utf8_fromcstr(copy);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-border.c,v 1.4 2026/09/11 10:17:16 nicm Exp $ */ /* $OpenBSD: window-border.c,v 1.3 2026/07/23 09:38:27 nicm Exp $ */
/* /*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -120,10 +120,6 @@ window_get_border_cell(struct window_pane *wp, enum pane_lines pane_lines,
gc->attr &= ~GRID_ATTR_CHARSET; gc->attr &= ~GRID_ATTR_CHARSET;
utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type)); utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
break; break;
case PANE_LINES_ROUNDED:
gc->attr &= ~GRID_ATTR_CHARSET;
utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type));
break;
case PANE_LINES_SIMPLE: case PANE_LINES_SIMPLE:
gc->attr &= ~GRID_ATTR_CHARSET; gc->attr &= ~GRID_ATTR_CHARSET;
utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]); utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-copy.c,v 1.429 2026/09/01 13:04:29 nicm Exp $ */ /* $OpenBSD: window-copy.c,v 1.431 2026/09/21 10:43:37 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -401,7 +401,7 @@ window_copy_clone_screen(struct screen *src, struct screen *hint, u_int *cx,
sy = screen_hsize(src) + screen_size_y(src); sy = screen_hsize(src) + screen_size_y(src);
if (trim) { if (trim) {
while (sy > screen_hsize(src)) { while (sy > screen_hsize(src) + 1) {
gl = grid_peek_line(src->grid, sy - 1); gl = grid_peek_line(src->grid, sy - 1);
if (gl == NULL || gl->cellused != 0) if (gl == NULL || gl->cellused != 0)
break; break;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window.c,v 1.374 2026/09/08 08:37:56 nicm Exp $ */ /* $OpenBSD: window.c,v 1.377 2026/09/21 10:33:16 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -449,9 +449,17 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel)
static void static void
window_destroy(struct window *w) window_destroy(struct window *w)
{ {
struct window_pane *wp;
log_debug("window @%u destroyed (%d references)", w->id, w->references); log_debug("window @%u destroyed (%d references)", w->id, w->references);
window_unzoom(w, 0); if (w->flags & WINDOW_ZOOMED) {
w->flags &= ~WINDOW_ZOOMED;
TAILQ_FOREACH(wp, &w->panes, entry) {
wp->flags &= ~PANE_ZOOMED;
wp->saved_layout_cell = NULL;
}
}
RB_REMOVE(windows, &windows, w); RB_REMOVE(windows, &windows, w);
layout_free_cell(w->layout_root, 0); layout_free_cell(w->layout_root, 0);
@@ -2906,3 +2914,15 @@ window_pane_is_floating(struct window_pane *wp)
return (0); return (0);
return (1); return (1);
} }
int
window_pane_is_floating_with_hidden(struct window_pane *wp)
{
struct layout_cell *lc = wp->layout_cell;
if (lc == NULL)
lc = wp->saved_layout_cell;
if (lc == NULL || (lc->flags & LAYOUT_CELL_FLOATING) == 0)
return (0);
return (1);
}