diff --git a/Makefile.am b/Makefile.am index 49e88b821..1035b76e5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,7 +4,7 @@ CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c # Distribution tarball options. EXTRA_DIST = \ - CHANGES README README.ja COPYING example_tmux.conf \ + CHANGES README COPYING example_tmux.conf \ osdep-*.c mdoc2man.awk tmux.1 dist_EXTRA_tmux_SOURCES = compat/*.[ch] diff --git a/README.ja b/README.ja deleted file mode 100644 index 3d3d0b43c..000000000 --- a/README.ja +++ /dev/null @@ -1,62 +0,0 @@ -tmuxへようこそ! - -tmuxはターミナルマルチプレクサーです。複数のターミナルを一つのスクリーン内に作成し、操作することができます。 -バックグラウンドで処理を実行中に一度スクリーンから離れて後から復帰することも可能です。 - -OpenBSD、FreeBSD、NetBSD、Linux、macOS、Solarisで実行できます。 - -tmuxはlibevent 2.x.に依存します。 下記からダウンロードしてください。 - - http://libevent.org - -また、ncursesも必要です。こちらからどうぞ。 - - http://invisible-island.net/ncurses/ - -tarballでのtmuxのビルドとインストール方法。 - - $ ./configure && make - $ sudo make install - -tmuxはutmp(5)をアップデートするためにutempterを使うことができます。もしインストール済みであればオプション「--enable-utempter」をつけて実行してください。 - -リポジトリから最新バージョンを手に入れるためには下記を実行。 - - $ git clone https://github.com/tmux/tmux.git - $ cd tmux - $ sh autogen.sh - $ ./configure && make - -(ビルドのためにはlibevent、ncurses libraries、headersに加えて、C compiler、make、autoconf、automake、pkg-configが必要です。) - -詳しい情報はhttp://git-scm.comをご覧ください。修正はメール宛、もしくはhttps://github.com/tmux/tmux/issuesにて受け付けています。 - -tmuxのドキュメントについてはtmux.1マニュアルをご覧ください。こちらのコマンドで参照可能です。 - - $ nroff -mdoc tmux.1|less - -サンプル設定は本リポジトリのexample_tmux.confに -また、bash-completionファイルは下記にあります。 - - https://github.com/scop/bash-completion/blob/main/completions/tmux - -「-v」や「-vv」を指定することでデバッグモードでの起動が可能です。カレントディレクトリにサーバーやクライアントのログファイルが生成されます。 - -議論やバグレポート用のメーリングリストにはこちらから参加可能です。 - - https://groups.google.com/forum/#!forum/tmux-users - -gitコミットについての連絡先 - - https://groups.google.com/forum/#!forum/tmux-git - -購読はまでメールをお願いします。 - -バグレポートや機能追加(特にコードへの貢献)は大歓迎です。こちらにご連絡ください。 - - tmux-users@googlegroups.com - -本ファイル、CHANGES、 FAQ、SYNCING.mdそしてTODOはISC licenseで保護されています。 -その他のファイルのライセンスや著作権については、ファイルの上部に明記されています。 - --- Nicholas Marriott 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 659f3bee0..1a1a0d8b8 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 @@ -424,7 +424,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')) @@ -433,22 +432,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 5de8a6755..9131ee720 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 @@ -40,8 +40,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] " @@ -103,6 +103,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); @@ -120,6 +131,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')) @@ -255,7 +268,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); } @@ -306,7 +319,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 97858447c..2378a75ab 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 @@ -633,6 +633,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 fc01cfc74..3b5d3c421 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 @@ -1088,6 +1088,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) @@ -3102,6 +3116,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) @@ -3684,6 +3707,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 }, @@ -3942,6 +3968,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/key-bindings.c b/key-bindings.c index c44d94d55..d8bc591eb 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -1,4 +1,4 @@ -/* $OpenBSD: key-bindings.c,v 1.188 2026/07/13 16:30:28 nicm Exp $ */ +/* $OpenBSD: key-bindings.c,v 1.189 2026/07/15 10:38:31 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -501,6 +501,7 @@ key_bindings_init(void) /* Mouse button 1 drag on pane. */ "bind -n MouseDrag1Pane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }", "bind -n C-MouseDrag1Pane { new-pane -M }", + "bind -n C-MouseDrag1Empty { new-pane -M }", "bind -n M-MouseDrag1Pane { move-pane -M }", /* Mouse wheel up on pane. */ diff --git a/layout-custom.c b/layout-custom.c index 9df0448ff..c58e92028 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -1,4 +1,4 @@ -/* $OpenBSD: layout-custom.c,v 1.37 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: layout-custom.c,v 1.38 2026/07/16 12:36:58 nicm Exp $ */ /* * Copyright (c) 2010 Nicholas Marriott @@ -177,7 +177,7 @@ layout_parse(struct window *w, const char *layout, char **cause) struct window_pane *wp; u_int npanes, ncells, sx = 0, sy = 0; u_short csum; - int n; + int n = 0; /* Check validity. */ if (sscanf(layout, "%hx,%n", &csum, &n) != 1 || n != 5) { 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 d06397849..d0a175b9f 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 @@ -1530,9 +1530,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/regress/modal-pane.sh b/regress/modal-pane.sh new file mode 100644 index 000000000..cd891281d --- /dev/null +++ b/regress/modal-pane.sh @@ -0,0 +1,308 @@ +#!/bin/sh + +# Tests for modal floating panes. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" + +cleanup() +{ + $TMUX kill-server >/dev/null 2>&1 + $TMUX2 kill-server >/dev/null 2>&1 +} + +fail() +{ + echo "$*" >&2 + cleanup + exit 1 +} + +must_equal() +{ + got=$1 + want=$2 + [ "$got" = "$want" ] || fail "got '$got', expected '$want'" +} + +check_ok() +{ + $TMUX "$@" || fail "command failed: $*" +} + +check_fail() +{ + exp="$1" + shift + out=$($TMUX "$@" 2>&1) + if [ $? -eq 0 ]; then + fail "command succeeded (expected failure): $*" + fi + must_equal "$out" "$exp" +} + +fmt() +{ + $TMUX display-message -p -t "$1" "$2" +} + +click() +{ + col="$1" + row="$2" + seq=$(printf '\033[<0;%s;%sM\033[<0;%s;%sm' \ + "$col" "$row" "$col" "$row") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 1 +} + +move_mouse() +{ + col="$1" + row="$2" + seq=$(printf '\033[<35;%s;%sM' "$col" "$row") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 1 +} + +ctrl_drag() +{ + scol="$1" + srow="$2" + ecol="$3" + erow="$4" + + seq=$(printf '\033[<16;%s;%sM' "$scol" "$srow") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 0.2 + seq=$(printf '\033[<48;%s;%sM' "$ecol" "$erow") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 0.2 + seq=$(printf '\033[<16;%s;%sm' "$ecol" "$erow") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 1 +} + +drag() +{ + scol="$1" + srow="$2" + ecol="$3" + erow="$4" + + seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 0.2 + seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 0.2 + seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow") + $TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null + sleep 1 +} + +cleanup + +check_ok new-session -d -s modal -x 80 -y 24 'cat' +sleep 1 +p0=$(fmt modal:0 '#{pane_id}') +check_ok split-window -h -t "$p0" 'cat' +sleep 1 +p1=$(fmt modal:0 '#{pane_id}') + +check_ok select-pane -t "$p0" +check_ok resize-pane -Z -t "$p0" +must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1 + +modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p1" \ + -x 20 -y 5 -X 20 -Y 10 'cat') || + fail "new-pane -O failed" +sleep 1 +must_equal "$(fmt "$modal" '#{pane_floating_flag}:#{pane_modal_flag}:#{pane_active}')" 1:1:1 +case "$(fmt "$modal" '#{pane_flags}')" in +*O*) ;; +*) fail "modal pane flags do not include O" ;; +esac +case "$(fmt modal:0 '#{window_flags}')" in +*O*) ;; +*) fail "modal window flags do not include O" ;; +esac +must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal" +must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 0 + +check_fail "window already has a modal pane" \ + new-pane -O -x 10 -y 4 'cat' +check_fail "modal pane must be floating" \ + new-pane -O -L 'cat' +check_fail "pane is modal" \ + break-pane -s "$modal" +check_fail "pane is modal" \ + join-pane -s "$modal" -t "$p0" +check_fail "pane is modal" \ + join-pane -s "$p0" -t "$modal" +check_fail "pane is modal" \ + swap-pane -s "$modal" -t "$p0" +check_fail "pane is modal" \ + swap-pane -s "$p0" -t "$modal" + +check_ok select-pane -t "$p1" +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" +check_ok last-pane -t modal:0 +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" + +under=$($TMUX split-window -PF '#{pane_id}' -t "$p0" 'cat') || + fail "split-window under modal failed" +sleep 1 +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" +must_equal "$(fmt "$under" '#{pane_active}')" 0 + +float=$($TMUX new-pane -PF '#{pane_id}' -x 10 -y 4 -X 5 -Y 3 'cat') || + fail "new floating pane under modal failed" +sleep 1 +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" +must_equal "$(fmt "$float" '#{pane_active}')" 0 + +check_ok new-window -d -t modal: -n other 'cat' +check_ok select-window -t modal:other +other=$(fmt modal:other '#{pane_id}') +must_equal "$(fmt modal:other '#{pane_id}')" "$other" +check_ok select-window -t modal:0 +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" + +check_ok send-keys -t modal:0 'modal-key' Enter +sleep 1 +case "$($TMUX capture-pane -pt "$modal")" in +*modal-key*) ;; +*) fail "keyboard input did not reach modal pane" ;; +esac +case "$($TMUX capture-pane -pt "$p0")" in +*modal-key*) fail "keyboard input reached pane below modal" ;; +esac + +$TMUX set -g mouse on +$TMUX set -g focus-follows-mouse on +$TMUX set -g @modal-mouse '' +$TMUX bind -n MouseDown1Pane run-shell \ + "$TMUX set -g @modal-mouse '#{mouse_pane}'" +$TMUX bind x set -g @modal-prefix yes + +$TMUX2 new-session -d -x 80 -y 24 "$TMUX attach -t modal" || + fail "outer session failed" +sleep 1 +OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1) +[ -n "$OUTER" ] || fail "no outer pane" + +click 1 1 +must_equal "$($TMUX show -gv @modal-mouse)" '' +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" + +panes=$(fmt modal:0 '#{window_panes}') +ctrl_drag 1 1 8 3 +must_equal "$(fmt modal:0 '#{window_panes}')" "$panes" +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" + +move_mouse 1 1 +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" + +left=$(fmt "$modal" '#{pane_left}') +top=$(fmt "$modal" '#{pane_top}') +click $((left + 1)) $((top + 1)) +must_equal "$($TMUX show -gv @modal-mouse)" "$modal" +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" + +width=$(fmt "$modal" '#{pane_width}') +right=$((left + width + 1)) +drag "$right" $((top + 1)) $((right + 5)) $((top + 1)) +new_width=$(fmt "$modal" '#{pane_width}') +[ "$new_width" -gt "$width" ] || + fail "modal pane did not grow after right-border drag" + +$TMUX2 send-keys -t "$OUTER" C-b x +sleep 1 +must_equal "$($TMUX show -gv @modal-prefix)" yes + +$TMUX set-buffer -b modal-edit-test 'test' +check_ok choose-buffer -t "$modal" +panes=$(fmt modal:0 '#{window_panes}') +$TMUX2 send-keys -t "$OUTER" e +sleep 1 +must_equal "$(fmt modal:0 '#{window_panes}')" "$panes" +must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal" +$TMUX2 send-keys -t "$OUTER" q +sleep 1 + +check_ok kill-pane -t "$modal" +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' +case "$(fmt modal:0 '#{window_flags}')" in +*O*) fail "modal window flag remained after modal pane closed" ;; +esac +must_equal "$(fmt modal:0 '#{pane_id}')" "$p0" +must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1 +check_ok resize-pane -Z -t "$p0" +must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 0 + +$TMUX set -g editor 'sh -c "sleep 10" sh' +check_ok resize-pane -Z -t "$p0" +check_ok choose-buffer -t "$p0" +panes=$(fmt modal:0 '#{window_panes}') +$TMUX2 send-keys -t "$OUTER" e +sleep 1 +editor=$(fmt modal:0 '#{window_modal_pane}') +[ -n "$editor" ] || fail "buffer editor did not open as modal pane" +must_equal "$(fmt modal:0 '#{window_panes}')" $((panes + 1)) +must_equal "$(fmt "$editor" '#{pane_modal_flag}:#{pane_active}')" 1:1 +check_ok kill-pane -t "$editor" +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' +must_equal "$(fmt modal:0 '#{pane_id}')" "$p0" +must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1 +check_ok resize-pane -Z -t "$p0" +must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 0 + +detached=$($TMUX new-pane -OdPF '#{pane_id}' -x 20 -y 5 -X 20 -Y 10 \ + 'cat') || fail "new detached modal failed" +sleep 1 +must_equal "$(fmt "$detached" '#{pane_modal_flag}:#{pane_active}')" 1:1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$detached" +check_ok kill-pane -t "$detached" +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' +must_equal "$(fmt modal:0 '#{pane_id}')" "$p0" + +$TMUX set -g @modal-custom old +check_ok customize-mode -t "$p0" \ + -f '#{==:#{option_name},@modal-custom}' +panes=$(fmt modal:0 '#{window_panes}') +$TMUX2 send-keys -t "$OUTER" j Right j e +sleep 1 +editor=$(fmt modal:0 '#{window_modal_pane}') +[ -n "$editor" ] || fail "customize editor did not open as modal pane" +must_equal "$(fmt modal:0 '#{window_panes}')" $((panes + 1)) +must_equal "$(fmt "$editor" '#{pane_modal_flag}:#{pane_active}')" 1:1 +check_ok kill-pane -t "$editor" +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' +$TMUX2 send-keys -t "$OUTER" q +sleep 1 + +modal=$($TMUX new-pane -OkPF '#{pane_id}' -x 20 -y 5 -X 20 -Y 10 'printf done') || + fail "new retained modal failed" +sleep 2 +must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}:#{pane_active}')" 1:1:1 +check_ok respawn-pane -k -t "$modal" 'cat' +sleep 1 +must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}:#{pane_active}')" 0:1:1 +check_ok select-pane -t "$p1" +must_equal "$(fmt modal:0 '#{pane_id}')" "$modal" +check_ok kill-pane -t "$modal" +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' +must_equal "$(fmt modal:0 '#{pane_id}')" "$p0" + +cleanup +exit 0 diff --git a/regress/new-pane-mouse.sh b/regress/new-pane-mouse.sh index d2ae9ee97..81f0e805b 100644 --- a/regress/new-pane-mouse.sh +++ b/regress/new-pane-mouse.sh @@ -75,7 +75,26 @@ must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 1 must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 6 must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 3 -$TMUX new-pane -d -M -L -t "$BASE" 'sleep 100' || fail "new-pane -M -L failed" +$TMUX kill-pane -t "$id" || fail "kill floating pane failed" +$TMUX break-pane -W -s "$BASE" || fail "break base pane failed" +$TMUX resize-pane -t "$BASE" -x10 -y4 || fail "resize floating base failed" +$TMUX move-pane -t "$BASE" -P top-left || fail "move floating base failed" + +# Drag in empty window space with no tiled pane underneath. +drag 40 10 50 15 + +id=$($TMUX list-panes -F '#{?pane_active,#{pane_id},}' | tail -n 1) +[ -n "$id" ] || fail "no floating pane created from empty area" +[ "$id" != "$BASE" ] || fail "empty-area drag did not create a new pane" + +must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 40 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 10 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 9 +must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 4 + +TILED=$($TMUX new-window -dPF '#{pane_id}' 'sleep 100') || + fail "new tiled window failed" +$TMUX new-pane -d -M -L -t "$TILED" 'sleep 100' || fail "new-pane -M -L failed" cleanup exit 0 diff --git a/screen-redraw.c b/screen-redraw.c index 454c5df6c..90fe709fe 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1,4 +1,4 @@ -/* $OpenBSD: screen-redraw.c,v 1.151 2026/07/14 19:07:03 nicm Exp $ */ +/* $OpenBSD: screen-redraw.c,v 1.152 2026/07/16 10:52:32 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -1858,9 +1858,12 @@ redraw_screen(struct client *c) { int flags = 0; - if (c->flags & CLIENT_REDRAWWINDOW) - redraw_draw(c, NULL, REDRAW_ALL); - else { + if (c->flags & CLIENT_REDRAWWINDOW) { + if (c->flags & CLIENT_REDRAWOVERLAY) + redraw_draw(c, NULL, REDRAW_ALL); + else + redraw_draw(c, NULL, REDRAW_ALL & ~REDRAW_OVERLAY); + } else { if (c->flags & CLIENT_REDRAWBORDERS) flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS); if (c->flags & CLIENT_REDRAWSTATUS) diff --git a/server-client.c b/server-client.c index c5457b14e..326bf9444 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.491 2026/07/14 19:07:03 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.494 2026/07/15 14:14:50 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -871,6 +871,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; @@ -1029,46 +1030,73 @@ have_event: * Not on status line. Adjust position and check for border, pane, or * scrollbar. */ - if (loc == KEYC_MOUSE_LOCATION_NOWHERE) { - if (c->tty.mouse_scrolling_flag) { - if (lwp != NULL) { - loc = KEYC_MOUSE_LOCATION_SCROLLBAR_SLIDER; - m->wp = lwp->id; - m->w = lwp->window->id; - } - } else { - px = x; - if (m->statusat == 0 && y >= m->statuslines) - py = y - m->statuslines; - else if (m->statusat > 0 && y >= (u_int)m->statusat) - py = m->statusat - 1; - else - py = y; + if (loc == KEYC_MOUSE_LOCATION_NOWHERE && c->tty.mouse_scrolling_flag) { + if (lwp != NULL) { + loc = KEYC_MOUSE_LOCATION_SCROLLBAR_SLIDER; + m->wp = lwp->id; + m->w = lwp->window->id; + } + } else if (loc == KEYC_MOUSE_LOCATION_NOWHERE) { + px = x; + if (m->statusat == 0 && y >= m->statuslines) + py = y - m->statuslines; + else if (m->statusat > 0 && y >= (u_int)m->statusat) + py = m->statusat - 1; + else + py = y; - tty_window_offset(&c->tty, &m->ox, &m->oy, &sx, &sy); - log_debug("mouse window @%u at %u,%u (%ux%u)", - w->id, m->ox, m->oy, sx, sy); - if (px > sx || py > sy) { + tty_window_offset(&c->tty, &m->ox, &m->oy, &sx, &sy); + log_debug("mouse window @%u at %u,%u (%ux%u)", w->id, m->ox, + m->oy, sx, sy); + if (px > sx || py > sy) { + server_client_update_scrollbar_hover(c, type, -1, -1); + return (KEYC_UNKNOWN); + } + 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); } - px = px + m->ox; - py = py + m->oy; - server_client_update_scrollbar_hover(c, type, px, py); + } + server_client_update_scrollbar_hover(c, type, px, py); - if (type == KEYC_TYPE_MOUSEDRAG && lwp != NULL) { - /* Use pane from last mouse event. */ - wp = lwp; - } else { - /* Try inside the pane. */ - wp = window_get_active_at(w, px, py); + 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 { + /* Try inside the pane. */ + wp = window_get_active_at(w, px, py); + } + if (wp == NULL) { + loc = KEYC_MOUSE_LOCATION_EMPTY; + m->w = w->id; + log_debug("mouse %u,%u on empty area", x, y); + } else { + if (!modal_drag) { + loc = server_client_check_mouse_in_pane(wp, px, + py, &sl_mpos); } - if (wp == NULL) - return (KEYC_UNKNOWN); - 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); @@ -3058,16 +3086,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); } @@ -3080,6 +3112,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 f11e2e2a0..1b382202b 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 @@ -271,6 +271,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. @@ -565,7 +576,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 @@ -681,6 +701,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) @@ -707,9 +730,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); } @@ -726,13 +750,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 372c47c7e..126a26690 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1140 2026/07/14 17:17:18 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1142 2026/07/15 13:02:33 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -14,7 +14,7 @@ .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: July 14 2026 $ +.Dd $Mdocdate: July 15 2026 $ .Dt TMUX 1 .Os .Sh NAME @@ -3647,7 +3647,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 @@ -3698,6 +3698,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 @@ -6622,6 +6627,7 @@ and a location suffix, one of the following: .It Li "ScrollbarSlider" Ta "the scrollbar slider" .It Li "ScrollbarUp" Ta "above the scrollbar slider" .It Li "ScrollbarDown" Ta "below the scrollbar slider" +.It Li "Empty" Ta "empty space in the window" .It Li "ControlN" Ta "on control range N" .El .Pp @@ -7321,6 +7327,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" @@ -7421,6 +7428,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 d2e3010d7..b23c61a56 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1404 2026/07/14 19:07:03 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1406 2026/07/15 13:02:33 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -193,6 +193,7 @@ enum key_code_mouse_location { KEYC_MOUSE_LOCATION_SCROLLBAR_UP, KEYC_MOUSE_LOCATION_SCROLLBAR_SLIDER, KEYC_MOUSE_LOCATION_SCROLLBAR_DOWN, + KEYC_MOUSE_LOCATION_EMPTY, KEYC_MOUSE_LOCATION_CONTROL0, /* keep order */ KEYC_MOUSE_LOCATION_CONTROL1, KEYC_MOUSE_LOCATION_CONTROL2, @@ -249,6 +250,7 @@ enum key_code_mouse_location { KEYC_MOUSE_KEY(KEYC_ ## t, KEYC_TYPE_ ## t, SCROLLBAR_UP), \ KEYC_MOUSE_KEY(KEYC_ ## t, KEYC_TYPE_ ## t, SCROLLBAR_SLIDER), \ KEYC_MOUSE_KEY(KEYC_ ## t, KEYC_TYPE_ ## t, SCROLLBAR_DOWN), \ + KEYC_MOUSE_KEY(KEYC_ ## t, KEYC_TYPE_ ## t, EMPTY), \ KEYC_MOUSE_KEY(KEYC_ ## t, KEYC_TYPE_ ## t, CONTROL0), \ KEYC_MOUSE_KEY(KEYC_ ## t, KEYC_TYPE_ ## t, CONTROL1), \ KEYC_MOUSE_KEY(KEYC_ ## t, KEYC_TYPE_ ## t, CONTROL2), \ @@ -283,6 +285,7 @@ enum key_code_mouse_location { { #s "ScrollbarUp", KEYC_ ## name ## _SCROLLBAR_UP }, \ { #s "ScrollbarSlider", KEYC_ ## name ## _SCROLLBAR_SLIDER }, \ { #s "ScrollbarDown", KEYC_ ## name ## _SCROLLBAR_DOWN }, \ + { #s "Empty", KEYC_ ## name ## _EMPTY }, \ { #s "Border", KEYC_ ## name ## _BORDER }, \ { #s "Control0", KEYC_ ## name ## _CONTROL0 }, \ { #s "Control1", KEYC_ ## name ## _CONTROL1 }, \ @@ -1415,6 +1418,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; @@ -1456,6 +1461,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; @@ -2486,6 +2492,7 @@ struct spawn_context { #define SPAWN_FLOATING 0x100 #define SPAWN_HORIZONTAL 0x200 #define SPAWN_SPLIT 0x400 +#define SPAWN_MODAL 0x800 }; /* Paste buffer. */ @@ -3636,6 +3643,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 *, @@ -3650,6 +3658,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-buffer.c b/window-buffer.c index 12c847944..ab15d87ed 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-buffer.c,v 1.50 2026/07/14 17:17:18 nicm Exp $ */ +/* $OpenBSD: window-buffer.c,v 1.51 2026/07/15 12:45:39 nicm Exp $ */ /* * Copyright (c) 2017 Nicholas Marriott @@ -525,6 +525,8 @@ window_buffer_draw_waiting(struct window_buffer_modedata *data) screen_write_start(&ctx, s); screen_write_cursormove(&ctx, x, y, 0); screen_write_box(&ctx, box_w, box_h, BOX_LINES_DEFAULT, &gc, NULL); + screen_write_cursormove(&ctx, x + 1, y + 1, 0); + screen_write_clearcharacter(&ctx, box_w - 2, gc.bg); screen_write_cursormove(&ctx, text_x, y + 1, 0); screen_write_nputs(&ctx, box_w - 2, &gc, "%s", text); screen_write_stop(&ctx); diff --git a/window-customize.c b/window-customize.c index 94716e34c..1b2588fd7 100644 --- a/window-customize.c +++ b/window-customize.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-customize.c,v 1.34 2026/07/14 17:17:18 nicm Exp $ */ +/* $OpenBSD: window-customize.c,v 1.35 2026/07/15 12:45:39 nicm Exp $ */ /* * Copyright (c) 2020 Nicholas Marriott @@ -397,6 +397,8 @@ window_customize_draw_waiting(struct window_customize_modedata *data) screen_write_start(&ctx, s); screen_write_cursormove(&ctx, x, y, 0); screen_write_box(&ctx, box_w, box_h, BOX_LINES_DEFAULT, &gc, NULL); + screen_write_cursormove(&ctx, x + 1, y + 1, 0); + screen_write_clearcharacter(&ctx, box_w - 2, gc.bg); screen_write_cursormove(&ctx, text_x, y + 1, 0); screen_write_nputs(&ctx, box_w - 2, &gc, "%s", text); screen_write_stop(&ctx); diff --git a/window.c b/window.c index abeb87a1c..ff2ff443c 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 @@ -633,6 +633,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) { @@ -690,6 +719,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; @@ -728,6 +759,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; @@ -780,6 +813,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 @@ -936,6 +975,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) { @@ -986,6 +1048,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); } @@ -996,14 +1060,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) @@ -1015,6 +1094,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); } @@ -1022,9 +1104,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); } @@ -1157,6 +1243,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'; @@ -1178,6 +1266,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); }