Compare commits

...
Author SHA1 Message Date
Nicholas Marriott 17d32ae40d Update CHANGES and tests. 2026-09-24 20:49:01 +01:00
Nicholas Marriott e4129d92c2 Test for newp -A and resize. 2026-09-24 20:13:35 +01:00
Nicholas Marriott 6a5de0d8e3 Test for resizing floating panes, from Noam Stolero. 2026-09-24 20:13:29 +01:00
Nicholas Marriott ff7efbe37c Some tests. 2026-09-24 20:13:22 +01:00
nicm ff7d45ad05 Do not cancel mark when click on border. 2026-09-24 20:12:52 +01:00
nicm 15a7944576 Add a feature for mintty's application escape to avoid dodgy terminals
whinging about not supporting it, GitHub issue 5626 from Pete Dietl.
2026-09-24 20:12:40 +01:00
nicm de69122034 Some terminals have extended DECSCUSR to allow it to reset to the
default style and put that as the Se capability in their terminfo entry.
Change so that if Ss and Se exist, tmux does not overwrite them with its
own. GitHub issue 5476.
2026-09-24 20:10:36 +01:00
nicm f38558ee12 Rather than allowing floating panes to remain outside the window and
invisible after resize, move them and resize them so they are fully
inside the window. GitHub issue 5582 from Noam Stolero.
2026-09-24 20:10:12 +01:00
nicm 38c7eb7413 Correctly write Z indexes to v2 layouts if zoomed, GitHub issue 5624. 2026-09-24 20:09:36 +01:00
nicm ad785736e9 Only rely on the previous line wrapping when the cursor is actually on
it, GitHub issue 5625 from Ben Maurer.
2026-09-24 20:09:25 +01:00
nicm 61687f2a5b Fix typo (too few 0s) in CLIENT_CONTROL_DISCARD, from someone in GitHub issue
5622.
2026-09-24 20:08:53 +01:00
nicm ed2b349c3e Do not leak waiting clients (in wait-for) if they are killed, GitHub
issue 5614.
2026-09-24 20:08:30 +01:00
nicm 92d29e54f4 Get x and y the right way round, fixes crash when opening a mode prompt
when the client is offset into the window; may be the crash reported by
sthen@.
2026-09-24 20:07:58 +01:00
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
58 changed files with 1431 additions and 197 deletions
+2 -2
View File
@@ -72,14 +72,14 @@ jobs:
- name: build - name: build
run: | run: |
sh autogen.sh sh autogen.sh
./configure ${{ matrix.configure }} ./configure ${{ matrix.configure }}
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)" ${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
- name: test - name: test
run: | run: |
cd regress cd regress
${{ matrix.make }} ${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
- name: logs - name: logs
if: failure() if: failure()
+37 -8
View File
@@ -10,8 +10,8 @@ CHANGES FROM 3.7c TO 3.8
- modal panes may be created with new-pane -O; a window can have one modal - modal panes may be created with new-pane -O; a window can have one modal
pane and it prevents interaction with the other panes while it is active. pane and it prevents interaction with the other panes while it is active.
-C closes the pane when the mouse is clicked outside it and -K sends all -C closes the pane when the mouse is clicked outside it and -K sends all
keys, including the prefix key, to the pane. A modal pane is now used for keys, including the prefix key, to the pane. -D closes the modal pane with
the editor in buffer mode; Escape or C-c. A modal pane is now used for the editor in buffer mode;
- new-pane -A creates a floating pane which remains above a zoomed pane; - new-pane -A creates a floating pane which remains above a zoomed pane;
modal panes do this by default; modal panes do this by default;
@@ -33,14 +33,15 @@ CHANGES FROM 3.7c TO 3.8
line; line;
- pane-border-status has top-floating and bottom-floating, and there are new - pane-border-status has top-floating and bottom-floating, and there are new
default bindings under C-b g for common move and resize operations; default bindings under C-b g for common move and resize operations.
- floating panes are unzoomed before creation to avoid a crash.
* Layout strings now use a JSON subset format which includes floating panes. * Layout strings now use a JSON subset format which includes floating panes.
The old format is still accepted; control mode clients receive old layouts The old format is still accepted; control mode clients receive old layouts
unless they set the new-layouts flag. unless they set the new-layouts flag.
* Add remain-on-exit failed-key to keep a pane open after a failed command
until a key is pressed.
* Add support for themes and improve default colours: * Add support for themes and improve default colours:
- tmux now has builtin light and dark colour themes for terminals with 256 or - tmux now has builtin light and dark colour themes for terminals with 256 or
@@ -117,6 +118,12 @@ CHANGES FROM 3.7c TO 3.8
in terminal-features, and add a utf8 feature for terminals which support in terminal-features, and add a utf8 feature for terminals which support
UTF-8 output. UTF-8 output.
* Add an appesc terminal feature for mintty's application escape key mode,
avoiding unsupported sequences on other terminals (Pete Dietl, issue 5626).
* Preserve existing Ss and Se terminfo capabilities for cursor styles and
restoring the default cursor (issue 5476).
* Add a #{A/count:frames} modifier to show a series of frames as an animation * Add a #{A/count:frames} modifier to show a series of frames as an animation
in the status line (issue 5412 from Fernando Daciuk). in the status line (issue 5412 from Fernando Daciuk).
@@ -132,6 +139,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.
@@ -189,10 +198,23 @@ CHANGES FROM 3.7c TO 3.8
sent inside %begin/%end (issue 5458), do not let a stuck client prevent the sent inside %begin/%end (issue 5458), do not let a stuck client prevent the
server from exiting (issue 5444), and reset control mode offsets when a pane server from exiting (issue 5444), and reset control mode offsets when a pane
is respawned (issue 5498). Bound buffered command replies so a control mode is respawned (issue 5498). Bound buffered command replies so a control mode
client that stops reading cannot consume unlimited memory (issue 5565). client that stops reading cannot consume unlimited memory (issue 5565), and
fix a collision between the output discard and bracketed paste flags (issue
5622).
* Fix grouped sessions sometimes being left as unusable command targets while * Fix grouped sessions sometimes being left as unusable command targets while
they are being killed (Bryce Miller, issue 5180). they are being killed (Bryce Miller, issue 5180), and prevent reuse of a
session with no windows while detaching (Heon Jeong, issue 5620).
* Free wait-for clients when they disconnect and do not run their remaining
queued commands (issue 5614).
* Expand formats in run-shell -c like other start-directory flags (reported by
Saul Nogueras).
* Fix session alert flag formats to check all windows (issue 5599).
* Do not clear the marked pane when clicking on a pane border.
* Fix choose mode filtering when more than one pane exists (issue 5326), and * Fix choose mode filtering when more than one pane exists (issue 5326), and
fix an infinite loop in customize mode when a filter does not match. fix an infinite loop in customize mode when a filter does not match.
@@ -203,11 +225,18 @@ CHANGES FROM 3.7c TO 3.8
status lines, menus, popups, wide characters and floating panes. This status lines, menus, popups, wide characters and floating panes. This
includes fixes for flicker with alternate screen applications and scrollbars includes fixes for flicker with alternate screen applications and scrollbars
(Michael Grant, issue 5351; Noam Stolero, issue 5350; Aung Myo Kyaw, issue (Michael Grant, issue 5351; Noam Stolero, issue 5350; Aung Myo Kyaw, issue
5098). 5098), and misplaced wrapped lines during partial redraws (Ben Maurer, issue
5625).
* Do not crash looking for the next or previous session (issue 5344), or when * Do not crash looking for the next or previous session (issue 5344), or when
no client is available. no client is available.
* Fix crashes when destroying a zoomed pane (Romain Francoise, issue 5591),
copying from a blank source pane (Kaixuan Li), or opening a mode prompt when
the client is offset into the window.
* Avoid an infinite loop when assigning a width to WCHAR_MAX (issue 5602).
CHANGES FROM 3.7b TO 3.7c CHANGES FROM 3.7b TO 3.7c
* Build with jemalloc on macOS to avoid what appears to be a bug in calloc * Build with jemalloc on macOS to avoid what appears to be a bug in calloc
+14 -5
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)
+4 -1
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-queue.c,v 1.123 2026/08/24 20:34:26 nicm Exp $ */ /* $OpenBSD: cmd-queue.c,v 1.124 2026/09/22 06:46:50 nicm Exp $ */
/* /*
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -580,6 +580,9 @@ cmdq_fire_command(struct cmdq_item *item)
int flags, quiet = 0; int flags, quiet = 0;
char *tmp; char *tmp;
if (item->client != NULL && (item->client->flags & CLIENT_DEAD))
return (CMD_RETURN_ERROR);
if (cfg_finished) if (cfg_finished)
cmdq_add_message(item); cmdq_add_message(item);
if (log_get_level() > 1) { if (log_get_level() > 1) {
+3 -2
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')) {
+2 -2
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));
+5 -3
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) {
+35 -1
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-wait-for.c,v 1.23 2026/07/10 13:38:45 nicm Exp $ */ /* $OpenBSD: cmd-wait-for.c,v 1.24 2026/09/22 06:46:50 nicm Exp $ */
/* /*
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -480,6 +480,40 @@ cmd_wait_for_unlock(struct cmdq_item *item, const char *name,
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
void
cmd_wait_for_client_lost(struct client *c)
{
struct wait_channel *wc, *wc1;
struct wait_item *wi, *wi1;
struct wait_event_item *wei, *wei1;
TAILQ_FOREACH_SAFE(wei, &wait_event_items, entry, wei1) {
if (cmdq_get_client(wei->item) == c) {
TAILQ_REMOVE(&wait_event_items, wei, entry);
cmdq_continue(wei->item);
cmd_wait_for_event_free(wei);
}
}
RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
if (cmdq_get_client(wi->item) == c) {
cmdq_continue(wi->item);
TAILQ_REMOVE(&wc->waiters, wi, entry);
free(wi);
}
}
TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
if (cmdq_get_client(wi->item) == c) {
cmdq_continue(wi->item);
TAILQ_REMOVE(&wc->lockers, wi, entry);
free(wi);
}
}
cmd_wait_for_remove_empty(wc);
}
}
void void
cmd_wait_for_flush(void) cmd_wait_for_flush(void)
{ {
+1 -1
View File
@@ -1,6 +1,6 @@
# configure.ac # configure.ac
AC_INIT([tmux], next-3.8) AC_INIT([tmux], 3.8-rc2)
AC_PREREQ([2.60]) AC_PREREQ([2.60])
AC_CONFIG_AUX_DIR(etc) AC_CONFIG_AUX_DIR(etc)
+6 -6
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);
} }
+2 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: key-bindings.c,v 1.192 2026/08/06 09:05:04 nicm Exp $ */ /* $OpenBSD: key-bindings.c,v 1.193 2026/09/21 20:45:48 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -523,7 +523,7 @@ key_bindings_init(void)
"bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }", "bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }",
/* Mouse button 1 on border. */ /* Mouse button 1 on border. */
"bind -n MouseDown1Border { select-pane -M }", "bind -n MouseDown1Border { select-pane -t= }",
/* Mouse button 1 drag on border. */ /* Mouse button 1 drag on border. */
"bind -n MouseDrag1Border { resize-pane -M }", "bind -n MouseDrag1Border { resize-pane -M }",
+68 -27
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.43 2026/09/22 06:48:01 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;
@@ -280,26 +283,59 @@ 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,"));
}
/* Get a floating pane cell's z-index in the layout being dumped. */
static u_int
layout_cell_zindex(struct layout_cell *lc)
{
struct window_pane *wp = lc->wp, *wq;
struct window *w = wp->window;
struct layout_cell *other;
int saved = (lc == wp->saved_layout_cell);
u_int i = 0;
if (saved &&
w->active != NULL &&
(w->active->flags & PANE_ZOOMED) &&
(w->active->saved_layout_cell->flags & LAYOUT_CELL_FLOATING)) {
if (wp == w->active)
return (0);
i++;
}
TAILQ_FOREACH(wq, &w->z_index, zentry) {
if (wq == wp)
break;
if (saved)
other = wq->saved_layout_cell;
else
other = wq->layout_cell;
if (other != NULL && (other->flags & LAYOUT_CELL_FLOATING))
i++;
}
return (i);
} }
/* Append information for a single cell in a JSON (v2) format. */ /* Append information for a single cell in a JSON (v2) format. */
@@ -310,7 +346,7 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
struct window_pane *wp; struct window_pane *wp;
enum layout_type type; enum layout_type type;
char c; char c;
u_int i, n; u_int i, n, z;
if (lc == NULL) if (lc == NULL)
return (-1); return (-1);
@@ -351,9 +387,10 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
if (window_pane_index(wp, &i) != 0) if (window_pane_index(wp, &i) != 0)
return (-1); return (-1);
layout_string_write(ls, ",\"i\":%u", i); layout_string_write(ls, ",\"i\":%u", i);
if ((lc->flags & LAYOUT_CELL_FLOATING) && if (lc->flags & LAYOUT_CELL_FLOATING) {
window_pane_zindex(wp, &i) == 0) z = layout_cell_zindex(lc);
layout_string_write(ls, ",\"z\":%u", i); layout_string_write(ls, ",\"z\":%u", z);
}
layout_string_write(ls, ",\"I\":\"%%%u\"", wp->id); layout_string_write(ls, ",\"I\":\"%%%u\"", wp->id);
} }
@@ -362,7 +399,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 +815,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 +864,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 +895,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 +1108,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);
} }
+64 -5
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: layout.c,v 1.99 2026/09/09 07:03:39 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,6 +768,49 @@ 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)
@@ -788,8 +831,11 @@ 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)
@@ -819,6 +865,7 @@ 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);
} }
@@ -1681,7 +1728,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;
@@ -1730,12 +1777,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;
@@ -1746,8 +1801,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;
+4 -3
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."
}, },
+23 -20
View File
@@ -1,33 +1,17 @@
TESTS!= echo *.sh TESTS!= echo *.sh
LOGDIR=logs LOGDIR=logs
.PHONY: all .PHONY: all prepare $(TESTS)
.NOTPARALLEL: all
all: all: prepare $(TESTS)
@mkdir -p "$(LOGDIR)"; \ @failed=0; failures=; \
rm -f "$(LOGDIR)"/*.log; \
failed=0; failures=; \
for test in $(TESTS); do \ for test in $(TESTS); do \
base=$${test##*/}; \ base=$${test##*/}; \
log="$(LOGDIR)/$${base%.sh}.log"; \ log="$(LOGDIR)/$${base%.sh}.log"; \
rm -f "$$log"; \ if [ -f "$$log" ]; then \
printf '%-40s ' "$$test"; \
start=$$(date +%s); \
env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
sh -x "$$test" >"$$log" 2>&1; \
if [ $$? -eq 0 ]; then \
end=$$(date +%s); \
rm -f "$$log"; \
echo "PASS ($$((end - start))s)"; \
else \
end=$$(date +%s); \
echo "FAIL ($$((end - start))s)"; \
echo " log: $$log"; \
failed=1; \ failed=1; \
failures="$$failures $$test"; \ failures="$$failures $$test"; \
fi; \ fi; \
sleep 1; \
done; \ done; \
if [ "$$failed" -ne 0 ]; then \ if [ "$$failed" -ne 0 ]; then \
echo; \ echo; \
@@ -39,3 +23,22 @@ all:
rmdir "$(LOGDIR)" 2>/dev/null || true; \ rmdir "$(LOGDIR)" 2>/dev/null || true; \
fi; \ fi; \
exit $$failed exit $$failed
prepare:
@mkdir -p "$(LOGDIR)"
@rm -f "$(LOGDIR)"/*.log
$(TESTS): prepare
@base="$@"; base=$${base##*/}; \
log="$(LOGDIR)/$${base%.sh}.log"; \
start=$$(date +%s); \
if env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
sh -x "$@" >"$$log" 2>&1; then \
end=$$(date +%s); \
rm -f "$$log"; \
printf '%-40s PASS (%ss)\n' "$@" "$$((end - start))"; \
else \
end=$$(date +%s); \
printf '%-40s FAIL (%ss)\n log: %s\n' \
"$@" "$$((end - start))" "$$log"; \
fi
+32 -2
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"
+53
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
+1 -1
View File
@@ -17,7 +17,7 @@ PATH=/bin:/usr/bin
TERM=screen TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest" TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
DIR=$(mktemp -d) DIR=$(mktemp -d)
+1 -1
View File
@@ -19,7 +19,7 @@ PATH=/bin:/usr/bin
TERM=screen TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest" TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
DIR=$(mktemp -d) DIR=$(mktemp -d)
+1 -1
View File
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
TERM=screen TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest" TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
OUT=$(mktemp) OUT=$(mktemp)
+5 -2
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]
+1 -1
View File
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
TERM=screen TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest" TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
FIFO=$(mktemp -u) FIFO=$(mktemp -u)
+5 -3
View File
@@ -153,11 +153,13 @@ wait_format "$p0" '#{pane_in_mode}' '0'
wait_option @picked "$p1" wait_option @picked "$p1"
# Commands after display-panes run immediately while the mode remains. # Commands after display-panes run immediately while the mode remains.
# Record the mode in the same command queue so client startup cannot race
# the 500ms timeout.
$TMUX set -g @after none || fail "set @after failed" $TMUX set -g @after none || fail "set @after failed"
$TMUX display-panes -Nd 500 -t "$p0" \; set -g @after fast || $TMUX display-panes -Nd 500 -t "$p0" \; \
set -gF -t "$p0" @after '#{pane_mode}' ||
fail "display-panes immediate command failed" fail "display-panes immediate command failed"
wait_option @after fast wait_option @after 'panes-mode'
wait_format "$p0" '#{pane_mode}' 'panes-mode'
wait_format "$p0" '#{pane_in_mode}' '0' wait_format "$p0" '#{pane_in_mode}' '0'
# Existing zoom is restored on exit. # Existing zoom is restored on exit.
+144
View File
@@ -268,5 +268,149 @@ $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
+158
View File
@@ -0,0 +1,158 @@
#!/bin/sh
# Moving, resizing and reordering an over-zoom float must leave the tiled
# pane zoomed, and the changes must survive an explicit unzoom.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
trap '$TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
fail()
{
echo "$*" >&2
exit 1
}
run()
{
$TMUX "$@" || fail "failed: $*"
}
check()
{
got=$(run display-message -p -t "$1" "$2") || exit 1
[ "$got" = "$3" ] || fail "$1 $2: got '$got', expected '$3'"
}
check_zoom()
{
check "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
check "$float" '#{pane_floating_flag}:#{pane_active}' "1:$active"
check "$base" '#{@unzoomed}' ''
}
run new-session -d -x 80 -y 24
base=$(run display-message -p '#{pane_id}') || exit 1
other=$(run split-window -dPF '#{pane_id}') || exit 1
layout=$(run display-message -p '#{window_layout}') || exit 1
run set-hook -g window-unzoomed 'set -g @unzoomed 1'
# Cover creation before and after zoom, with both the float and the tiled
# pane active. Modal panes use the same over-zoom flag.
for mode in before after detached modal; do
if [ "$mode" != before ]; then
run resize-pane -Z -t "$base"
fi
case "$mode" in
detached) flags=-Ad; active=0 ;;
modal) flags=-O; active=1 ;;
*) flags=-A; active=1 ;;
esac
float=$(run new-pane "$flags" -PF '#{pane_id}' -t "$base" \
-x 20 -y 8 -X 8 -Y 3 '') || exit 1
if [ "$mode" = before ]; then
run resize-pane -Z -t "$base"
fi
run set -gu @unzoomed
check_zoom
run move-pane -t "$float" -D
check "$float" '#{pane_top}' 5
check_zoom
run move-pane -t "$float" -U 2 -R 3 -L 1
check "$float" '#{pane_left}:#{pane_top}' '11:3'
check_zoom
run move-pane -t "$float" -X 25% -Y 25%
check "$float" '#{pane_left}:#{pane_top}' '21:7'
check_zoom
run move-pane -t "$float" -P bottom-right
check "$float" '#{pane_left}:#{pane_top}' '61:17'
check_zoom
run resize-pane -t "$float" -x 30 -y 10
check "$float" '#{pane_width}:#{pane_height}' '28:8'
check_zoom
run resize-pane -t "$float" -U 1 -L 2
check "$float" '#{pane_left}:#{pane_top}:#{pane_width}:#{pane_height}' \
'59:16:30:9'
check_zoom
run move-pane -t "$float" -P centre
check "$float" '#{pane_left}:#{pane_top}' '25:7'
check_zoom
# Failed commands must not change zoom either.
for command in 'move-pane -D invalid' 'move-pane -P invalid' \
'move-pane -z invalid' 'resize-pane -x invalid' \
'resize-pane -D invalid'; do
$TMUX $command -t "$float" 2>/dev/null &&
fail "unexpected success: $command"
check_zoom
done
run resize-pane -Z -t "$base"
check "$float" '#{pane_left}:#{pane_top}:#{pane_width}:#{pane_height}' \
'25:7:30:9'
run kill-pane -t "$float"
check "$base" '#{window_layout}' "$layout"
done
# Put an ordinary (hidden while zoomed) float between two over-zoom floats
# in the stacking order. Reordering must skip it when counting visible
# positions, but keep all floats ahead of the tiled panes after unzoom.
back=$(run new-pane -AdPF '#{pane_id}' -t "$base" '') || exit 1
hidden=$(run new-pane -dPF '#{pane_id}' -t "$base" '') || exit 1
float=$(run new-pane -AdPF '#{pane_id}' -t "$base" '') || exit 1
active=0
run resize-pane -Z -t "$base"
run set -gu @unzoomed
for position in backward back forward-loop; do
run move-pane -t "$float" -P front
run move-pane -t "$float" -P "$position"
check "$float" '#{pane_z}' 1
check "$back" '#{pane_z}' 0
check_zoom
done
for position in forward front backward-loop; do
run move-pane -t "$float" -P back
run move-pane -t "$float" -P "$position"
check "$float" '#{pane_z}' 0
check "$back" '#{pane_z}' 1
check_zoom
done
for z in 1 0 99; do
run move-pane -t "$float" -z "$z"
want=$z
[ "$z" = 99 ] && want=1
check "$float" '#{pane_z}' "$want"
check_zoom
done
run resize-pane -Z -t "$base"
check "$float" '#{pane_z}' 2
check "$base" '#{pane_z}' 4
run kill-pane -t "$hidden"
run kill-pane -t "$back"
# Explicit zoom toggling and operations on tiled panes retain their
# existing behaviour. The over-zoom flag alone does not make a pane a float:
# zooming the float itself gives it a tiled cell until unzoom.
run resize-pane -Z -t "$float"
check "$float" '#{pane_zoomed_flag}:#{pane_floating_flag}' '1:0'
run resize-pane -t "$float" -x 25
check "$float" '#{window_zoomed_flag}:#{pane_floating_flag}:#{pane_width}' \
'0:1:23'
run resize-pane -Z -t "$base"
run resize-pane -t "$other" -D 1
check "$base" '#{window_zoomed_flag}' 0
# A hidden ordinary float must still be unzoomed before resizing it.
hidden=$(run new-pane -dPF '#{pane_id}' -t "$base" '') || exit 1
run resize-pane -Z -t "$base"
run resize-pane -t "$hidden" -x 25
check "$hidden" '#{window_zoomed_flag}:#{pane_floating_flag}:#{pane_width}' \
'0:1:23'
exit 0
+2
View File
@@ -7,6 +7,8 @@ TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
trap '$TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
# test_format $format $expected_result # test_format $format $expected_result
test_format() test_format()
+20 -4
View File
@@ -18,6 +18,24 @@ export TZ LANG LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
HOLD1=
HOLD2=
CC1=
CC2=
cleanup()
{
for pid in $HOLD1 $HOLD2 $CC1 $CC2; do
kill "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
done
$TMUX kill-server 2>/dev/null
rm -f "$FIFO1" "$FIFO2"
}
trap cleanup 0
trap 'exit 1' 1 2 15
ESC=$(printf '\033') ESC=$(printf '\033')
@@ -579,8 +597,6 @@ assert_alive "verbose loop expansion"
# L loops over attached clients. Attach two control-mode clients, each held # L loops over attached clients. Attach two control-mode clients, each held
# open by a background process keeping a FIFO's write end open. # open by a background process keeping a FIFO's write end open.
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
rm -f "$FIFO1" "$FIFO2" rm -f "$FIFO1" "$FIFO2"
mkfifo "$FIFO1" "$FIFO2" || exit 1 mkfifo "$FIFO1" "$FIFO2" || exit 1
# Hold the write ends open so the control clients stay attached. # Hold the write ends open so the control clients stay attached.
@@ -605,9 +621,9 @@ test_format "#{L/nr:x}" "xx"
test_format "#{L/r:x}" "xx" test_format "#{L/r:x}" "xx"
# Now detach one and confirm the count drops to one. # Now detach one and confirm the count drops to one.
kill $HOLD2 2>/dev/null kill $HOLD2 2>/dev/null
wait $HOLD2 2>/dev/null
HOLD2=
sleep 1 sleep 1
test_format "#{L:x}" "x" test_format "#{L:x}" "x"
kill $HOLD1 $CC1 $CC2 2>/dev/null
rm -f "$FIFO1" "$FIFO2"
exit 0 exit 0
+2
View File
@@ -7,6 +7,8 @@ TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
trap '$TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
# test_format $format $expected_result # test_format $format $expected_result
test_format() test_format()
+6 -6
View File
@@ -76,14 +76,14 @@ $TMUX set-hook -g session-closed \
fail "set-hook session-closed failed" fail "set-hook session-closed failed"
# The only pane of the only window of a session exits: pane-exited, then # The only pane of the only window of a session exits: pane-exited, then
# window-unlinked, then session-closed, each seeing the dead object in the # session-closed, then window-unlinked, each seeing the dead object in the
# hook formats. # hook formats.
pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') || pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') ||
fail "new-session doomed failed" fail "new-session doomed failed"
wait_for @log \ wait_for @log \
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed" "|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
assert_unchanged @log \ assert_unchanged @log \
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed" "|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
# The dead pane, window and session cannot be used as targets but the # The dead pane, window and session cannot be used as targets but the
# server survives. # server survives.
@@ -110,14 +110,14 @@ $TMUX set-hook -g pane-exited \
'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' || 'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' ||
fail "restore pane-exited hook failed" fail "restore pane-exited hook failed"
# kill-window on the last window: window-unlinked then session-closed but # kill-window on the last window: session-closed then window-unlinked but
# no pane-exited for the panes in the killed window. # no pane-exited for the panes in the killed window.
$TMUX set -g @log '' || fail "reset @log failed" $TMUX set -g @log '' || fail "reset @log failed"
$TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed" $TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed"
$TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed" $TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed"
$TMUX kill-window -t doomed2:0 || fail "kill-window failed" $TMUX kill-window -t doomed2:0 || fail "kill-window failed"
wait_for @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2' wait_for @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
assert_unchanged @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2' assert_unchanged @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
$TMUX has -t main || fail "server died after kill-window chain" $TMUX has -t main || fail "server died after kill-window chain"
# kill-session: session-closed fires first, then window-unlinked for its # kill-session: session-closed fires first, then window-unlinked for its
+2 -1
View File
@@ -12,7 +12,8 @@ $TMUX kill-server 2>/dev/null
TMP=$(mktemp) TMP=$(mktemp)
OUT=$(mktemp) OUT=$(mktemp)
trap "rm -f $TMP $OUT" 0 1 15 trap 'rm -f "$TMP" "$OUT"; $TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
cat <<EOF >$TMP cat <<EOF >$TMP
if 'true' 'wibble wobble' if 'true' 'wibble wobble'
+5 -2
View File
@@ -4,6 +4,9 @@ TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
# Keep panes alive until the next case or cleanup, without reading replies.
INPUT_HOLD='while :; do sleep 3600; done'
TMP=$(mktemp) TMP=$(mktemp)
EXP=$(mktemp) EXP=$(mktemp)
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15 trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
@@ -37,10 +40,10 @@ start_pane_hlimit()
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
sleep 0.1 sleep 0.1
$TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1 $TMUX new-session -d -x 1 -y 1 -s test-setup "$INPUT_HOLD" || exit 1
$TMUX set-option -g history-limit "$hlimit" || exit 1 $TMUX set-option -g history-limit "$hlimit" || exit 1
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \ $TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \
"printf '$seq'; sleep 2" || exit 1 "printf '$seq'; $INPUT_HOLD" || exit 1
$TMUX kill-session -t test-setup $TMUX kill-session -t test-setup
sleep 0.3 sleep 0.3
} }
+13 -1
View File
@@ -22,7 +22,19 @@ assert_key () {
clear-history -t$W \; \ clear-history -t$W \; \
send-keys -t$W "$key" 'EOL' || exit 1 send-keys -t$W "$key" 'EOL' || exit 1
actual_code=$($TMUX capturep -pt$W | \ # cat echoes the keys back asynchronously, so wait for the EOL marker
# to reach the pane instead of capturing straight away.
i=0
while [ $i -lt 50 ]; do
screen=$($TMUX capturep -pt$W)
case "$screen" in
*EOL*) break ;;
esac
i=$((i + 1))
sleep 0.1
done
actual_code=$(printf '%s\n' "$screen" | \
head -1 | \ head -1 | \
sed -e 's/EOL.*$//') sed -e 's/EOL.*$//')
+34 -7
View File
@@ -2,6 +2,38 @@
. ./input-common.inc . ./input-common.inc
# Wait for the terminator and following text to reach the parser.
check_discard()
{
name=$1
printf 'OK\n' >"$EXP"
i=0
while [ "$i" -lt 300 ]; do
capture_grid "$name" >"$TMP"
cmp -s "$TMP" "$EXP" && return 0
sleep 0.05
i=$((i + 1))
done
fail "$name (timed out waiting for discard)"
}
# The missing-terminator timer may expire before the string limit is reached
# on a slow build. Clear any payload printed after the timeout, then check
# that the string did not change the title and normal output has resumed.
test_discard()
{
name=$1
prefix=$2
start_cmd "$name" 8 3 "$INPUT_HOLD"
$TMUX select-pane -T discard-test || exit 1
$TMUX respawn-pane -k \
"perl -e 'print qq{$prefix}, q{x} x 1100000, qq{\e\\\\\e[H\e[2JOK}'; $INPUT_HOLD" || exit 1
check_discard "$name"
$TMUX display-message -p -t "$name:" '#{pane_title}' >"$TMP" || exit 1
printf 'discard-test\n' >"$EXP"
cmp "$TMP" "$EXP" || fail "$name title"
}
start_cmd csi-param-discard 8 3 \ start_cmd csi-param-discard 8 3 \
"perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2" "perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2"
check_capture csi-param-discard 'OK' check_capture csi-param-discard 'OK'
@@ -10,13 +42,8 @@ start_cmd csi-interm-discard 8 3 \
"perl -e 'print qq{\e[ \030OK}'; sleep 2" "perl -e 'print qq{\e[ \030OK}'; sleep 2"
check_capture csi-interm-discard 'OK' check_capture csi-interm-discard 'OK'
start_cmd osc-discard 8 3 \ test_discard osc-discard '\e]2;'
"perl -e 'print qq{\e]2;}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2" test_discard apc-discard '\e_'
check_capture osc-discard 'OK'
start_cmd apc-discard 8 3 \
"perl -e 'print qq{\e_}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
check_capture apc-discard 'OK'
start_pane unknown-csi 8 3 '\033[?9999zOK' start_pane unknown-csi 8 3 '\033[?9999zOK'
check_capture unknown-csi 'OK' check_capture unknown-csi 'OK'
+1 -1
View File
@@ -51,7 +51,7 @@ $TMUX kill-server 2>/dev/null
sleep 0.1 sleep 0.1
$TMUX new-session -d -x 5 -y 3 -s history \; \ $TMUX new-session -d -x 5 -y 3 -s history \; \
set-option -g history-limit 3 \; \ set-option -g history-limit 3 \; \
respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; sleep 2" || exit 1 respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; $INPUT_HOLD" || exit 1
sleep 0.3 sleep 0.3
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP" $TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
printf "%s\n" '01 printf "%s\n" '01
+28
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
+43 -9
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;
@@ -376,10 +377,7 @@ must_equal 'Layout after select-pane back' "$(layout L:two)" "$SPLIT"
# With nothing zoomed the two layout formats agree. # With nothing zoomed the two layout formats agree.
# #
# The zoomed case is deliberately not covered here. While a pane is zoomed # Zoomed layouts are checked below with floating panes.
# #{window_layout} dumps the saved (unzoomed) layout and
# #{window_visible_layout} the zoomed one, but that depends on how zooming
# stashes the layout root rather than on anything in layout-custom.c.
must_equal 'Visible layout' "$(visible_layout L:two)" "$SPLIT" must_equal 'Visible layout' "$(visible_layout L:two)" "$SPLIT"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -703,6 +701,42 @@ must_contain 'Floating layout back z-index' "$floating" '"z":1'
check_ok select-layout -t L:float "$floating" check_ok select-layout -t L:float "$floating"
must_equal 'Floating layout after round trip' "$(raw_layout L:float)" "$floating" must_equal 'Floating layout after round trip' "$(raw_layout L:float)" "$floating"
# A dump taken while zoomed must retain the unzoomed floating z-indexes.
# Check hidden floats, floats above zoom, and a mixture of the two.
for flags in '' A mixed; do
check_ok new-window -d -t L: -n zoom
zt=$($TMUX display-message -p -t L:zoom '#{pane_id}')
check_ok split-window -d -h -t "$zt"
case "$flags" in
A|mixed) first=-Ad ;;
*) first=-d ;;
esac
case "$flags" in
A) second=-Ad ;;
*) second=-d ;;
esac
check_ok new-pane "$first" -t "$zt" -x 20 -y 6 ''
check_ok new-pane "$second" -t "$zt" -x 30 -y 8 ''
for pane in 0 2 3; do
check_ok select-pane -t "L:zoom.$pane"
before=$(raw_layout L:zoom)
legacy=$(v1_layout L:zoom)
check_ok resize-pane -Z -t "L:zoom.$pane"
during=$(raw_layout L:zoom)
must_equal 'Layout while zoomed' "$during" "$before"
must_equal 'Legacy layout while zoomed' \
"$(v1_layout L:zoom)" "$legacy"
must_equal 'Zoom after dumping layout' \
"$($TMUX display-message -p -t L:zoom '#{window_zoomed_flag}')" 1
must_differ 'Visible layout while zoomed' \
"$(visible_layout L:zoom)" "$(layout L:zoom)"
check_ok select-layout -t L:zoom "$during"
must_equal 'Round trip from zoomed layout' \
"$(raw_layout L:zoom)" "$before"
done
check_ok kill-window -t L:zoom
done
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Floating panes and the legacy (v1) format. # Floating panes and the legacy (v1) format.
# #
@@ -838,12 +872,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 +890,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;
+43 -2
View File
@@ -337,9 +337,9 @@ $TMUX set -g @modal-prefix no
$TMUX set -g @modal-root no $TMUX set -g @modal-root no
$TMUX bind -n z set -g @modal-root yes $TMUX bind -n z set -g @modal-root yes
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \ modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'cat') || -x 20 -y 5 -X 20 -Y 10 'cat') ||
fail "new-pane -OK failed" fail "new-pane -ODK failed"
sleep 1 sleep 1
$TMUX2 send-keys -t "$OUTER" C-b x z Enter $TMUX2 send-keys -t "$OUTER" C-b x z Enter
sleep 1 sleep 1
@@ -356,9 +356,50 @@ new_left=$(fmt "$modal" '#{pane_left}')
new_top=$(fmt "$modal" '#{pane_top}') new_top=$(fmt "$modal" '#{pane_top}')
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] || [ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
fail "key-capturing modal pane did not move" fail "key-capturing modal pane did not move"
$TMUX2 send-keys -t "$OUTER" Escape
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'trap "" INT; exec cat') ||
fail "new-pane -ODK failed"
sleep 1
$TMUX2 send-keys -t "$OUTER" C-c
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
# A dead modal does not close on Escape or C-c without -D.
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
fail "new-pane -O failed"
check_ok set-option -p -t "$modal" remain-on-exit on
sleep 2
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
$TMUX2 send-keys -t "$OUTER" Escape
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
check_ok kill-pane -t "$modal" check_ok kill-pane -t "$modal"
sleep 1 sleep 1
# failed-key closes successful panes and retains failed panes until a key.
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
fail "new-pane -O failed"
check_ok set-option -p -t "$modal" remain-on-exit failed-key
sleep 2
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'sleep 1; exit 1') ||
fail "new-pane -O failed"
check_ok set-option -p -t "$modal" remain-on-exit failed-key
sleep 2
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
must_equal "$($TMUX show-options -pv -t "$modal" remain-on-exit)" failed-key
$TMUX2 send-keys -t "$OUTER" a
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
# A nonmodal floating pane may remain above zoom, and switching between it and # A nonmodal floating pane may remain above zoom, and switching between it and
# the zoomed tiled pane must not unzoom the window. # the zoomed tiled pane must not unzoom the window.
check_ok new-window -d -t modal: -n float-over-zoom 'cat' check_ok new-window -d -t modal: -n float-over-zoom 'cat'
+24 -5
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'
+1 -1
View File
@@ -15,7 +15,7 @@ PATH=/bin:/usr/bin
TERM=screen TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest" TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
DIR=$(mktemp -d) DIR=$(mktemp -d)
+55
View File
@@ -0,0 +1,55 @@
#!/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
}
# The start path is available immediately, before the child is scheduled.
# Use it to test the format context in the same command queue as creation.
$TMUX new-session -d -s test -c "$TMP/first" 'sleep 60' \; \
run-shell -c '#{pane_start_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
# The current path depends on the operating system finding the child process.
i=0
while [ "$($TMUX display-message -p -t "$pane" '#{pane_current_path}')" != \
"$TMP/second dir" ]; do
if [ "$i" -ge 100 ]; then
echo "Timed out waiting for pane current directory"
exit 1
fi
sleep 0.05
i=$((i + 1))
done
$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
+2 -1
View File
@@ -95,7 +95,8 @@ layout_tee_down() { # top/bottom, bottom split: a top tee
$TMUX kill-server 2>/dev/null $TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null
$TMUX2 new -d -x40 -y14 "sh -c 'exec sleep 100'" || exit 1 # Keep the anchor window alive until cleanup, even on slow test runs.
$TMUX2 new -d -x40 -y14 'exec cat' || exit 1
$TMUX2 set -g status off || exit 1 $TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1 $TMUX2 set -g window-size manual || exit 1
$TMUX2 set -g pane-border-format " #{pane_index} " || exit 1 $TMUX2 set -g pane-border-format " #{pane_index} " || exit 1
+116
View File
@@ -0,0 +1,116 @@
#!/bin/sh
# When only some lines are redrawn after a synchronized update, a line
# following a wrapped line must be drawn at its own position, not where the
# cursor happens to be.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
DIR=$(mktemp -d) || exit 1
TMUX_TMPDIR=$DIR
export TMUX_TMPDIR
INNER="$TEST_TMUX -Li$$ -f/dev/null"
OUTER="$TEST_TMUX -Lo$$ -f/dev/null"
CONTROL=$DIR/control
EMITTER=$DIR/emitter.pl
fail()
{
echo "$*" >&2
exit 1
}
cleanup()
{
$OUTER kill-server 2>/dev/null
$INNER kill-server 2>/dev/null
rm -rf "$DIR"
}
trap cleanup 0 1 15
wait_for_file()
{
i=0
while [ "$i" -lt 50 ] && [ ! -e "$1" ]; do
sleep 0.1
i=$((i + 1))
done
[ -e "$1" ] || fail "$2"
}
wait_for_client()
{
i=0
while [ "$i" -lt 50 ]; do
$INNER list-clients -F '#{client_termfeatures}' 2>/dev/null |
grep -q 'sync' && return 0
sleep 0.1
i=$((i + 1))
done
fail "sync-capable client did not attach"
}
cat >"$EMITTER" <<'PERL'
use strict;
use warnings;
my $control = $ENV{CONTROL};
(my $dir = $control) =~ s{/[^/]+$}{};
# Fill the screen with 80 column lines and no newlines so every line wraps.
my $out = "\e[H\e[2J";
for my $row (1 .. 24) {
$out .= substr(sprintf("ROW%02d_", $row) . ('x' x 80), 0, 80);
}
syswrite STDOUT, $out . "\e[1;1H";
open my $painted, '>', "$dir/painted" or die "$dir/painted: $!\n";
close $painted;
while (!-e $control) {
select undef, undef, undef, 0.01;
}
# Change rows 2 and 6 in one synchronized update.
my $frame = "\e[?2026h" .
"\e[2;1H" . substr("NEW02_" . ('#' x 80), 0, 80) .
"\e[6;1H" . substr("NEW06_" . ('%' x 80), 0, 80) .
"\e[24;1H" .
"\e[?2026l";
my $written = syswrite STDOUT, $frame;
die "short synchronized frame write\n"
unless defined $written && $written == length $frame;
open my $done, '>', "$dir/done" or die "$dir/done: $!\n";
close $done;
select undef, undef, undef, 3;
PERL
$INNER new-session -d -s inner -x 80 -y 24 \
"CONTROL='$CONTROL' perl '$EMITTER'" || exit 1
$INNER set-option -g status off || exit 1
$INNER set-option -g window-size manual || exit 1
$INNER set-option -as terminal-features '*:sync' || exit 1
$OUTER new-session -d -s outer -x 80 -y 24 \
"$TEST_TMUX -Li$$ -f/dev/null attach-session -t inner" ||
exit 1
$OUTER set-option -g status off || exit 1
$OUTER set-option -g window-size manual || exit 1
wait_for_client
wait_for_file "$DIR/painted" "application did not paint"
sleep 1
: >"$CONTROL"
wait_for_file "$DIR/done" "application did not finish"
sleep 1
$OUTER capture-pane -p -t outer:0.0 >"$DIR/screen" || exit 1
sed -n 2p "$DIR/screen" | grep -q '^NEW02_#' || fail "row 2 not redrawn"
sed -n 3p "$DIR/screen" | grep -q '^ROW03_x' || fail "row 3 overwritten"
sed -n 6p "$DIR/screen" | grep -q '^NEW06_%' || fail "row 6 not redrawn"
exit 0
+61 -10
View File
@@ -9,14 +9,37 @@ $TMUX kill-server 2>/dev/null
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
$TMUX2 kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null
TMP=$(mktemp) TMP=$(mktemp) || exit 1
trap "rm -f $TMP" 0 1 15 prompt_pid=
cleanup () {
if [ -n "$prompt_pid" ]; then
kill "$prompt_pid" 2>/dev/null
wait "$prompt_pid" 2>/dev/null
fi
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
rm -f "$TMP"
}
trap cleanup 0
trap 'exit 1' 1 2 15
fail () {
echo "[FAIL] $*" >&2
exit 1
}
$TMUX2 -f/dev/null new -d || exit 1 $TMUX2 -f/dev/null new -d || exit 1
$TMUX -f/dev/null new -d "$TMUX2 attach" || exit 1 $TMUX -f/dev/null new -d "$TMUX2 attach" || exit 1
sleep 1 i=0
while [ -z "$($TMUX2 list-clients -F '#{client_name}')" ]; do
[ "$i" -lt 100 ] || fail "inner client did not attach"
sleep 0.05
i=$((i + 1))
done
exit_status=0 exit_status=0
assertion=0
format_string () { format_string () {
case $1 in case $1 in
@@ -34,12 +57,33 @@ assert_key () {
expected_name=$2 expected_name=$2
format_string=$(format_string "$expected_name") format_string=$(format_string "$expected_name")
$TMUX2 command-prompt -k 'display-message -pl '"$format_string" > "$TMP" & # Use a different prompt each time so a stale redraw cannot look ready.
sleep 0.05 assertion=$((assertion + 1))
prompt="tty-keys-$assertion:"
$TMUX2 command-prompt -k -p "$prompt" \
'display-message -pl '"$format_string" > "$TMP" &
prompt_pid=$!
i=0
while ! $TMUX capture-pane -p | grep -Fq "$prompt"; do
kill -0 "$prompt_pid" 2>/dev/null || \
fail "$keys -> $expected_name: prompt exited before becoming ready"
[ "$i" -lt 100 ] || \
fail "$keys -> $expected_name: timed out waiting for prompt"
sleep 0.05
i=$((i + 1))
done
$TMUX send-keys $keys $TMUX send-keys $keys || fail "could not send $keys"
wait i=0
while kill -0 "$prompt_pid" 2>/dev/null; do
[ "$i" -lt 100 ] || \
fail "$keys -> $expected_name: timed out waiting for key"
sleep 0.05
i=$((i + 1))
done
wait "$prompt_pid" || fail "$keys -> $expected_name: prompt failed"
prompt_pid=
keys=$(printf '%s' "$keys" | sed -e 's/Escape/\\\\033/g' | tr -d '[:space:]') keys=$(printf '%s' "$keys" | sed -e 's/Escape/\\\\033/g' | tr -d '[:space:]')
actual_name=$(tr -d '[:space:]' < "$TMP") actual_name=$(tr -d '[:space:]' < "$TMP")
@@ -293,6 +337,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'
@@ -355,7 +409,4 @@ assert_key 'Escape [32;2u' 'S-Space'
assert_key 'Escape [9;5u' 'C-Tab' assert_key 'Escape [9;5u' 'C-Tab'
assert_key 'Escape [1;5Z' 'C-S-Tab' assert_key 'Escape [1;5Z' 'C-S-Tab'
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
exit $exit_status exit $exit_status
+164
View File
@@ -0,0 +1,164 @@
#!/bin/sh
# A disconnected waiter must release both its wait and its command queue.
PATH=/bin:/usr/bin
TERM=screen
export TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
OUT=$(mktemp -d)
TMUX="$TEST_TMUX -S$OUT/socket -f/dev/null"
pids=
cleanup()
{
for pid in $pids; do
kill "$pid" 2>/dev/null || true
done
$TMUX kill-server 2>/dev/null || true
rm -rf "$OUT"
}
trap cleanup EXIT
fail()
{
echo "$*" >&2
exit 1
}
wait_list()
{
expected=$1
shift
i=0
while [ $i -lt 100 ]; do
value=$($TMUX wait-for -l "$@") || fail "list waiters failed"
[ "$value" = "$expected" ] && return
i=$((i + 1))
sleep 0.05
done
fail "expected waiters '$expected', got '$value'"
}
wait_free()
{
client_pid=$1
i=0
while [ $i -lt 100 ]; do
awk -v pid="$client_pid" '
$4 == "IDENTIFY_CLIENTPID" && $5 == pid { address = $3 }
address != "" && $2 == "free" && $3 == "client" &&
$4 == address && $5 == "(0" { freed = 1 }
END { exit !freed }
' "$OUT"/tmux-server-*.log && return
i=$((i + 1))
sleep 0.05
done
fail "client $client_pid was not freed"
}
cd "$OUT" || exit 1
$TMUX -vv new-session -d -s test || fail "new-session failed"
$TMUX set -g @continued 0 || fail "set option failed"
for signal in TERM KILL; do
for kind in channel lock event; do
name="$kind-$signal"
case $kind in
channel) args="$name"; list_args="$name" ;;
lock)
$TMUX wait-for -L "$name" || fail "lock failed"
args="-L $name"; list_args="$name"
;;
event) args="-E @$name"; list_args="-E @$name" ;;
esac
$TMUX wait-for $args \; set -g @continued 1 &
dead=$!
pids="$dead"
wait_list "client-$dead" $list_args
$TMUX wait-for $args &
live=$!
pids="$dead $live"
wait_list "$(printf 'client-%s\nclient-%s' "$dead" "$live")" \
$list_args
kill -"$signal" "$dead" || fail "kill failed"
wait "$dead" 2>/dev/null || true
pids="$live"
wait_list "client-$live" $list_args
wait_free "$dead"
[ "$($TMUX show -gqv @continued)" = 0 ] ||
fail "disconnected client's next command ran"
case $kind in
channel) $TMUX wait-for -S "$name" || fail "signal failed" ;;
lock) $TMUX wait-for -U "$name" || fail "unlock failed" ;;
event)
$TMUX wait-for -E -w "client-$live" "@$name" ||
fail "wake event waiter failed"
;;
esac
wait_list "" $list_args
wait_free "$live"
wait "$live" || fail "surviving waiter failed"
pids=
if [ "$kind" = lock ]; then
$TMUX wait-for -U "$name" || fail "final unlock failed"
fi
done
done
# Removing the last waiter must remove its otherwise unused channel, and must
# not create a pending signal. Unrelated pending signals must be preserved.
$TMUX wait-for -S saved || fail "save signal failed"
$TMUX wait-for last &
dead=$!
pids="$dead"
wait_list "client-$dead" last
kill -KILL "$dead" || fail "kill last waiter failed"
wait "$dead" 2>/dev/null || true
pids=
wait_list "" last
wait_free "$dead"
grep -F 'remove empty wait channel last' "$OUT"/tmux-server-*.log \
>/dev/null || fail "empty channel was not removed"
$TMUX wait-for saved \; wait-for last &
live=$!
pids="$live"
wait_list "client-$live" last
$TMUX wait-for -S last || fail "signal last failed"
wait_free "$live"
wait "$live" || fail "pending signal was not preserved"
pids=
# Source-file leaves a cleanup callback behind the blocked command.
printf 'wait-for sourced\nset -g @continued 1\n' >"$OUT/source.conf"
$TMUX source-file "$OUT/source.conf" &
dead=$!
pids="$dead"
wait_list "client-$dead" sourced
kill -KILL "$dead" || fail "kill source-file waiter failed"
wait "$dead" 2>/dev/null || true
pids=
wait_list "" sourced
wait_free "$dead"
[ "$($TMUX show -gqv @continued)" = 0 ] ||
fail "disconnected source-file client's next command ran"
# Firing an event after cancellation must not call the removed event sink.
$TMUX wait-for -E window-renamed &
dead=$!
pids="$dead"
wait_list "client-$dead" -E window-renamed
kill -KILL "$dead" || fail "kill event waiter failed"
wait "$dead" 2>/dev/null || true
pids=
wait_list "" -E window-renamed
wait_free "$dead"
$TMUX rename-window -t test:0 renamed || fail "rename-window failed"
$TMUX display-message -p '#{pid}' >/dev/null || fail "server exited"
exit 0
+2 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: screen-redraw.c,v 1.159 2026/09/09 08:31:42 nicm Exp $ */ /* $OpenBSD: screen-redraw.c,v 1.161 2026/09/22 16:56:07 nicm Exp $ */
/* /*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1673,7 +1673,7 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
prompt_draw(wp->prompt, &pdd); prompt_draw(wp->prompt, &pdd);
screen_write_stop(&ctx); screen_write_stop(&ctx);
tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL); tty_draw_line(tty, &screen, offset, 0, width, px, cy, NULL);
screen_free(&screen); screen_free(&screen);
} }
+23 -9
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.514 2026/09/22 06:46:50 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -502,6 +502,9 @@ server_client_lost(struct client *c)
TAILQ_REMOVE(&clients, c, entry); TAILQ_REMOVE(&clients, c, entry);
log_debug("lost client %p", c); log_debug("lost client %p", c);
cmd_wait_for_client_lost(c);
cmdq_next(c);
if (c->flags & CLIENT_ATTACHED) { if (c->flags & CLIENT_ATTACHED) {
server_client_attached_lost(c); server_client_attached_lost(c);
events_fire_client("client-detached", c); events_fire_client("client-detached", c);
@@ -1391,15 +1394,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 +1763,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 +1788,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);
+4 -3
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.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;
@@ -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 */
+9 -4
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);
} }
+3 -3
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);
+20 -4
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.1172 2026/09/22 06:58:05 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 22 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
@@ -5078,6 +5085,9 @@ The available features are:
.Bl -tag -width Ds .Bl -tag -width Ds
.It 256 .It 256
Supports 256 colours with the SGR escape sequences. Supports 256 colours with the SGR escape sequences.
.It appesc
Supports application escape key mode, where the Escape key sends a sequence
that cannot be mistaken for the start of another key.
.It clipboard .It clipboard
Allows setting the system clipboard. Allows setting the system clipboard.
.It ccolour .It ccolour
@@ -6328,7 +6338,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 +6348,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.
@@ -8957,6 +8971,8 @@ These are set automatically if the
capability is present. capability is present.
.It Em \&Dseks , \&Eneks .It Em \&Dseks , \&Eneks
Disable and enable extended keys. Disable and enable extended keys.
.It Em \&Dsesc , \&Enesc
Disable and enable application escape key mode.
.It Em \&Dsfcs , \&Enfcs .It Em \&Dsfcs , \&Enfcs
Disable and enable focus reporting. Disable and enable focus reporting.
These are set automatically if the These are set automatically if the
+9 -3
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.1447 2026/09/22 14:10:26 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -464,6 +464,7 @@ enum tty_code_code {
TTYC_DL1, TTYC_DL1,
TTYC_DSBP, TTYC_DSBP,
TTYC_DSEKS, TTYC_DSEKS,
TTYC_DSESC,
TTYC_DSFCS, TTYC_DSFCS,
TTYC_DSMG, TTYC_DSMG,
TTYC_E3, TTYC_E3,
@@ -474,6 +475,7 @@ enum tty_code_code {
TTYC_ENACS, TTYC_ENACS,
TTYC_ENBP, TTYC_ENBP,
TTYC_ENEKS, TTYC_ENEKS,
TTYC_ENESC,
TTYC_ENFCS, TTYC_ENFCS,
TTYC_ENMG, TTYC_ENMG,
TTYC_FSL, TTYC_FSL,
@@ -1339,6 +1341,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;
@@ -1737,6 +1740,7 @@ struct tty_term {
#define TERM_VT100LIKE 0x20 #define TERM_VT100LIKE 0x20
#define TERM_SIXEL 0x40 #define TERM_SIXEL 0x40
#define TERM_INVALIDMS 0x80 #define TERM_INVALIDMS 0x80
#define TERM_NOREPLACE 0x100
int flags; int flags;
LIST_ENTRY(tty_term) entry; LIST_ENTRY(tty_term) entry;
@@ -2302,7 +2306,7 @@ struct client {
#define CLIENT_ASSUMEPASTING 0x2000000000ULL #define CLIENT_ASSUMEPASTING 0x2000000000ULL
#define CLIENT_WRITE_ACK 0x4000000000ULL #define CLIENT_WRITE_ACK 0x4000000000ULL
#define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL #define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL
#define CLIENT_CONTROL_DISCARD 0x1000000000ULL #define CLIENT_CONTROL_DISCARD 0x10000000000ULL
#define CLIENT_ALLREDRAWFLAGS \ #define CLIENT_ALLREDRAWFLAGS \
(CLIENT_REDRAWWINDOW| \ (CLIENT_REDRAWWINDOW| \
CLIENT_REDRAWSTATUS| \ CLIENT_REDRAWSTATUS| \
@@ -3006,7 +3010,7 @@ void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *);
/* tty-term.c */ /* tty-term.c */
extern struct tty_terms tty_terms; extern struct tty_terms tty_terms;
u_int tty_term_ncodes(void); u_int tty_term_ncodes(void);
void tty_term_apply(struct tty_term *, const char *, int); void tty_term_apply(struct tty_term *, const char *, int, int);
void tty_term_apply_overrides(struct tty_term *); void tty_term_apply_overrides(struct tty_term *);
struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, char **); struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, char **);
void tty_term_free(struct tty_term *); void tty_term_free(struct tty_term *);
@@ -3226,6 +3230,7 @@ void cmdq_print_data(struct cmdq_item *, struct evbuffer *);
void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...); void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...);
/* cmd-wait-for.c */ /* cmd-wait-for.c */
void cmd_wait_for_client_lost(struct client *);
void cmd_wait_for_flush(void); void cmd_wait_for_flush(void);
/* client.c */ /* client.c */
@@ -3832,6 +3837,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 *);
+7 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: tty-draw.c,v 1.15 2026/07/26 09:02:08 nicm Exp $ */ /* $OpenBSD: tty-draw.c,v 1.17 2026/09/22 06:49:47 nicm Exp $ */
/* /*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -219,7 +219,12 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
} }
/* Did the previous line wrap on to this one? */ /* Did the previous line wrap on to this one? */
if (py != 0 && atx == 0 && tty->cx >= tty->sx && nx == tty->sx) { if (py != 0 &&
atx == 0 &&
tty->cx >= tty->sx &&
tty->cy != UINT_MAX &&
tty->cy + 1 == aty &&
nx == tty->sx) {
gl = grid_get_line(gd, gd->hsize + py - 1); gl = grid_get_line(gd, gd->hsize + py - 1);
if (gl->flags & GRID_LINE_WRAPPED) if (gl->flags & GRID_LINE_WRAPPED)
wrapped = 1; wrapped = 1;
+18 -4
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: tty-features.c,v 1.43 2026/08/31 12:41:03 kirill Exp $ */ /* $OpenBSD: tty-features.c,v 1.45 2026/09/22 14:10:26 nicm Exp $ */
/* /*
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -189,6 +189,18 @@ static const struct tty_feature tty_feature_focus = {
0 0
}; };
/* Terminal supports application escape key mode. */
static const char *const tty_feature_appesc_capabilities[] = {
"Enesc=\\E[?7727h",
"Dsesc=\\E[?7727l",
NULL
};
static const struct tty_feature tty_feature_appesc = {
"appesc",
tty_feature_appesc_capabilities,
0
};
/* Terminal supports cursor styles. */ /* Terminal supports cursor styles. */
static const char *const tty_feature_cstyle_capabilities[] = { static const char *const tty_feature_cstyle_capabilities[] = {
"Ss=\\E[%p1%d q", "Ss=\\E[%p1%d q",
@@ -198,7 +210,7 @@ static const char *const tty_feature_cstyle_capabilities[] = {
static const struct tty_feature tty_feature_cstyle = { static const struct tty_feature tty_feature_cstyle = {
"cstyle", "cstyle",
tty_feature_cstyle_capabilities, tty_feature_cstyle_capabilities,
0 TERM_NOREPLACE
}; };
/* Terminal supports cursor colours. */ /* Terminal supports cursor colours. */
@@ -378,6 +390,7 @@ static const struct tty_feature tty_feature_utf8 = {
/* Available terminal features. */ /* Available terminal features. */
static const struct tty_feature *const tty_features[] = { static const struct tty_feature *const tty_features[] = {
&tty_feature_256, &tty_feature_256,
&tty_feature_appesc,
&tty_feature_bpaste, &tty_feature_bpaste,
&tty_feature_ccolour, &tty_feature_ccolour,
&tty_feature_clipboard, &tty_feature_clipboard,
@@ -541,11 +554,11 @@ tty_apply_features(struct tty_term *term)
capability = tf->capabilities; capability = tf->capabilities;
while (*capability != NULL) { while (*capability != NULL) {
log_debug("adding capability: %s", *capability); log_debug("adding capability: %s", *capability);
tty_term_apply(term, *capability, 1); tty_term_apply(term, *capability, 1, tf->flags);
capability++; capability++;
} }
} }
term->flags |= tf->flags; term->flags |= (tf->flags & ~TERM_NOREPLACE);
if (tf == &tty_feature_utf8) if (tf == &tty_feature_utf8)
c->flags |= CLIENT_UTF8; c->flags |= CLIENT_UTF8;
} }
@@ -568,6 +581,7 @@ tty_default_features(struct client *c, const char *name, u_int version)
"256,RGB,bpaste,clipboard,mouse,strikethrough,title" "256,RGB,bpaste,clipboard,mouse,strikethrough,title"
{ .name = "mintty", { .name = "mintty",
.features = TTY_FEATURES_BASE_MODERN_XTERM "," .features = TTY_FEATURES_BASE_MODERN_XTERM ","
"appesc,"
"ccolour," "ccolour,"
"cstyle," "cstyle,"
"extkeys," "extkeys,"
+9 -3
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: tty-term.c,v 1.109 2026/08/25 08:37:08 nicm Exp $ */ /* $OpenBSD: tty-term.c,v 1.111 2026/09/22 14:10:26 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -90,6 +90,7 @@ static const struct tty_term_code_entry tty_term_codes[] = {
[TTYC_DL1] = { TTYCODE_STRING, "dl1" }, [TTYC_DL1] = { TTYCODE_STRING, "dl1" },
[TTYC_DL] = { TTYCODE_STRING, "dl" }, [TTYC_DL] = { TTYCODE_STRING, "dl" },
[TTYC_DSEKS] = { TTYCODE_STRING, "Dseks" }, [TTYC_DSEKS] = { TTYCODE_STRING, "Dseks" },
[TTYC_DSESC] = { TTYCODE_STRING, "Dsesc" },
[TTYC_DSFCS] = { TTYCODE_STRING, "Dsfcs" }, [TTYC_DSFCS] = { TTYCODE_STRING, "Dsfcs" },
[TTYC_DSBP] = { TTYCODE_STRING, "Dsbp" }, [TTYC_DSBP] = { TTYCODE_STRING, "Dsbp" },
[TTYC_DSMG] = { TTYCODE_STRING, "Dsmg" }, [TTYC_DSMG] = { TTYCODE_STRING, "Dsmg" },
@@ -101,6 +102,7 @@ static const struct tty_term_code_entry tty_term_codes[] = {
[TTYC_ENACS] = { TTYCODE_STRING, "enacs" }, [TTYC_ENACS] = { TTYCODE_STRING, "enacs" },
[TTYC_ENBP] = { TTYCODE_STRING, "Enbp" }, [TTYC_ENBP] = { TTYCODE_STRING, "Enbp" },
[TTYC_ENEKS] = { TTYCODE_STRING, "Eneks" }, [TTYC_ENEKS] = { TTYCODE_STRING, "Eneks" },
[TTYC_ENESC] = { TTYCODE_STRING, "Enesc" },
[TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" }, [TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" },
[TTYC_ENMG] = { TTYCODE_STRING, "Enmg" }, [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" },
[TTYC_FSL] = { TTYCODE_STRING, "fsl" }, [TTYC_FSL] = { TTYCODE_STRING, "fsl" },
@@ -362,7 +364,8 @@ tty_term_override_next(const char *s, size_t *offset)
} }
void void
tty_term_apply(struct tty_term *term, const char *capabilities, int quiet) tty_term_apply(struct tty_term *term, const char *capabilities, int quiet,
int flags)
{ {
const struct tty_term_code_entry *ent; const struct tty_term_code_entry *ent;
struct tty_code *code; struct tty_code *code;
@@ -405,6 +408,9 @@ tty_term_apply(struct tty_term *term, const char *capabilities, int quiet)
if (strcmp(s, ent->name) != 0) if (strcmp(s, ent->name) != 0)
continue; continue;
code = &term->codes[i]; code = &term->codes[i];
if ((flags & TERM_NOREPLACE) &&
code->type != TTYCODE_NONE)
continue;
if (remove) { if (remove) {
code->type = TTYCODE_NONE; code->type = TTYCODE_NONE;
@@ -457,7 +463,7 @@ tty_term_apply_overrides(struct tty_term *term)
offset = 0; offset = 0;
first = tty_term_override_next(s, &offset); first = tty_term_override_next(s, &offset);
if (first != NULL && fnmatch(first, term->name, 0) == 0) if (first != NULL && fnmatch(first, term->name, 0) == 0)
tty_term_apply(term, s + offset, 0); tty_term_apply(term, s + offset, 0, 0);
a = options_array_next(a); a = options_array_next(a);
} }
+3 -5
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: tty.c,v 1.480 2026/08/25 08:37:08 nicm Exp $ */ /* $OpenBSD: tty.c,v 1.482 2026/09/22 06:58:06 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -503,8 +503,7 @@ tty_stop_tty(struct tty *tty)
if (tty_term_has(tty->term, TTYC_DSBP)) if (tty_term_has(tty->term, TTYC_DSBP))
tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP)); tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP));
if (tty->term->flags & TERM_VT100LIKE) tty_raw(tty, tty_term_string(tty->term, TTYC_DSESC));
tty_raw(tty, "\033[?7727l");
tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS)); tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS));
tty_raw(tty, tty_term_string(tty->term, TTYC_DSEKS)); tty_raw(tty, tty_term_string(tty->term, TTYC_DSEKS));
@@ -563,8 +562,7 @@ tty_update_features(struct tty *tty)
tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS)); tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS));
if (options_get_number(global_options, "focus-events")) if (options_get_number(global_options, "focus-events"))
tty_puts(tty, tty_term_string(tty->term, TTYC_ENFCS)); tty_puts(tty, tty_term_string(tty->term, TTYC_ENFCS));
if (tty->term->flags & TERM_VT100LIKE) tty_puts(tty, tty_term_string(tty->term, TTYC_ENESC));
tty_puts(tty, "\033[?7727h");
/* /*
* Features might have changed since the first draw during attach. For * Features might have changed since the first draw during attach. For
+7 -2
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);
+2 -2
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;
+22 -2
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);
}