Compare commits

...

5 Commits

Author SHA1 Message Date
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
11 changed files with 96 additions and 38 deletions

View File

@@ -130,6 +130,8 @@ CHANGES FROM 3.7c TO 3.8
* Add capture-pane -I to include the time each line entered history. * Add capture-pane -I to include the time each line entered history.
* Various memory leak fixes from Heon Jeong.
* Add a default C-b T binding to change the current pane title. * Add a default C-b T binding to change the current pane title.
* Menus now belong to the window, so appear on all clients. * Menus now belong to the window, so appear on all clients.

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-split-window.c,v 1.150 2026/08/20 09:19:24 nicm Exp $ */ /* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
.name = "new-pane", .name = "new-pane",
.alias = "newp", .alias = "newp",
.args = { "AbB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, .args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
.usage = "[-AbCdefhIkKLMOPvWZ] [-B border-lines] " .usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] "
"[-c start-directory] [-e environment] " "[-c start-directory] [-e environment] "
"[-F format] [-l size] [-m message] [-p percentage] " "[-F format] [-l size] [-m message] [-p percentage] "
"[-s style] [-S active-border-style] " "[-s style] [-S active-border-style] "
@@ -219,6 +219,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
new_wp->flags |= PANE_CAPTUREALLKEYS; new_wp->flags |= PANE_CAPTUREALLKEYS;
if (args_has(args, 'C') && args_has(args, 'O')) if (args_has(args, 'C') && args_has(args, 'O'))
new_wp->flags |= PANE_CLOSEONCLICK; new_wp->flags |= PANE_CLOSEONCLICK;
if (args_has(args, 'D') && args_has(args, 'O'))
new_wp->flags |= PANE_CLOSEONCANCEL;
style = args_get(args, 's'); style = args_get(args, 's');
if (style != NULL) { if (style != NULL) {

View File

@@ -1,6 +1,6 @@
# configure.ac # configure.ac
AC_INIT([tmux], 3.8-rc) 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: layout-custom.c,v 1.40 2026/09/09 07:03:39 nicm Exp $ */ /* $OpenBSD: layout-custom.c,v 1.41 2026/09/09 09:01:19 nicm Exp $ */
/* /*
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -28,8 +28,8 @@
#include "tmux.h" #include "tmux.h"
/* /*
* Layouts can be represented as strings in a JSON format (v2). The legacy * Layouts can be represented as strings in a JSON format (v2). The version 1
* format (v1) will be removed in the future and should no longer be used. * format will be removed in the future and should no longer be used.
* *
* The current (v2) format is JSON. The top level has two keys: * The current (v2) format is JSON. The top level has two keys:
* "V": version number, currently 2 * "V": version number, currently 2
@@ -57,6 +57,9 @@
* "z": z-index, if a floating pane * "z": z-index, if a floating pane
*/ */
/* Maximum nesting depth for version 1 layouts. */
#define LAYOUT_V1_MAX_DEPTH 1000
/* Layout string. */ /* Layout string. */
struct layout_string { struct layout_string {
char *dat; char *dat;
@@ -362,7 +365,7 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
return (0); return (0);
} }
/* Append information for a single cell in the legacy (v1) format. */ /* Append information for a single cell in the version 1 format. */
static int static int
layout_append_v1(struct layout_cell *lc, struct layout_string *ls) layout_append_v1(struct layout_cell *lc, struct layout_string *ls)
{ {
@@ -778,7 +781,7 @@ layout_assign(struct window *w, struct layout_parse_ctx *pctx)
layout_assign_fallback(w, w->layout_root); layout_assign_fallback(w, w->layout_root);
} }
/* Construct a cell from the legacy (v1) format. */ /* Construct a cell from the version 1 format. */
static struct layout_cell * static struct layout_cell *
layout_construct_cell(struct layout_cell *lcparent, const char **layout) layout_construct_cell(struct layout_cell *lcparent, const char **layout)
{ {
@@ -827,12 +830,15 @@ layout_construct_cell(struct layout_cell *lcparent, const char **layout)
return (lc); return (lc);
} }
/* Construct a layout from the legacy (v1) format. */ /* Construct a layout from the version 1 format. */
static struct layout_cell * static struct layout_cell *
layout_construct_v1(struct layout_cell *lcparent, const char **layout) layout_construct_v1(struct layout_cell *lcparent, const char **layout, u_int depth)
{ {
struct layout_cell *lc, *lcchild; struct layout_cell *lc, *lcchild;
if (depth > LAYOUT_V1_MAX_DEPTH)
return (NULL);
lc = layout_construct_cell(lcparent, layout); lc = layout_construct_cell(lcparent, layout);
if (lc == NULL) if (lc == NULL)
return (NULL); return (NULL);
@@ -855,7 +861,7 @@ layout_construct_v1(struct layout_cell *lcparent, const char **layout)
do { do {
(*layout)++; (*layout)++;
lcchild = layout_construct_v1(lc, layout); lcchild = layout_construct_v1(lc, layout, depth + 1);
if (lcchild == NULL) if (lcchild == NULL)
goto fail; goto fail;
TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry); TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
@@ -1068,7 +1074,8 @@ layout_construct(const char *input, struct layout_parse_ctx *pctx)
*pctx->cause = xstrdup("invalid layout checksum"); *pctx->cause = xstrdup("invalid layout checksum");
return (-1); return (-1);
} }
if ((pctx->root = layout_construct_v1(NULL, &input)) == NULL) { pctx->root = layout_construct_v1(NULL, &input, 0);
if (pctx->root == NULL) {
*pctx->cause = xstrdup("invalid layout"); *pctx->cause = xstrdup("invalid layout");
return (-1); return (-1);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 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>
@@ -91,7 +91,7 @@ static const char *options_table_window_size_list[] = {
"largest", "smallest", "manual", "latest", NULL "largest", "smallest", "manual", "latest", NULL
}; };
static const char *options_table_remain_on_exit_list[] = { static const char *options_table_remain_on_exit_list[] = {
"off", "on", "failed", "key", NULL "off", "on", "failed", "key", "failed-key", NULL
}; };
static const char *options_table_destroy_unattached_list[] = { static const char *options_table_destroy_unattached_list[] = {
"off", "on", "keep-last", "keep-group", NULL "off", "on", "keep-last", "keep-group", NULL
@@ -1698,7 +1698,8 @@ const struct options_table_entry options_table[] = {
.choices = options_table_remain_on_exit_list, .choices = options_table_remain_on_exit_list,
.default_num = 0, .default_num = 0,
.text = "Whether panes should remain ('on'), remain until a key is " .text = "Whether panes should remain ('on'), remain until a key is "
"pressed ('key') or be automatically killed ('off' or " "pressed after any exit ('key') or after a failure "
"('failed-key'), or be automatically killed ('off' or "
"'failed') when the program inside exits." "'failed') when the program inside exits."
}, },

View File

@@ -30,8 +30,11 @@ killw
EOF EOF
sleep 1 sleep 1
$TMUX has || exit 1 $TMUX has || exit 1
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1 # Use a control client to request legacy layouts, keeping only pane lines
cat <<EOF|cmp -s $TMP - || exit 1 # from the control protocol output.
$TMUX -C lsp -aF '#{pane_id} #{window_layout}' |
grep '^%[0-9]' >$TMP || exit 1
cat <<EOF|cmp $TMP - || exit 1
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3] %0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3] %3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4] %2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]

