From d98635e1bd645b75f6eefde16aac1799e1b2b1f4 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 15 Jul 2026 13:02:33 +0000 Subject: [PATCH 1/2] Add modal panes, created currently with new-pane -O. There is one modal pane per window and it must be a floating pane. These are intended to replace popups. Currently a modal pane will unzoom a zoomed window and rezoom it when it is closed (a bit like modes do), but this is planned to change when we get an always-on-top flag. --- cmd-break-pane.c | 7 +++- cmd-join-pane.c | 22 ++++++++--- cmd-split-window.c | 24 +++++++++--- cmd-swap-pane.c | 7 +++- cmd.c | 4 +- format.c | 31 ++++++++++++++- layout.c | 7 +++- options-table.c | 7 ++-- server-client.c | 15 ++++++-- spawn.c | 33 ++++++++++++++-- tmux.1 | 11 +++++- tmux.h | 9 ++++- window.c | 94 +++++++++++++++++++++++++++++++++++++++++++++- 13 files changed, 239 insertions(+), 32 deletions(-) diff --git a/cmd-break-pane.c b/cmd-break-pane.c index dcc5a6af4..4fe6397c9 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-break-pane.c,v 1.73 2026/07/13 13:01:14 nicm Exp $ */ +/* $OpenBSD: cmd-break-pane.c,v 1.74 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -103,6 +103,11 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) int idx = target->idx, before, old_idx = wl->idx; const char *template, *name = args_get(args, 'n'); + if (wp == w->modal) { + cmdq_error(item, "pane is modal"); + return (CMD_RETURN_ERROR); + } + if (args_has(args, 'W')) return (cmd_break_pane_float(item, args, w, wp)); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 927ccda43..df8b345d2 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-join-pane.c,v 1.71 2026/07/13 13:01:14 nicm Exp $ */ +/* $OpenBSD: cmd-join-pane.c,v 1.72 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2011 George Nachman @@ -425,7 +425,6 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) dst_wp = target->wp; dst_w = dst_wl->window; dst_idx = dst_wl->idx; - server_unzoom_window(dst_w); if (cmd_get_entry(self) == &cmd_move_pane_entry) { if (args_has(args, 'M')) @@ -434,22 +433,35 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "pane is not floating"); return (CMD_RETURN_ERROR); } - if ((s = args_get(args, 'P')) != NULL) + if ((s = args_get(args, 'P')) != NULL) { + server_unzoom_window(dst_w); 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) { + server_unzoom_window(dst_w); return (cmd_join_pane_zindex(item, dst_wl, dst_wp, s)); + } if (args_has(args, 'X') || args_has(args, 'Y') || args_has(args, 'U') || args_has(args, 'D') || args_has(args, 'L') || - args_has(args, 'R')) + args_has(args, 'R')) { + server_unzoom_window(dst_w); return (cmd_join_pane_move(item, args, dst_wl, dst_wp)); + } } src_wl = source->wl; src_wp = source->wp; src_w = src_wl->window; + + if (src_wp == src_w->modal || dst_wp == dst_w->modal) { + cmdq_error(item, "pane is modal"); + return (CMD_RETURN_ERROR); + } + + server_unzoom_window(dst_w); server_unzoom_window(src_w); if (src_wp == dst_wp) { diff --git a/cmd-split-window.c b/cmd-split-window.c index caaa7c02e..bcda1d97f 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-split-window.c,v 1.144 2026/07/14 15:06:54 nicm Exp $ */ +/* $OpenBSD: cmd-split-window.c,v 1.145 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -41,8 +41,8 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bB:c:de:EfF:hIkl:LMm:p:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, - .usage = "[-bdefhIklMPvWZ] [-B border-lines] " + .args = { "bB:c:de:EfF:hIkl:LMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, + .usage = "[-bdefhIklMOPvWZ] [-B border-lines] " "[-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " @@ -104,6 +104,17 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) flags |= SPAWN_SPLIT; } + if (args_has(args, 'O')) { + if (!is_floating) { + cmdq_error(item, "modal pane must be floating"); + return (CMD_RETURN_ERROR); + } + if (w->modal != NULL) { + cmdq_error(item, "window already has a modal pane"); + return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'M') && is_floating) { if (event == NULL || !event->m.valid || tc == NULL) return (CMD_RETURN_NORMAL); @@ -121,6 +132,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) flags |= SPAWN_DETACHED; if (args_has(args, 'Z')) flags |= SPAWN_ZOOM; + if (args_has(args, 'O')) + flags |= SPAWN_MODAL; input = args_has(args, 'I'); if (input || (count == 1 && *args_string(args, 0) == '\0')) @@ -256,7 +269,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) if (~flags & SPAWN_DETACHED) cmd_find_from_winlink_pane(current, wl, new_wp, 0); - if (~flags & SPAWN_FLOATING) { + if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) { window_pop_zoom(wp->window); server_redraw_window(wp->window); } @@ -307,7 +320,8 @@ fail: if (!is_floating) layout_close_pane(new_wp); window_remove_pane(wp->window, new_wp); - } + } else if (args_has(args, 'O')) + window_pop_modal_zoom(wp->window); if (sc.argv != NULL) cmd_free_argv(sc.argc, sc.argv); environ_free(sc.environ); diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index b2e53f315..04e1cc7a9 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-swap-pane.c,v 1.54 2026/07/13 13:01:14 nicm Exp $ */ +/* $OpenBSD: cmd-swap-pane.c,v 1.55 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -77,6 +77,11 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) src_wp = source->wp; src_idx = source->wl->idx; + if (src_wp == src_w->modal || dst_wp == dst_w->modal) { + cmdq_error(item, "pane is modal"); + return (CMD_RETURN_ERROR); + } + if (window_push_zoom(dst_w, 0, args_has(args, 'Z'))) server_redraw_window(dst_w); diff --git a/cmd.c b/cmd.c index 01f11834b..3b7586084 100644 --- a/cmd.c +++ b/cmd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd.c,v 1.187 2026/07/13 09:37:39 nicm Exp $ */ +/* $OpenBSD: cmd.c,v 1.188 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -833,6 +833,8 @@ cmd_mouse_pane(struct mouse_event *m, struct session **sp, if (!window_has_pane(wl->window, wp)) return (NULL); } + if (wl->window->modal != NULL && wp != wl->window->modal) + return (NULL); if (wlp != NULL) *wlp = wl; diff --git a/format.c b/format.c index 77be4edb9..9354b5a24 100644 --- a/format.c +++ b/format.c @@ -1,4 +1,4 @@ -/* $OpenBSD: format.c,v 1.404 2026/07/14 15:06:54 nicm Exp $ */ +/* $OpenBSD: format.c,v 1.405 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -1074,6 +1074,20 @@ format_cb_pane_floating_flag(struct format_tree *ft) return (NULL); } +/* Callback for pane_modal_flag. */ +static void * +format_cb_pane_modal_flag(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL) { + if (wp == wp->window->modal) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + /* Callback for pane_bg. */ static void * format_cb_pane_bg(struct format_tree *ft) @@ -3084,6 +3098,15 @@ format_cb_window_marked_flag(struct format_tree *ft) return (NULL); } +/* Callback for window_modal_pane. */ +static void * +format_cb_window_modal_pane(struct format_tree *ft) +{ + if (ft->w != NULL && ft->w->modal != NULL) + return (format_printf("%%%u", ft->w->modal->id)); + return (NULL); +} + /* Callback for window_name. */ static void * format_cb_window_name(struct format_tree *ft) @@ -3666,6 +3689,9 @@ static const struct format_table_entry format_table[] = { { "pane_marked_set", FORMAT_TABLE_STRING, format_cb_pane_marked_set }, + { "pane_modal_flag", FORMAT_TABLE_STRING, + format_cb_pane_modal_flag + }, { "pane_mode", FORMAT_TABLE_STRING, format_cb_pane_mode }, @@ -3924,6 +3950,9 @@ static const struct format_table_entry format_table[] = { { "window_marked_flag", FORMAT_TABLE_STRING, format_cb_window_marked_flag }, + { "window_modal_pane", FORMAT_TABLE_STRING, + format_cb_window_modal_pane + }, { "window_name", FORMAT_TABLE_STRING, format_cb_window_name }, diff --git a/layout.c b/layout.c index c3554718c..b8ba1c2ef 100644 --- a/layout.c +++ b/layout.c @@ -1,4 +1,4 @@ -/* $OpenBSD: layout.c,v 1.95 2026/07/14 17:17:17 nicm Exp $ */ +/* $OpenBSD: layout.c,v 1.96 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -1677,7 +1677,10 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, return (NULL); } - window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); + if (flags & SPAWN_MODAL) + window_push_modal_zoom(w); + else + window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM)); lcnew = layout_floating_pane(w, wp, &fg); return (lcnew); } diff --git a/options-table.c b/options-table.c index 6a33b1151..dabf53934 100644 --- a/options-table.c +++ b/options-table.c @@ -1,4 +1,4 @@ -/* $OpenBSD: options-table.c,v 1.237 2026/07/14 17:17:17 nicm Exp $ */ +/* $OpenBSD: options-table.c,v 1.238 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -1531,9 +1531,10 @@ const struct options_table_entry options_table[] = { { .name = "pane-active-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "fg=#{?pane_marked,thememagenta," + .default_str = "fg=#{?pane_modal_flag,themeblue," + "#{?pane_marked,thememagenta," "#{?synchronize-panes,themered," - "#{?pane_in_mode,themeyellow,themegreen}}}", + "#{?pane_in_mode,themeyellow,themegreen}}}}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the active pane border." diff --git a/server-client.c b/server-client.c index cebf213bb..1232e3bb5 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.492 2026/07/15 10:38:31 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.493 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -875,6 +875,7 @@ server_client_check_mouse(struct client *c, struct key_event *event) u_int x, y, sx, sy, px, py, n, sl_mpos = 0; u_int b, bn; int ignore = 0; + int modal_drag = 0; key_code key; struct timeval tv; struct style_range *sr; @@ -3082,16 +3083,20 @@ struct window_pane * server_client_get_pane(struct client *c) { struct session *s = c->session; + struct window *w; struct client_window *cw; if (s == NULL) return (NULL); + w = s->curw->window; + if (w->modal != NULL) + return (w->modal); if (~c->flags & CLIENT_ACTIVEPANE) - return (s->curw->window->active); - cw = server_client_get_client_window(c, s->curw->window->id); + return (w->active); + cw = server_client_get_client_window(c, w->id); if (cw == NULL) - return (s->curw->window->active); + return (w->active); return (cw->pane); } @@ -3104,6 +3109,8 @@ server_client_set_pane(struct client *c, struct window_pane *wp) if (s == NULL) return; + if (wp->window->modal != NULL && wp != wp->window->modal) + return; cw = server_client_add_client_window(c, s->curw->window->id); cw->pane = wp; diff --git a/spawn.c b/spawn.c index 5d020b297..d533c0a04 100644 --- a/spawn.c +++ b/spawn.c @@ -1,4 +1,4 @@ -/* $OpenBSD: spawn.c,v 1.48 2026/07/13 13:01:14 nicm Exp $ */ +/* $OpenBSD: spawn.c,v 1.49 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -273,6 +273,17 @@ spawn_pane(struct spawn_context *sc, char **cause) spawn_log(__func__, sc); + if (sc->flags & SPAWN_MODAL) { + if (~sc->flags & SPAWN_FLOATING) { + xasprintf(cause, "modal pane must be floating"); + return (NULL); + } + if (w->modal != NULL) { + xasprintf(cause, "window already has a modal pane"); + return (NULL); + } + } + /* * Work out the current working directory. If respawning, use * the pane's stored one unless specified. @@ -545,7 +556,16 @@ complete: if (sc->flags & SPAWN_RESPAWN) return (new_wp); - if ((~sc->flags & SPAWN_DETACHED) || w->active == NULL) { + if (sc->flags & SPAWN_MODAL) { + w->modal_last = w->active; + w->modal = new_wp; + window_redraw_active_switch(w, new_wp); + if (sc->flags & SPAWN_NONOTIFY) + window_set_active_pane(w, new_wp, 0); + else + window_set_active_pane(w, new_wp, 1); + } else if (((~sc->flags & SPAWN_DETACHED) || w->active == NULL) && + w->modal == NULL) { if (sc->flags & SPAWN_NONOTIFY) window_set_active_pane(w, new_wp, 0); else @@ -661,6 +681,9 @@ spawn_editor(struct client *c, const char *buf, size_t len, const char *editor; int fd; + if (w->modal != NULL) + return (NULL); + editor = options_get_string(global_options, "editor"); fd = mkstemp(path); if (fd == -1) @@ -687,9 +710,10 @@ spawn_editor(struct client *c, const char *buf, size_t len, lg.sy = w->sy * 9 / 10; lg.xoff = w->sx / 2 - lg.sx / 2; lg.yoff = w->sy / 2 - lg.sy / 2; - window_push_zoom(w, 1, 0); + window_push_modal_zoom(w); lc = layout_floating_pane(w, NULL, &lg); if (lc == NULL) { + window_pop_modal_zoom(w); spawn_editor_free(es); return (NULL); } @@ -706,13 +730,14 @@ spawn_editor(struct client *c, const char *buf, size_t len, sc.environ = env; sc.idx = -1; sc.cwd = _PATH_TMP; - sc.flags = SPAWN_FLOATING; + sc.flags = SPAWN_FLOATING|SPAWN_MODAL; wp = spawn_pane(&sc, &cause); free(cmd); environ_free(env); if (wp == NULL) { free(cause); + window_pop_modal_zoom(w); spawn_editor_free(es); return (NULL); } diff --git a/tmux.1 b/tmux.1 index 5ab57c8b1..2d3087ea2 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1141 2026/07/15 10:38:31 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1142 2026/07/15 13:02:33 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -3645,7 +3645,7 @@ but a different format may be specified with .Fl F . .Tg newp .It Xo Ic new\-pane -.Op Fl bdefhIkLMPvWZ +.Op Fl bdefhIkLMOPvWZ .Op Fl B Ar border\-lines .Op Fl c Ar start\-directory .Op Fl e Ar environment @@ -3696,6 +3696,11 @@ may be used when bound to a mouse drag key; the new floating pane is resized to follow the mouse drag. It has no effect with .Fl L . +.Fl O +creates a modal pane. +A modal pane is always the active pane and prevents interaction with any other +panes while it is active. +A window can only have one modal pane and it must be a floating pane. .Pp The .Fl L @@ -7318,6 +7323,7 @@ The following variables are available, where appropriate: .It Li "pane_left" Ta "" Ta "Left of pane" .It Li "pane_marked" Ta "" Ta "1 if this is the marked pane" .It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set" +.It Li "pane_modal_flag" Ta "" Ta "1 if pane is modal" .It Li "pane_mode" Ta "" Ta "Name of pane mode, if any" .It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)" .It Li "pane_pid" Ta "" Ta "PID of first process in pane" @@ -7418,6 +7424,7 @@ The following variables are available, where appropriate: .It Li "window_manual_height" Ta "" Ta "Manual height of window, if set" .It Li "window_manual_width" Ta "" Ta "Manual width of window, if set" .It Li "window_marked_flag" Ta "" Ta "1 if window contains the marked pane" +.It Li "window_modal_pane" Ta "" Ta "Modal pane in window, if any" .It Li "window_name" Ta "#W" Ta "Name of window" .It Li "window_offset_x" Ta "" Ta "X offset into window if larger than client" .It Li "window_offset_y" Ta "" Ta "Y offset into window if larger than client" diff --git a/tmux.h b/tmux.h index abba612ea..947401b4e 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1405 2026/07/15 10:38:31 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1406 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1381,6 +1381,8 @@ struct window { struct timeval creation_time; struct window_pane *active; + struct window_pane *modal; + struct window_pane *modal_last; struct window_panes last_panes; struct window_panes z_index; struct window_panes panes; @@ -1422,6 +1424,7 @@ struct window { #define WINDOW_ZOOMED 0x8 #define WINDOW_WASZOOMED 0x10 #define WINDOW_RESIZE 0x20 +#define WINDOW_WASMODALZOOMED 0x40 #define WINDOW_ALERTFLAGS (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_SILENCE) int alerts_queued; @@ -2472,6 +2475,7 @@ struct spawn_context { #define SPAWN_FLOATING 0x100 #define SPAWN_HORIZONTAL 0x200 #define SPAWN_SPLIT 0x400 +#define SPAWN_MODAL 0x800 }; /* Paste buffer. */ @@ -3638,6 +3642,7 @@ struct window_pane *window_get_active_at(struct window *, u_int, u_int); struct window_pane *window_find_string(struct window *, const char *); int window_has_floating_panes(struct window *); int window_has_pane(struct window *, struct window_pane *); +int window_pane_contains(struct window_pane *, u_int, u_int); int window_set_active_pane(struct window *, struct window_pane *, int); void window_fire_pane_moved(struct window_pane *, struct window *, @@ -3652,6 +3657,8 @@ void window_resize(struct window *, u_int, u_int, int, int); void window_pane_send_resize(struct window_pane *, u_int, u_int); int window_zoom(struct window_pane *); int window_unzoom(struct window *, int); +void window_push_modal_zoom(struct window *); +int window_pop_modal_zoom(struct window *); int window_push_zoom(struct window *, int, int); int window_pop_zoom(struct window *); void window_lost_pane(struct window *, struct window_pane *); diff --git a/window.c b/window.c index 487aaa3f7..e8fc60e38 100644 --- a/window.c +++ b/window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window.c,v 1.363 2026/07/14 19:07:03 nicm Exp $ */ +/* $OpenBSD: window.c,v 1.364 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -626,6 +626,35 @@ window_has_pane(struct window *w, struct window_pane *wp) return (0); } +int +window_pane_contains(struct window_pane *wp, u_int x, u_int y) +{ + int xoff, yoff; + u_int sx, sy; + + if (!window_pane_is_visible(wp)) + return (0); + + window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); + if (!window_pane_is_floating(wp)) { + if ((int)x < xoff || x > xoff + sx) + return (0); + if ((int)y < yoff || y > yoff + sy) + return (0); + } else if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE) { + if ((int)x < xoff || (int)x >= xoff + (int)sx) + return (0); + if ((int)y < yoff || (int)y >= yoff + (int)sy) + return (0); + } else { + if ((int)x < xoff - 1 || x > xoff + sx) + return (0); + if ((int)y < yoff - 1 || y > yoff + sy) + return (0); + } + return (1); +} + void window_update_focus(struct window *w) { @@ -683,6 +712,8 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) if (wp == w->active) return (0); + if (w->modal != NULL && wp != w->modal) + return (0); if (w->flags & WINDOW_ZOOMED) window_unzoom(w, 1); lastwp = w->active; @@ -721,6 +752,8 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) struct grid_cell *gc1, *gc2; int c1, c2; + if (w->modal != NULL && wp != w->modal) + return; if (wp == w->active) return; @@ -773,6 +806,12 @@ window_get_active_at(struct window *w, u_int x, u_int y) pane_status = window_get_pane_status(w); + if (w->modal != NULL) { + if (window_pane_contains(w->modal, x, y)) + return (w->modal); + return (NULL); + } + if (pane_status == PANE_STATUS_TOP) { /* * Prefer a pane's top border status line over the pane above's @@ -929,6 +968,29 @@ window_unzoom(struct window *w, int notify) return (0); } +void +window_push_modal_zoom(struct window *w) +{ + if (w->flags & WINDOW_ZOOMED) + w->flags |= WINDOW_WASMODALZOOMED; + else + w->flags &= ~WINDOW_WASMODALZOOMED; + window_unzoom(w, 1); +} + +int +window_pop_modal_zoom(struct window *w) +{ + struct window_pane *wp = w->active; + + if (~w->flags & WINDOW_WASMODALZOOMED) + return (0); + w->flags &= ~WINDOW_WASMODALZOOMED; + if (wp != NULL && window_has_pane(w, wp)) + return (window_zoom(wp) == 0); + return (0); +} + int window_push_zoom(struct window *w, int always, int flag) { @@ -979,6 +1041,8 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, } if (~flags & SPAWN_FLOATING) TAILQ_INSERT_TAIL(&w->z_index, wp, zentry); + else if (w->modal != NULL) + TAILQ_INSERT_AFTER(&w->z_index, w->modal, wp, zentry); else { TAILQ_INSERT_HEAD(&w->z_index, wp, zentry); } @@ -989,14 +1053,29 @@ window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, void window_lost_pane(struct window *w, struct window_pane *wp) { + struct window_pane *lastwp; + log_debug("%s: @%u pane %%%u", __func__, w->id, wp->id); if (wp == marked_pane.wp) server_clear_marked(); + if (wp == w->modal_last) + w->modal_last = NULL; + if (w->modal_last == NULL) + w->flags &= ~WINDOW_WASMODALZOOMED; window_pane_stack_remove(&w->last_panes, wp); if (wp == w->active) { - w->active = TAILQ_FIRST(&w->last_panes); + lastwp = NULL; + if (wp == w->modal) { + lastwp = w->modal_last; + w->modal = NULL; + w->modal_last = NULL; + } + if (lastwp != NULL && window_has_pane(w, lastwp)) + w->active = lastwp; + else + w->active = TAILQ_FIRST(&w->last_panes); if (w->active == NULL) { w->active = TAILQ_PREV(wp, window_panes, entry); if (w->active == NULL) @@ -1008,6 +1087,9 @@ window_lost_pane(struct window *w, struct window_pane *wp) window_fire_pane_changed(w, w->active, wp); window_update_focus(w); } + } else if (wp == w->modal) { + w->modal = w->modal_last = NULL; + w->flags &= ~WINDOW_WASMODALZOOMED; } redraw_invalidate_scene(w); } @@ -1015,9 +1097,13 @@ window_lost_pane(struct window *w, struct window_pane *wp) void window_remove_pane(struct window *w, struct window_pane *wp) { + int pop = (wp == w->modal); + window_lost_pane(w, wp); TAILQ_REMOVE(&w->panes, wp, entry); TAILQ_REMOVE(&w->z_index, wp, zentry); + if (pop && window_pop_modal_zoom(w)) + server_redraw_window(w); redraw_invalidate_scene(w); window_pane_destroy(wp); } @@ -1150,6 +1236,8 @@ window_printable_flags(struct winlink *wl, int escape) flags[pos++] = '-'; if (server_check_marked() && wl == marked_pane.wl) flags[pos++] = 'M'; + if (wl->window->modal != NULL) + flags[pos++] = 'O'; if (wl->window->flags & WINDOW_ZOOMED) flags[pos++] = 'Z'; flags[pos] = '\0'; @@ -1171,6 +1259,8 @@ window_pane_printable_flags(struct window_pane *wp) flags[pos++] = 'Z'; if (window_pane_is_floating(wp)) flags[pos++] = 'F'; + if (wp == w->modal) + flags[pos++] = 'O'; flags[pos] = '\0'; return (flags); } From edefce8eb56fae365ee0bfac56e9e8f714efcb01 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 15 Jul 2026 14:14:50 +0000 Subject: [PATCH 2/2] Small section missed from previous. --- server-client.c | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/server-client.c b/server-client.c index 1232e3bb5..eb516187b 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.493 2026/07/15 13:02:33 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.494 2026/07/15 14:14:50 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -1058,9 +1058,34 @@ have_event: } px = px + m->ox; py = py + m->oy; + if (w->modal != NULL && + !window_pane_contains(w->modal, px, py)) { + if (lwp == w->modal && + c->tty.mouse_drag_flag != 0 && + (type == KEYC_TYPE_MOUSEDRAG || + type == KEYC_TYPE_MOUSEUP)) { + modal_drag = 1; + wp = lwp; + loc = KEYC_MOUSE_LOCATION_PANE; + m->wp = wp->id; + m->w = wp->window->id; + } else { + server_client_update_scrollbar_hover(c, type, + -1, -1); + c->tty.mouse_drag_update = NULL; + c->tty.mouse_drag_release = NULL; + c->tty.mouse_drag_flag = 0; + c->tty.mouse_scrolling_flag = 0; + c->tty.mouse_slider_mpos = -1; + c->tty.mouse_last_pane = -1; + return (KEYC_UNKNOWN); + } + } server_client_update_scrollbar_hover(c, type, px, py); - if (type == KEYC_TYPE_MOUSEDRAG && lwp != NULL) { + if (modal_drag) { + /* Keep the drag with the modal pane. */ + } else if (type == KEYC_TYPE_MOUSEDRAG && lwp != NULL) { /* Use pane from last mouse event. */ wp = lwp; } else { @@ -1072,8 +1097,10 @@ have_event: m->w = w->id; log_debug("mouse %u,%u on empty area", x, y); } else { - loc = server_client_check_mouse_in_pane(wp, px, py, - &sl_mpos); + if (!modal_drag) { + loc = server_client_check_mouse_in_pane(wp, px, + py, &sl_mpos); + } if (loc == KEYC_MOUSE_LOCATION_PANE) { log_debug("mouse %u,%u on pane %%%u", x, y, wp->id);