Compare commits

..
Author SHA1 Message Date
Michael GrantandClaude Sonnet 5 6933bc417c tty: drop WT_SESSION-based Windows Terminal detection
nicm is opposed to identifying a terminal via an environment variable, so
remove the WindowsTerminal tty_default_features() entry and the
WT_SESSION check in tty_term_create() entirely, pending some other
accepted identification mechanism for Windows Terminal (it cannot be
identified via the existing XTVERSION path at all - see the previous
commit's now-removed comment for why).

This leaves WezTerm and ghostty's margins grants in place - both are
identified via the existing, already-accepted XTVERSION mechanism, with
DECSLRM support confirmed directly in their own source, independent of
this change.

regress/tty-margins-wt-session.sh tested only the removed mechanism and
is replaced by regress/tty-margins-scrollbar.sh, which checks the same
underlying scrollbar/margins scroll-decision mechanism (still real, and
still what WezTerm's and ghostty's table entries rely on) via the
generic terminal-features option instead of simulating any one
terminal's identification handshake.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 09:02:13 +01:00
Michael GrantandClaude Sonnet 5 20f76fb069 tty: broaden DECSLRM (margins) terminal-feature coverage
Any pane that doesn't span the terminal's full width - because
pane-scrollbars is on (the scrollbar occupies a column) or the pane is one
of a side-by-side split - needs DECSLRM (left/right margin) support to use
the fast native-scroll path (tty_cmd_linefeed()/scrollup()/scrolldown()/
reverseindex(), tty.c: "(!tty_full_width(tty, ctx) && !tty_use_margin(tty))").
Without it, every single scroll falls back to tty_redraw_region()'s full
manual repaint of the whole region - confirmed via -vv log
("tty_redraw_region: ... large region redraw") - which is a real source of
flicker, and separately implicated in reports of image content not
surviving a scroll in this area of the tree.

The DECSLRM machinery itself (tty_margin_pane()/tty_margin()) is already
built and already called from all four native-scroll dispatch functions.
What was missing was terminal-capability coverage:

  - WezTerm and ghostty are already correctly identified via the existing
    XTVERSION mechanism (tty_keys_extended_device_attributes(),
    tty-keys.c), and both genuinely implement DECSLRM - confirmed directly
    in their own source (WezTerm: decslrm() in csi.rs, documented in their
    escape-sequences reference, a changelog entry fixing a DECSLRM bug
    confirming active use; ghostty: handled in dcs.zig/stream.zig,
    explicitly emitted alongside DECSTBM in their own formatter.zig, with
    its own terminfo entry for it). Neither's entry in
    tty_default_features()'s table (tty-features.c) granted "margins".
    This was a gap, not a detection problem, backed by source-level
    evidence rather than guesswork - kitty and Rio show zero DECSLRM
    references in their own source, so their omission is left as-is.

  - Windows Terminal's DECSLRM support was independently confirmed by
    direct, non-tmux escape-sequence testing, but it can never be
    identified via XTVERSION - the tracking issue for that
    (github.com/microsoft/terminal#18382) was explicitly closed
    not_planned by the maintainer, calling XTVERSION "not extensible or
    helpful for feature detection". Detected instead via the WT_SESSION
    environment variable Windows Terminal sets for every child process -
    a long-standing, stable signal already used for this exact purpose by
    many other tools. Checked in tty_term_create() (tty-term.c) alongside
    the existing COLORTERM-based RGB/256 detection, which already reads a
    named variable out of the attaching client's own environment
    (c->environ, populated via the MSG_IDENTIFY_ENVIRON handshake) the
    same way. Deliberately grants only "margins" for now, not the full
    modern-xterm feature bundle other entries get, since no other
    capability has been verified for it.

New regress/tty-margins-wt-session.sh reproduces the scrollbar-pane
scenario with WT_SESSION injected into the attaching client's environment
and checks, via the -vv log, that no tty_redraw_region() fallback occurs -
plus a sanity phase confirming the same scenario does fall back without
WT_SESSION set, proving the test isn't accidentally trivial. Verified
failing 3/3 against the pre-fix code and passing 5/5 standalone; full
regress suite passes clean twice, with only pre-existing, unrelated
failures (check-names.sh - confirmed identical on unmodified master;
prompt-words-history.sh - already-known flake; four untracked
image-*-noflash scratch tests left over from unrelated work on another
branch, not part of this tree's tracked suite).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 07:37:06 +01:00
tmux update bot 5e860c4e6c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  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-22 20:02:41 +00:00
nicm 183c3432b0 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-22 20:02:40 +00:00
tmux update bot d1ee699dd1 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  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-22 16:48:21 +00:00
nicm e7618f41c2 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-22 16:48:18 +00:00
Nicholas Marriott 83c8962cf9 Some tests. 2026-09-22 14:25:51 +01:00
tmux update bot fca57a5fc9 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a feature for mintty's application escape to avoid dodgy terminals whinging about not supporting it, GitHub issue 5626 from Pete Dietl.
  Only rely on the previous line wrapping when the cursor is actually on it, GitHub issue 5625 from Ben Maurer.
  Correctly write Z indexes to v2 layouts if zoomed, GitHub issue 5624.
  Do not leak waiting clients (in wait-for) if they are killed, GitHub issue 5614.
2026-09-22 11:58:43 +00:00
nicm 83f62d0acc 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-22 11:58:41 +00:00
nicm 19b7660778 Only rely on the previous line wrapping when the cursor is actually on
it, GitHub issue 5625 from Ben Maurer.
2026-09-22 11:58:41 +00:00
nicm 4624bc2129 Correctly write Z indexes to v2 layouts if zoomed, GitHub issue 5624. 2026-09-22 11:58:41 +00:00
nicm 0e1f6ed80d Do not leak waiting clients (in wait-for) if they are killed, GitHub
issue 5614.
2026-09-22 11:58:41 +00:00
Nicholas Marriott 21ea1c91c5 Merge pull request #5627 from pdietl/regress-input-keys-race
Wait for the pane to echo the key before capturing in input-keys.sh
2026-09-22 07:55:13 +01:00
Nicholas Marriott cb9f9b588f Fix test. 2026-09-22 07:52:51 +01:00
Pete Dietl d6db719b19 Wait for the pane to echo the key before capturing in input-keys.sh 2026-09-22 00:21:23 -05:00
tmux update bot 9860e60afa Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not cancel mark when click on border.
2026-09-21 22:31:27 +00:00
nicm ca5c28d224 Do not cancel mark when click on border. 2026-09-21 22:31:26 +00:00
Nicholas Marriott c3326194b6 Turn on Linux arm also. 2026-09-21 22:34:05 +01:00
Nicholas Marriott 6a8c21fb41 macOS parallel issues. 2026-09-21 22:27:43 +01:00
Nicholas Marriott 326b9a4f16 Another timing race. 2026-09-21 22:18:44 +01:00
Nicholas Marriott 6871fcb157 Add missing test cleanup. 2026-09-21 22:16:06 +01:00
Nicholas Marriott 1d9943c6e6 Keep window alive. 2026-09-21 22:12:33 +01:00
Nicholas Marriott f5c4998db3 Make regress parallel friendly. 2026-09-21 22:00:47 +01:00
Nicholas Marriott cc81429d8a Do not make test so timing sensitive. 2026-09-21 21:51:30 +01:00
tmux update bot 3a6c2e7877 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  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 18:56:57 +00:00
nicm 21b3da3bab 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 18:56:55 +00:00
tmux update bot a7bd2415ba Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add support for borderless menus, like panes. GitHub issue 5447 from harikp2002 at gmail dot com.
  Do not trim all lines to make a zero line grid which reflow does not like, from Kaixuan Li.
  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 13:44:41 +00:00
nicm dda0e4d489 Add support for borderless menus, like panes. GitHub issue 5447 from harikp2002
at gmail dot com.
2026-09-21 13:44:39 +00:00
nicm e361b8f8ff Do not trim all lines to make a zero line grid which reflow does not like, from
Kaixuan Li.
2026-09-21 13:44:39 +00:00
nicm 9be3a35178 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 13:44:39 +00:00
Nicholas Marriott 324c636c5c Test for 5447 and for copy-mode zero line crash. 2026-09-21 13:14:44 +01:00
Nicholas Marriott 166851bf87 Update tests and bits after popups removed. 2026-09-21 12:05:37 +01:00
Nicholas Marriott 5aa17a0cfa Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Remove popups and all the associated overlay machinery (they were the last user of it). display-popup stays but becomes an (undocumented) compatibility command to open a floating pane.
  Fix typo (too few 0s) in CLIENT_CONTROL_DISCARD, from someone in GitHub issue 5622.
2026-09-21 12:03:10 +01:00
nicm 34cd5da4e3 Remove popups and all the associated overlay machinery (they were the last user
of it). display-popup stays but becomes an (undocumented) compatibility command
to open a floating pane.
2026-09-21 11:01:12 +00:00
nicm 3d5f946f35 Fix typo (too few 0s) in CLIENT_CONTROL_DISCARD, from someone in GitHub issue
5622.
2026-09-21 11:01:12 +00:00
Nicholas Marriott 81794f3047 Test for newp -A and resize. 2026-09-21 11:39:09 +01:00
Thomas Adam c612f35fd8 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  build tmux with debug symbols, ok claudio nicm
  If no floating panes, reset default starting position, and reset if any part of the pane goes outside the window.
  Empty string for invalid old-style formats causes old iTerm2 versions to crash, so emit "0000," instead.
  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.
  Do not loop forever if someone tries to give WCHAR_MAX a width, GitHub issue 5602.
  Expand -c for run-shell like the other -c flags, reported by Saúl Nogueras.
  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-20 20:05:57 +01:00
sthen 56b36d671e build tmux with debug symbols, ok claudio nicm 2026-09-20 12:06:27 +00:00
nicm eee95e6479 If no floating panes, reset default starting position, and reset if any part of
the pane goes outside the window.
2026-09-20 12:06:27 +00:00
nicm bbd00148de Empty string for invalid old-style formats causes old iTerm2 versions to crash,
so emit "0000," instead.
2026-09-20 12:06:27 +00:00
nicm 2e9189c01d 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-20 12:06:27 +00:00
nicm d4dc032599 Do not loop forever if someone tries to give WCHAR_MAX a width, GitHub issue
5602.
2026-09-20 12:06:27 +00:00
nicm d1d07f9790 Expand -c for run-shell like the other -c flags, reported by Saúl Nogueras. 2026-09-20 12:06:27 +00:00
nicm b25af0a08c 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-20 12:06:27 +00:00
Nicholas Marriott 1eed85c344 Change tests for 0000, for invalid layouts. 2026-09-20 09:37:59 +01:00
Nicholas Marriott 73042686d8 Test for 5599. 2026-09-20 09:19:35 +01:00
Nicholas Marriott 313ad6fdc5 Tests for 5602 and for run-shell -c. 2026-09-20 09:11:24 +01:00
Nicholas Marriott 594465fbe7 Regress test for 5591 and another one I forget. 2026-09-20 08:44:56 +01:00
tmux update bot e880cf63e0 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add rounded borders option for panes like popups.
2026-09-11 17:00:20 +00:00
nicm 325b40e8c4 Add rounded borders option for panes like popups. 2026-09-11 17:00:18 +00:00
tmux update bot f95363aa83 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  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-11 12:58:31 +00:00
nicm f8fc53b6f8 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-11 12:58:29 +00:00
Nicholas Marriott d215280b00 Add rounded border tests. 2026-09-11 11:17:35 +01:00
Nicholas Marriott d859a0f410 Test for resizing floating panes, from Noam Stolero. 2026-09-11 09:16:55 +01:00
Nicholas Marriott 10d00cc7cb Modal pane test changes. 2026-09-11 09:10:14 +01:00
Nicholas Marriott 14a01dd337 Note changes from Heon Jeong. 2026-09-11 09:05:06 +01:00
tmux update bot a958883625 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  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-10 13:04:13 +00:00
nicm d202aa7ac1 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-10 13:04:11 +00:00
Nicholas Marriott d9a5e82678 Fix tests. 2026-09-10 12:00:01 +01:00
tmux update bot 13c10f672c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  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-09 19:23:09 +00:00
nicm b7c5030004 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-09 19:23:07 +00:00
Nicholas Marriott 4839123510 Tweak changes. 2026-09-09 13:50:42 +01:00
Nicholas Marriott 9b3268a2a0 Bump version. 2026-09-09 13:28:07 +01:00
Nicholas Marriott 5aeacf1cae Update CHANGES. 2026-09-09 13:06:56 +01:00
tmux update bot 7774731b95 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Draw bottom border with pane status line at the top.
  Bound the memory used by buffered control mode command replies (to 64 MB), GitHub issue 5565 from kagari dot shusei at proton dot me.
  Do not adjust prompt position on invalid Unicode, from Kaixuan Li.
2026-09-09 10:12:52 +00:00
nicm 0544f0b210 Draw bottom border with pane status line at the top. 2026-09-09 10:12:50 +00:00
nicm a3129249d1 Bound the memory used by buffered control mode command replies (to 64
MB), GitHub issue 5565 from kagari dot shusei at proton dot me.
2026-09-09 10:12:50 +00:00
nicm 930c81b819 Do not adjust prompt position on invalid Unicode, from Kaixuan Li. 2026-09-09 10:12:50 +00:00
Nicholas Marriott fc695b4e86 Test for v1 nesting limit, based on a changed from Afonso Januário. 2026-09-09 10:01:32 +01:00
Nicholas Marriott 36fb7ab084 Tests of border. 2026-09-09 09:32:14 +01:00
tmux update bot 90a7dd4654 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add new layout format which includes floating panes. The new format is now a JSON subset which is less fragile and easier to handle than the old custom format. The old (version 1) format is still supported for control mode clients for now - they must set the new-layouts flag to receive the new format. From Dane Jensen.
2026-09-09 08:27:51 +00:00
nicm bf43fdc0c7 Add new layout format which includes floating panes. The new format is
now a JSON subset which is less fragile and easier to handle than the
old custom format. The old (version 1) format is still supported for
control mode clients for now - they must set the new-layouts flag to
receive the new format. From Dane Jensen.
2026-09-09 08:27:49 +00:00
Nicholas Marriott 077d6e53cd Test for invalid prompt. 2026-09-09 08:53:51 +01:00
Nicholas Marriott 4e19f7a8dc New layout format regress test, from Dane Jensen. 2026-09-09 08:04:23 +01:00
tmux update bot 7941f7b863 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  The client for the layout format is the client we are sending it to, not the target client.
2026-09-08 21:40:59 +00:00
nicm c7102ebbe5 The client for the layout format is the client we are sending it to, not
the target client.
2026-09-08 21:40:57 +00:00
tmux update bot 1b87cb752e Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add new-layouts flag for control clients so they can default to old layouts for backwards compatibility.
2026-09-08 11:38:06 +00:00
nicm d9692f7ecf Add new-layouts flag for control clients so they can default to old
layouts for backwards compatibility.
2026-09-08 11:38:04 +00:00
Nicholas Marriott 98637576bd Test for new-layouts flag. 2026-09-08 11:27:25 +01:00
Nicholas Marriott d8bdb23108 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a function to find last pane index, from Dane Jensen.
  Add a parser for a subset of JSON, will be used for new layout strings (and maybe some other stuff), from Dane Jensen.
  Add capture-pane -I to show times.
2026-09-08 11:25:17 +01:00
nicm 2191c9b172 Add a function to find last pane index, from Dane Jensen. 2026-09-08 10:22:40 +00:00
nicm 01d1d6580a Add a parser for a subset of JSON, will be used for new layout strings
(and maybe some other stuff), from Dane Jensen.
2026-09-08 10:22:40 +00:00
nicm 96f4c7b9fa Add capture-pane -I to show times. 2026-09-08 10:22:40 +00:00
Nicholas Marriott 6a24d5437b JSON test. 2026-09-08 09:36:26 +01:00
Nicholas Marriott 8396a63128 capture-pane -I test. 2026-09-08 08:32:05 +01:00
tmux update bot d44bfda26d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Error on invalid relative targets, such as +foo or -0. GitHub issue 5576 from imcusg at gmail dot com.
  Do not silently make a session monitor if the target is unknown, GitHub issue 5575 from zzchun12826 at gmail dot com.
2026-09-07 17:56:25 +00:00
nicm 00f3899aea Error on invalid relative targets, such as +foo or -0. GitHub issue 5576
from imcusg at gmail dot com.
2026-09-07 17:56:23 +00:00
nicm 557967c36c Do not silently make a session monitor if the target is unknown, GitHub
issue 5575 from zzchun12826 at gmail dot com.
2026-09-07 17:56:23 +00:00
Nicholas Marriott 73db0a54e5 Tests from GitHub issue 5576. 2026-09-07 13:05:38 +01:00
114 changed files with 5939 additions and 4435 deletions
+6 -6
View File
@@ -27,10 +27,10 @@ jobs:
runner: ubuntu-24.04
make: make
configure: --enable-utf8proc --enable-asan
#- name: ubuntu-24.04-arm64
# runner: ubuntu-24.04-arm
# make: make
# configure: --enable-utf8proc --enable-asan
- name: ubuntu-24.04-arm64
runner: ubuntu-24.04-arm
make: make
configure: --enable-utf8proc --enable-asan
- name: macos-26-arm64
runner: macos-26
make: gmake
@@ -72,14 +72,14 @@ jobs:
- name: build
run: |
sh autogen.sh
sh autogen.sh
./configure ${{ matrix.configure }}
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
- name: test
run: |
cd regress
${{ matrix.make }}
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
- name: logs
if: failure()
+12 -3
View File
@@ -33,9 +33,11 @@ CHANGES FROM 3.7c TO 3.8
line;
- 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.
The old format is still accepted; control mode clients receive old layouts
unless they set the new-layouts flag.
* Add support for themes and improve default colours:
@@ -126,6 +128,10 @@ CHANGES FROM 3.7c TO 3.8
* Add new-window -E, respawn-pane -E and respawn-window -E as more convenient
methods to create an empty pane (rather than using '' for the command).
* 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.
* Menus now belong to the window, so appear on all clients.
@@ -172,6 +178,8 @@ CHANGES FROM 3.7c TO 3.8
* The mouse option now defaults to on.
* The active-pane client flag has been removed.
* Fix send-keys -K so keys are inserted in the correct place in the input
queue, like keys from key bindings (issue 3476).
@@ -180,7 +188,8 @@ CHANGES FROM 3.7c TO 3.8
already exiting (Ben Maurer, issue 5357). Queue notifications so they are not
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
is respawned (issue 5498).
is respawned (issue 5498). Bound buffered command replies so a control mode
client that stops reading cannot consume unlimited memory (issue 5565).
* Fix grouped sessions sometimes being left as unusable command targets while
they are being killed (Bryce Miller, issue 5180).
+1 -1
View File
@@ -174,6 +174,7 @@ dist_tmux_SOURCES = \
input-keys.c \
input.c \
job.c \
json.c \
key-bindings.c \
key-string.c \
layout-custom.c \
@@ -187,7 +188,6 @@ dist_tmux_SOURCES = \
options-table.c \
options.c \
paste.c \
popup.c \
proc.c \
prompt-history.c \
prompt.c \
+71 -8
View File
@@ -35,7 +35,7 @@ The usual local layout is:
```sh
cd /some/where/useful
git clone https://github.com/tmux/tmux.git tmux-portable
git clone https://github.com/ThomasAdam/tmux-obsd.git tmux-openbsd-cutover
git clone https://github.com/tmux/tmux-openbsd-cutover.git tmux-openbsd-cutover
```
The exact directory names do not matter, but the examples below use:
@@ -56,18 +56,19 @@ The cutover repository has three important branches:
# Adding the OpenBSD remote to portable
In the portable repository, add the cutover repository as a remote:
In the portable repository, add the published cutover repository as a remote.
This works regardless of which branch is checked out in a local cutover clone:
```sh
cd /path/to/tmux-portable
git remote add tmux-openbsd /path/to/tmux-openbsd-cutover
git remote add tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
git config remote.tmux-openbsd.tagOpt --no-tags
```
If the remote already exists, update it instead:
```sh
git remote set-url tmux-openbsd /path/to/tmux-openbsd-cutover
git remote set-url tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
git config remote.tmux-openbsd.tagOpt --no-tags
```
@@ -77,6 +78,32 @@ Fetch the cutover master branch explicitly:
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
```
To merge unpublished changes from a local cutover clone instead, first ensure
it has an up-to-date local `master` branch. A normal clone may check out
`automation` and have only `origin/master`; fetching `master` from that clone
will then fail with `couldn't find remote ref master`.
With a clean cutover working tree:
```sh
cd /path/to/tmux-openbsd-cutover
git fetch --no-tags origin
git switch master
git merge --ff-only origin/master
```
`git switch master` creates a tracking branch from `origin/master` if there is
no local `master` yet. If the fast-forward fails, reconcile the local cutover
changes before continuing; do not reset them away.
Then, in portable, point the remote at that clone and fetch its local `master`:
```sh
cd /path/to/tmux-portable
git remote set-url tmux-openbsd /path/to/tmux-openbsd-cutover
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
```
# Automated syncing
The normal sync is performed by the GitHub Actions workflow in the
@@ -99,7 +126,9 @@ OpenBSD changes.
If the workflow fails while merging into portable, do the merge locally and
push the result.
Start from an up-to-date portable master:
Start with a clean working tree and an up-to-date portable master. If a merge
is already in progress, skip to resolving conflicts, or abort it before
starting again:
```sh
cd /path/to/tmux-portable
@@ -108,10 +137,15 @@ git checkout master
git pull --ff-only origin master
```
Fetch the cutover branch:
Fetch the published cutover branch. Update an existing remote as well, since
it may point at a local clone without a `master` branch or with a stale one:
```sh
git remote add tmux-openbsd /path/to/tmux-openbsd-cutover 2>/dev/null || true
if git remote get-url tmux-openbsd >/dev/null 2>&1; then
git remote set-url tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
else
git remote add tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
fi
git config remote.tmux-openbsd.tagOpt --no-tags
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
```
@@ -122,7 +156,22 @@ Merge it:
git merge --no-ff --log refs/remotes/tmux-openbsd/master
```
Resolve conflicts by deciding whether portable or OpenBSD owns the file.
If merging a local cutover branch instead, use the local-clone preparation
and fetch commands above in place of this fetch block.
When the merge reports conflicts, it leaves the merge in progress. List the
unresolved files, edit the conflict markers to combine the required portable
and OpenBSD changes, then stage each resolved file:
```sh
git diff --name-only --diff-filter=U
git diff -- path/to/file
git add path/to/file
```
For files that should come entirely from one side, decide whether portable or
OpenBSD owns the file before using the commands below. They replace the whole
file, including changes outside the conflicting hunks.
Useful commands:
@@ -140,11 +189,25 @@ git add path/to/file
This takes the OpenBSD/cutover version of a conflicted file.
For a modify/delete conflict, the side that deleted the file has no version
to check out. For example, portable generates `Makefile` using autotools and
does not track OpenBSD's `Makefile`. If Git reports that `Makefile` was deleted
in HEAD and modified in cutover, keep the portable deletion with:
```sh
git rm -- Makefile
```
This removes the OpenBSD file left by the merge; regenerate the portable
`Makefile` with your usual configure command before building. Use this only
for the unmerged OpenBSD file, not an existing generated build file.
Before committing, inspect the result:
```sh
git status
git diff --check
git diff --cached --check
git diff --cached --stat
```
+13 -7
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-capture-pane.c,v 1.68 2026/07/20 11:16:33 nicm Exp $ */
/* $OpenBSD: cmd-capture-pane.c,v 1.69 2026/09/08 07:31:59 nicm Exp $ */
/*
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
.name = "capture-pane",
.alias = "capturep",
.args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL },
.usage = "[-aCeFHJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
.args = { "ab:CeE:FHIJLMNpPqRS:Tt:", 0, 0, NULL },
.usage = "[-aCeFHIJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
"[-S start-line] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -200,7 +200,7 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
tmp[0] = line[i];
tmp[1] = '\0';
} else
xsnprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
snprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
buf = cmd_capture_pane_append(buf, len, tmp,
strlen(tmp));
}
@@ -259,7 +259,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
struct grid_cell *gc = NULL;
struct window_mode_entry *wme;
int n, join_lines, number_lines, flags = 0;
int show_flags, hyperlinks;
int show_flags, show_time, hyperlinks;
u_int *links = NULL, nlinks = 0;
u_int i, sx, top, bottom, tmp;
char *cause, *buf = NULL, *line, b[64], *cp;
@@ -342,6 +342,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
flags |= GRID_STRING_TRIM_SPACES;
number_lines = args_has(args, 'L');
show_flags = args_has(args, 'F');
show_time = args_has(args, 'I');
hyperlinks = args_has(args, 'H');
if (hyperlinks)
links = xreallocarray(NULL, gd->sx, sizeof *links);
@@ -358,6 +359,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
free(line);
continue;
}
gl = grid_peek_line(gd, i);
if (number_lines) {
if (i >= gd->hsize)
@@ -368,11 +370,16 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
if (n >= 0)
buf = cmd_capture_pane_append(buf, len, b, n);
}
if (show_time) {
n = snprintf(b, sizeof b, "%llu ",
(unsigned long long)grid_line_time(gl));
if (n >= 0)
buf = cmd_capture_pane_append(buf, len, b, n);
}
if (show_flags) {
cp = b;
*cp = '\0';
gl = grid_peek_line(gd, i);
if (gl->flags & GRID_LINE_DEAD)
*cp++ = 'D';
if (gl->flags & GRID_LINE_HYPERLINK)
@@ -393,7 +400,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
}
buf = cmd_capture_pane_append(buf, len, line, linelen);
gl = grid_peek_line(gd, i);
if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
buf[(*len)++] = '\n';
+2 -2
View File
@@ -30,8 +30,8 @@ const struct cmd_entry cmd_copy_mode_entry = {
.name = "copy-mode",
.alias = NULL,
.args = { "UcdekHMqSs:t:u", 0, 0, NULL },
.usage = "[-UcdekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
.args = { "dekHMqSs:t:u", 0, 0, NULL },
.usage = "[-dekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
.source = { 's', CMD_FIND_PANE, 0 },
.target = { 't', CMD_FIND_PANE, 0 },
+188 -317
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-display-menu.c,v 1.53 2026/08/31 07:46:55 nicm Exp $ */
/* $OpenBSD: cmd-display-menu.c,v 1.55 2026/09/21 12:14:32 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -54,8 +54,8 @@ const struct cmd_entry cmd_display_popup_entry = {
.name = "display-popup",
.alias = "popup",
.args = { "Bb:Cc:d:e:Eh:kNs:S:t:T:w:x:y:", 0, -1, NULL },
.usage = "[-BCEkN] [-b border-lines] [-c target-client] "
.args = { "Bb:Cc:d:e:Eh:ks:S:t:T:w:x:y:", 0, -1, NULL },
.usage = "[-BCEk] [-b border-lines] [-c target-client] "
"[-d start-directory] [-e environment] [-h height] "
"[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
" [-T title] [-w width] [-x position] [-y position] "
@@ -91,203 +91,6 @@ cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause)
return (type);
}
static int
cmd_display_menu_get_popup_pos(struct client *tc, struct cmdq_item *item,
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
{
struct tty *tty = &tc->tty;
struct cmd_find_state *target = cmdq_get_target(item);
struct key_event *event = cmdq_get_event(item);
struct session *s = tc->session;
struct winlink *wl = target->wl;
struct window_pane *wp = target->wp;
struct style_ranges *ranges = NULL;
struct style_range *sr = NULL;
const char *xp, *yp;
char *p;
int top;
u_int line, ox, oy, sx, sy, lines, position;
long n;
struct format_tree *ft;
/*
* Work out the position from the -x and -y arguments. This is the
* bottom-left position.
*/
/* If the popup is too big, stop now. */
if (w > tty->sx || h > tty->sy)
return (0);
/* Create format with mouse position if any. */
ft = format_create_from_target(item);
if (event->m.valid) {
format_add(ft, "popup_mouse_x", "%u", event->m.x);
format_add(ft, "popup_mouse_y", "%u", event->m.y);
}
/* Position of the previous menu, for -x/-y L. */
format_add(ft, "popup_last_x", "%u", target->w->menu_last_px);
format_add(ft, "popup_last_y", "%u", target->w->menu_last_py + h);
/*
* If there are any status lines, add this window position and the
* status line position.
*/
top = status_at_line(tc);
if (top != -1) {
lines = status_line_size(tc);
if (top == 0)
top = lines;
else
top = 0;
position = options_get_number(s->options, "status-position");
for (line = 0; line < lines; line++) {
ranges = &tc->status.entries[line].ranges;
TAILQ_FOREACH(sr, ranges, entry) {
if (sr->type != STYLE_RANGE_WINDOW)
continue;
if (sr->argument == (u_int)wl->idx)
break;
}
if (sr != NULL)
break;
}
if (sr != NULL) {
format_add(ft, "popup_window_status_line_x", "%u",
sr->start);
if (position == 0) {
format_add(ft, "popup_window_status_line_y",
"%u", line + 1 + h);
} else {
format_add(ft, "popup_window_status_line_y",
"%u", tty->sy - lines + line);
}
}
if (position == 0)
format_add(ft, "popup_status_line_y", "%u", lines + h);
else {
format_add(ft, "popup_status_line_y", "%u",
tty->sy - lines);
}
} else
top = 0;
/* Popup width and height. */
format_add(ft, "popup_width", "%u", w);
format_add(ft, "popup_height", "%u", h);
/* Position so popup is in the centre. */
n = (long)(tty->sx - 1) / 2 - w / 2;
if (n < 0)
format_add(ft, "popup_centre_x", "%u", 0);
else
format_add(ft, "popup_centre_x", "%ld", n);
n = (tty->sy - 1) / 2 + h / 2;
if (n >= tty->sy)
format_add(ft, "popup_centre_y", "%u", tty->sy - h);
else
format_add(ft, "popup_centre_y", "%ld", n);
/* Position of popup relative to mouse. */
if (event->m.valid) {
n = (long)event->m.x - w / 2;
if (n < 0)
format_add(ft, "popup_mouse_centre_x", "%u", 0);
else
format_add(ft, "popup_mouse_centre_x", "%ld", n);
n = event->m.y - h / 2;
if (n + h >= tty->sy) {
format_add(ft, "popup_mouse_centre_y", "%u",
tty->sy - h);
} else
format_add(ft, "popup_mouse_centre_y", "%ld", n);
n = (long)event->m.y + h;
if (n >= tty->sy)
format_add(ft, "popup_mouse_top", "%u", tty->sy - 1);
else
format_add(ft, "popup_mouse_top", "%ld", n);
n = event->m.y - h;
if (n < 0)
format_add(ft, "popup_mouse_bottom", "%u", 0);
else
format_add(ft, "popup_mouse_bottom", "%ld", n);
}
/* Position in pane. */
tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy);
n = top + wp->yoff - oy + h;
if (n >= tty->sy)
format_add(ft, "popup_pane_top", "%u", tty->sy - h);
else
format_add(ft, "popup_pane_top", "%ld", n);
format_add(ft, "popup_pane_bottom", "%u", top + wp->yoff + wp->sy - oy);
format_add(ft, "popup_pane_left", "%u", wp->xoff - ox);
n = (long)wp->xoff + wp->sx - ox - w;
if (n < 0)
format_add(ft, "popup_pane_right", "%u", 0);
else
format_add(ft, "popup_pane_right", "%ld", n);
/* Expand horizontal position. */
xp = args_get(args, 'x');
if (xp == NULL || strcmp(xp, "C") == 0)
xp = "#{popup_centre_x}";
else if (strcmp(xp, "R") == 0)
xp = "#{popup_pane_right}";
else if (strcmp(xp, "P") == 0)
xp = "#{popup_pane_left}";
else if (strcmp(xp, "M") == 0)
xp = "#{popup_mouse_centre_x}";
else if (strcmp(xp, "L") == 0)
xp = "#{popup_last_x}";
else if (strcmp(xp, "W") == 0)
xp = "#{popup_window_status_line_x}";
p = format_expand(ft, xp);
n = strtol(p, NULL, 10);
if (n + w >= tty->sx)
n = tty->sx - w;
else if (n < 0)
n = 0;
*px = n;
log_debug("%s: -x: %s = %s = %u (-w %u)", __func__, xp, p, *px, w);
free(p);
/* Expand vertical position */
yp = args_get(args, 'y');
if (yp == NULL || strcmp(yp, "C") == 0)
yp = "#{popup_centre_y}";
else if (strcmp(yp, "P") == 0)
yp = "#{popup_pane_bottom}";
else if (strcmp(yp, "M") == 0)
yp = "#{popup_mouse_top}";
else if (strcmp(yp, "L") == 0)
yp = "#{popup_last_y}";
else if (strcmp(yp, "S") == 0)
yp = "#{popup_status_line_y}";
else if (strcmp(yp, "W") == 0)
yp = "#{popup_window_status_line_y}";
p = format_expand(ft, yp);
n = strtol(p, NULL, 10);
if (n < h)
n = 0;
else
n -= h;
if (n + h >= tty->sy)
n = tty->sy - h;
else if (n < 0)
n = 0;
*py = n;
log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h);
free(p);
format_free(ft);
return (1);
}
static int
cmd_display_menu_get_menu_pos(struct client *tc, struct cmdq_item *item,
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
@@ -482,8 +285,8 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
enum box_lines lines = BOX_LINES_DEFAULT;
char *title, *cause = NULL;
int flags = 0, starting_choice = 0;
u_int px, py, i, count = args_count(args);
struct options *o = target->s->curw->window->options;
u_int px, py, sx, sy, i, count = args_count(args);
struct options *o = target->w->options;
struct options_entry *oe;
if (args_has(args, 'C')) {
@@ -531,9 +334,6 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
}
if (menu->count == 0)
goto out;
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py,
menu->width + 4, menu->count + 2))
goto out;
value = args_get(args, 'b');
if (value != NULL) {
@@ -544,7 +344,11 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
cmdq_error(item, "menu-border-lines %s", cause);
goto fail;
}
}
} else
lines = options_get_number(o, "menu-border-lines");
menu_get_size(menu, lines, &sx, &sy);
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py, sx, sy))
goto out;
if (args_has(args, 'O'))
flags |= MENU_STAYOPEN;
@@ -565,6 +369,22 @@ fail:
return (CMD_RETURN_ERROR);
}
static enum pane_lines
cmd_display_popup_get_lines(const char *value, char **cause)
{
const struct options_table_entry *oe;
if (value == NULL)
value = "single";
else if (strcmp(value, "rounded") == 0)
value = "single";
else if (strcmp(value, "padded") == 0)
value = "spaces";
oe = options_search("pane-border-lines");
return (options_find_choice(oe, value, cause));
}
static enum cmd_retval
cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
{
@@ -572,148 +392,199 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s;
struct client *tc = cmdq_get_target_client(item);
struct tty *tty = &tc->tty;
const char *value, *shell, *shellcmd = NULL;
const char *style = args_get(args, 's');
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct window_pane *wp = target->wp, *new_wp = NULL;
struct spawn_context sc = { 0 };
struct layout_cell *lc = NULL;
struct layout_geometry lg;
struct event_payload *ep;
struct cmd_find_state fs;
const char *value, *style = args_get(args, 's');
const char *border_style = args_get(args, 'S');
char *cwd = NULL, *cause = NULL, **argv = NULL;
char *title = NULL;
int modify = popup_present(tc);
int flags = -1, argc = 0;
enum box_lines lines = BOX_LINES_DEFAULT;
u_int px, py, w, h, count = args_count(args);
char *cause = NULL, *title = NULL;
enum pane_lines lines = PANE_LINES_SINGLE;
u_int px, py, sx, sy, count = args_count(args);
struct args_value *av;
struct environ *env = NULL;
struct options *o = s->curw->window->options;
struct options_entry *oe;
long long ll;
if (args_has(args, 'C')) {
server_client_clear_overlay(tc);
if (w->modal != NULL)
server_kill_pane(w->modal);
return (CMD_RETURN_NORMAL);
}
if (tc->flags & CLIENT_CONTROL)
return (CMD_RETURN_NORMAL);
if (!modify && tc->overlay_draw != NULL)
if (w->modal != NULL)
return (CMD_RETURN_NORMAL);
if (!modify) {
h = tty->sy / 2;
if (args_has(args, 'h')) {
h = args_percentage(args, 'h', 1, tty->sy, tty->sy,
&cause);
if (cause != NULL) {
cmdq_error(item, "height %s", cause);
goto fail;
}
}
w = tty->sx / 2;
if (args_has(args, 'w')) {
w = args_percentage(args, 'w', 1, tty->sx, tty->sx,
&cause);
if (cause != NULL) {
cmdq_error(item, "width %s", cause);
goto fail;
}
}
if (w > tty->sx)
w = tty->sx;
if (h > tty->sy)
h = tty->sy;
if (!cmd_display_menu_get_popup_pos(tc, item, args, &px, &py,
w, h))
goto out;
value = args_get(args, 'd');
if (value != NULL)
cwd = format_single_from_target(item, value);
else
cwd = xstrdup(server_client_get_cwd(tc, s));
if (count == 0) {
shellcmd = options_get_string(s->options,
"default-command");
} else if (count == 1)
shellcmd = args_string(args, 0);
if (count <= 1 && (shellcmd == NULL || *shellcmd == '\0')) {
shellcmd = NULL;
shell = options_get_string(s->options, "default-shell");
if (!checkshell(shell))
shell = _PATH_BSHELL;
cmd_append_argv(&argc, &argv, shell);
} else
args_to_vector(args, &argc, &argv);
if (args_has(args, 'e') >= 1) {
env = environ_create();
av = args_first_value(args, 'e');
while (av != NULL) {
environ_put(env, av->string, 0);
av = args_next_value(av);
}
}
}
value = args_get(args, 'b');
if (args_has(args, 'B'))
lines = BOX_LINES_NONE;
lines = PANE_LINES_NONE;
else if (value != NULL) {
oe = options_get(o, "popup-border-lines");
lines = options_find_choice(options_table_entry(oe), value,
&cause);
lines = cmd_display_popup_get_lines(value, &cause);
if (cause != NULL) {
cmdq_error(item, "popup-border-lines %s", cause);
cmdq_error(item, "pane-border-lines %s", cause);
goto fail;
}
}
sy = w->sy / 2;
if (args_has(args, 'h')) {
ll = args_percentage(args, 'h', 1, w->sy, w->sy, &cause);
if (cause != NULL) {
cmdq_error(item, "height %s", cause);
goto fail;
}
sy = ll;
}
sx = w->sx / 2;
if (args_has(args, 'w')) {
ll = args_percentage(args, 'w', 1, w->sx, w->sx, &cause);
if (cause != NULL) {
cmdq_error(item, "width %s", cause);
goto fail;
}
sx = ll;
}
if (sx > w->sx)
sx = w->sx;
if (sy > w->sy)
sy = w->sy;
if ((lines == PANE_LINES_NONE && (sx < 1 || sy < 1)) ||
(lines != PANE_LINES_NONE && (sx < 3 || sy < 3)))
goto out;
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py, sx, sy))
goto out;
lg.sx = sx;
lg.sy = sy;
lg.xoff = px;
lg.yoff = py;
if (lines != PANE_LINES_NONE) {
lg.sx -= 2;
lg.sy -= 2;
lg.xoff++;
lg.yoff++;
}
window_push_zoom(w, 0, 1);
lc = layout_floating_pane(w, wp, &lg);
if (lc == NULL) {
window_pop_zoom(w);
goto out;
}
sc.item = item;
sc.s = s;
sc.wl = wl;
sc.tc = tc;
sc.wp0 = wp;
sc.lc = lc;
sc.idx = -1;
sc.cwd = args_get(args, 'd');
sc.flags = (SPAWN_FLOATING|SPAWN_MODAL|SPAWN_FLOATOVERZOOM);
if (count != 1 || *args_string(args, 0) != '\0')
args_to_vector(args, &sc.argc, &sc.argv);
sc.environ = environ_create();
av = args_first_value(args, 'e');
while (av != NULL) {
environ_put(sc.environ, av->string, 0);
av = args_next_value(av);
}
new_wp = spawn_pane(&sc, &cause);
if (new_wp == NULL) {
cmdq_error(item, "create pane failed: %s", cause);
free(cause);
cause = NULL;
window_pop_zoom(w);
goto fail;
}
window_pop_zoom(w);
new_wp->flags |= PANE_CAPTUREALLKEYS;
if (!args_has(args, 'E'))
new_wp->flags |= PANE_CLOSEONCANCEL;
options_set_number(new_wp->options, "pane-border-lines", lines);
if (args_has(args, 'E') > 1) {
if (args_has(args, 'k'))
options_set_number(new_wp->options, "remain-on-exit", 4);
else
options_set_number(new_wp->options, "remain-on-exit", 2);
} else if (args_has(args, 'E'))
options_set_number(new_wp->options, "remain-on-exit", 0);
else if (args_has(args, 'k'))
options_set_number(new_wp->options, "remain-on-exit", 3);
else
options_set_number(new_wp->options, "remain-on-exit", 1);
options_set_string(new_wp->options, "remain-on-exit-format", 0, "%s", "");
if (style != NULL) {
if (options_set_string(new_wp->options, "window-style", 0,
"%s", style) == NULL) {
cmdq_error(item, "bad style: %s", style);
goto fail;
}
options_set_string(new_wp->options, "window-active-style", 0,
"%s", style);
new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|
PANE_THEMECHANGED);
}
if (border_style != NULL) {
if (options_set_string(new_wp->options, "pane-border-style", 0,
"%s", border_style) == NULL) {
cmdq_error(item, "bad border style: %s", border_style);
goto fail;
}
options_set_string(new_wp->options, "pane-active-border-style",
0, "%s", border_style);
}
if (args_has(args, 'T'))
title = format_single_from_target(item, args_get(args, 'T'));
else
title = xstrdup("");
if (args_has(args, 'N') || !modify)
flags = 0;
if (args_has(args, 'E') > 1) {
if (flags == -1)
flags = 0;
flags |= POPUP_CLOSEEXITZERO;
} else if (args_has(args, 'E')) {
if (flags == -1)
flags = 0;
flags |= POPUP_CLOSEEXIT;
}
if (args_has(args, 'k')) {
if (flags == -1)
flags = 0;
flags |= POPUP_CLOSEANYKEY;
if (title != NULL) {
options_set_number(new_wp->options, "pane-border-status",
PANE_STATUS_TOP);
options_set_string(new_wp->options, "pane-border-format", 0,
"%s", "#{pane_title}");
screen_set_title(&new_wp->base, title, 0);
ep = event_payload_create();
cmd_find_from_pane(&fs, new_wp, 0);
event_payload_set_target(ep, &fs);
event_payload_set_pane(ep, "pane", new_wp);
event_payload_set_window(ep, "window", new_wp->window);
event_payload_set_string(ep, "new_title", "%s", title);
events_fire("pane-title-changed", ep);
}
if (modify) {
popup_modify(tc, title, style, border_style, lines, flags);
goto out;
}
if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0)
goto out;
environ_free(env);
free(cwd);
new_wp->wait_item = item;
server_redraw_session(s);
if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
free(title);
cmd_free_argv(argc, argv);
return (CMD_RETURN_WAIT);
out:
cmd_free_argv(argc, argv);
environ_free(env);
free(cwd);
if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
free(title);
return (CMD_RETURN_NORMAL);
fail:
free(cause);
cmd_free_argv(argc, argv);
environ_free(env);
free(cwd);
if (new_wp != NULL) {
server_client_remove_pane(new_wp);
layout_close_pane(new_wp);
window_remove_pane(new_wp->window, new_wp);
}
if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
free(title);
return (CMD_RETURN_ERROR);
}
+23 -7
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-display-message.c,v 1.65 2026/02/23 08:46:57 nicm Exp $ */
/* $OpenBSD: cmd-display-message.c,v 1.66 2026/09/08 08:33:10 nicm Exp $ */
/*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = {
.name = "display-message",
.alias = "display",
.args = { "aCc:d:lINpt:F:v", 0, 1, NULL },
.usage = "[-aCIlNpv] [-c target-client] [-d delay] [-F format] "
.args = { "aCc:d:jlINpt:F:v", 0, 1, NULL },
.usage = "[-aCIjlNpv] [-c target-client] [-d delay] [-F format] "
CMD_TARGET_PANE_USAGE " [message]",
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
@@ -67,14 +67,15 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
struct winlink *wl = target->wl;
struct window_pane *wp = target->wp;
const char *template;
char *msg, *cause;
char *msg, *cause = NULL;
int delay = -1, flags, Nflag = args_has(args, 'N');
int Cflag = args_has(args, 'C');
struct format_tree *ft;
u_int count = args_count(args);
struct evbuffer *evb;
struct json_node *jn;
if (args_has(args, 'I')) {
if (args_has(args, 'I') && !args_has(args, 'j')) {
if (wp == NULL)
return (CMD_RETURN_NORMAL);
switch (window_pane_start_input(wp, item, &cause)) {
@@ -107,7 +108,9 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
template = args_string(args, 0);
else
template = args_get(args, 'F');
if (template == NULL)
if (args_has(args, 'j') && template == NULL)
template = "";
else if (template == NULL)
template = DISPLAY_MESSAGE_TEMPLATE;
/*
@@ -129,7 +132,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
format_defaults(ft, c, s, wl, wp);
if (args_has(args, 'a')) {
if (args_has(args, 'a') && !args_has(args, 'j')) {
format_each(ft, cmd_display_message_each, item);
format_free(ft);
return (CMD_RETURN_NORMAL);
@@ -139,6 +142,19 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
msg = xstrdup(template);
else
msg = format_expand_time(ft, template);
if (args_has(args, 'j')) {
jn = json_parse(msg, &cause);
if (jn == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
free(msg);
format_free(ft);
return (CMD_RETURN_ERROR);
}
free(msg);
msg = json_to_string(jn);
json_destroy_node(jn);
}
if (cmdq_get_client(item) == NULL)
cmdq_error(item, "%s", msg);
+11 -7
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-find.c,v 1.87 2026/07/17 12:42:51 nicm Exp $ */
/* $OpenBSD: cmd-find.c,v 1.88 2026/09/07 12:05:12 nicm Exp $ */
/*
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -388,9 +388,11 @@ cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window)
/* Try as an offset. */
if (!exact && (window[0] == '+' || window[0] == '-')) {
if (window[1] != '\0')
n = strtonum(window + 1, 1, INT_MAX, NULL);
else
if (window[1] != '\0') {
n = strtonum(window + 1, 1, INT_MAX, &errstr);
if (errstr != NULL)
return (-1);
} else
n = 1;
s = fs->s;
if (fs->flags & CMD_FIND_WINDOW_INDEX) {
@@ -626,9 +628,11 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane)
/* Try as an offset. */
if (pane[0] == '+' || pane[0] == '-') {
if (pane[1] != '\0')
n = strtonum(pane + 1, 1, INT_MAX, NULL);
else
if (pane[1] != '\0') {
n = strtonum(pane + 1, 1, INT_MAX, &errstr);
if (errstr != NULL)
return (-1);
} else
n = 1;
wp = fs->w->active;
if (pane[0] == '+')
+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>
@@ -133,7 +133,7 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
} else if (strcmp(position, "back") == 0) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp))
if (!window_pane_is_floating_with_hidden(owp))
break;
}
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);
} else if (strcmp(position, "forward") == 0) {
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) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_BEFORE(owp, wp, zentry);
}
} else if (strcmp(position, "backward") == 0) {
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)) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
}
} else if (strcmp(position, "forward-loop") == 0) {
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);
if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry);
else {
TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp))
if (!window_pane_is_floating_with_hidden(owp))
break;
}
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) {
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)) {
TAILQ_REMOVE(&w->z_index, 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;
TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp))
if (!window_pane_is_floating_with_hidden(owp))
break;
if (owp->layout_cell == NULL)
continue;
if (n >= z)
break;
n++;
@@ -443,7 +453,6 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
cmdq_error(item, "pane is not floating");
return (CMD_RETURN_ERROR);
}
server_unzoom_window(dst_w);
if ((s = args_get(args, 'P')) != NULL)
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
if ((s = args_get(args, 'z')) != NULL)
+3 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-list-panes.c,v 1.40 2026/06/01 14:01:09 nicm Exp $ */
/* $OpenBSD: cmd-list-panes.c,v 1.41 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -97,6 +97,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
struct cmdq_item *item, int type)
{
struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_client(item);
struct window_pane *wp, **l;
u_int i, n;
struct format_tree *ft;
@@ -147,7 +148,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
wp = l[i];
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", n);
format_defaults(ft, NULL, s, wl, wp);
format_defaults(ft, c, s, wl, wp);
if (filter != NULL) {
expanded = format_expand(ft, filter);
+3 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-list-sessions.c,v 1.36 2026/02/27 08:25:12 nicm Exp $ */
/* $OpenBSD: cmd-list-sessions.c,v 1.37 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -53,6 +53,7 @@ static enum cmd_retval
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_client(item);
struct session **l;
u_int n, i;
struct format_tree *ft;
@@ -76,7 +77,7 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
for (i = 0; i < n; i++) {
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", i);
format_defaults(ft, NULL, l[i], NULL, NULL);
format_defaults(ft, c, l[i], NULL, NULL);
if (filter != NULL) {
expanded = format_expand(ft, filter);
+3 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-list-windows.c,v 1.50 2026/02/27 08:25:12 nicm Exp $ */
/* $OpenBSD: cmd-list-windows.c,v 1.51 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -60,6 +60,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct client *c = cmdq_get_client(item);
struct winlink *wl, **l;
struct session *s;
u_int i, n;
@@ -94,7 +95,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
s = wl->session;
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", n);
format_defaults(ft, NULL, s, wl, NULL);
format_defaults(ft, c, s, wl, NULL);
if (filter != NULL) {
expanded = format_expand(ft, filter);
+2 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-parse.y,v 1.59 2026/08/31 07:51:56 nicm Exp $ */
/* $OpenBSD: cmd-parse.y,v 1.60 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -210,7 +210,7 @@ expanded : format
cmd_find_from_client(&fs, c, 0);
fsp = &fs;
}
ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
ft = format_create(c, pi->item, FORMAT_NONE, flags);
format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
$$ = format_expand(ft, $1);
+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>
@@ -580,6 +580,9 @@ cmdq_fire_command(struct cmdq_item *item)
int flags, quiet = 0;
char *tmp;
if (item->client != NULL && (item->client->flags & CLIENT_DEAD))
return (CMD_RETURN_ERROR);
if (cfg_finished)
cmdq_add_message(item);
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>
@@ -91,7 +91,8 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
server_redraw_window(w);
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 */
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>
@@ -164,7 +164,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
if (cdata->client != NULL)
cdata->client->references++;
if (args_has(args, 'c'))
cdata->cwd = xstrdup(args_get(args, 'c'));
cdata->cwd = format_single_from_target(item, args_get(args, 'c'));
else
cdata->cwd = xstrdup(server_client_get_cwd(c, s));
+9 -4
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-select-layout.c,v 1.43 2026/07/10 13:38:45 nicm Exp $ */
/* $OpenBSD: cmd-select-layout.c,v 1.45 2026/09/09 07:03:39 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -73,12 +73,13 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct client *c = cmdq_get_target_client(item);
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct window_pane *wp = target->wp;
const char *layoutname;
char *oldlayout, *cause;
int next, previous, layout;
char *oldlayout, *cause = NULL;
int next, previous, layout, flags = 0;
server_unzoom_window(w);
@@ -89,8 +90,12 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'p'))
previous = 1;
if (c != NULL &&
(c->flags & CLIENT_CONTROL) &&
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
oldlayout = w->old_layout;
w->old_layout = layout_dump(w, w->layout_root);
w->old_layout = layout_dump(w, w->layout_root, flags);
if (next || previous) {
if (next)
+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>
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
.name = "new-pane",
.alias = "newp",
.args = { "AbB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
.usage = "[-AbCdefhIkKLMOPvWZ] [-B border-lines] "
.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] "
"[-c start-directory] [-e environment] "
"[-F format] [-l size] [-m message] [-p percentage] "
"[-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;
if (args_has(args, 'C') && args_has(args, 'O'))
new_wp->flags |= PANE_CLOSEONCLICK;
if (args_has(args, 'D') && args_has(args, 'O'))
new_wp->flags |= PANE_CLOSEONCANCEL;
style = args_get(args, 's');
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>
@@ -480,6 +480,40 @@ cmd_wait_for_unlock(struct cmdq_item *item, const char *name,
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
cmd_wait_for_flush(void)
{
+1 -1
View File
@@ -1,6 +1,6 @@
# configure.ac
AC_INIT([tmux], next-3.8)
AC_INIT([tmux], next-3.9)
AC_PREREQ([2.60])
AC_CONFIG_AUX_DIR(etc)
+14 -7
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: control-notify.c,v 1.38 2026/08/03 13:38:42 nicm Exp $ */
/* $OpenBSD: control-notify.c,v 1.39 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -70,6 +70,7 @@ control_window_layout_changed_cb(__unused const char *name,
struct session *s;
struct winlink *wl;
struct window *w = event_payload_get_window(ep, "window");
struct format_tree *ft;
const char *template;
char *cp;
@@ -84,19 +85,25 @@ control_window_layout_changed_cb(__unused const char *name,
* and we don't need to inform the client about the layout change
* because the whole window will go away soon.
*/
wl = TAILQ_FIRST(&w->winlinks);
if (wl == NULL || w->layout_root == NULL)
if (TAILQ_FIRST(&w->winlinks) == NULL || w->layout_root == NULL)
return;
cp = format_single(NULL, template, NULL, NULL, wl, NULL);
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue;
s = c->session;
if (winlink_find_by_window_id(&s->windows, w->id) != NULL)
control_notify_write(c, "%s", cp);
wl = winlink_find_by_window_id(&s->windows, w->id);
if (wl == NULL)
continue;
ft = format_create(c, NULL, FORMAT_NONE, 0);
format_defaults(ft, c, s, wl, NULL);
cp = format_expand(ft, template);
format_free(ft);
control_notify_write(c, "%s", cp);
free(cp);
}
free(cp);
}
/* Notify control clients that window pane changed. */
+53 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: control.c,v 1.67 2026/09/03 21:35:38 nicm Exp $ */
/* $OpenBSD: control.c,v 1.68 2026/09/09 08:30:05 nicm Exp $ */
/*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -108,6 +108,7 @@ struct control_state {
u_int pending_count;
TAILQ_HEAD(, control_block) all_blocks;
size_t queued_reply_bytes;
struct bufferevent *read_event;
struct bufferevent *write_event;
@@ -132,6 +133,9 @@ struct control_state {
/* Maximum age for clients that are not using pause mode. */
#define CONTROL_MAXIMUM_AGE 300000
/* Maximum buffered command replies for a client that is not reading. */
#define CONTROL_MAXIMUM_REPLY_BUFFER (64 * 1024 * 1024)
/* Flags to ignore client. */
#define CONTROL_IGNORE_FLAGS \
(CLIENT_CONTROL_NOOUTPUT| \
@@ -165,6 +169,15 @@ RB_GENERATE_STATIC(control_windows, control_window, entry, control_window_cmp);
static void
control_free_block(struct control_state *cs, struct control_block *cb)
{
size_t size;
if (cb->size == 0 && cb->line != NULL) {
size = strlen(cb->line) + 1;
if (cs->queued_reply_bytes > size)
cs->queued_reply_bytes -= size;
else
cs->queued_reply_bytes = 0;
}
free(cb->line);
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
free(cb);
@@ -407,16 +420,52 @@ control_reset_pane(struct client *c, struct window_pane *wp)
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
}
/*
* Check if the replies buffered for a client, including one about to be
* added, have grown too large and kill it if so. Returns 1 if further output
* for the client should be dropped.
*/
static int
control_check_reply_buffer(struct client *c, size_t added)
{
struct control_state *cs = c->control_state;
size_t size;
if (c->flags & CLIENT_CONTROL_DISCARD)
return (1);
size = EVBUFFER_LENGTH(cs->write_event->output);
size += cs->queued_reply_bytes;
size += added;
if (size < CONTROL_MAXIMUM_REPLY_BUFFER)
return (0);
log_debug("%s: %s: %zu bytes of replies buffered", __func__, c->name,
size);
if (~c->flags & CLIENT_EXIT) {
c->exit_message = xstrdup("too far behind");
c->flags |= CLIENT_EXIT;
control_discard(c);
}
c->flags |= CLIENT_CONTROL_DISCARD;
return (1);
}
/* Write an already-formatted line, queueing it behind %output if needed. */
static void
control_write_line(struct client *c, char *line)
{
struct control_state *cs = c->control_state;
struct control_block *cb;
size_t size = strlen(line) + 1;
if (control_check_reply_buffer(c, size)) {
free(line);
return;
}
if (TAILQ_EMPTY(&cs->all_blocks)) {
log_debug("%s: %s: writing line: %s", __func__, c->name, line);
bufferevent_write(cs->write_event, line, strlen(line));
bufferevent_write(cs->write_event, line, size - 1);
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
free(line);
@@ -426,6 +475,7 @@ control_write_line(struct client *c, char *line)
cb = xcalloc(1, sizeof *cb);
cb->line = line;
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
cs->queued_reply_bytes += size;
cb->t = get_timer();
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
@@ -1007,6 +1057,7 @@ control_discard_all(struct client *c)
control_discard(c);
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
control_free_block(cs, cb);
cs->queued_reply_bytes = 0;
bufferevent_disable(cs->write_event, EV_WRITE);
}
+27 -10
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: format.c,v 1.415 2026/08/31 19:34:09 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>
@@ -855,26 +855,42 @@ format_cb_window_active_clients_list(struct format_tree *ft)
static void *
format_cb_window_layout(struct format_tree *ft)
{
struct window *w = ft->w;
struct client *c = ft->client;
struct window *w = ft->w;
struct layout_cell *lcroot;
int flags = 0;
if (w == NULL)
return (NULL);
if (w->saved_layout_root != NULL)
return (layout_dump(w, w->saved_layout_root));
return (layout_dump(w, w->layout_root));
lcroot = w->saved_layout_root;
else
lcroot = w->layout_root;
if (c != NULL &&
(c->flags & CLIENT_CONTROL) &&
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
return (layout_dump(w, lcroot, flags));
}
/* Callback for window_visible_layout. */
static void *
format_cb_window_visible_layout(struct format_tree *ft)
{
struct client *c = ft->client;
struct window *w = ft->w;
int flags = 0;
if (w == NULL)
return (NULL);
return (layout_dump(w, w->layout_root));
if (c != NULL &&
(c->flags & CLIENT_CONTROL) &&
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
return (layout_dump(w, w->layout_root, flags));
}
/* Callback for pane_start_command. */
@@ -2860,10 +2876,10 @@ format_cb_session_activity_flag(struct format_tree *ft)
if (ft->s != NULL) {
RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (ft->wl->flags & WINLINK_ACTIVITY)
if (wl->flags & WINLINK_ACTIVITY)
return (xstrdup("1"));
return (xstrdup("0"));
}
return (xstrdup("0"));
}
return (NULL);
}
@@ -2878,8 +2894,8 @@ format_cb_session_bell_flag(struct format_tree *ft)
RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (wl->flags & WINLINK_BELL)
return (xstrdup("1"));
return (xstrdup("0"));
}
return (xstrdup("0"));
}
return (NULL);
}
@@ -2892,10 +2908,10 @@ format_cb_session_silence_flag(struct format_tree *ft)
if (ft->s != NULL) {
RB_FOREACH(wl, winlinks, &ft->s->windows) {
if (ft->wl->flags & WINLINK_SILENCE)
if (wl->flags & WINLINK_SILENCE)
return (xstrdup("1"));
return (xstrdup("0"));
}
return (xstrdup("0"));
}
return (NULL);
}
@@ -5234,6 +5250,7 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
format_log(es, "session loop: $%u", s->id);
if (active != NULL &&
ft->c != NULL &&
ft->c->session != NULL &&
s->id == ft->c->session->id)
use = active;
else
+1 -1
View File
@@ -44,7 +44,7 @@ LLVMFuzzerTestOneInput(const u_char *data, size_t size)
w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0);
wp = window_add_pane(w, NULL, 0, 0);
bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty);
wp->ictx = input_init(wp, vpty[0], NULL, NULL);
wp->ictx = input_init(wp, vpty[0], NULL);
window_add_ref(w, __func__);
wp->fd = open("/dev/null", O_WRONLY);
-2
View File
@@ -1760,8 +1760,6 @@ grid_line_flags_string(int flags)
strlcat(s, "START_OUTPUT,", sizeof s);
if (flags & GRID_LINE_END_OUTPUT)
strlcat(s, "END_OUTPUT,", sizeof s);
if (flags & GRID_LINE_END_OUTPUT_STATUS)
strlcat(s, "END_OUTPUT_STATUS,", sizeof s);
if (flags & GRID_LINE_HYPERLINK)
strlcat(s, "HYPERLINK,", sizeof s);
if (*s == '\0')
+22 -68
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: input.c,v 1.271 2026/08/31 19:34:09 nicm Exp $ */
/* $OpenBSD: input.c,v 1.272 2026/09/21 10:22:31 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -101,7 +101,6 @@ struct input_ctx {
struct bufferevent *event;
struct screen_write_ctx ctx;
struct colour_palette *palette;
struct client *c;
struct input_cell cell;
struct input_cell old_cell;
@@ -874,7 +873,7 @@ input_restore_state(struct input_ctx *ictx)
/* Initialise input parser. */
struct input_ctx *
input_init(struct window_pane *wp, struct bufferevent *bev,
struct colour_palette *palette, struct client *c)
struct colour_palette *palette)
{
struct input_ctx *ictx;
@@ -882,7 +881,6 @@ input_init(struct window_pane *wp, struct bufferevent *bev,
ictx->wp = wp;
ictx->event = bev;
ictx->palette = palette;
ictx->c = c;
ictx->input_space = INPUT_BUF_START;
ictx->input_buf = xmalloc(INPUT_BUF_START);
@@ -3194,14 +3192,13 @@ input_osc_112(struct input_ctx *ictx, const char *p)
/* Parse the OSC 133 D exit status. */
static int
input_osc_133_exit_status(const char *p, int *present)
input_osc_133_exit_status(const char *p)
{
const char *end;
char *copy;
char *endptr;
const char *errstr;
long long status;
*present = 0;
if (p[1] != ';' || p[2] == '\0' || strchr(p + 2, '=') == p + 2)
return (0);
end = strchr(p + 2, ';');
@@ -3215,11 +3212,9 @@ input_osc_133_exit_status(const char *p, int *present)
free(copy);
return (0);
}
*present = 1;
errno = 0;
status = (int)strtol(copy, &endptr, 10);
status = strtonum(copy, 0, 255, &errstr);
free(copy);
if (errno != 0 || endptr == copy || status < 0 || status > 255)
if (errstr != NULL)
return (255);
return (status);
}
@@ -3264,22 +3259,6 @@ input_fire_command_event(struct window_pane *wp, const char *name)
events_fire(name, ep);
}
/* Check if an OSC 133 prompt is secondary or a continuation. */
static int
input_osc_133_secondary_prompt(const char *p)
{
const char *cp;
while ((cp = strstr(p, ";k=")) != NULL) {
cp += 3;
if ((*cp == 's' || *cp == 'c') &&
(cp[1] == '\0' || cp[1] == ';'))
return (1);
p = cp;
}
return (0);
}
/* Handle the OSC 133 sequence. */
static void
input_osc_133(struct input_ctx *ictx, const char *p)
@@ -3289,7 +3268,8 @@ input_osc_133(struct input_ctx *ictx, const char *p)
struct grid *gd = s->grid;
u_int line = s->cy + gd->hsize;
struct grid_line *gl = NULL;
int status, status_present;
const char *cp;
int status;
if (line < gd->hsize + gd->sy)
gl = grid_get_line(gd, line);
@@ -3298,17 +3278,9 @@ input_osc_133(struct input_ctx *ictx, const char *p)
case 'A':
case 'N':
if (gl != NULL) {
if (!(gl->flags & (GRID_LINE_START_PROMPT|
GRID_LINE_SECOND_PROMPT))) {
gl->osc133_data.prompt_col = s->cx;
if (input_osc_133_secondary_prompt(p))
gl->flags |= GRID_LINE_SECOND_PROMPT;
else
gl->flags |= GRID_LINE_START_PROMPT;
log_debug("%s: osc133 %s at %u,%u", __func__,
input_osc_133_secondary_prompt(p) ? "secondary prompt" :
"prompt", s->cx, line);
}
memset(&gl->osc133_data, 0, sizeof gl->osc133_data);
gl->osc133_data.prompt_col = s->cx;
gl->flags |= GRID_LINE_START_PROMPT;
}
if (wp != NULL) {
wp->last_prompt_time = time(NULL);
@@ -3317,33 +3289,25 @@ input_osc_133(struct input_ctx *ictx, const char *p)
break;
case 'P':
if (gl != NULL) {
if (!(gl->flags & (GRID_LINE_START_PROMPT|
GRID_LINE_SECOND_PROMPT))) {
gl->osc133_data.prompt_col = s->cx;
if (input_osc_133_secondary_prompt(p))
gl->flags |= GRID_LINE_SECOND_PROMPT;
else
gl->flags |= GRID_LINE_START_PROMPT;
log_debug("%s: osc133 prompt at %u,%u", __func__, s->cx,
line);
}
cp = strstr(p, ";k=s");
if (cp != NULL && (cp[4] == ';' || cp[4] == '\0'))
gl->flags |= GRID_LINE_SECOND_PROMPT;
else
gl->flags |= GRID_LINE_START_PROMPT;
gl->osc133_data.prompt_col = s->cx;
}
break;
case 'B':
case 'I':
if (gl != NULL && !(gl->flags & GRID_LINE_START_COMMAND)) {
if (gl != NULL) {
gl->flags |= GRID_LINE_START_COMMAND;
gl->osc133_data.cmd_col = s->cx;
log_debug("%s: osc133 command at %u,%u", __func__, s->cx,
line);
}
break;
case 'C':
if (gl != NULL && !(gl->flags & GRID_LINE_START_OUTPUT)) {
if (gl != NULL) {
gl->flags |= GRID_LINE_START_OUTPUT;
gl->osc133_data.out_start_col = s->cx;
log_debug("%s: osc133 output start at %u,%u", __func__, s->cx,
line);
}
if (wp != NULL) {
wp->cmd_start_time = time(NULL);
@@ -3354,7 +3318,7 @@ input_osc_133(struct input_ctx *ictx, const char *p)
}
break;
case 'D':
status = input_osc_133_exit_status(p, &status_present);
status = input_osc_133_exit_status(p);
if (wp != NULL) {
wp->cmd_end_time = time(NULL);
wp->flags &= ~PANE_CMDRUNNING;
@@ -3363,12 +3327,8 @@ input_osc_133(struct input_ctx *ictx, const char *p)
}
if (gl != NULL) {
gl->flags |= GRID_LINE_END_OUTPUT;
if (status_present)
gl->flags |= GRID_LINE_END_OUTPUT_STATUS;
gl->osc133_data.out_end_col = s->cx;
gl->osc133_data.exit_status = status;
log_debug("%s: osc133 output end at %u,%u (status %d)",
__func__, s->cx, line, status);
}
break;
}
@@ -3463,15 +3423,9 @@ input_osc_52(struct input_ctx *ictx, const char *p)
return;
if (wp == NULL) {
/* Popup window. */
if (ictx->c == NULL) {
free(out);
return;
}
tty_set_selection(&ictx->c->tty, clip, out, outlen);
paste_add(NULL, out, outlen);
free(out);
return;
} else {
/* Normal window. */
screen_write_start_pane(&ctx, wp, NULL);
screen_write_setselection(&ctx, clip, out, outlen);
screen_write_stop(&ctx);
+1001
View File
File diff suppressed because it is too large Load Diff
+5 -17
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>
@@ -52,9 +52,7 @@
" '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}' '<' {send -X history-top}" \
" '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}' '>' {send -X history-bottom}" \
" ''" \
" '#{?#{==:#{pane_mode},copy-mode},#{?copy_line_numbers,Line Numbers Off,Line Numbers On},}' 'L' {send -X line-numbers-toggle}" \
" '#{?#{==:#{pane_mode},copy-mode},#{?copy_fold_view,Fold View Off,Fold View On},}' 'O' {send -X fold-view-toggle}" \
" '#{?#{==:#{pane_mode},copy-mode},Edit,}' 'e' {if -F '#{selection_present}' {send -X open-selection} {send -X open-output}}" \
" '#{?#{==:#{pane_mode},copy-mode},#{?copy_line_numbers,Hide Line Numbers,Show Line Numbers},}' 'L' {send -X line-numbers-toggle}" \
" '#{?#{==:#{pane_mode},copy-mode},#{?refresh_active,Refresh Off,Refresh On},}' 'r' {send -X refresh-toggle}" \
" ''" \
" '#{?#{&&:#{buffer_size},#{!:#{pane_in_mode}}},Paste #[underscore]#{=/9/...:buffer_sample},}' 'p' {paste-buffer}" \
@@ -525,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 } }",
/* Mouse button 1 on border. */
"bind -n MouseDown1Border { select-pane -M }",
"bind -n MouseDown1Border { select-pane -t= }",
/* Mouse button 1 drag on border. */
"bind -n MouseDrag1Border { resize-pane -M }",
@@ -577,7 +575,6 @@ key_bindings_init(void)
"bind -Tcopy-mode C-l { send -X recentre-top-bottom }",
"bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }",
"bind -Tcopy-mode C-n { send -X cursor-down }",
"bind -Tcopy-mode C-o { send -X select-output }",
"bind -Tcopy-mode C-p { send -X cursor-up }",
"bind -Tcopy-mode C-r { command-prompt -P -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }",
"bind -Tcopy-mode C-s { command-prompt -P -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }",
@@ -586,19 +583,15 @@ key_bindings_init(void)
"bind -Tcopy-mode Escape { send -X cancel }",
"bind -Tcopy-mode C-[ { send -X cancel }",
"bind -Tcopy-mode Space { send -X page-down }",
"bind -Tcopy-mode Tab { send -X toggle-output }",
"bind -Tcopy-mode BTab { send -X toggle-output -a }",
"bind -Tcopy-mode , { send -X jump-reverse }",
"bind -Tcopy-mode \\; { send -X jump-again }",
"bind -Tcopy-mode F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
"bind -Tcopy-mode L { send -X line-numbers-toggle }",
"bind -Tcopy-mode N { send -X search-reverse }",
"bind -Tcopy-mode O { send -X fold-view-toggle }",
"bind -Tcopy-mode P { send -X toggle-position }",
"bind -Tcopy-mode R { send -X rectangle-toggle }",
"bind -Tcopy-mode T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
"bind -Tcopy-mode X { send -X set-mark }",
"bind -Tcopy-mode e { if -F '#{selection_present}' { send -X open-selection } { send -X open-output } }",
"bind -Tcopy-mode f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
"bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }",
"bind -Tcopy-mode n { send -X search-again }",
@@ -607,7 +600,7 @@ key_bindings_init(void)
"bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
"bind -Tcopy-mode Home { send -X start-of-line }",
"bind -Tcopy-mode End { send -X end-of-line }",
"bind -Tcopy-mode MouseDown1Pane { select-pane; send -X toggle-output -m }",
"bind -Tcopy-mode MouseDown1Pane select-pane",
"bind -Tcopy-mode MouseDrag1Pane { select-pane; send -X begin-selection }",
"bind -Tcopy-mode MouseDragEnd1Pane { send -X copy-pipe-and-cancel }",
"bind -Tcopy-mode WheelUpPane { select-pane; send -N5 -X scroll-up }",
@@ -667,8 +660,6 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi Escape { send -X clear-selection }",
"bind -Tcopy-mode-vi C-[ { send -X clear-selection }",
"bind -Tcopy-mode-vi Space { send -X begin-selection }",
"bind -Tcopy-mode-vi Tab { send -X toggle-output }",
"bind -Tcopy-mode-vi BTab { send -X toggle-output -a }",
"bind -Tcopy-mode-vi '$' { send -X end-of-line }",
"bind -Tcopy-mode-vi , { send -X jump-reverse }",
"bind -Tcopy-mode-vi / { command-prompt -P -T search -p'(search down)' { send -X search-forward -- '%%' } }",
@@ -696,10 +687,7 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi K { send -X scroll-up }",
"bind -Tcopy-mode-vi L { send -X bottom-line }",
"bind -Tcopy-mode-vi M { send -X middle-line }",
"bind -Tcopy-mode-vi M-e { if -F '#{selection_present}' { send -X open-selection } { send -X open-output } }",
"bind -Tcopy-mode-vi M-o { send -X select-output }",
"bind -Tcopy-mode-vi N { send -X search-reverse }",
"bind -Tcopy-mode-vi O { send -X fold-view-toggle }",
"bind -Tcopy-mode-vi P { send -X toggle-position }",
"bind -Tcopy-mode-vi T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
"bind -Tcopy-mode-vi V { send -X select-line }",
@@ -727,7 +715,7 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi % { send -X next-matching-bracket }",
"bind -Tcopy-mode-vi Home { send -X start-of-line }",
"bind -Tcopy-mode-vi End { send -X end-of-line }",
"bind -Tcopy-mode-vi MouseDown1Pane { select-pane; send -X toggle-output -m }",
"bind -Tcopy-mode-vi MouseDown1Pane { select-pane }",
"bind -Tcopy-mode-vi MouseDrag1Pane { select-pane; send -X begin-selection }",
"bind -Tcopy-mode-vi MouseDragEnd1Pane { send -X copy-pipe-and-cancel }",
"bind -Tcopy-mode-vi WheelUpPane { select-pane; send -N5 -X scroll-up }",
+969 -145
View File
File diff suppressed because it is too large Load Diff
+71 -33
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: layout.c,v 1.98 2026/08/25 18:38:05 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>
@@ -106,7 +106,7 @@ layout_free_cell(struct layout_cell *lc, int only_nodes)
}
break;
case LAYOUT_WINDOWPANE:
if (lc->wp != NULL) {
if (lc->wp != NULL && lc->wp->layout_cell != NULL) {
lc->wp->layout_cell->parent = NULL;
lc->wp->layout_cell = NULL;
}
@@ -233,29 +233,6 @@ layout_make_node(struct layout_cell *lc, enum layout_type type)
lc->wp = NULL;
}
/* Fix z-indexes. */
void
layout_fix_zindexes(struct window *w, struct layout_cell *lc)
{
struct layout_cell *lcchild;
if (lc == NULL)
return;
switch (lc->type) {
case LAYOUT_WINDOWPANE:
TAILQ_INSERT_TAIL(&w->z_index, lc->wp, zentry);
break;
case LAYOUT_LEFTRIGHT:
case LAYOUT_TOPBOTTOM:
TAILQ_FOREACH(lcchild, &lc->cells, entry)
layout_fix_zindexes(w, lcchild);
return;
default:
fatalx("bad layout type");
}
}
int
layout_cell_is_tiled(struct layout_cell *lc)
{
@@ -265,7 +242,7 @@ layout_cell_is_tiled(struct layout_cell *lc)
return is_leaf && !is_floating;
}
static int
int
layout_cell_has_tiled_child(struct layout_cell *lc)
{
struct layout_cell *lcchild;
@@ -509,18 +486,20 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
/* Count the number of available cells in a layout. */
u_int
layout_count_cells(struct layout_cell *lc)
layout_count_cells(struct layout_cell *lc, int with_floating)
{
struct layout_cell *lcchild;
u_int count = 0;
switch (lc->type) {
case LAYOUT_WINDOWPANE:
if (lc->flags & LAYOUT_CELL_FLOATING && !with_floating)
return 0;
return (1);
case LAYOUT_LEFTRIGHT:
case LAYOUT_TOPBOTTOM:
TAILQ_FOREACH(lcchild, &lc->cells, entry)
count += layout_count_cells(lcchild);
count += layout_count_cells(lcchild, with_floating);
return (count);
default:
fatalx("bad layout type");
@@ -721,7 +700,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
/* If no parent, this is the last pane in a window. */
lcparent = lc->parent;
if (lcparent == NULL) {
if (lc->wp != NULL)
if (*lcroot == lc)
*lcroot = NULL;
layout_free_cell(lc, 0);
return;
@@ -789,6 +768,49 @@ layout_free(struct window *w, int 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. */
void
layout_resize(struct window *w, u_int sx, u_int sy)
@@ -809,8 +831,11 @@ layout_resize(struct window *w, u_int sx, u_int sy)
* out proportionately - this should leave the layout fitting the new
* 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;
}
xchange = sx - lc->g.sx;
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
if (xchange < 0 && xchange < -xlimit)
@@ -840,6 +865,7 @@ layout_resize(struct window *w, u_int sx, u_int sy)
/* Fix cell offsets. */
layout_fix_offsets(w);
layout_clamp_floating_panes(w, sx, sy);
layout_fix_panes(w, NULL);
}
@@ -1702,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,
char **cause)
{
int sx, sy, ox, oy;
int sx, sy, ox, oy, pad;
char *error = NULL;
sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx;
@@ -1751,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 (w->last_new_pane_x == 0)
ox = 4;
else {
if (lines != PANE_LINES_NONE)
pad = 1;
else
pad = 0;
ox = w->last_new_pane_x + 4;
if (w->last_new_pane_x > w->sx)
if (ox + sx + pad > (int)w->sx)
ox = 4;
}
w->last_new_pane_x = ox;
@@ -1767,8 +1801,12 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
if (w->last_new_pane_y == 0)
oy = 2;
else {
if (lines != PANE_LINES_NONE)
pad = 1;
else
pad = 0;
oy = w->last_new_pane_y + 2;
if (w->last_new_pane_y > w->sy)
if (oy + sy + pad > (int)w->sy)
oy = 2;
}
w->last_new_pane_y = oy;
+38 -21
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: menu.c,v 1.70 2026/08/17 07:33:55 nicm Exp $ */
/* $OpenBSD: menu.c,v 1.71 2026/09/21 12:14:32 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -51,6 +51,18 @@ struct menu_data {
void *data;
};
void
menu_get_size(struct menu *menu, enum box_lines lines, u_int *sx, u_int *sy)
{
if (lines == BOX_LINES_NONE) {
*sx = menu->item_width + 2;
*sy = menu->count;
} else {
*sx = menu->width + 4;
*sy = menu->count + 2;
}
}
void
menu_add_items(struct menu *menu, const struct menu_item *items,
struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
@@ -142,6 +154,8 @@ menu_add_item(struct menu *menu, const struct menu_item *item,
width = format_width(new_item->name);
if (*new_item->name == '-')
width--;
if (width > menu->item_width)
menu->item_width = width;
if (width > menu->width)
menu->width = width;
}
@@ -238,7 +252,7 @@ menu_update(struct menu_data *md)
screen_write_clearscreen(&ctx, 8);
if (md->border_lines != BOX_LINES_NONE) {
screen_write_box(&ctx, menu->width + 4, menu->count + 2,
screen_write_box(&ctx, menu_width(md), menu_height(md),
md->border_lines, &md->border_style_gc, menu->title);
}
@@ -286,11 +300,13 @@ menu_destroy(struct window *w)
void
menu_get_cursor(struct menu_data *md, u_int *cx, u_int *cy)
{
*cx = md->px + 2;
u_int border = (md->border_lines != BOX_LINES_NONE);
*cx = md->px + 1 + border;
if (md->choice == -1)
*cy = md->py;
else
*cy = md->py + 1 + md->choice;
*cy = md->py + border + md->choice;
}
struct screen *
@@ -302,13 +318,19 @@ menu_screen(struct menu_data *md)
u_int
menu_width(struct menu_data *md)
{
return (md->menu->width + 4);
u_int sx, sy;
menu_get_size(md->menu, md->border_lines, &sx, &sy);
return (sx);
}
u_int
menu_height(struct menu_data *md)
{
return (md->menu->count + 2);
u_int sx, sy;
menu_get_size(md->menu, md->border_lines, &sx, &sy);
return (sy);
}
u_int
@@ -338,6 +360,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
enum cmd_parse_status status;
char *error;
key_code key;
u_int border;
if (KEYC_IS_MOUSE(event->key)) {
/*
@@ -353,10 +376,10 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
return (1);
return (0);
}
if (m->x < md->px ||
m->x > md->px + 4 + menu->width ||
m->y < md->py + 1 ||
m->y > md->py + 1 + n - 1) {
border = (md->border_lines != BOX_LINES_NONE);
if (m->x < md->px || m->x >= md->px + menu_width(md) ||
m->y < md->py + border ||
m->y >= md->py + border + n) {
if (~md->flags & MENU_STAYOPEN) {
if (!move && MOUSE_RELEASE(m->b))
return (1);
@@ -379,7 +402,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
goto chosen;
}
md->choice = m->y - (md->py + 1);
md->choice = m->y - (md->py + border);
if (md->choice != old)
server_redraw_window_menu(md->w);
return (0);
@@ -546,20 +569,16 @@ menu_resize(struct menu_data *md, struct window *w)
if (md == NULL)
return;
nx = md->px;
ny = md->py;
sx = md->menu->width + 4;
sy = md->menu->count + 2;
menu_get_size(md->menu, md->border_lines, &sx, &sy);
if (nx + sx > w->sx) {
if (w->sx <= sx)
nx = 0;
else
nx = w->sx - sx;
}
if (ny + sy > w->sy) {
if (w->sy <= sy)
ny = 0;
@@ -591,8 +610,9 @@ menu_display(struct menu *menu, int flags, int starting_choice,
w = fs->w;
o = w->options;
sx = menu->width + 4;
sy = menu->count + 2;
if (lines == BOX_LINES_DEFAULT)
lines = options_get_number(o, "menu-border-lines");
menu_get_size(menu, lines, &sx, &sy);
if (sx >= w->sx)
px = 0;
else if (px + sx > w->sx)
@@ -604,9 +624,6 @@ menu_display(struct menu *menu, int flags, int starting_choice,
w->menu_last_px = px;
w->menu_last_py = py;
if (lines == BOX_LINES_DEFAULT)
lines = options_get_number(o, "menu-border-lines");
md = xcalloc(1, sizeof *md);
md->w = w;
md->flags = flags;
+11 -6
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: mode-tree.c,v 1.101 2026/08/05 07:50:21 nicm Exp $ */
/* $OpenBSD: mode-tree.c,v 1.102 2026/09/21 12:14:32 nicm Exp $ */
/*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1375,12 +1375,14 @@ static void
mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
u_int y, int outside)
{
struct window *w = mtd->wp->window;
struct mode_tree_item *mti;
struct menu *menu;
const struct menu_item *items;
struct mode_tree_menu *mtm;
char *title;
u_int line;
enum box_lines lines;
u_int line, sx, sy;
if (mtd->offset + y > mtd->line_size - 1)
line = mtd->current;
@@ -1405,14 +1407,17 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
mtm->line = line;
mtd->references++;
if (x >= (menu->width + 4) / 2)
x -= (menu->width + 4) / 2;
lines = options_get_number(w->options, "menu-border-lines");
menu_get_size(menu, lines, &sx, &sy);
if (x >= sx / 2)
x -= sx / 2;
else
x = 0;
x += mtd->wp->xoff;
y += mtd->wp->yoff;
if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL,
NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0) {
if (menu_display(menu, 0, 0, NULL, x, y, c, lines, NULL, NULL, NULL,
NULL, mode_tree_menu_callback, mtm) != 0) {
mode_tree_remove_ref(mtd);
free(mtm);
menu_free(menu);
+4 -2
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: monitor.c,v 1.7 2026/07/27 19:15:58 nicm Exp $ */
/* $OpenBSD: monitor.c,v 1.8 2026/09/07 10:15:25 nicm Exp $ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -614,8 +614,10 @@ monitor_parse(const char *value, char **name, enum monitor_type *type, int *id,
*type = MONITOR_ALL_WINDOWS;
else if (sscanf(what, "@%d", id) == 1 && *id >= 0)
*type = MONITOR_WINDOW;
else
else if (*what == '\0')
*type = MONITOR_SESSION;
else
goto fail;
*name = xstrdup(copy);
*format = xstrdup(split);
+9 -40
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.247 2026/09/21 10:22:31 nicm Exp $ */
/*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -76,9 +76,10 @@ static const char *options_table_pane_border_indicators_list[] = {
"off", "colour", "arrows", "both", NULL
};
static const char *options_table_pane_border_lines_list[] = {
"single", "double", "heavy", "simple", "number", "spaces", "none", NULL
"single", "double", "heavy", "simple", "number", "spaces", "none",
"rounded", NULL
};
static const char *options_table_popup_border_lines_list[] = {
static const char *options_table_menu_border_lines_list[] = {
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
};
static const char *options_table_set_clipboard_list[] = {
@@ -91,7 +92,7 @@ static const char *options_table_window_size_list[] = {
"largest", "smallest", "manual", "latest", NULL
};
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[] = {
"off", "on", "keep-last", "keep-group", NULL
@@ -114,6 +115,7 @@ static const char *options_table_theme_list[] = {
static const char *options_table_copy_mode_line_numbers_list[] = {
"off", "default", "absolute", "relative", "hybrid", NULL
};
/* Status line format. */
#define OPTIONS_TABLE_STATUS_FORMAT1 \
"#[align=left range=left #{E:status-left-style}]" \
@@ -491,7 +493,7 @@ const struct options_table_entry options_table[] = {
{ .name = "menu-border-lines",
.type = OPTIONS_TABLE_CHOICE,
.scope = OPTIONS_TABLE_WINDOW,
.choices = options_table_popup_border_lines_list,
.choices = options_table_menu_border_lines_list,
.default_num = BOX_LINES_SINGLE,
.text = "Type of characters used to draw menu border lines. Some of "
"these are only supported on terminals with UTF-8 support."
@@ -1383,13 +1385,6 @@ const struct options_table_entry options_table[] = {
.text = "Style of search matches in copy mode."
},
{ .name = "copy-mode-exit-status-format",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_str = "#{?exit_status,#[fg=themered]!, }",
.text = "Format of OSC 133 exit status indicator in copy mode."
},
{ .name = "copy-mode-current-match-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW,
@@ -1671,40 +1666,14 @@ const struct options_table_entry options_table[] = {
.text = "Pane scrollbar position."
},
{ .name = "popup-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW,
.default_str = "bg=themedarkgrey,fg=themewhite",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Default style of popups."
},
{ .name = "popup-border-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW,
.default_str = "bg=themedarkgrey,fg=themelightgrey",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Default style of popup borders."
},
{ .name = "popup-border-lines",
.type = OPTIONS_TABLE_CHOICE,
.scope = OPTIONS_TABLE_WINDOW,
.choices = options_table_popup_border_lines_list,
.default_num = BOX_LINES_SINGLE,
.text = "Type of characters used to draw popup border lines. Some of "
"these are only supported on terminals with UTF-8 support."
},
{ .name = "remain-on-exit",
.type = OPTIONS_TABLE_CHOICE,
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.choices = options_table_remain_on_exit_list,
.default_num = 0,
.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."
},
-661
View File
@@ -1,661 +0,0 @@
/* $OpenBSD: popup.c,v 1.77 2026/08/28 08:02:16 nicm Exp $ */
/*
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "tmux.h"
struct popup_data {
struct client *c;
struct cmdq_item *item;
int flags;
char *title;
char *style;
char *border_style;
struct grid_cell border_cell;
enum box_lines border_lines;
struct screen s;
struct grid_cell defaults;
struct colour_palette palette;
struct visible_ranges r;
struct job *job;
struct input_ctx *ictx;
int status;
popup_close_cb cb;
void *arg;
int close;
/* Current position and size. */
u_int px;
u_int py;
u_int sx;
u_int sy;
/* Preferred position and size. */
u_int ppx;
u_int ppy;
u_int psx;
u_int psy;
enum { OFF, MOVE, SIZE } dragging;
u_int dx;
u_int dy;
u_int lx;
u_int ly;
u_int lb;
};
static void
popup_free(struct popup_data *pd)
{
server_client_unref(pd->c);
if (pd->job != NULL)
job_free(pd->job);
if (pd->ictx != NULL)
input_free(pd->ictx);
free(pd->r.ranges);
screen_free(&pd->s);
colour_palette_free(&pd->palette);
free(pd->title);
free(pd->style);
free(pd->border_style);
free(pd);
}
static void
popup_reapply_styles(struct popup_data *pd)
{
struct client *c = pd->c;
struct session *s = c->session;
struct options *o;
struct format_tree *ft;
struct style sytmp;
if (s == NULL)
return;
o = s->curw->window->options;
ft = format_create_defaults(NULL, c, s, s->curw, NULL);
/* Reapply popup style from options. */
memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
style_apply(&pd->defaults, o, "popup-style", ft);
if (pd->style != NULL) {
style_set(&sytmp, &grid_default_cell);
if (style_parse(&sytmp, &pd->defaults, pd->style) == 0) {
pd->defaults.fg = sytmp.gc.fg;
pd->defaults.bg = sytmp.gc.bg;
}
}
pd->defaults.attr = 0;
/* Reapply border style from options. */
memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
style_apply(&pd->border_cell, o, "popup-border-style", ft);
if (pd->border_style != NULL) {
style_set(&sytmp, &grid_default_cell);
if (style_parse(&sytmp, &pd->border_cell,
pd->border_style) == 0) {
pd->border_cell.fg = sytmp.gc.fg;
pd->border_cell.bg = sytmp.gc.bg;
}
}
pd->border_cell.attr = 0;
format_free(ft);
}
static void
popup_redraw_cb(const struct tty_ctx *ttyctx)
{
struct popup_data *pd = ttyctx->arg;
pd->c->flags |= CLIENT_REDRAWOVERLAY;
}
static int
popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
{
struct popup_data *pd = ttyctx->arg;
if (c != pd->c)
return (0);
if (pd->c->flags & CLIENT_REDRAWOVERLAY)
return (0);
ttyctx->wox = 0;
ttyctx->woy = 0;
ttyctx->wsx = c->tty.sx;
ttyctx->wsy = c->tty.sy;
if (pd->border_lines == BOX_LINES_NONE) {
ttyctx->xoff = ttyctx->rxoff = pd->px;
ttyctx->yoff = ttyctx->ryoff = pd->py;
} else {
ttyctx->xoff = ttyctx->rxoff = pd->px + 1;
ttyctx->yoff = ttyctx->ryoff = pd->py + 1;
}
return (1);
}
static void
popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
{
struct popup_data *pd = ctx->arg;
memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults);
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
ttyctx->style_ctx.defaults = &ttyctx->defaults;
ttyctx->style_ctx.palette = &pd->palette;
ttyctx->redraw_cb = popup_redraw_cb;
ttyctx->set_client_cb = popup_set_client_cb;
ttyctx->arg = pd;
}
static struct screen *
popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
{
struct popup_data *pd = data;
if (pd->border_lines == BOX_LINES_NONE) {
*cx = pd->px + pd->s.cx;
*cy = pd->py + pd->s.cy;
} else {
*cx = pd->px + 1 + pd->s.cx;
*cy = pd->py + 1 + pd->s.cy;
}
return (&pd->s);
}
/* Return parts of the input range which are not obstructed by the popup. */
static struct visible_ranges *
popup_check_cb(__unused struct client* c, void *data, u_int px, u_int py,
u_int nx)
{
struct popup_data *pd = data;
struct visible_ranges *r = &pd->r;
server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx,
r);
return (r);
}
static void
popup_draw_cb(struct client *c, void *data)
{
struct popup_data *pd = data;
struct tty *tty = &c->tty;
struct screen s;
struct screen_write_ctx ctx;
u_int i, px = pd->px, py = pd->py;
struct grid_cell defaults;
struct tty_style_ctx style_ctx;
popup_reapply_styles(pd);
screen_init(&s, pd->sx, pd->sy, 0);
if (pd->s.hyperlinks != NULL) {
hyperlinks_free(s.hyperlinks);
s.hyperlinks = hyperlinks_copy(pd->s.hyperlinks);
}
screen_write_start(&ctx, &s);
screen_write_clearscreen(&ctx, 8);
if (pd->border_lines == BOX_LINES_NONE) {
screen_write_cursormove(&ctx, 0, 0, 0);
screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx, pd->sy);
} else if (pd->sx > 2 && pd->sy > 2) {
screen_write_box(&ctx, pd->sx, pd->sy, pd->border_lines,
&pd->border_cell, pd->title);
screen_write_cursormove(&ctx, 1, 1, 0);
screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2,
pd->sy - 2);
}
screen_write_stop(&ctx);
memcpy(&defaults, &pd->defaults, sizeof defaults);
if (defaults.fg == 8)
defaults.fg = pd->palette.fg;
if (defaults.bg == 8)
defaults.bg = pd->palette.bg;
style_ctx.defaults = &defaults;
style_ctx.palette = &pd->palette;
style_ctx.dim = 0;
style_ctx.hyperlinks = s.hyperlinks;
c->overlay_check = NULL;
c->overlay_data = NULL;
for (i = 0; i < pd->sy; i++)
tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &style_ctx);
screen_free(&s);
c->overlay_check = popup_check_cb;
c->overlay_data = pd;
}
static void
popup_free_cb(__unused struct client *c, void *data)
{
struct popup_data *pd = data;
struct cmdq_item *item = pd->item;
if (pd->cb != NULL)
pd->cb(pd->status, pd->arg);
if (item != NULL) {
if (cmdq_get_client(item) != NULL &&
cmdq_get_client(item)->session == NULL)
cmdq_get_client(item)->retval = pd->status;
cmdq_continue(item);
}
popup_free(pd);
}
static void
popup_resize_cb(__unused struct client *c, void *data)
{
struct popup_data *pd = data;
struct tty *tty = &c->tty;
if (pd == NULL)
return;
/* Adjust position and size. */
if (pd->psy > tty->sy)
pd->sy = tty->sy;
else
pd->sy = pd->psy;
if (pd->psx > tty->sx)
pd->sx = tty->sx;
else
pd->sx = pd->psx;
if (pd->ppy + pd->sy > tty->sy)
pd->py = tty->sy - pd->sy;
else
pd->py = pd->ppy;
if (pd->ppx + pd->sx > tty->sx)
pd->px = tty->sx - pd->sx;
else
pd->px = pd->ppx;
/* Avoid zero size screens. */
if (pd->border_lines == BOX_LINES_NONE) {
screen_resize(&pd->s, pd->sx, pd->sy, 0);
if (pd->job != NULL)
job_resize(pd->job, pd->sx, pd->sy );
} else if (pd->sx > 2 && pd->sy > 2) {
screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
if (pd->job != NULL)
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
}
}
static void
popup_handle_drag(struct client *c, struct popup_data *pd,
struct mouse_event *m)
{
u_int px, py;
if (!MOUSE_DRAG(m->b))
pd->dragging = OFF;
else if (pd->dragging == MOVE) {
if (m->x < pd->dx)
px = 0;
else if (m->x - pd->dx + pd->sx > c->tty.sx)
px = c->tty.sx - pd->sx;
else
px = m->x - pd->dx;
if (m->y < pd->dy)
py = 0;
else if (m->y - pd->dy + pd->sy > c->tty.sy)
py = c->tty.sy - pd->sy;
else
py = m->y - pd->dy;
pd->px = px;
pd->py = py;
pd->dx = m->x - pd->px;
pd->dy = m->y - pd->py;
pd->ppx = px;
pd->ppy = py;
server_redraw_client(c);
} else if (pd->dragging == SIZE) {
if (pd->border_lines == BOX_LINES_NONE) {
if (m->x < pd->px + 1)
return;
if (m->y < pd->py + 1)
return;
} else {
if (m->x < pd->px + 3)
return;
if (m->y < pd->py + 3)
return;
}
pd->sx = m->x - pd->px;
pd->sy = m->y - pd->py;
pd->psx = pd->sx;
pd->psy = pd->sy;
if (pd->border_lines == BOX_LINES_NONE) {
screen_resize(&pd->s, pd->sx, pd->sy, 0);
if (pd->job != NULL)
job_resize(pd->job, pd->sx, pd->sy);
} else {
screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
if (pd->job != NULL)
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
}
server_redraw_client(c);
}
}
static int
popup_key_cb(struct client *c, void *data, struct key_event *event)
{
struct popup_data *pd = data;
struct mouse_event *m = &event->m;
const char *buf;
size_t len;
u_int px, py;
enum { NONE, LEFT, RIGHT, TOP, BOTTOM } border = NONE;
if (KEYC_IS_MOUSE(event->key)) {
if (pd->dragging != OFF) {
popup_handle_drag(c, pd, m);
goto out;
}
if (m->x < pd->px ||
m->x > pd->px + pd->sx - 1 ||
m->y < pd->py ||
m->y > pd->py + pd->sy - 1) {
return (0);
}
if (pd->border_lines != BOX_LINES_NONE) {
if (m->x == pd->px)
border = LEFT;
else if (m->x == pd->px + pd->sx - 1)
border = RIGHT;
else if (m->y == pd->py)
border = TOP;
else if (m->y == pd->py + pd->sy - 1)
border = BOTTOM;
}
if ((m->b & MOUSE_MASK_MODIFIERS) == 0 &&
MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3 &&
(border == LEFT || border == TOP))
goto out;
if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) ||
(border != NONE && !MOUSE_DRAG(m->lb))) {
if (!MOUSE_DRAG(m->b))
goto out;
if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_1)
pd->dragging = MOVE;
else if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_3)
pd->dragging = SIZE;
pd->dx = m->lx - pd->px;
pd->dy = m->ly - pd->py;
goto out;
}
}
if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) ||
pd->job == NULL) &&
(event->key == '\033' || event->key == ('c'|KEYC_CTRL)))
return (1);
if (pd->job == NULL && (pd->flags & POPUP_CLOSEANYKEY) &&
!KEYC_IS_MOUSE(event->key) && !KEYC_IS_PASTE(event->key))
return (1);
if (pd->job != NULL) {
if (KEYC_IS_MOUSE(event->key)) {
/* Must be inside, checked already. */
if (pd->border_lines == BOX_LINES_NONE) {
px = m->x - pd->px;
py = m->y - pd->py;
} else {
px = m->x - pd->px - 1;
py = m->y - pd->py - 1;
}
if (!input_key_get_mouse(&pd->s, m, px, py, &buf, &len))
return (0);
bufferevent_write(job_get_event(pd->job), buf, len);
return (0);
}
input_key(&pd->s, job_get_event(pd->job), event->key);
}
return (0);
out:
pd->lx = m->x;
pd->ly = m->y;
pd->lb = m->b;
return (0);
}
static void
popup_job_update_cb(struct job *job)
{
struct popup_data *pd = job_get_data(job);
struct evbuffer *evb = job_get_event(job)->input;
struct client *c = pd->c;
struct screen *s = &pd->s;
void *data = EVBUFFER_DATA(evb);
size_t size = EVBUFFER_LENGTH(evb);
if (size == 0)
return;
c->overlay_check = NULL;
c->overlay_data = NULL;
input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size);
c->overlay_check = popup_check_cb;
c->overlay_data = pd;
evbuffer_drain(evb, size);
}
static void
popup_job_complete_cb(struct job *job)
{
struct popup_data *pd = job_get_data(job);
int status;
status = job_get_status(pd->job);
if (WIFEXITED(status))
pd->status = WEXITSTATUS(status);
else if (WIFSIGNALED(status))
pd->status = WTERMSIG(status);
else
pd->status = 0;
pd->job = NULL;
if ((pd->flags & POPUP_CLOSEEXIT) ||
((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
server_client_clear_overlay(pd->c);
}
int
popup_present(struct client *c)
{
return (c->overlay_draw == popup_draw_cb);
}
int
popup_modify(struct client *c, const char *title, const char *style,
const char *border_style, enum box_lines lines, int flags)
{
struct popup_data *pd = c->overlay_data;
struct style sytmp;
if (title != NULL) {
if (pd->title != NULL)
free(pd->title);
pd->title = xstrdup(title);
}
if (border_style != NULL) {
free(pd->border_style);
pd->border_style = xstrdup(border_style);
style_set(&sytmp, &pd->border_cell);
if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
pd->border_cell.fg = sytmp.gc.fg;
pd->border_cell.bg = sytmp.gc.bg;
}
}
if (style != NULL) {
free(pd->style);
pd->style = xstrdup(style);
style_set(&sytmp, &pd->defaults);
if (style_parse(&sytmp, &pd->defaults, style) == 0) {
pd->defaults.fg = sytmp.gc.fg;
pd->defaults.bg = sytmp.gc.bg;
}
}
if (lines != BOX_LINES_DEFAULT) {
if (lines == BOX_LINES_NONE && pd->border_lines != lines) {
screen_resize(&pd->s, pd->sx, pd->sy, 1);
job_resize(pd->job, pd->sx, pd->sy);
} else if (pd->border_lines == BOX_LINES_NONE &&
pd->border_lines != lines) {
screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 1);
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
}
pd->border_lines = lines;
tty_resize(&c->tty);
}
if (flags != -1)
pd->flags = flags;
server_redraw_client(c);
return (0);
}
int
popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd,
int argc, char **argv, const char *cwd, const char *title, struct client *c,
struct session *s, const char *style, const char *border_style,
popup_close_cb cb, void *arg)
{
struct popup_data *pd;
u_int jx, jy;
struct options *o;
struct style sytmp;
if (s != NULL)
o = s->curw->window->options;
else
o = c->session->curw->window->options;
if (lines == BOX_LINES_DEFAULT)
lines = options_get_number(o, "popup-border-lines");
if (lines == BOX_LINES_NONE) {
if (sx < 1 || sy < 1)
return (-1);
jx = sx;
jy = sy;
} else {
if (sx < 3 || sy < 3)
return (-1);
jx = sx - 2;
jy = sy - 2;
}
if (c->tty.sx < sx || c->tty.sy < sy)
return (-1);
pd = xcalloc(1, sizeof *pd);
pd->item = item;
pd->flags = flags;
if (title != NULL)
pd->title = xstrdup(title);
if (style != NULL)
pd->style = xstrdup(style);
if (border_style != NULL)
pd->border_style = xstrdup(border_style);
pd->c = c;
pd->c->references++;
pd->cb = cb;
pd->arg = arg;
pd->status = 128 + SIGHUP;
pd->border_lines = lines;
memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
style_apply(&pd->border_cell, o, "popup-border-style", NULL);
if (border_style != NULL) {
style_set(&sytmp, &grid_default_cell);
if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
pd->border_cell.fg = sytmp.gc.fg;
pd->border_cell.bg = sytmp.gc.bg;
}
}
pd->border_cell.attr = 0;
screen_init(&pd->s, jx, jy, 0);
screen_set_default_cursor(&pd->s, global_w_options);
colour_palette_init(&pd->palette);
colour_palette_from_option(&pd->palette, global_w_options);
memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
style_apply(&pd->defaults, o, "popup-style", NULL);
if (style != NULL) {
style_set(&sytmp, &grid_default_cell);
if (style_parse(&sytmp, &pd->defaults, style) == 0) {
pd->defaults.fg = sytmp.gc.fg;
pd->defaults.bg = sytmp.gc.bg;
}
}
pd->defaults.attr = 0;
pd->px = px;
pd->py = py;
pd->sx = sx;
pd->sy = sy;
pd->ppx = px;
pd->ppy = py;
pd->psx = sx;
pd->psy = sy;
pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
popup_job_update_cb, popup_job_complete_cb, NULL, pd,
JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
if (pd->job == NULL) {
popup_free(pd);
return (-1);
}
pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette, c);
server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
return (0);
}
+5 -3
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: prompt.c,v 1.6 2026/08/17 06:45:16 nicm Exp $ */
/* $OpenBSD: prompt.c,v 1.7 2026/09/09 07:53:03 nicm Exp $ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1493,9 +1493,11 @@ append_key:
utf8_set(&tmp, key);
if (key <= 0x1f || key == 0x7f)
tmp.width = 2;
} else if (KEYC_IS_UNICODE(key))
} else if (KEYC_IS_UNICODE(key)) {
utf8_to_data(key, &tmp);
else
if (tmp.size == 0)
return (PROMPT_KEY_HANDLED);
} else
return (PROMPT_KEY_HANDLED);
pr->buffer = xreallocarray(pr->buffer, size + 2,
+23 -20
View File
@@ -1,33 +1,17 @@
TESTS!= echo *.sh
LOGDIR=logs
.PHONY: all
.NOTPARALLEL: all
.PHONY: all prepare $(TESTS)
all:
@mkdir -p "$(LOGDIR)"; \
rm -f "$(LOGDIR)"/*.log; \
failed=0; failures=; \
all: prepare $(TESTS)
@failed=0; failures=; \
for test in $(TESTS); do \
base=$${test##*/}; \
log="$(LOGDIR)/$${base%.sh}.log"; \
rm -f "$$log"; \
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"; \
if [ -f "$$log" ]; then \
failed=1; \
failures="$$failures $$test"; \
fi; \
sleep 1; \
done; \
if [ "$$failed" -ne 0 ]; then \
echo; \
@@ -39,3 +23,22 @@ all:
rmdir "$(LOGDIR)" 2>/dev/null || true; \
fi; \
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
# alert-activity, alert-bell and alert-silence hooks and the winlink alert
# flags (window_activity_flag, window_bell_flag, window_silence_flag and
# the #, !, ~ characters in window_flags). The sessions are detached so
# flags (window_activity_flag, window_bell_flag, window_silence_flag,
# 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
# 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
@@ -111,6 +112,24 @@ flags_have()
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()
{
target=$1
@@ -150,10 +169,12 @@ $TMUX set-hook -g alert-silence \
# 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"
assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0
assert_session_flag bell 0 bellw
flags_lack mon:bellw '!'
bell mon:bellw
wait_for @log '|alert-bell:mon:bellw'
wait_for_fmt mon:bellw '#{window_bell_flag}' 1
assert_session_flag bell 1 bellw
flags_have mon:bellw '!'
# 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.
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
wait_for_fmt mon:bellw '#{window_bell_flag}' 0
assert_session_flag bell 0 bellw
flags_lack mon:bellw '!'
$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
wait_for @log '|alert-bell:mon:w0'
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:w0 || fail "select-window w0 failed"
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"
activity mon:actw
assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0
assert_session_flag activity 0 actw
assert_unchanged @log ''
# 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
wait_for @log '|alert-activity:mon:actw'
wait_for_fmt mon:actw '#{window_activity_flag}' 1
assert_session_flag activity 1 actw
flags_have mon:actw '#'
# 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
$TMUX selectw -t mon:actw || fail "select-window actw failed"
wait_for_fmt mon:actw '#{window_activity_flag}' 0
assert_session_flag activity 0 actw
flags_lack mon:actw '#'
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
exec 3>&-
@@ -250,6 +276,7 @@ $TMUX set -wt mon:w0 monitor-activity on ||
$TMUX set -g @log '' || fail "reset @log failed"
activity mon:w0
wait_for_fmt mon:w0 '#{window_activity_flag}' 1
assert_session_flag activity 1 actw
assert_unchanged @log ''
$TMUX set -wut mon:w0 monitor-activity ||
fail "unset monitor-activity w0 failed"
@@ -273,16 +300,19 @@ $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
# running.
$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed"
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 -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed"
wait_for @log '|alert-silence:mon:silw'
wait_for_fmt mon:silw '#{window_silence_flag}' 1
assert_session_flag silence 1 silw
flags_have mon:silw '~'
assert_unchanged @log '|alert-silence:mon:silw'
$TMUX set -wt mon:silw monitor-silence 0 ||
fail "reset monitor-silence failed"
$TMUX selectw -t mon:silw || fail "select-window silw failed"
wait_for_fmt mon:silw '#{window_silence_flag}' 0
assert_session_flag silence 0 silw
flags_lack mon:silw '~'
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
# capture-pane -I line timestamps
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMP=$(mktemp)
trap '$TMUX kill-server 2>/dev/null; rm -f "$TMP"' 0 1 15
$TMUX kill-server 2>/dev/null
before=$(date +%s)
$TMUX new-session -d -x 40 -y 5 'seq 1 12; sleep 10' || exit 1
sleep 1
after=$(date +%s)
$TMUX capture-pane -pILF -S - -E - >"$TMP" || exit 1
awk -v before="$before" -v after="$after" '
$1 !~ /^-?[0-9]+$/ || $2 !~ /^[0-9]+$/ { exit 1 }
$4 == "1" {
if ($2 < before || $2 > after)
exit 1
history = 1
}
$4 == "9" {
if ($2 != 0)
exit 1
visible = 1
}
END { if (!history || !visible) exit 1 }
' "$TMP" || {
cat "$TMP"
exit 1
}
exit 0
+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
-81
View File
@@ -1,81 +0,0 @@
#!/bin/sh
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 new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo hi\\n\\033]133;C\\007hello\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007\"; exec sleep 100'" || exit 1
sleep 1
$TMUX copy-mode -c || exit 1
$TMUX send-keys -X search-backward hello || exit 1
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
[ "$hidden" != hello ] || exit 1
$TMUX send-keys -X search-backward separator || exit 1
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
[ "$hidden" != separator ] || exit 1
$TMUX send-keys BTab || exit 1
$TMUX send-keys -X search-backward hello || exit 1
shown=$($TMUX display-message -p '#{copy_cursor_line}')
[ "$shown" = hello ] || exit 1
$TMUX send-keys -X select-line || exit 1
$TMUX send-keys -X copy-selection || exit 1
selected=$($TMUX show-buffer)
[ "$selected" = hello ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX kill-server
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"first\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007\"; exec sleep 100'" || exit 1
sleep 1
$TMUX copy-mode -c || exit 1
$TMUX send-keys -X search-backward separator || exit 1
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
[ "$hidden" != separator ] || exit 1
$TMUX send-keys BTab || exit 1
$TMUX send-keys -X search-backward separator || exit 1
shown=$($TMUX display-message -p '#{copy_cursor_line}')
[ "$shown" = separator ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX kill-server
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007P> \\033]133;B\\007for i in 1 2 3; do\\n\\033]133;A;k=s\\007> \\033]133;B\\007echo \\$i\\n\\033]133;P;k=s\\007> \\033]133;B\\007done\\033]133;C\\007\\nRESULT\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007P> \\033]133;B\\007\"; exec sleep 100'" || exit 1
sleep 1
$TMUX copy-mode -c || exit 1
$TMUX send-keys -X search-backward RESULT || exit 1
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
[ "$hidden" != RESULT ] || exit 1
$TMUX send-keys -X search-backward 'echo $i' || exit 1
shown=$($TMUX display-message -p '#{copy_cursor_line}')
[ "$shown" = 'echo $i' ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX copy-mode -U || exit 1
$TMUX send-keys -X search-backward RESULT || exit 1
$TMUX send-keys Tab || exit 1
cursor=$($TMUX display-message -p '#{copy_cursor_line}')
[ "$cursor" = done ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX kill-server
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007P0> \\033]133;B\\007\\r\\n\\033]133;D;0\\007empty-separator\\n\\033]133;A\\007P1> \\033]133;B\\007\"; exec sleep 100'" || exit 1
sleep 1
$TMUX copy-mode -c || exit 1
collapsed=$($TMUX capture-pane -p)
case "$collapsed" in
*"+ P0>"*) ;;
*) exit 1 ;;
esac
$TMUX send-keys -X search-backward empty-separator || exit 1
hidden=$($TMUX display-message -p '#{copy_cursor_line}')
[ "$hidden" != empty-separator ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX kill-server 2>/dev/null
exit 0
+1 -1
View File
@@ -17,7 +17,7 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest"
TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null
DIR=$(mktemp -d)
+1 -1
View File
@@ -19,7 +19,7 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest"
TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null
DIR=$(mktemp -d)
+123
View File
@@ -0,0 +1,123 @@
#!/bin/sh
# Check that control client flags are available in every format expansion path
# used to produce layouts. The layout strings themselves are tested separately.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
LANG=C.UTF-8
export PATH TERM LC_ALL LANG
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
DIR=$(mktemp -d) || exit 1
FIFO=$DIR/input
OUT=$DIR/output
CFG=$DIR/flags.conf
PID=
fail()
{
echo "$*" >&2
exit 1
}
cleanup()
{
exec 3>&-
[ -n "$PID" ] && kill "$PID" 2>/dev/null
$TMUX kill-server 2>/dev/null
rm -rf "$DIR"
}
trap cleanup EXIT
wait_for()
{
pattern=$1
i=0
while [ "$i" -lt 50 ]; do
grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0
if [ -n "$PID" ] && ! kill -0 "$PID" 2>/dev/null; then
fail "control client exited waiting for: $pattern"
fi
sleep 0.1
i=$((i + 1))
done
fail "missing: $pattern"
}
send()
{
printf '%s\n' "$*" >&3
}
check_commands()
{
name=$1
expected=$2
match='#{m:*new-layouts*,#{client_flags}}'
send "display-message -p 'DISPLAY-$name $match'"
wait_for "DISPLAY-$name $expected"
send "list-panes -F 'PANES-$name $match'"
wait_for "PANES-$name $expected"
send "list-windows -F 'WINDOWS-$name $match'"
wait_for "WINDOWS-$name $expected"
send "list-sessions -F 'SESSIONS-$name $match'"
wait_for "SESSIONS-$name $expected"
send "list-clients -F 'CLIENTS-$name $match'"
wait_for "CLIENTS-$name $expected"
}
check_config()
{
name=$1
expected=$2
send "source-file '$CFG'"
send "display-message -p 'CONFIG-$name #{@config-new-layouts}'"
wait_for "CONFIG-$name $expected"
}
cat >"$CFG" <<'EOF'
%if #{m:*new-layouts*,#{client_flags}}
set-option -g @config-new-layouts 1
%else
set-option -g @config-new-layouts 0
%endif
EOF
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -s layouts -x 80 -y 24 || exit 1
$TMUX split-window -h -t layouts: || exit 1
mkfifo "$FIFO" || exit 1
: >"$OUT"
$TMUX -C attach-session -t layouts <"$FIFO" >"$OUT" 2>&1 &
PID=$!
exec 3>"$FIFO"
send 'display-message -p READY'
wait_for READY
check_commands OFF 0
check_config OFF 0
send 'refresh-client -f new-layouts'
check_commands ON 1
check_config ON 1
send 'refresh-client -f !new-layouts'
check_commands OFF-AGAIN 0
check_config OFF-AGAIN 0
# The list commands also run for unattached command clients. Supplying that
# client to formats must not make the session loop dereference a NULL session.
$TMUX list-sessions -F '#{S:all,active}' >/dev/null ||
fail "session loop failed for unattached command client"
$TMUX has-session -t layouts || fail "server exited"
exit 0
+2 -1
View File
@@ -1,6 +1,7 @@
#!/bin/sh
# Popups require a tty overlay and cannot be displayed by a control client.
# Popups require an attached terminal and cannot be displayed by a control
# client.
# A popup command from control mode must be ignored cleanly, leaving the
# client command queue and server usable.
+1 -1
View File
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest"
TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null
OUT=$(mktemp)
+5 -2
View File
@@ -30,8 +30,11 @@ killw
EOF
sleep 1
$TMUX has || exit 1
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1
cat <<EOF|cmp -s $TMP - || exit 1
# Use a control client to request legacy layouts, keeping only pane lines
# 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]
%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]
+1 -1
View File
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest"
TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null
FIFO=$(mktemp -u)
-102
View File
@@ -1,102 +0,0 @@
#!/bin/sh
# Check OSC 133 exit status indicators in copy mode using a real client.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
fail() {
echo "$*" >&2
exit 1
}
capture() {
$TMUX capture-pane -pS0 -E- >$TMP || exit 1
}
check_grep() {
grep -Fq "$1" $TMP || fail "missing pattern: $1"
}
check_no_grep() {
grep -Fq "$1" $TMP && fail "unexpected pattern: $1"
}
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
TMP=$(mktemp)
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" 0 1 15
$TMUX2 new-session -d -x80 -y10 \
"printf '\033]133;A\007p\$ \033]133;B\007one\n\033]133;C\007out1\n\033]133;D;0\007\033]133;A\007p\$ \033]133;B\007two\n\033]133;C\007out2\n\033]133;D;123\007\033]133;A\007p\$ \033]133;B\007silent\n\033]133;C\007\033]133;D;7\007\033]133;A\007p\$ \033]133;B\007three\n\033]133;C\007out3\n\033]133;D\007\033]133;A\007p\$ \033]133;B\007'; exec sleep 100" || \
exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g copy-mode-position-format '#[align=left]POS' || exit 1
$TMUX2 list-keys -Tcopy-mode O | grep -Fq 'fold-view-toggle' || exit 1
$TMUX2 list-keys -Tcopy-mode-vi O | grep -Fq 'fold-view-toggle' || exit 1
$TMUX new-session -d -x80 -y10 || exit 1
$TMUX set -g status off || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
$TMUX2 copy-mode -U || exit 1
$TMUX2 send -X history-top || exit 1
sleep 1
capture
check_grep "!- p\$ two"
check_grep "! p\$ silent"
check_no_grep "!+ p\$ silent"
check_grep "POS"
# Rebuilding with collapsed output must not expand copy-mode formats before
# the viewport has been restored for the shorter backing grid.
$TMUX2 send -X collapse-output -a || exit 1
sleep 1
capture
check_grep "!+ p\$ two"
check_grep "! p\$ silent"
check_no_grep "!+ p\$ silent"
$TMUX2 send -X line-numbers-on || exit 1
sleep 1
capture
check_grep " 3 !+ p\$ two"
$TMUX2 send -X fold-view-toggle || exit 1
[ "$($TMUX2 display -p '#{copy_fold_view}')" = 0 ] || exit 1
sleep 1
capture
check_grep "out2"
check_no_grep "!+ p\$ two"
check_grep " 3 p\$ two"
$TMUX2 send -X fold-view-toggle || exit 1
[ "$($TMUX2 display -p '#{copy_fold_view}')" = 1 ] || exit 1
sleep 1
capture
check_grep " 3 !- p\$ two"
# A format wider than the standard three-column gutter is not truncated.
$TMUX2 send -X cancel || exit 1
$TMUX2 set -g copy-mode-exit-status-format \
'#[align=right]#{?exit_status,!#{exit_status}, }' || exit 1
$TMUX2 copy-mode -c || exit 1
$TMUX2 send -X history-top || exit 1
sleep 1
capture
check_grep "!123+ p\$ two"
$TMUX2 send -X select-line || exit 1
$TMUX2 send -X copy-selection || exit 1
selection=$($TMUX2 show-buffer)
case $selection in
*RC=*) fail "exit status was copied" ;;
esac
exit 0
+63
View File
@@ -0,0 +1,63 @@
#!/bin/sh
# Trimming a blank source must leave a visible line for cursor reflow.
PATH=/bin:/usr/bin
TERM=screen
export PATH TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
fail()
{
echo "$*" >&2
exit 1
}
cleanup()
{
$TMUX kill-server 2>/dev/null
}
trap cleanup 0
trap 'exit 1' 1 2 3 15
$TMUX new-session -d -x80 -y24 -s test 'sleep 100' || exit 1
$TMUX set-option -g window-size manual || exit 1
$TMUX new-window -t test:1 'sleep 100' || exit 1
$TMUX resize-window -t test:0 -x20 -y24 || exit 1
# Exercise widening, narrowing, and equal widths with no source history.
for pair in '0 1' '1 0' '1 2'; do
set -- $pair
if [ "$2" = 2 ]; then
$TMUX new-window -t test:2 'sleep 100' || exit 1
$TMUX resize-window -t test:2 -x80 -y24 || exit 1
fi
source=test:$1.0
target=test:$2.0
[ "$($TMUX display-message -p -t "$source" '#{history_size}')" = 0 ] ||
fail "source has unexpected history"
$TMUX copy-mode -s "$source" -t "$target" || exit 1
$TMUX has-session -t test || fail "server died copying blank source"
[ "$($TMUX display-message -p -t "$target" \
'#{pane_in_mode} #{copy_cursor_x} #{copy_cursor_y}')" = '1 0 0' ] ||
fail "blank source did not enter copy mode at the first cell"
$TMUX send-keys -t "$target" -X cancel || exit 1
done
# Real content still trims trailing empty lines, clamping the cursor to beta.
$TMUX respawn-pane -k -t test:0.0 \
"printf 'alpha\r\nbeta\r\n\r\n\r\n'; exec sleep 100" || exit 1
i=0
while [ "$($TMUX display-message -p -t test:0.0 '#{cursor_y}')" != 4 ]; do
i=$((i + 1))
[ "$i" -lt 10 ] || fail "source output did not arrive"
sleep 1
done
$TMUX copy-mode -s test:0.0 -t test:1.0 || exit 1
[ "$($TMUX display-message -p -t test:1.0 \
'#{pane_in_mode} #{copy_cursor_y} #{copy_cursor_line}')" = '1 1 beta' ] ||
fail "nonblank source content or trailing-line trimming changed"
exit 0
+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 "$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
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)
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()
+20 -4
View File
@@ -18,6 +18,24 @@ export TZ LANG LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
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')
@@ -579,8 +597,6 @@ assert_alive "verbose loop expansion"
# L loops over attached clients. Attach two control-mode clients, each held
# 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"
mkfifo "$FIFO1" "$FIFO2" || exit 1
# 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"
# Now detach one and confirm the count drops to one.
kill $HOLD2 2>/dev/null
wait $HOLD2 2>/dev/null
HOLD2=
sleep 1
test_format "#{L:x}" "x"
kill $HOLD1 $CC1 $CC2 2>/dev/null
rm -f "$FIFO1" "$FIFO2"
exit 0
+2
View File
@@ -7,6 +7,8 @@ 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
# test_format $format $expected_result
test_format()
+6 -6
View File
@@ -76,14 +76,14 @@ $TMUX set-hook -g session-closed \
fail "set-hook session-closed failed"
# 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.
pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') ||
fail "new-session doomed failed"
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 \
"|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
# server survives.
@@ -110,14 +110,14 @@ $TMUX set-hook -g pane-exited \
'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' ||
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.
$TMUX set -g @log '' || fail "reset @log 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 kill-window -t doomed2:0 || fail "kill-window failed"
wait_for @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2'
assert_unchanged @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2'
wait_for @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
assert_unchanged @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
$TMUX has -t main || fail "server died after kill-window chain"
# 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)
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
if 'true' 'wibble wobble'
+5 -2
View File
@@ -4,6 +4,9 @@ TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
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)
EXP=$(mktemp)
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
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 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
sleep 0.3
}
+13 -1
View File
@@ -22,7 +22,19 @@ assert_key () {
clear-history -t$W \; \
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 | \
sed -e 's/EOL.*$//')
+34 -7
View File
@@ -2,6 +2,38 @@
. ./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 \
"perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2"
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"
check_capture csi-interm-discard 'OK'
start_cmd osc-discard 8 3 \
"perl -e 'print qq{\e]2;}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
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'
test_discard osc-discard '\e]2;'
test_discard apc-discard '\e_'
start_pane unknown-csi 8 3 '\033[?9999zOK'
check_capture unknown-csi 'OK'
+5 -11
View File
@@ -5,12 +5,9 @@
start_pane alternate 10 3 'MAIN\033[?1049hALT\033[?1049lZ\n'
check_capture alternate 'MAINZ'
start_pane osc133 20 12 'xx\033]133;A\007p>\033]133;B\007cmd\nxy\033]133;P;k=s\007more\nxz\033]133;A;k=s\007more\nxw\033]133;P;k=c\007more\nxv\033]133;A;k=c\007more\nzz\033]133;C\007out\033]133;D;7\007\nq\033]133;C\007bad\033]133;D;-1\007\nqq\033]133;C\007big\033]133;D;300\007\nzzz\033]133;C\007ok\033]133;D\007\n'
start_pane osc133 20 8 'xx\033]133;A\007p>\033]133;B\007cmd\nxy\033]133;P;k=s\007more\nzz\033]133;C\007out\033]133;D;7\007\nq\033]133;C\007bad\033]133;D;-1\007\nqq\033]133;C\007big\033]133;D;300\007\nzzz\033]133;C\007ok\033]133;D\007\n'
check_capture osc133 'xxp>cmd
xymore
xzmore
xwmore
xvmore
zzout
qbad
qqbig
@@ -18,13 +15,10 @@ zzzok'
check_raw_matches osc133 \
'L 0 \(0\) flags=START_PROMPT,START_COMMAND\[[0-9a-f]+\].* osc133=2,4,0,0,0' \
'L 1 \(1\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
'L 2 \(2\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
'L 3 \(3\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
'L 4 \(4\) flags=SECOND_PROMPT\[[0-9a-f]+\].* osc133=2,0,0,0,0' \
'L 5 \(5\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,2,5,7' \
'L 6 \(6\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,1,4,255' \
'L 7 \(7\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,2,5,255' \
'L 8 \(8\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,3,5,0'
'L 2 \(2\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,2,5,7' \
'L 3 \(3\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,1,4,255' \
'L 4 \(4\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,2,5,255' \
'L 5 \(5\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,3,5,0'
$TMUX kill-server 2>/dev/null
exit $exit_status
+1 -1
View File
@@ -51,7 +51,7 @@ $TMUX kill-server 2>/dev/null
sleep 0.1
$TMUX new-session -d -x 5 -y 3 -s history \; \
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
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
printf "%s\n" '01
+137
View File
@@ -0,0 +1,137 @@
#!/bin/sh
# Test parsing and printing JSON with display-message -j.
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
fail()
{
echo "$1"
$TMUX kill-server 2>/dev/null
exit 1
}
check()
{
actual=$($TMUX display-message -plj "$1" 2>&1) ||
fail "JSON was rejected: $1: $actual"
[ "$actual" = "$2" ] ||
fail "JSON output mismatch: $1: expected $2, got $actual"
}
check_fail()
{
if $TMUX display-message -plj "$1" >/dev/null 2>&1; then
fail "invalid JSON was accepted: $1"
fi
}
$TMUX new-session -d || exit 1
check '{}' '{}'
check ' { "z":true, "a":-9223372036854775808, "m":[{}, {"x":false}] } ' \
'{"a":-9223372036854775808,"m":[{},{"x":false}],"z":true}'
check '{"max":9223372036854775807,"min":-9223372036854775808}' \
'{"max":9223372036854775807,"min":-9223372036854775808}'
check '{" key":" value","{key":"[value"}' \
'{" key":" value","{key":"[value"}'
check '{"brace":"{value","bracket":"[value","colon":":value",'\
'"comma":",value","space":" value"}' \
'{"brace":"{value","bracket":"[value","colon":":value",'\
'"comma":",value","space":" value"}'
check '{"esc":"\"\\\/\b\f\n\r\t\u0041"}' \
'{"esc":"\"\\\/\b\f\n\r\t\u0041"}'
check '{"unicode":"\u0123\uabcd\uABCD"}' \
'{"unicode":"\u0123\uabcd\uABCD"}'
actual=$($TMUX display-message -palj '{}' 2>&1) ||
fail "display-message -aj rejected JSON: $actual"
[ "$actual" = '{}' ] ||
fail "display-message -aj did not ignore -a: got $actual"
actual=$($TMUX display-message -pIlj '{}' 2>&1) ||
fail "display-message -Ij rejected JSON: $actual"
[ "$actual" = '{}' ] ||
fail "display-message -Ij did not ignore -I: got $actual"
check_fail ''
check_fail ' '
check_fail '[]'
check_fail 'true'
check_fail '1'
check_fail '"string"'
check_fail '{}{}'
check_fail '{'
check_fail '{x:1}'
check_fail '{"":1}'
check_fail '{"x" 1}'
check_fail '{"x",1}'
check_fail '{"x":}'
check_fail '{"x":,}'
check_fail '{"x":1'
check_fail '{"x":1 "y":2}'
check_fail '{"x":1,,"y":2}'
check_fail '{"x":1,}'
check_fail '{"x":1,"x":2}'
check_fail '{"x":{}}{}'
check_fail '{"x":{}} trailing'
check_fail '{"x":{'
check_fail '{"x":{}'
check_fail '{"x":['
check_fail '{"x":[}'
check_fail '{"x":[{}'
check_fail '{"x":[{},}'
check_fail '{"x":[{},]}'
check_fail '{"x":[{}{}]}'
check_fail '{"x":[{},,{}]}'
check_fail '{"x":[{"y":}]}'
check_fail '{"x":""}'
check_fail '{"x":null}'
check_fail '{"x":[null]}'
check_fail '{"x":[true]}'
check_fail '{"x":["string"]}'
check_fail '{"x":[[]]}'
check_fail '{"x":1.0}'
check_fail '{"x":1e2}'
check_fail '{"x":+1}'
check_fail '{"x":-}'
check_fail '{"x":0x10}'
check_fail '{"x":01}'
check_fail '{"x":-01}'
check_fail '{"x":[1]}'
check_fail '{"x":9223372036854775808}'
check_fail '{"x":-9223372036854775809}'
check_fail '{"x":tru}'
check_fail '{"x":True}'
check_fail '{"x":falsee}'
check_fail '{"x":"bad\q"}'
check_fail '{"x":"bad\x20"}'
check_fail '{"x":"bad\u123"}'
check_fail '{"x":"bad\u12x4"}'
check_fail '{"x":"unterminated}'
check_fail '{"x":"escaped quote\"}'
check_fail '{"x":"line
break"}'
# The maximum object nesting depth is 200.
json='{}'
n=1
while [ "$n" -lt 200 ]; do
json='{"x":'"$json"'}'
n=$((n + 1))
done
check "$json" "$json"
check_fail '{"x":'"$json"'}'
if $TMUX display-message -pj >/dev/null 2>&1; then
fail "display-message -j accepted a missing message"
fi
$TMUX kill-server 2>/dev/null
exit 0
+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
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
#!/bin/sh
# Version 1 layouts must reject excessive nesting without losing the session or
# changing its layout. Check both split types and the depth limit boundary.
PATH=/bin:/usr/bin
TERM=screen
export PATH TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
DIR=$(mktemp -d) || exit 1
TMUX="$TEST_TMUX -S$DIR/socket -f/dev/null"
fail()
{
echo "$*" >&2
exit 1
}
cleanup()
{
$TMUX kill-server 2>/dev/null
rm -rf "$DIR"
}
trap cleanup 0
trap 'exit 1' 1 2 15
# Generate a checksum-valid v1 layout with one child in every nested group.
layout()
{
awk -v depth="$1" -v open="$2" 'BEGIN {
closing = (open == "{" ? "}" : "]")
body = ""
for (i = 0; i < depth; i++)
body = body "1x1,0,0" open
body = body "1x1,0,0"
for (i = 0; i < depth; i++)
body = body closing
ord["0"] = 48; ord["1"] = 49; ord["x"] = 120
ord[","] = 44; ord["{"] = 123; ord["}"] = 125
ord["["] = 91; ord["]"] = 93
csum = 0
for (i = 1; i <= length(body); i++) {
c = substr(body, i, 1)
bit = csum % 2
csum = int(csum / 2) + bit * 32768
csum = (csum + ord[c]) % 65536
}
printf "%04x,%s", csum, body
}'
}
$TMUX new-session -d -s deep -x80 -y24 'exec sleep 100' || exit 1
for open in '{' '['; do
# A valid checksum and nesting up to the limit must still be accepted.
value=$(layout 1000 "$open") || fail "could not generate layout"
$TMUX select-layout -t deep "$value" ||
fail "layout at the depth limit was rejected ($open)"
before=$($TMUX display-message -p -t deep \
'#{pane_id} #{pane_width} #{pane_height} #{window_layout}') || exit 1
for depth in 1001 1500; do
value=$(layout "$depth" "$open") || fail "could not generate layout"
$TMUX select-layout -t deep "$value" >/dev/null 2>&1 &&
fail "excessive nesting was accepted ($open, $depth)"
$TMUX has-session -t deep ||
fail "server died on excessive nesting ($open, $depth)"
after=$($TMUX display-message -p -t deep \
'#{pane_id} #{pane_width} #{pane_height} #{window_layout}') || exit 1
[ "$before" = "$after" ] ||
fail "pane or layout changed after rejection ($open, $depth)"
done
done
exit 0
+144 -2
View File
@@ -337,9 +337,9 @@ $TMUX set -g @modal-prefix no
$TMUX set -g @modal-root no
$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') ||
fail "new-pane -OK failed"
fail "new-pane -ODK failed"
sleep 1
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
sleep 1
@@ -356,9 +356,151 @@ new_left=$(fmt "$modal" '#{pane_left}')
new_top=$(fmt "$modal" '#{pane_top}')
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
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"
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}')" ''
$TMUX bind P display-popup -E -t "$p0" -w 20 -h 5 -T popup-title 'cat'
$TMUX2 send-keys -t "$OUTER" C-b P
sleep 1
modal=$(fmt modal:0 '#{window_modal_pane}')
[ -n "$modal" ] || fail "display-popup did not create a modal pane"
must_equal "$(fmt "$modal" '#{pane_title}')" popup-title
must_equal "$($TMUX show-options -pv -t "$modal" pane-border-status)" top
must_equal "$($TMUX show-options -pv -t "$modal" pane-border-format)" \
'#{pane_title}'
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
sleep 1
must_equal "$($TMUX show -gv @modal-prefix)" no
must_equal "$($TMUX show -gv @modal-root)" no
case "$($TMUX capture-pane -pt "$modal")" in
*xz*) ;;
*) fail "keys did not reach display-popup pane" ;;
esac
$TMUX2 send-keys -t "$OUTER" Escape
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
$TMUX2 send-keys -t "$OUTER" C-c
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
# Creating a popup pane must not fire the split-window hook.
check_ok set-hook -t modal after-split-window \
"set-option -g @popup-after-split yes"
check_ok set-option -g @popup-after-split no
check_ok display-popup -E -t "$p0" true
must_equal "$($TMUX show-option -gv @popup-after-split)" no
check_ok set-hook -u -t modal after-split-window
# A borderless popup must not create a zero-sized pane in a tiny window.
check_ok new-window -d -t modal: -n popup-small 'cat'
check_ok set-option -w -t modal:popup-small window-size manual
check_ok resize-window -t modal:popup-small -x 1 -y 1
small=$(fmt modal:popup-small '#{pane_id}')
check_ok bind Z display-popup -B -t "$small" 'cat'
$TMUX2 send-keys -t "$OUTER" C-b Z
sleep 1
must_equal "$(fmt modal:popup-small '#{window_panes}')" 1
must_equal "$(fmt modal:popup-small '#{window_modal_pane}')" ''
$TMUX bind D display-popup -t "$p0" -w 20 -h 5 'printf done'
$TMUX2 send-keys -t "$OUTER" C-b D
sleep 2
modal=$(fmt modal:0 '#{window_modal_pane}')
[ -n "$modal" ] || fail "retained display-popup was not created"
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
case "$($TMUX capture-pane -pt "$modal")" in
*'Pane is dead'*) fail "display-popup showed remain-on-exit message" ;;
esac
$TMUX2 send-keys -t "$OUTER" a
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
$TMUX2 send-keys -t "$OUTER" Escape
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
$TMUX bind K display-popup -k -t "$p0" -w 20 -h 5 'printf done'
$TMUX2 send-keys -t "$OUTER" C-b K
sleep 2
modal=$(fmt modal:0 '#{window_modal_pane}')
[ -n "$modal" ] || fail "display-popup -k was not created"
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
$TMUX2 send-keys -t "$OUTER" a
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
$TMUX bind F display-popup -EE -t "$p0" -w 20 -h 5 'exit 1'
$TMUX2 send-keys -t "$OUTER" C-b F
sleep 2
modal=$(fmt modal:0 '#{window_modal_pane}')
[ -n "$modal" ] || fail "failed display-popup -EE did not remain"
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
$TMUX2 send-keys -t "$OUTER" a
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
$TMUX2 send-keys -t "$OUTER" Escape
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
check_ok kill-pane -t "$modal"
sleep 1
check_ok display-popup -EE -t "$p0" true
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
$TMUX bind G display-popup -EE -k -t "$p0" -w 20 -h 5 'exit 1'
$TMUX2 send-keys -t "$OUTER" C-b G
sleep 2
modal=$(fmt modal:0 '#{window_modal_pane}')
[ -n "$modal" ] || fail "failed display-popup -EE -k did not remain"
must_equal "$(fmt "$modal" '#{pane_dead}')" 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}')" ''
check_ok display-popup -EE -k -t "$p0" true
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
# A nonmodal floating pane may remain above zoom, and switching between it and
# the zoomed tiled pane must not unzoom the window.
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
+71
View File
@@ -0,0 +1,71 @@
#!/bin/sh
# Chooser menus are centred on the mouse using their displayed width. A
# hidden title must not move a borderless menu away from the mouse.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -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
}
trap cleanup 0 1 15
fail()
{
echo "$*" >&2
exit 1
}
$TMUX new-session -d -s short -x 100 -y 30 'sleep 100' || exit 1
$TMUX set -g mouse on || exit 1
$TMUX set -g status off || exit 1
$TMUX2 new-session -d -x 100 -y 30 "$TMUX attach" || exit 1
sleep 1
for name in short aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; do
$TMUX rename-session "$name" || exit 1
for lines in none single padded; do
$TMUX set -g menu-border-lines "$lines" || exit 1
$TMUX choose-tree -s || exit 1
sleep 1
# Right-button press at window (60, 0), away from either edge.
seq=$(printf '\033[<2;61;1M')
$TMUX2 send-keys -l "$seq" || exit 1
sleep 1
row=2
column=54
if [ "$lines" = none ]; then
row=1
elif [ "$name" != short ]; then
column=31
fi
text=$($TMUX2 capture-pane -p | awk -v row="$row" \
-v column="$column" 'NR == row {
# Keep byte offsets equal to columns with non-Unicode awk.
gsub(/│/, "|")
print substr($0, column, 7)
}')
[ "$text" = 'Select ' ] || \
fail "$lines menu for $name: expected Select at ($column, $row), got '$text'"
# Close the menu, release the button, then leave the chooser.
$TMUX2 send-keys Escape || exit 1
seq=$(printf '\033[<2;61;1m')
$TMUX2 send-keys -l "$seq" || exit 1
$TMUX2 send-keys q || exit 1
sleep 1
done
done
exit 0
+5
View File
@@ -86,6 +86,11 @@ check_value "-gv status-keys" "vi"
check_fail "unknown value: bogus" set -g status-keys bogus
check_value "-gv status-keys" "vi"
# pane-border-lines accepts rounded as a pane border style.
check_ok set -gw pane-border-lines rounded
check_value "-gwv pane-border-lines" "rounded"
check_ok set -gw pane-border-lines single
# --- flag options ---------------------------------------------------------
#
# focus-events is an on/off flag. Setting with no value toggles it; explicit
-168
View File
@@ -1,168 +0,0 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -L${TEST_SOCKET:-testO$$} -f/dev/null"
OUT=/tmp/tmux-output-commands-$$
EXPECTED=$(printf 'one\ntwo')
cleanup()
{
$TMUX kill-server 2>/dev/null
rm -f "$OUT"
}
trap cleanup EXIT HUP INT TERM
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\ntwo\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007broken\\n\\033]133;C\\007unfinished\"; exec sleep 100'" || exit 1
sleep 1
$TMUX copy-mode -U || exit 1
$TMUX send-keys -X copy-output || exit 1
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
$TMUX send-keys -X pipe-output "wc -c >$OUT" || exit 1
sleep 1
[ "$(cat "$OUT")" = 10 ] || exit 1
$TMUX send-keys -X copy-pipe-output "wc -c >$OUT" output || exit 1
sleep 1
[ "$(cat "$OUT")" = 10 ] || exit 1
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
$TMUX send-keys -X select-output || exit 1
$TMUX send-keys -X copy-selection || exit 1
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX copy-mode -U || exit 1
$TMUX send-keys -X search-backward separator || exit 1
$TMUX send-keys -X copy-output || exit 1
copied=$($TMUX show-buffer)
[ "$copied" = "$EXPECTED" ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX copy-mode -U || exit 1
$TMUX send-keys -X search-backward one || exit 1
$TMUX send-keys -X copy-output || exit 1
copied=$($TMUX show-buffer)
[ "$copied" = "$EXPECTED" ] || exit 1
$TMUX send-keys -X pipe-output "wc -l >$OUT" || exit 1
sleep 1
[ "$(cat "$OUT")" = 2 ] || exit 1
$TMUX send-keys -X copy-pipe-output "wc -l >$OUT" output || exit 1
sleep 1
[ "$(cat "$OUT")" = 2 ] || exit 1
copied=$($TMUX show-buffer)
[ "$copied" = "$EXPECTED" ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX copy-mode -c || exit 1
$TMUX send-keys -X search-backward echo || exit 1
$TMUX send-keys -X select-output || exit 1
$TMUX send-keys -X copy-selection || exit 1
selected=$($TMUX show-buffer)
[ "$selected" = "$EXPECTED" ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX set-buffer -b keep unchanged || exit 1
$TMUX copy-mode -U || exit 1
$TMUX send-keys -X search-backward unfinished || exit 1
$TMUX send-keys -X copy-output || exit 1
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX new-window -d -n plain "printf 'alpha\\nbeta\\n'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -t :plain || exit 1
$TMUX send-keys -t :plain.0 -X copy-output -a || exit 1
all=$($TMUX show-buffer)
case "$all" in
*alpha*beta*) ;;
*) exit 1 ;;
esac
$TMUX send-keys -t :plain.0 -X cancel || exit 1
$TMUX set-buffer -b keep unchanged || exit 1
$TMUX copy-mode -t :plain || exit 1
$TMUX send-keys -t :plain.0 -X copy-output || exit 1
[ "$($TMUX show-buffer -b keep)" = unchanged ] || exit 1
$TMUX send-keys -t :plain.0 -X cancel || exit 1
$TMUX copy-mode -t :plain || exit 1
$TMUX send-keys -t :plain.0 -X select-output || exit 1
$TMUX send-keys -t :plain.0 -X select-output -a || exit 1
$TMUX send-keys -t :plain.0 -X copy-selection || exit 1
all=$($TMUX show-buffer)
case "$all" in
*alpha*beta*) ;;
*) exit 1 ;;
esac
$TMUX send-keys -t :plain.0 -X cancel || exit 1
$TMUX new-window -d -n empty "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007true\\033]133;C\\033]133;D;0\\007'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -t :empty || exit 1
$TMUX send-keys -t :empty.0 -X copy-output || exit 1
[ "$($TMUX show-buffer)" = one ] || exit 1
$TMUX send-keys -t :empty.0 -X cancel || exit 1
$TMUX new-window -d -n prompt "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -U -t :prompt || exit 1
$TMUX send-keys -t :prompt.0 -X copy-output || exit 1
[ "$($TMUX show-buffer)" = one ] || exit 1
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
$TMUX copy-mode -c -t :prompt || exit 1
$TMUX send-keys -t :prompt.0 -X expand-output || exit 1
$TMUX send-keys -t :prompt.0 -X -N 10 cursor-down || exit 1
$TMUX send-keys -t :prompt.0 -X select-output || exit 1
[ "$($TMUX display-message -p -t :prompt.0 '#{selection_present}')" = 1 ] || exit 1
$TMUX send-keys -t :prompt.0 -X copy-selection || exit 1
[ "$($TMUX show-buffer)" = one ] || exit 1
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
$TMUX new-window -d -n below "printf '\\033]133;A\\007p\\$ \\033]133;B\\007old\\n\\033]133;C\\007old1\\nold2\\nold3\\nold4\\nold5\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007ps\\n\\033]133;C\\007PID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -c -t :below || exit 1
$TMUX send-keys -t :below.0 -X search-backward ps || exit 1
$TMUX send-keys -t :below.0 -X expand-output || exit 1
$TMUX send-keys -t :below.0 -X -N 100 cursor-down || exit 1
$TMUX send-keys -t :below.0 C-o || exit 1
[ "$($TMUX display-message -p -t :below.0 '#{selection_present}')" = 1 ] || exit 1
$TMUX send-keys -t :below.0 -X copy-selection || exit 1
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\\n1 pts/0')" ] || exit 1
$TMUX send-keys -t :below.0 -X cancel || exit 1
$TMUX new-window -d -n clear "printf 'old1\\nold2\\nold3\\nold4\\nold5\\nold6\\n\\033]133;A\\007p\\$ \\033]133;B\\007echo 1; clear; ps\\n\\033]133;C\\0071\\n\\033[H\\033[2JPID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -t :clear || exit 1
$TMUX send-keys -t :clear.0 C-o || exit 1
$TMUX send-keys -t :clear.0 -X copy-selection || exit 1
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\\n1 pts/0')" ] || exit 1
$TMUX send-keys -t :clear.0 -X cancel || exit 1
$TMUX new-window -d -n same "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -U -t :same || exit 1
$TMUX send-keys -t :same.0 -X search-backward one || exit 1
$TMUX send-keys -t :same.0 -X select-output || exit 1
$TMUX send-keys -t :same.0 -X copy-selection || exit 1
[ "$($TMUX show-buffer)" = one ] || exit 1
$TMUX send-keys -t :same.0 -X cancel || exit 1
$TMUX copy-mode -t :plain || exit 1
$TMUX send-keys -t :plain.0 -X copy-output -a || exit 1
all=$($TMUX show-buffer)
case "$all" in
*alpha*beta*) ;;
*) exit 1 ;;
esac
$TMUX send-keys -t :plain.0 -X cancel || exit 1
exit 0
+24 -5
View File
@@ -104,6 +104,25 @@ assert_alive()
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.
@@ -309,12 +328,12 @@ check_ok kill-pane -t "$p6"
# Zoom and unzoom preserve the exact tiled layout. Selecting another 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 resize-pane -Z -t "$p0"
check_ok select-pane -t "$p2"
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 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}' \
'1:1:1'
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
# 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}' \
'1:1:1'
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.
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}' \
'1:1:1'
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.
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
+33 -4
View File
@@ -217,7 +217,36 @@ $OUT send-keys M-r || exit 1
settle
status_line | grep -q '>' || fail "status-line prompt not drawn on the status line"
# --- 11. emacs cursor-marker edit, accept recovers the exact buffer. ---
# --- 11. Invalid UTF-8 input must not desynchronize the prompt buffer. ---
$OUT send-keys -H e6 85 5f || exit 1 # invalid UTF-8, then "_"
settle
$IN display-message -p '#{version}' >/dev/null 2>&1 || \
fail "invalid UTF-8 append killed inner tmux"
status_line | grep -qF "> _" || \
fail "invalid UTF-8 append did not keep prompt usable (got '$(status_line)')"
$OUT send-keys Enter || exit 1
settle
[ "$($IN show -gv @r)" = "_" ] || \
fail "invalid UTF-8 append recovered '$($IN show -gv @r)', wanted '_'"
$IN set -g @r "SENTINEL" || exit 1
$OUT send-keys M-r || exit 1
settle
$OUT send-keys -H e6 85 04 || exit 1 # invalid UTF-8, then C-d
settle
$IN display-message -p '#{version}' >/dev/null 2>&1 || \
fail "invalid UTF-8 delete killed inner tmux"
status_line | grep -q '>' || \
fail "invalid UTF-8 delete closed the prompt (got '$(status_line)')"
$OUT send-keys Escape || exit 1
settle
[ "$($IN show -gv @r)" = "SENTINEL" ] || \
fail "invalid UTF-8 delete accepted the prompt"
# --- 12. emacs cursor-marker edit, accept recovers the exact buffer. ---
$IN set -g @r "" || exit 1
$OUT send-keys M-r || exit 1
settle
$OUT send-keys -l "abc" || exit 1
$OUT send-keys Home || exit 1
$OUT send-keys -l "X" || exit 1
@@ -229,7 +258,7 @@ settle
[ "$($IN show -gv @r)" = "Xabc" ] || \
fail "status-line accept recovered '$($IN show -gv @r)', wanted 'Xabc'"
# --- 12. Unicode on the status line: insert, move, delete wide char. ---
# --- 13. Unicode on the status line: insert, move, delete wide char. ---
$IN set -g @r "" || exit 1
$OUT send-keys M-r || exit 1
settle
@@ -249,7 +278,7 @@ settle
[ "$($IN show -gv @r)" = "Za" ] || \
fail "status-line wide edit recovered '$($IN show -gv @r)', wanted 'Za'"
# --- 13. Overflow: more text than fits stays within the line and is kept. ---
# --- 14. Overflow: more text than fits stays within the line and is kept. ---
big="0123456789012345678901234567890123456789012345678901234567890123456789ABCDEFGHIJ"
$IN set -g @r "" || exit 1
$OUT send-keys M-r || exit 1
@@ -264,7 +293,7 @@ settle
# The whole buffer was kept despite only part being visible.
[ "$($IN show -gv @r)" = "$big" ] || fail "overflowing prompt lost buffer content"
# --- 14. Escape closes the status-line prompt cleanly. ---
# --- 15. Escape closes the status-line prompt cleanly. ---
$IN set -g @r "SENTINEL" || exit 1
$OUT send-keys M-r || exit 1
settle
+1 -1
View File
@@ -15,7 +15,7 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest"
TMUX="$TEST_TMUX -Ltest$$"
$TMUX kill-server 2>/dev/null
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
+6
View File
@@ -107,6 +107,12 @@ $TMUX2 new-pane -x28 -y8 -X4 -Y1 -B double \
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
compare floating-border-double
# Larger floating pane with rounded border lines.
new_scene 40 12
$TMUX2 new-pane -x28 -y8 -X4 -Y1 -B rounded \
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
compare floating-border-rounded
# Floating pane with no border lines: redraw_mark_pane_borders returns early so
# the float has no border at all, only its (clipped) content over the base pane.
new_scene 40 12
+20 -1
View File
@@ -106,9 +106,28 @@ compare menu-over-split
# Menu with no border lines.
setup 40 14
menu -b none -x6 -y8
$TMUX2 display-menu -T "This title must not reserve width" -C 1 \
-b none -x6 -y8 \
"Alpha item" a "" \
"Beta item" b "" \
"" "" "" \
"Gamma item" g "" || exit 1
sleep 1
compare menu-noborder
# Menu border style follows the explicitly targeted window, not the current
# window when a different pane is selected.
setup 40 14
$TMUX2 set menu-border-lines none || exit 1
$TMUX2 neww || exit 1
$TMUX2 display-menu -t%0 -T "Menu" -C 1 \
"Alpha item" a "" \
"Beta item" b "" \
"" "" "" \
"Gamma item" g "" || exit 1
$TMUX2 selectw -t%0 || exit 1
compare menu-target-window-noborder
# Menu with double border lines.
setup 40 14
menu -b double -x6 -y8
+9
View File
@@ -93,6 +93,15 @@ $TMUX2 splitw -v "$C" || exit 1
$TMUX2 select-layout tiled || exit 1
compare outside-both-2x2
# Top pane status supplies internal horizontal borders, but there is no pane
# below the window to supply its bottom edge.
new_scene 28 9
$TMUX2 setw pane-border-status top || exit 1
$TMUX2 setw pane-border-format "" || exit 1
compare outside-both-status-top-single
$TMUX2 splitw -v "$C" || exit 1
compare outside-both-status-top-split
# Window BIGGER than the client: only part of the window is viewed and the view
# can be panned (refresh-client). This exercises a non-zero scene offset.
# A 2x2 grid in a 60x20 window viewed through the 40x14 client.
-101
View File
@@ -1,101 +0,0 @@
#!/bin/sh
# Exercise drawing of popups (display-popup) over the window scene. A popup is an
# overlay drawn on top of the redraw scene (the overlay_draw path in
# screen-redraw.c), so this guards against regressions in how popups appear.
#
# A popup is modal and stays open until its command exits, so each scene fully
# re-creates the servers and re-attaches; the popup is opened in the background
# (display-popup blocks the client that runs it) and the outer pane is captured
# while it is open.
#
# Run with GENERATE=1 to (re)create the golden files.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX=
TMUX2=
SETUP=0
RESULTS=screen-redraw-results
TMP=$(mktemp)
cleanup() {
rm -f "$TMP"
[ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null
[ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null
}
trap cleanup 0 1 15
fail() {
echo "$*" >&2
exit 1
}
compare() {
sleep 1
$TMUX capturep -p >$TMP || exit 1
if [ -n "$GENERATE" ]; then
cp $TMP "$RESULTS/$1.result" || exit 1
echo "generated $1"
else
cmp -s $TMP "$RESULTS/$1.result" || \
fail "scene $1 differs from $RESULTS/$1.result"
fi
}
C="sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"POP%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'"
# setup: fresh inner window attached inside a fresh outer pane, 40x14.
setup() {
[ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null
[ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null
SETUP=$((SETUP + 1))
TMUX="$TEST_TMUX -LtestA$$-$SETUP -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$-$SETUP -f/dev/null"
$TMUX2 new -d -x40 -y14 "$C" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
$TMUX2 resizew -x40 -y14 || exit 1
$TMUX new -d -x40 -y14 || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g window-size manual || exit 1
$TMUX set -g default-terminal "tmux-256color" || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
}
# popup <args>: open a popup running a fixed command, in the background (it stays
# open because the command sleeps; the servers are killed at the next setup).
popup() {
$TMUX2 display-popup "$@" -E "sh -c 'printf POPUP; exec sleep 100'" &
sleep 1
}
# Basic popup over a single pane.
setup
popup -w20 -h6 -x6 -y3
compare popup-basic
# Popup over a split: drawn on top of the pane border.
setup
$TMUX2 splitw -h "$C" || exit 1
popup -w24 -h8 -x8 -y3
compare popup-over-split
# Popup with no border lines (-B).
setup
popup -B -w20 -h6 -x6 -y3
compare popup-noborder
# Popup with double border lines.
setup
popup -b double -w20 -h6 -x6 -y3
compare popup-double
exit 0
@@ -0,0 +1,12 @@
base
╭──────────────────────────╮
│FLOAT │
│ │
│ │
│ │
│ │
│ │
╰──────────────────────────╯
@@ -1,11 +1,11 @@
MENU00 abcdefghij
MENU01 abcdefghij
MENU02 Menu 
MENU03  Alpha item (a)  
MENU04  Beta item (b)  
MENU05 
MENU06  Gamma item (g)  
MENU07 
MENU02 abcdefghij
MENU03 abcdefghij
MENU04 Alpha item (a) 
MENU05 Beta item (b) 
MENU06 
MENU07 Gamma item (g) 
MENU08 abcdefghij
MENU09 abcdefghij
MENU10 abcdefghij
@@ -0,0 +1,13 @@
MENU00 abcdefghij
MENU01 abcdefghij
MENU02 abcdefghij
MENU03 abcdefghij
MENU04 abcd Alpha item (a) 
MENU05 abcd Beta item (b) 
MENU06 abcd 
MENU07 abcd Gamma item (g) 
MENU08 abcdefghij
MENU09 abcdefghij
MENU10 abcdefghij
MENU11 abcdefghij
MENU12 abcdefghij
@@ -0,0 +1,14 @@
────────────────────────────┐···········
OUT01 abcdefghij │···········
OUT02 abcdefghij │···········
OUT03 abcdefghij │···········
OUT04 abcdefghij │···········
OUT05 abcdefghij │···········
OUT06 abcdefghij │···········
OUT07 abcdefghij │···········
│···········
────────────────────────────┘···········
········································
········································
········································
········································
@@ -0,0 +1,14 @@
────────────────────────────┐···········
OUT06 abcdefghij │···········
OUT07 abcdefghij │···········
│···········
────────────────────────────┤···········
PAN05 abcdefghij │···········
PAN06 abcdefghij │···········
PAN07 abcdefghij │···········
│···········
────────────────────────────┘···········
········································
········································
········································
········································
@@ -1,14 +0,0 @@
POP00 ┌──────────────────┐
POP01 │POPUP │
POP02 │ │
POP03 │ │
POP04 │ │
POP05 └──────────────────┘
POP06 abcdefghij
POP07 abcdefghij
POP08 abcdefghij
POP09 abcdefghij
POP10 abcdefghij
POP11 abcdefghij
POP12 abcdefghij
@@ -1,14 +0,0 @@
POP00 ╔══════════════════╗
POP01 ║POPUP ║
POP02 ║ ║
POP03 ║ ║
POP04 ║ ║
POP05 ╚══════════════════╝
POP06 abcdefghij
POP07 abcdefghij
POP08 abcdefghij
POP09 abcdefghij
POP10 abcdefghij
POP11 abcdefghij
POP12 abcdefghij
@@ -1,14 +0,0 @@
POP00 POPUP
POP01
POP02
POP03
POP04
POP05
POP06 abcdefghij
POP07 abcdefghij
POP08 abcdefghij
POP09 abcdefghij
POP10 abcdefghij
POP11 abcdefghij
POP12 abcdefghij
@@ -1,14 +0,0 @@
POP00 ab┌──────────────────────┐fghij
POP01 ab│POPUP │fghij
POP02 ab│ │fghij
POP03 ab│ │fghij
POP04 ab│ │fghij
POP05 ab│ │fghij
POP06 ab│ │fghij
POP07 ab└──────────────────────┘fghij
POP08 abcdefghij │POP08 abcdefghij
POP09 abcdefghij │POP09 abcdefghij
POP10 abcdefghij │POP10 abcdefghij
POP11 abcdefghij │POP11 abcdefghij
POP12 abcdefghij │POP12 abcdefghij
│
+2 -1
View File
@@ -95,7 +95,8 @@ layout_tee_down() { # top/bottom, bottom split: a top tee
$TMUX 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 window-size manual || 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
+4
View File
@@ -97,6 +97,10 @@ check "p:.{top-left}" "%0"
check_ok select-pane -t p:0.%0
check "p:0.+" "1" '#{pane_index}' # next pane
check "p:0.-" "3" '#{pane_index}' # previous pane (wraps)
check_fail "can't find pane: +0" "p:0.+0"
check_fail "can't find pane: +foo" "p:0.+foo"
check_fail "can't find pane: -0" "p:0.-0"
check_fail "can't find pane: -foo" "p:0.-foo"
# --- last pane (!) --------------------------------------------------------
check_ok select-pane -t p:0.%2
+7
View File
@@ -154,6 +154,13 @@ check "alpha:{previous}" "3"
check "alpha:!" "2" # last window
check "alpha:{last}" "2"
# Malformed relative offsets must fail rather than silently selecting the
# current window.
check_fail "can't find window: +0" "alpha:+0"
check_fail "can't find window: +foo" "alpha:+foo"
check_fail "can't find window: -0" "alpha:-0"
check_fail "can't find window: -foo" "alpha:-foo"
# --- combined and empty forms ---------------------------------------------
#
# Empty targets use the current pane from TMUX_PANE when there is no client.
+61 -10
View File
@@ -9,14 +9,37 @@ $TMUX kill-server 2>/dev/null
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
$TMUX2 kill-server 2>/dev/null
TMP=$(mktemp)
trap "rm -f $TMP" 0 1 15
TMP=$(mktemp) || exit 1
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
$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
assertion=0
format_string () {
case $1 in
@@ -34,12 +57,33 @@ assert_key () {
expected_name=$2
format_string=$(format_string "$expected_name")
$TMUX2 command-prompt -k 'display-message -pl '"$format_string" > "$TMP" &
sleep 0.05
# Use a different prompt each time so a stale redraw cannot look ready.
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:]')
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 [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
assert_key 'Escape [200~' 'PasteStart'
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 [1;5Z' 'C-S-Tab'
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
exit $exit_status
+117
View File
@@ -0,0 +1,117 @@
#!/bin/sh
# A pane that doesn't span the terminal's full width - because
# pane-scrollbars is on (the scrollbar occupies a column) or the pane is
# one of a side-by-side split - needs DECSLRM (left/right margin) support
# to use the fast native-scroll path (tty_cmd_linefeed()/scrollup()/
# scrolldown()/reverseindex(), tty.c:
# "(!tty_full_width(tty, ctx) && !tty_use_margin(tty))"). Without it, every
# single scroll falls back to tty_redraw_region()'s full manual repaint of
# the whole region - a real, confirmed source of flicker (and, separately,
# of image content not surviving a scroll in branches with image support).
#
# tty_default_features() (tty-features.c) grants the "margins" feature to
# several terminals it can positively identify via XTVERSION/DA2 (mintty,
# iTerm2, WezTerm, ghostty, XTerm-as-VT420) - this checks the actual
# server-side scroll decision via the -vv log for the underlying mechanism
# those table entries all rely on, using the terminal-features option
# directly (which any of them - or a user's own terminal-overrides -
# ultimately feed into) rather than simulating any one terminal's
# identification handshake.
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
cd "$DIR" || exit 1
INNER="$TEST_TMUX -vv -Lmarginsscrollbar-inner-$$ -f/dev/null"
OUTER="$TEST_TMUX -Lmarginsscrollbar-outer-$$ -f/dev/null"
fail()
{
echo "$*" >&2
exit 1
}
cleanup()
{
$OUTER kill-server 2>/dev/null
$INNER kill-server 2>/dev/null
cd /
rm -rf "$DIR"
}
trap cleanup 0 1 15
wait_for_client()
{
i=0
while [ "$i" -lt 50 ]; do
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
[ -n "$CLIENT" ] && return 0
sleep 0.1
i=$((i + 1))
done
fail "inner client did not attach"
}
run_scroll_phase()
{
label=$1
margins=$2
rm -f tmux-server*.log
$INNER new-session -d -s inner -x 40 -y 6 'exec sh' || exit 1
$INNER set -g status off || exit 1
$INNER set -g window-size manual || exit 1
$INNER set -g pane-scrollbars on || exit 1
if [ "$margins" = "on" ]; then
$INNER set -as terminal-features ',*:margins' || exit 1
fi
$OUTER new-session -d -x 40 -y 6 || exit 1
OUTERPANE=$($OUTER list-panes -F '#{pane_id}') || exit 1
$OUTER set -g status off || exit 1
$OUTER set -g window-size manual || exit 1
$OUTER set -g default-terminal screen-256color || exit 1
$OUTER send-keys -t "$OUTERPANE" -l "$INNER attach -t inner" || exit 1
$OUTER send-keys -t "$OUTERPANE" Enter || exit 1
sleep 1
wait_for_client
i=0
while [ "$i" -lt 8 ]; do
$INNER send-keys -t inner Enter || exit 1
sleep 0.2
i=$((i + 1))
done
sleep 0.3
LOG=$(ls tmux-server*.log 2>/dev/null | head -1)
[ -n "$LOG" ] || fail "$label: sanity: no server -vv log was produced"
n=$(grep -c "tty_redraw_region.*large region redraw" "$LOG")
$OUTER kill-server 2>/dev/null
$INNER kill-server 2>/dev/null
echo "$n"
}
# Phase 1: margins granted - must never fall back to a full region redraw.
n_with=$(run_scroll_phase "with margins" "on")
[ "$n_with" -eq 0 ] ||
fail "with margins granted, scrolling a scrollbar-enabled pane still fell back to a full region redraw ($n_with times)"
# Phase 2: sanity check - without margins, the same scenario must actually
# hit the fallback, proving phase 1 wasn't accidentally trivial.
n_without=$(run_scroll_phase "without margins" "off")
[ "$n_without" -gt 0 ] ||
fail "sanity: without margins, scrolling a scrollbar-enabled pane never fell back to a full region redraw - this scenario no longer exercises the bug this test checks for"
exit 0
+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
+28 -63
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: screen-redraw.c,v 1.158 2026/09/01 19:50:58 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>
@@ -33,7 +33,7 @@
* this is done at various points, such as when a pane is moved or resized. The
* scene only includes the part of the client used for the window: panes, pane
* status lines, borders, scrollbars, and any area outside the window. The
* client status line and overlay are not included.
* client status line is not included.
*
* A scene is made from spans. A span is a horizontal run of cells on one
* visible line that can be drawn in the same way. Each span has a type, for
@@ -93,7 +93,6 @@ enum redraw_span_type {
#define REDRAW_PANE_SCROLLBAR 0x20
#define REDRAW_STATUS 0x40
#define REDRAW_MENU 0x80
#define REDRAW_OVERLAY 0x100
/* Draw everything. */
#define REDRAW_ALL 0x7fffffff
@@ -264,8 +263,6 @@ redraw_flags_to_string(int flags)
strlcat(s, "scrollbar ", sizeof s);
if (flags & REDRAW_MENU)
strlcat(s, "menu ", sizeof s);
if (flags & REDRAW_OVERLAY)
strlcat(s, "overlay ", sizeof s);
if (REDRAW_IS_ALL(flags))
strlcat(s, "all ", sizeof s);
if (*s != '\0')
@@ -713,7 +710,7 @@ redraw_mark_pane_borders(struct redraw_build_ctx *bctx, struct window_pane *wp,
} else {
mark_right = (right <= (int)bctx->w->sx);
mark_bottom = (bottom <= (int)bctx->w->sy);
if (pane_status == PANE_STATUS_TOP)
if (pane_status == PANE_STATUS_TOP && bottom < (int)bctx->w->sy)
mark_bottom = 0;
else if (pane_status == PANE_STATUS_BOTTOM)
mark_top = 0;
@@ -1381,45 +1378,30 @@ static void
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
u_int y)
{
struct redraw_scene *scene = dctx->scene;
struct redraw_span_data *data = &span->data;
enum redraw_span_type type = data->type;
struct client *c = scene->c;
struct tty *tty = &c->tty;
struct visible_ranges *r;
struct visible_range *rr;
u_int i, x, n;
if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS)
return;
r = tty_check_overlay_range(tty, span->x, y, span->width);
for (i = 0; i < r->used; i++) {
rr = &r->ranges[i];
if (rr->nx == 0)
continue;
x = rr->px;
n = rr->nx;
switch (span->data.type) {
case REDRAW_SPAN_PANE:
redraw_draw_pane_span(dctx, span, x, y, n);
break;
case REDRAW_SPAN_BORDER:
case REDRAW_SPAN_EMPTY:
case REDRAW_SPAN_OUTSIDE:
redraw_draw_border_span(dctx, span, x, y, n);
break;
case REDRAW_SPAN_STATUS:
redraw_draw_status_span(dctx, span, x, y, n);
break;
case REDRAW_SPAN_SCROLLBAR:
redraw_draw_scrollbar_span(dctx, span, x, y, n);
break;
case REDRAW_SPAN_MENU:
redraw_draw_menu_span(dctx, span, x, y, n);
break;
}
switch (span->data.type) {
case REDRAW_SPAN_PANE:
redraw_draw_pane_span(dctx, span, span->x, y, span->width);
break;
case REDRAW_SPAN_BORDER:
case REDRAW_SPAN_EMPTY:
case REDRAW_SPAN_OUTSIDE:
redraw_draw_border_span(dctx, span, span->x, y, span->width);
break;
case REDRAW_SPAN_STATUS:
redraw_draw_status_span(dctx, span, span->x, y, span->width);
break;
case REDRAW_SPAN_SCROLLBAR:
redraw_draw_scrollbar_span(dctx, span, span->x, y, span->width);
break;
case REDRAW_SPAN_MENU:
redraw_draw_menu_span(dctx, span, span->x, y, span->width);
break;
}
}
@@ -1673,7 +1655,7 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
prompt_draw(wp->prompt, &pdd);
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);
}
@@ -1688,10 +1670,8 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
struct screen *sl;
struct redraw_scene *scene;
struct window_pane *loop;
u_int width, i, y, lines, j;
u_int width, i, y, lines;
struct redraw_span *first;
struct visible_ranges *r;
struct visible_range *rr;
int redraw;
if (c->flags & CLIENT_SUSPENDED)
@@ -1799,19 +1779,9 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
else
y = c->tty.sy - lines;
sl = c->status.active;
for (i = 0; i < lines; i++) {
r = tty_check_overlay_range(tty, 0, y + i, tty->sx);
for (j = 0; j < r->used; j++) {
rr = &r->ranges[j];
if (rr->nx == 0)
continue;
tty_draw_line(tty, sl, rr->px, i, rr->nx,
rr->px, y + i, NULL);
}
}
for (i = 0; i < lines; i++)
tty_draw_line(tty, sl, 0, i, tty->sx, 0, y + i, NULL);
}
if (c->overlay_draw != NULL && (flags & REDRAW_OVERLAY))
c->overlay_draw(c, c->overlay_data);
tty_reset(tty);
@@ -1867,18 +1837,13 @@ redraw_screen(struct client *c)
{
int flags = 0;
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_REDRAWWINDOW)
redraw_draw(c, NULL, REDRAW_ALL);
else {
if (c->flags & CLIENT_REDRAWBORDERS)
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
if (c->flags & CLIENT_REDRAWSTATUS)
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
if (c->flags & CLIENT_REDRAWOVERLAY)
flags |= REDRAW_OVERLAY;
if (c->flags & CLIENT_REDRAWMENU)
flags |= REDRAW_MENU;
if (c->session->curw->window->menu != NULL)
+24 -24
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: screen-write.c,v 1.290 2026/08/24 15:05:26 nicm Exp $ */
/* $OpenBSD: screen-write.c,v 1.292 2026/09/21 12:14:32 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -305,21 +305,16 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
if (~ctx->flags & SCREEN_WRITE_SYNC) {
/*
* For the active pane showing its base screen or for an
* overlay (no pane), only use synchronized updates if
* requested (commands that move the cursor); for other panes
* or a pane in a mode, always use it, since the cursor will
* have to move.
* For the active pane showing its base screen, only use
* synchronized updates if requested (commands that move the
* cursor); for other panes or a pane in a mode, always use it,
* since the cursor will have to move.
*/
if (ctx->wp != NULL && (ctx->wp != ctx->wp->window->active ||
ctx->wp->screen != &ctx->wp->base))
ttyctx->flags |= TTY_CTX_SYNC;
else {
if (ctx->wp == NULL)
ttyctx->flags |= TTY_CTX_OVERLAY_SYNC;
if (is_sync)
ttyctx->flags |= TTY_CTX_SYNC;
}
else if (is_sync)
ttyctx->flags |= TTY_CTX_SYNC;
tty_write(tty_cmd_syncstart, ttyctx);
ctx->flags |= SCREEN_WRITE_SYNC;
}
@@ -714,7 +709,7 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src,
if (!window_position_is_visible(r, xoff + s->cx))
break;
ttyctx.cell = &gc;
ttyctx.flags &= (TTY_CTX_OVERLAY_SYNC|TTY_CTX_SYNC);
ttyctx.flags &= TTY_CTX_SYNC;
tty_write(tty_cmd_cell, &ttyctx);
ttyctx.ocx++;
@@ -838,34 +833,39 @@ screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, int choice,
struct screen *s = ctx->s;
struct grid_cell default_gc;
const struct grid_cell *gc = &default_gc;
u_int cx, cy, i, j, width = menu->width;
u_int border, cx = s->cx, cy = s->cy, i, j, width;
const char *name;
cx = s->cx;
cy = s->cy;
memcpy(&default_gc, menu_gc, sizeof default_gc);
screen_write_box(ctx, menu->width + 4, menu->count + 2, lines,
border_gc, menu->title);
if (lines == BOX_LINES_NONE) {
border = 0;
width = menu->item_width;
} else {
border = 1;
width = menu->width;
screen_write_box(ctx, width + 4, menu->count + 2, lines,
border_gc, menu->title);
}
for (i = 0; i < menu->count; i++) {
name = menu->items[i].name;
if (name == NULL) {
screen_write_cursormove(ctx, cx, cy + 1 + i, 0);
screen_write_hline(ctx, width + 4, 1, 1, lines,
border_gc);
screen_write_cursormove(ctx, cx, cy + border + i, 0);
screen_write_hline(ctx, width + 2 + (2 * border), 1, 1,
lines, border_gc);
continue;
}
if (choice >= 0 && i == (u_int)choice && *name != '-')
gc = choice_gc;
screen_write_cursormove(ctx, cx + 1, cy + 1 + i, 0);
screen_write_cursormove(ctx, cx + border, cy + border + i, 0);
for (j = 0; j < width + 2; j++)
screen_write_putc(ctx, gc, ' ');
screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0);
screen_write_cursormove(ctx, cx + border + 1, cy + border + i,
0);
if (*name == '-') {
default_gc.attr |= GRID_ATTR_DIM;
format_draw(ctx, gc, width, name + 1, NULL, 0);
+34 -158
View File
@@ -1,4 +1,4 @@
/* $OpenBSD: server-client.c,v 1.510 2026/09/01 19:50:58 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>
@@ -67,77 +67,6 @@ server_client_how_many(void)
return (n);
}
/* Overlay timer callback. */
static void
server_client_overlay_timer(__unused int fd, __unused short events, void *data)
{
server_client_clear_overlay(data);
}
/* Set an overlay on client. */
void
server_client_set_overlay(struct client *c, u_int delay,
overlay_check_cb checkcb, overlay_mode_cb modecb,
overlay_draw_cb drawcb, overlay_key_cb keycb, overlay_free_cb freecb,
overlay_resize_cb resizecb, void *data)
{
struct timeval tv;
if (c->overlay_draw != NULL)
server_client_clear_overlay(c);
tv.tv_sec = delay / 1000;
tv.tv_usec = (delay % 1000) * 1000L;
if (event_initialized(&c->overlay_timer))
evtimer_del(&c->overlay_timer);
evtimer_set(&c->overlay_timer, server_client_overlay_timer, c);
if (delay != 0)
evtimer_add(&c->overlay_timer, &tv);
c->overlay_check = checkcb;
c->overlay_mode = modecb;
c->overlay_draw = drawcb;
c->overlay_key = keycb;
c->overlay_free = freecb;
c->overlay_resize = resizecb;
c->overlay_data = data;
if (c->overlay_check == NULL)
c->tty.flags |= TTY_FREEZE;
if (c->overlay_mode == NULL)
c->tty.flags |= TTY_NOCURSOR;
window_update_focus(c->session->curw->window);
server_redraw_client(c);
}
/* Clear overlay mode on client. */
void
server_client_clear_overlay(struct client *c)
{
if (c->overlay_draw == NULL)
return;
if (event_initialized(&c->overlay_timer))
evtimer_del(&c->overlay_timer);
if (c->overlay_free != NULL)
c->overlay_free(c, c->overlay_data);
c->overlay_check = NULL;
c->overlay_mode = NULL;
c->overlay_draw = NULL;
c->overlay_key = NULL;
c->overlay_free = NULL;
c->overlay_resize = NULL;
c->overlay_data = NULL;
c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR);
if (c->session != NULL)
window_update_focus(c->session->curw->window);
server_redraw_client(c);
}
/* Are these ranges empty? That is, nothing is visible. */
int
server_client_ranges_is_empty(struct visible_ranges *r)
@@ -161,52 +90,6 @@ server_client_ensure_ranges(struct visible_ranges *r, u_int n)
r->size = n;
}
/*
* Given overlay position and dimensions, return parts of the input range which
* are visible.
*/
void
server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px,
u_int py, u_int nx, struct visible_ranges *r)
{
u_int ox, onx;
/* Trivial case of no overlap in the y direction. */
if (py < y || py > y + sy - 1) {
server_client_ensure_ranges(r, 1);
r->ranges[0].px = px;
r->ranges[0].nx = nx;
r->used = 1;
return;
}
server_client_ensure_ranges(r, 2);
/* Visible bit to the left of the popup. */
if (px < x) {
r->ranges[0].px = px;
r->ranges[0].nx = x - px;
if (r->ranges[0].nx > nx)
r->ranges[0].nx = nx;
} else {
r->ranges[0].px = 0;
r->ranges[0].nx = 0;
}
/* Visible bit to the right of the popup. */
ox = x + sx;
if (px > ox)
ox = px;
onx = px + nx;
if (onx > ox) {
r->ranges[1].px = ox;
r->ranges[1].nx = onx - ox;
} else {
r->ranges[1].px = 0;
r->ranges[1].nx = 0;
}
r->used = 2;
}
/* Check if this client is inside this server. */
int
server_client_check_nested(struct client *c)
@@ -490,7 +373,6 @@ server_client_lost(struct client *c)
cfg_client = NULL;
c->flags |= CLIENT_DEAD;
server_client_clear_overlay(c);
status_prompt_clear(c);
status_message_clear(c);
@@ -502,6 +384,9 @@ server_client_lost(struct client *c)
TAILQ_REMOVE(&clients, c, entry);
log_debug("lost client %p", c);
cmd_wait_for_client_lost(c);
cmdq_next(c);
if (c->flags & CLIENT_ATTACHED) {
server_client_attached_lost(c);
events_fire_client("client-detached", c);
@@ -1391,15 +1276,19 @@ server_client_repeat_time(struct client *c, struct key_binding *bd)
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
server_client_handle_dead_key(struct window_pane *wp, key_code key)
{
int remain_on_exit;
if (wp == NULL ||
(~wp->flags & PANE_EXITED) ||
KEYC_IS_MOUSE(key) ||
KEYC_IS_PASTE(key) ||
options_get_number(wp->options, "remain-on-exit") != 3)
KEYC_IS_PASTE(key))
return (0);
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
if (remain_on_exit != 3 && remain_on_exit != 4)
return (0);
options_set_number(wp->options, "remain-on-exit", 0);
server_destroy_pane(wp, 0);
@@ -1742,10 +1631,6 @@ server_client_handle_key0(struct client *c, struct key_event *event,
if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
return (0);
/*
* Handle theme reporting keys before overlays so they work even when a
* popup is open.
*/
if (event->key == KEYC_REPORT_LIGHT_THEME) {
server_client_report_theme(c, THEME_LIGHT);
return (0);
@@ -1756,9 +1641,9 @@ server_client_handle_key0(struct client *c, struct key_event *event,
}
/*
* Key presses in overlay mode, for panes capturing all keys and in the
* command prompt are a special case. The queue might be blocked so they
* need to be processed immediately rather than queued.
* Dead panes waiting for a key, modal cancel keys, panes capturing all keys
* and the command prompt are special cases. The queue might be blocked so
* they need to be processed immediately rather than queued.
*/
if (~c->flags & CLIENT_READONLY) {
if (c->message_string != NULL) {
@@ -1767,25 +1652,20 @@ server_client_handle_key0(struct client *c, struct key_event *event,
status_message_clear(c);
}
if (c->overlay_key != NULL) {
switch (c->overlay_key(c, c->overlay_data, event)) {
case 0:
return (0);
case 1:
server_client_clear_overlay(c);
return (0);
}
}
server_client_clear_overlay(c);
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 &&
(wp->flags & PANE_CAPTUREALLKEYS) &&
TAILQ_EMPTY(&wp->modes) &&
!KEYC_IS_MOUSE(event->key)) {
if (server_client_handle_dead_key(wp, event->key))
return (0);
if (~wp->flags & PANE_EXITED) {
window_pane_key(wp, c, s, s->curw, event->key,
&event->m);
@@ -2166,11 +2046,8 @@ server_client_reset_state(struct client *c)
flags = (tty->flags & TTY_BLOCK);
tty->flags &= ~TTY_BLOCK;
/* Get mode from overlay if any, else from screen. */
if (c->overlay_draw != NULL) {
if (c->overlay_mode != NULL)
s = c->overlay_mode(c, c->overlay_data, &cx, &cy);
} else if (w->menu != NULL) {
/* Get mode from the menu if any, else from the screen. */
if (w->menu != NULL) {
menu_get_cursor(w->menu, &cx, &cy);
s = menu_screen(w->menu);
} else if (wp != NULL && c->prompt == NULL)
@@ -2192,7 +2069,7 @@ server_client_reset_state(struct client *c)
if (c->prompt != NULL) {
prompt = 1;
status_prompt_cursor(c, &cx, &cy);
} else if (wp != NULL && c->overlay_draw == NULL) {
} else if (wp != NULL) {
if (w->menu != NULL) {
tty_window_offset(tty, &ox, &oy, &sx, &sy);
if (cx < ox || cx >= ox + sx ||
@@ -2247,7 +2124,7 @@ server_client_reset_state(struct client *c)
if (!cursor)
mode &= ~MODE_CURSOR;
}
} else if (c->overlay_mode == NULL || s == NULL)
} else if (s == NULL)
mode &= ~MODE_CURSOR;
if (~pane_mode & MODE_SYNC) {
log_debug("%s: cursor to %u,%u", __func__, cx, cy);
@@ -2264,7 +2141,7 @@ server_client_reset_state(struct client *c)
* movement events.
*/
if (options_get_number(oo, "mouse")) {
if (c->overlay_draw == NULL && w->menu == NULL) {
if (w->menu == NULL) {
mode &= ~ALL_MOUSE_MODES;
TAILQ_FOREACH(loop, &w->panes, entry) {
if (loop->screen->mode & MODE_MOUSE_ALL)
@@ -2280,7 +2157,7 @@ server_client_reset_state(struct client *c)
}
/* Clear bracketed paste mode if at the prompt. */
if (c->overlay_draw == NULL && prompt)
if (prompt)
mode &= ~MODE_BRACKETPASTE;
/* Set the terminal mode and reset attributes. */
@@ -2482,11 +2359,10 @@ server_client_check_redraw(struct client *c)
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
return;
if (c->flags & CLIENT_ALLREDRAWFLAGS) {
log_debug("%s: redraw%s%s%s%s%s", c->name,
log_debug("%s: redraw%s%s%s%s", c->name,
(c->flags & CLIENT_REDRAWWINDOW) ? " window" : "",
(c->flags & CLIENT_REDRAWSTATUS) ? " status" : "",
(c->flags & CLIENT_REDRAWBORDERS) ? " borders" : "",
(c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : "",
(c->flags & CLIENT_REDRAWMENU) ? " menu" : "");
}
@@ -2694,10 +2570,6 @@ server_client_dispatch(struct imsg *imsg, void *arg)
tty_resize(&c->tty);
tty_repeat_requests(&c->tty, 0);
recalculate_sizes();
if (c->overlay_resize == NULL)
server_client_clear_overlay(c);
else
c->overlay_resize(c, c->overlay_data);
server_redraw_client(c);
if (c->session != NULL)
server_client_fire_resized(c, old_sx, old_sy);
@@ -3084,6 +2956,8 @@ server_client_control_flags(struct client *c, const char *next)
return (CLIENT_CONTROL_NOOUTPUT);
if (strcmp(next, "wait-exit") == 0)
return (CLIENT_CONTROL_WAITEXIT);
if (strcmp(next, "new-layouts") == 0)
return (CLIENT_CONTROL_NEWLAYOUTS);
return (0);
}
@@ -3150,6 +3024,8 @@ server_client_get_flags(struct client *c)
strlcat(s, "no-output,", sizeof s);
if (c->flags & CLIENT_CONTROL_WAITEXIT)
strlcat(s, "wait-exit,", sizeof s);
if (c->flags & CLIENT_CONTROL_NEWLAYOUTS)
strlcat(s, "new-layouts,", sizeof s);
if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
xsnprintf(tmp, sizeof tmp, "pause-after=%u,",
c->pause_age / 1000);
+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>
@@ -310,8 +310,8 @@ server_link_window(struct session *src, struct winlink *srcwl,
}
if (killflag) {
/*
* Can't use session_detach as it will destroy session
* if this makes it empty.
* Can't use session_detach as it won't detach the only
* window.
*/
events_fire_winlink("window-unlinked", dstwl);
dstwl->flags &= ~WINLINK_ALERTFLAGS;
@@ -386,6 +386,7 @@ server_destroy_pane(struct window_pane *wp, int notify)
case 0:
break;
case 2:
case 4:
if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
break;
/* 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>
@@ -336,10 +336,17 @@ session_attach(struct session *s, struct window *w, int idx, char **cause)
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
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 &&
session_last(s) != 0 &&
session_previous(s, 0) != 0)
@@ -352,8 +359,6 @@ session_detach(struct session *s, struct winlink *wl)
session_group_synchronize_from(s);
if (RB_EMPTY(&s->windows))
return (1);
return (0);
}

Some files were not shown because too many files have changed in this diff Show More