View File

@@ -104,6 +104,25 @@ assert_alive()
fi fi
} }
# Compare layout geometry and pane ordering, ignoring the active pane and
# last-pane history in the JSON layout. Selection is checked separately.
pane_layout()
{
$TMUX display-message -p -t "$1" '#{window_layout}' |
sed 's/,"a":true//g; s/,"l":[0-9][0-9]*//g'
}
check_layout()
{
out=$(pane_layout "$1")
if [ "$out" != "$2" ]; then
echo "Layout for '$1' wrong."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# split-window geometry. # split-window geometry.
@@ -309,12 +328,12 @@ check_ok kill-pane -t "$p6"
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane # Zoom and unzoom preserve the exact tiled layout. Selecting another pane
# without -Z unzooms, while -Z transfers zoom to the selected pane. # without -Z unzooms, while -Z transfers zoom to the selected pane.
layout=$($TMUX display-message -p -t P:0 '#{window_layout}') layout=$(pane_layout P:0)
check_ok select-pane -t "$p0" check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0" check_ok resize-pane -Z -t "$p0"
check_ok select-pane -t "$p2" check_ok select-pane -t "$p2"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1' check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
check_fmt P:0 '#{window_layout}' "$layout" check_layout P:0 "$layout"
check_ok select-pane -t "$p0" check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0" check_ok resize-pane -Z -t "$p0"
@@ -322,7 +341,7 @@ check_ok select-pane -Z -t "$p2"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \ check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1' '1:1:1'
check_ok resize-pane -Z -t "$p2" check_ok resize-pane -Z -t "$p2"
check_fmt P:0 '#{window_layout}' "$layout" check_layout P:0 "$layout"
# Directional selection temporarily restores the full layout to find its # Directional selection temporarily restores the full layout to find its
# neighbour, then follows the same unzoom or -Z transfer rules. # neighbour, then follows the same unzoom or -Z transfer rules.
@@ -340,7 +359,7 @@ check_ok select-pane -D -Z -t "$p0"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \ check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1' '1:1:1'
check_ok resize-pane -Z -t "$p2" check_ok resize-pane -Z -t "$p2"
check_fmt P:0 '#{window_layout}' "$layout" check_layout P:0 "$layout"
# The last-pane path has separate zoom handling, both with and without -Z. # The last-pane path has separate zoom handling, both with and without -Z.
check_ok select-pane -t "$p0" check_ok select-pane -t "$p0"
@@ -357,7 +376,7 @@ check_ok select-pane -l -Z -t P:0
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \ check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1' '1:1:1'
check_ok resize-pane -Z -t "$p0" check_ok resize-pane -Z -t "$p0"
check_fmt P:0 '#{window_layout}' "$layout" check_layout P:0 "$layout"
# Killing either a hidden ordinary pane or the zoom target unzooms. # Killing either a hidden ordinary pane or the zoom target unzooms.
check_ok new-window -d -t P:12 -n zoom-kill 'cat' check_ok new-window -d -t P:12 -n zoom-kill 'cat'

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server-client.c,v 1.511 2026/09/08 10:20:08 nicm Exp $ */ /* $OpenBSD: server-client.c,v 1.512 2026/09/10 11:02:18 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1391,15 +1391,19 @@ server_client_repeat_time(struct client *c, struct key_binding *bd)
return (repeat); return (repeat);
} }
/* Handle a key press on a dead pane waiting for a key. */ /* Handle a key press which closes a dead pane. */
static int static int
server_client_handle_dead_key(struct window_pane *wp, key_code key) server_client_handle_dead_key(struct window_pane *wp, key_code key)
{ {
int remain_on_exit;
if (wp == NULL || if (wp == NULL ||
(~wp->flags & PANE_EXITED) || (~wp->flags & PANE_EXITED) ||
KEYC_IS_MOUSE(key) || KEYC_IS_MOUSE(key) ||
KEYC_IS_PASTE(key) || KEYC_IS_PASTE(key))
options_get_number(wp->options, "remain-on-exit") != 3) return (0);
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
if (remain_on_exit != 3 && remain_on_exit != 4)
return (0); return (0);
options_set_number(wp->options, "remain-on-exit", 0); options_set_number(wp->options, "remain-on-exit", 0);
server_destroy_pane(wp, 0); server_destroy_pane(wp, 0);
@@ -1756,9 +1760,10 @@ server_client_handle_key0(struct client *c, struct key_event *event,
} }
/* /*
* Key presses in overlay mode, for panes capturing all keys and in the * Key presses in overlay mode, dead panes waiting for a key, modal cancel
* command prompt are a special case. The queue might be blocked so they * keys, panes capturing all keys and the command prompt are special cases.
* need to be processed immediately rather than queued. * The queue might be blocked so they need to be processed immediately
* rather than queued.
*/ */
if (~c->flags & CLIENT_READONLY) { if (~c->flags & CLIENT_READONLY) {
if (c->message_string != NULL) { if (c->message_string != NULL) {
@@ -1780,12 +1785,18 @@ server_client_handle_key0(struct client *c, struct key_event *event,
server_client_clear_overlay(c); server_client_clear_overlay(c);
wp = s->curw->window->active; wp = s->curw->window->active;
if (server_client_handle_dead_key(wp, event->key))
return (0);
if (wp != NULL && wp == wp->window->modal &&
(wp->flags & PANE_CLOSEONCANCEL) &&
(event->key == '\033' || event->key == ('c'|KEYC_CTRL))) {
server_kill_pane(wp);
return (0);
}
if (wp != NULL && if (wp != NULL &&
(wp->flags & PANE_CAPTUREALLKEYS) && (wp->flags & PANE_CAPTUREALLKEYS) &&
TAILQ_EMPTY(&wp->modes) && TAILQ_EMPTY(&wp->modes) &&
!KEYC_IS_MOUSE(event->key)) { !KEYC_IS_MOUSE(event->key)) {
if (server_client_handle_dead_key(wp, event->key))
return (0);
if (~wp->flags & PANE_EXITED) { if (~wp->flags & PANE_EXITED) {
window_pane_key(wp, c, s, s->curw, event->key, window_pane_key(wp, c, s, s->curw, event->key,
&event->m); &event->m);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server-fn.c,v 1.152 2026/09/03 19:12:36 nicm Exp $ */ /* $OpenBSD: server-fn.c,v 1.153 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>
@@ -386,6 +386,7 @@ server_destroy_pane(struct window_pane *wp, int notify)
case 0: case 0:
break; break;
case 2: case 2:
case 4:
if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0) if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
break; break;
/* FALLTHROUGH */ /* FALLTHROUGH */

19
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1168 2026/09/09 07:03:39 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 9 2026 $ .Dd $Mdocdate: September 10 2026 $
.Dt TMUX 1 .Dt TMUX 1
.Os .Os
.Sh NAME .Sh NAME
@@ -3655,7 +3655,7 @@ but a different format may be specified with
.Fl F . .Fl F .
.Tg newp .Tg newp
.It Xo Ic new\-pane .It Xo Ic new\-pane
.Op Fl AbCdefhIkKLMOPvWZ .Op Fl AbCDefhIkKLMOPvWZ
.Op Fl B Ar border\-lines .Op Fl B Ar border\-lines
.Op Fl c Ar start\-directory .Op Fl c Ar start\-directory
.Op Fl e Ar environment .Op Fl e Ar environment
@@ -3717,6 +3717,13 @@ all keys, including the prefix key, are passed directly to the modal pane.
With With
.Fl C , .Fl C ,
the modal pane is closed when the mouse is clicked outside it. the modal pane is closed when the mouse is clicked outside it.
With
.Fl D ,
the modal pane is closed when
.Ql Escape
or
.Ql C-c
is pressed.
.Pp .Pp
The The
.Fl L .Fl L
@@ -6328,7 +6335,7 @@ uses when the colour with that index is requested.
The index may be from zero to 255. The index may be from zero to 255.
.Pp .Pp
.It Xo Ic remain\-on\-exit .It Xo Ic remain\-on\-exit
.Op Ic on | off | failed | key .Op Ic on | off | failed | key | failed\-key
.Xc .Xc
A pane with this flag set is not destroyed when the program running in it A pane with this flag set is not destroyed when the program running in it
exits. exits.
@@ -6338,6 +6345,10 @@ then only when the program exit status is not zero.
If set to If set to
.Ic key , .Ic key ,
the pane stays open and closes when a key is pressed. the pane stays open and closes when a key is pressed.
If set to
.Ic failed\-key ,
the pane stays open and closes when a key is pressed only if the program exit
status is not zero.
The pane may be reactivated with the The pane may be reactivated with the
.Ic respawn\-pane .Ic respawn\-pane
command. command.

3
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1438 2026/09/09 08:30:05 nicm Exp $ */ /* $OpenBSD: tmux.h,v 1.1439 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>
@@ -1339,6 +1339,7 @@ struct window_pane {
#define PANE_CLOSEONCLICK 0x80000 #define PANE_CLOSEONCLICK 0x80000
#define PANE_CAPTUREALLKEYS 0x100000 #define PANE_CAPTUREALLKEYS 0x100000
#define PANE_FLOATOVERZOOM 0x200000 #define PANE_FLOATOVERZOOM 0x200000
#define PANE_CLOSEONCANCEL 0x400000
bitstr_t *sync_dirty; bitstr_t *sync_dirty;
u_int sync_dirty_size; u_int sync_dirty_size;