Compare commits

...

1434 Commits

Author SHA1 Message Date
Nicholas Marriott
c624de9f3c Merge branch 'master' into command_parser
# Conflicts:
#	arguments.c
#	cmd-command-prompt.c
#	cmd-parse.y
#	cmd-run-shell.c
#	window-panes.c
2026-09-01 20:17:10 +01:00
tmux update bot
9b0ad73a4b Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  When changing selection-mode to line, set up the selection start and end correctly, GitHub issue 5545.
  Extend word commands to use any Unicode whitespace character, GitHub issue 5562 from Hongyi Zhao.
2026-09-01 14:55:38 +00:00
nicm
bd8218e85d When changing selection-mode to line, set up the selection start and end
correctly, GitHub issue 5545.
2026-09-01 14:55:35 +00:00
nicm
8f3101d820 Extend word commands to use any Unicode whitespace character, GitHub
issue 5562 from Hongyi Zhao.
2026-09-01 14:55:35 +00:00
Nicholas Marriott
77309c07b0 Whitespace regress test from Hongyi Zhao. 2026-09-01 13:50:56 +01:00
tmux update bot
c8e889694d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a generation counter to pane output, from Michael K Darling.
  Add format variables for history change counters, from Michael K Darling.
2026-09-01 00:07:34 +00:00
nicm
b402687fcd Add a generation counter to pane output, from Michael K Darling. 2026-09-01 00:07:32 +00:00
nicm
0530a7d89a Add format variables for history change counters, from Michael K Darling. 2026-09-01 00:07:32 +00:00
tmux update bot
6bdb9ba54c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  tmux: add utf8 terminal-features
2026-08-31 20:43:13 +00:00
kirill
f2bfd611e2 tmux: add utf8 terminal-features
OK: nicm@
2026-08-31 20:43:11 +00:00
Nicholas Marriott
9c1d5a080b Use hash for actions, from Andreas Fehlner. 2026-08-31 20:52:54 +01:00
tmux update bot
b28244e2fe Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not leak temporary commands when expanding aliases, from Jeong, Heon.
  Do not allow popups for control clients, from Noam Stolero.
  Layout cell can be replaced by unzoom, fixes use after free. From Uzair Aftab.
  Do not try to append zero size cells, from Uzair Aftab.
  Clear stale marks when a search regex fails to compile, GitHub issue 5547 from Uzair Aftab.
2026-08-31 14:56:14 +00:00
nicm
8b3834c34f Do not leak temporary commands when expanding aliases, from Jeong, Heon. 2026-08-31 14:56:12 +00:00
nicm
af3e4d2e5b Do not allow popups for control clients, from Noam Stolero. 2026-08-31 14:56:12 +00:00
nicm
c552d1bf16 Layout cell can be replaced by unzoom, fixes use after free. From Uzair
Aftab.
2026-08-31 14:56:12 +00:00
nicm
6a1ecdcce3 Do not try to append zero size cells, from Uzair Aftab. 2026-08-31 14:56:12 +00:00
nicm
39357c829c Clear stale marks when a search regex fails to compile, GitHub issue
5547 from Uzair Aftab.
2026-08-31 14:56:12 +00:00
tmux update bot
40381bdcd9 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not walk off end of grid lines if last line is wrapped, reported by Moe Khalilov.
  Do no free input context if it is NULL when popup create fails.
2026-08-28 10:32:01 +00:00
nicm
7bad4f7722 Do not walk off end of grid lines if last line is wrapped, reported by
Moe Khalilov.
2026-08-28 10:32:00 +00:00
nicm
65e811570b Do no free input context if it is NULL when popup create fails. 2026-08-28 10:32:00 +00:00
tmux update bot
dd31900f80 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Ignore focus events earlier to avoid them interfering with prefix, GitHub issue 5539.
2026-08-28 09:02:23 +00:00
nicm
013ada58c8 Ignore focus events earlier to avoid them interfering with prefix,
GitHub issue 5539.
2026-08-28 09:02:22 +00:00
Nicholas Marriott
78dad45f3b Add timeout for lock and don't keep credentials for regress, from Andreas
Fehlner.
2026-08-28 09:07:16 +01:00
Nicholas Marriott
52b452948f Check for buffer write error. 2026-08-28 08:56:12 +01:00
tmux update bot
267746603a Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Fix tiled pane resizing with floating layout cells, reported by Kevin Hovsäter.
2026-08-25 21:31:27 +00:00
nicm
b26b240613 Fix tiled pane resizing with floating layout cells, reported by Kevin
Hovsäter.
2026-08-25 21:31:25 +00:00
Nicholas Marriott
8e48c889dd Add a resize test. 2026-08-25 19:36:06 +01:00
tmux update bot
f4445a310a Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a clear-on-attach option which when turned off scrolls away the existing terminal state instead of entering the alternate screen. This is useful in environments where tmux is started as a login shell but it is important that any pre-tmux messages are still visible. GitHub issue 5508 from Josef Řídký.
2026-08-25 11:30:47 +00:00
nicm
4157b64754 Add a clear-on-attach option which when turned off scrolls away the
existing terminal state instead of entering the alternate screen. This
is useful in environments where tmux is started as a login shell but it
is important that any pre-tmux messages are still visible. GitHub issue
5508 from Josef Řídký.
2026-08-25 11:30:45 +00:00
tmux update bot
0d2aafe7b7 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not pass NULL to qsort, and add checks for invalid session or window after swap.
2026-08-25 09:38:42 +00:00
nicm
6f4c9b32af Do not pass NULL to qsort, and add checks for invalid session or window
after swap.
2026-08-25 09:38:40 +00:00
tmux update bot
eb77a75ab7 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not leak exit message on client free, from Jeong, Heon.
  Tidy up args_make_commands reference counting to fix a memory leak, from Jeong, Heon.
2026-08-25 07:59:42 +00:00
nicm
0851650c6b Do not leak exit message on client free, from Jeong, Heon. 2026-08-25 07:59:40 +00:00
nicm
1459c90a7f Tidy up args_make_commands reference counting to fix a memory leak, from
Jeong, Heon.
2026-08-25 07:59:40 +00:00
tmux update bot
af29c080ba Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Correct a comparison ('\0' should be 0).
  If a pane has no scrollbar, don't bother to redraw it, and if redraw is deferred change scrollbar redraws into redraw-all-scrollbars not redraw entire window. GitHub issue 5529.
2026-08-25 01:46:56 +00:00
nicm
50ea32162f Correct a comparison ('\0' should be 0). 2026-08-25 01:46:55 +00:00
nicm
2d5328a860 If a pane has no scrollbar, don't bother to redraw it, and if redraw is
deferred change scrollbar redraws into redraw-all-scrollbars not redraw
entire window. GitHub issue 5529.
2026-08-25 01:46:55 +00:00
tmux update bot
e5db7598b2 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not add messages to cfg_causes after config has finished, GitHub issue 5528.
2026-08-24 22:03:29 +00:00
nicm
dc78d8f9ea Do not add messages to cfg_causes after config has finished, GitHub
issue 5528.
2026-08-24 22:03:27 +00:00
tmux update bot
022b8451e3 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Always use synchronized updates in copy mode (like when not in the active pane), since copy mode will almost always moved the cursor. From Artem Goldenberg in GitHub issue 5525.
2026-08-24 16:44:37 +00:00
nicm
5b54c7d41a Always use synchronized updates in copy mode (like when not in the
active pane), since copy mode will almost always moved the cursor. From
Artem Goldenberg in GitHub issue 5525.
2026-08-24 16:44:34 +00:00
tmux update bot
f94036b2b0 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Free any remaining items when destroying screen, from Jeong, Heon.
2026-08-24 15:57:10 +00:00
nicm
7d358208b9 Free any remaining items when destroying screen, from Jeong, Heon. 2026-08-24 15:57:09 +00:00
tmux update bot
c8ff8ea96f Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add {} to shell escape characters (special in bash/ksh/csh), GitHub issue 5514.
2026-08-24 15:02:17 +00:00
nicm
402d366bba Add {} to shell escape characters (special in bash/ksh/csh), GitHub
issue 5514.
2026-08-24 15:02:15 +00:00
Nicholas Marriott
41e701ad84 Return early if image is zero height when generating fallback, otherwise we can
allocated too little space. Reported by Vivek Parikh.
2026-08-24 15:28:17 +01:00
tmux update bot
f763f94556 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not leak cached status line, from Jeong, Heon.
  Free pane fallback range when freeing pane, from Jeong, Heon.
  Do not leak path when destroying client, from Jeong, Heon.
  Do not leak format when drawing preview, from Jeong, Heon.
  Do not leak buffer name on failure, from Jeong, Heon.
2026-08-24 08:28:15 +00:00
nicm
c9f5ecb4b5 Do not leak cached status line, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
nicm
7792a74c76 Free pane fallback range when freeing pane, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
nicm
4cabc2ae85 Do not leak path when destroying client, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
nicm
995b395795 Do not leak format when drawing preview, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
nicm
3d44f75d09 Do not leak buffer name on failure, from Jeong, Heon. 2026-08-24 08:28:13 +00:00
tmux update bot
c1f947a3c5 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Use correct X position when combining characters, GitHub issue 5511 from Francesc Rosas.
2026-08-21 11:56:01 +00:00
nicm
696a16cca2 Use correct X position when combining characters, GitHub issue 5511 from
Francesc Rosas.
2026-08-21 11:55:59 +00:00
Nicholas Marriott
936bb6e7cd Regress for GitHub issue 5511 from Francesc Rosas. 2026-08-21 10:54:29 +01:00
tmux update bot
8dfa903346 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a way for floating panes to stay above zoom (-A) flag. Use by default for modal panes to match popups.
2026-08-20 12:57:29 +00:00
nicm
95a7170709 Add a way for floating panes to stay above zoom (-A) flag. Use by
default for modal panes to match popups.
2026-08-20 12:57:26 +00:00
Nicholas Marriott
554c4bbde4 Tests for modal panes on top. 2026-08-20 10:19:19 +01:00
Nicholas Marriott
ac23f21a2c More regress tests for zoom. 2026-08-20 09:18:58 +01:00
tmux update bot
6696151008 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Stop at the right end line when walking wrapped lines, GitHub issue 5479.
2026-08-19 21:05:08 +00:00
nicm
50d0348b64 Stop at the right end line when walking wrapped lines, GitHub issue 5479. 2026-08-19 21:05:06 +00:00
Nicholas Marriott
cf3ab3cad9 Merge branch 'master' into command_parser 2026-08-19 20:57:59 +01:00
tmux update bot
bf7bbba7ea Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a -K flag to new-pane to make a modal pane capture all keys like popups used to.
2026-08-19 13:10:09 +00:00
nicm
b28b1b7b9a Add a -K flag to new-pane to make a modal pane capture all keys like
popups used to.
2026-08-19 13:10:08 +00:00
Nicholas Marriott
8499d8007e new-pane -K test bits. 2026-08-19 11:56:27 +01:00
tmux update bot
e5a2058c7c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Use DECRQM to detect mode 2026, from Michael Grant.
2026-08-18 12:03:17 +00:00
nicm
e94fcd96f9 Use DECRQM to detect mode 2026, from Michael Grant. 2026-08-18 12:03:15 +00:00
Nicholas Marriott
c5017e6da8 Bump time limit. 2026-08-18 11:49:34 +01:00
tmux update bot
0169f9e18c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Change correct depth counter for source-file when used from hooks, GitHub issue 5437.
2026-08-18 09:49:28 +00:00
nicm
b170c19274 Change correct depth counter for source-file when used from hooks,
GitHub issue 5437.
2026-08-18 09:49:26 +00:00
tmux update bot
22b3679752 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  When a pane is respawned, reset the control mode offsets. GitHub issue 5498 from Alex Rattray.
  Correctly clear clients referring to a session with destroy-unattached, GitHub issue 5497 from Jeong, Heon.
  Restore umask if bind fails, from nachalsa at naver dot com.
2026-08-18 09:04:02 +00:00
nicm
4714f478fe When a pane is respawned, reset the control mode offsets. GitHub issue
5498 from Alex Rattray.
2026-08-18 09:04:00 +00:00
nicm
72c5037f06 Correctly clear clients referring to a session with destroy-unattached,
GitHub issue 5497 from Jeong, Heon.
2026-08-18 09:04:00 +00:00
nicm
6238793ad7 Restore umask if bind fails, from nachalsa at naver dot com. 2026-08-18 09:04:00 +00:00
Nicholas Marriott
da641e4e8d Add test for GitHub issue 5498 from Alex Rattray. 2026-08-18 08:45:11 +01:00
Nicholas Marriott
19a085a672 Fix socket names for macOS. 2026-08-18 08:23:16 +01:00
tmux update bot
f672a0f677 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Log unknown message types.
2026-08-17 23:38:12 +00:00
nicm
db194ced5b Log unknown message types. 2026-08-17 23:38:11 +00:00
tmux update bot
11ed661e2c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Flush output before ending sync. GitHub issue 5495 from xiangzhedev at gmail dot com.
2026-08-17 21:11:33 +00:00
nicm
b2de803871 Flush output before ending sync. GitHub issue 5495 from xiangzhedev at
gmail dot com.
2026-08-17 21:11:31 +00:00
Nicholas Marriott
30abbe36a8 Regress for sync, from xiangzhedev@gmail.com. 2026-08-17 21:04:26 +01:00
tmux update bot
62044f02df Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Allow features to be disabled using @ suffix in terminal-features, from Michael Grant.
2026-08-17 16:39:40 +00:00
nicm
a18d4e0067 Allow features to be disabled using @ suffix in terminal-features, from
Michael Grant.
2026-08-17 16:39:38 +00:00
Nicholas Marriott
0f241b7447 Update CHANGES. 2026-08-17 13:59:10 +01:00
tmux update bot
efaa1fe31c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  If writing a file fails, propagate the error to the server via a new message. Use a client flag rather than bumping the protocol version. GitHub issue 5451.
  Add default terminal features for Rio, GitHub issue 5489 from Raphael Amorim.
2026-08-17 09:45:11 +00:00
nicm
1729bb8e8a If writing a file fails, propagate the error to the server via a new
message. Use a client flag rather than bumping the protocol version.
GitHub issue 5451.
2026-08-17 09:45:10 +00:00
nicm
9fdd04ae61 Add default terminal features for Rio, GitHub issue 5489 from Raphael
Amorim.
2026-08-17 09:45:10 +00:00
tmux update bot
87a8bda023 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Remove flags from both keys for menus, GitHub issue 5484.
2026-08-17 08:54:28 +00:00
nicm
d384557b63 Remove flags from both keys for menus, GitHub issue 5484. 2026-08-17 08:54:26 +00:00
tmux update bot
534a4f81fc Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Attach window to session earlier so resize cannot cause customize mode to blow up. Reported by Marcel Partap.
  Do not copy too many positions when deleting, GitHub issue 5478 from Uzair Aftab.
2026-08-17 07:58:26 +00:00
nicm
a0d3585c6c Attach window to session earlier so resize cannot cause customize mode
to blow up. Reported by Marcel Partap.
2026-08-17 07:58:24 +00:00
nicm
ec34ff52cc Do not copy too many positions when deleting, GitHub issue 5478 from
Uzair Aftab.
2026-08-17 07:58:24 +00:00
Nicholas Marriott
0ac8138ce3 Add a break-pane test for customize mode. 2026-08-17 08:06:31 +01:00
Nicholas Marriott
c41cad4c5f Merge pull request #5481 from r1w1s1/fix-getpeereid-socklen
compat: use socklen_t for getsockopt length
2026-08-17 07:52:02 +01:00
r1w1s1
f466d6289e compat: use socklen_t for getsockopt length 2026-08-10 15:38:55 -03:00
Nicholas Marriott
c6f62289b0 Merge branch 'master' into command_parser 2026-08-07 16:08:26 +01:00
tmux update bot
851c5a933d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Only unzoom in the floating-split case, modal panes take care of themselves.
2026-08-07 09:33:41 +00:00
nicm
10048f6579 Only unzoom in the floating-split case, modal panes take care of themselves. 2026-08-07 09:33:39 +00:00
tmux update bot
a598b2a910 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Always unzoom before splitting window, for floating panes also. We already unzoomed for tiled panes and this does the same for floating panes (until we support having them float over a zoomed pane). From Éric NICOLAS.
2026-08-07 03:40:47 +00:00
nicm
2ec1038ffd Always unzoom before splitting window, for floating panes also. We
already unzoomed for tiled panes and this does the same for floating
panes (until we support having them float over a zoomed pane). From Éric
NICOLAS.
2026-08-07 03:40:45 +00:00
Nicholas Marriott
cbb1da66c3 Merge pull request #5471 from mathstuf/actions-only-on-main-repo
ci: only run `lock` and `regress` workflows on the main repo
2026-08-06 15:44:32 +01:00
Ben Boeckel
35ab660042 ci: only run lock and regress workflows on the main repo
Forks are just wasting CI cycles spinning actions which are probably not
looked at. Also avoid locking issues and PRs on any forks with
upstream's policies.
2026-08-06 10:23:46 -04:00
tmux update bot
40a485db9c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add T key to change pane title, from Fouad Wahabi in GitHub issue 5461.
2026-08-06 11:42:00 +00:00
nicm
641e75e6e2 Add T key to change pane title, from Fouad Wahabi in GitHub issue 5461. 2026-08-06 11:41:58 +00:00
tmux update bot
2e394bef11 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a refresh-now command and change the r binding back to it in copy mode.
2026-08-05 15:40:06 +00:00
nicm
4a9dbac435 Add a refresh-now command and change the r binding back to it in copy mode. 2026-08-05 15:40:04 +00:00
tmux update bot
d60dbcf91c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Ignore Ms if it is invalid (for example no %p1) and report in client mode, GitHub issue 5460.
2026-08-05 11:31:10 +00:00
nicm
bd483a63cf Ignore Ms if it is invalid (for example no %p1) and report in client
mode, GitHub issue 5460.
2026-08-05 11:31:08 +00:00
tmux update bot
d5dc0ba855 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Use ACS arrows for tree mode.
  Do not crash when synchronizing groups and there is no current window. GitHub issue 5467 from Ju-an Zhang.
  Add pane_private_modes format with list of DEC private modes, from George Nachman.
  Do not log when logging is off, from Michael Grant.
2026-08-05 09:10:55 +00:00
nicm
e56a042153 Use ACS arrows for tree mode. 2026-08-05 09:10:54 +00:00
nicm
1995b5559d Do not crash when synchronizing groups and there is no current window.
GitHub issue 5467 from Ju-an Zhang.
2026-08-05 09:10:54 +00:00
nicm
fe384e06bc Add pane_private_modes format with list of DEC private modes, from
George Nachman.
2026-08-05 09:10:54 +00:00
nicm
0198ee9e51 Do not log when logging is off, from Michael Grant. 2026-08-05 09:10:54 +00:00
Nicholas Marriott
72e705cbd6 Tweak times for macOS. 2026-08-05 08:26:10 +01:00
tmux update bot
d5afb67a81 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Switch from imsg_get to imsgbuf_get and switch type of n to int.
2026-08-04 17:23:12 +00:00
claudio
3894397b07 Switch from imsg_get to imsgbuf_get and switch type of n to int.
OK tb@
2026-08-04 17:23:10 +00:00
tmux update bot
36381f0884 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not attempt to drain write buffer on stuck clients, since it requires a weird dance to make libevent do it. Instead, just ignore the client and destroy the buffer normally if not drained in 10 seconds.
2026-08-04 15:37:15 +00:00
nicm
5cd1976a4a Do not attempt to drain write buffer on stuck clients, since it requires
a weird dance to make libevent do it. Instead, just ignore the client
and destroy the buffer normally if not drained in 10 seconds.
2026-08-04 15:37:14 +00:00
tmux update bot
c9ae3f6a02 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Check for floating only for flags that require a floating pane, reported by Ilya Grigoriev.
2026-08-03 23:50:33 +00:00
nicm
d2322d0114 Check for floating only for flags that require a floating pane, reported
by Ilya Grigoriev.
2026-08-03 23:50:32 +00:00
tmux update bot
7a431ec7b9 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not let a stuck client prevent the server from exiting - give up after 10 seconds. GitHub issue 5444 from Ben Maurer.
2026-08-03 21:33:23 +00:00
nicm
114aa68082 Do not let a stuck client prevent the server from exiting - give up
after 10 seconds. GitHub issue 5444 from Ben Maurer.
2026-08-03 21:33:21 +00:00
Nicholas Marriott
f07a76d959 Use move-pane for a test. 2026-08-03 21:30:44 +01:00
Nicholas Marriott
ca0d77855f Client exit test from Ben Maurer. 2026-08-03 21:19:10 +01:00
Nicholas Marriott
cd3f577c85 Add test for control mode events and guards. 2026-08-03 20:27:10 +01:00
tmux update bot
9228f97d7d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Queue control mode notifications rather than emitting them inside %begin/%end, GitHub issue 5458 from George Nachman.
2026-08-03 16:29:11 +00:00
nicm
6db5175e4e Queue control mode notifications rather than emitting them inside
%begin/%end, GitHub issue 5458 from George Nachman.
2026-08-03 16:29:09 +00:00
Nicholas Marriott
fba5ece05c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Store background colour in padding cells and correctly clear adjacent cells when tabs are overwritten, GitHu issue 5441 from Ayman Bagabas.
  Exit failure on socket failure, from Rayan Salhab.
  Pessimize compiler flags for screen-redraw.c to prevent tmux from dumping core upon startup on landisk.
2026-08-03 17:27:11 +01:00
nicm
01a775a46c Store background colour in padding cells and correctly clear adjacent
cells when tabs are overwritten, GitHu issue 5441 from Ayman Bagabas.
2026-08-03 13:59:13 +00:00
nicm
4918360ea1 Exit failure on socket failure, from Rayan Salhab. 2026-08-03 13:59:13 +00:00
Nicholas Marriott
433a5cf580 Add regress test from Ayman Bagabas. 2026-08-03 13:59:27 +01:00
Nicholas Marriott
fc9270c943 Test for socket error from Rayan Salhab. 2026-08-03 11:09:04 +01:00
Nicholas Marriott
fa8c5789a6 Sleep after kill. 2026-08-03 09:53:50 +01:00
miod
cce56fa19b Pessimize compiler flags for screen-redraw.c to prevent tmux from dumping core
upon startup on landisk.
2026-08-02 23:13:19 +00:00
tmux update bot
31dccb6bc9 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Fix unzoomed width when scrollbars are hidden.
2026-07-30 04:22:52 +00:00
nicm
8dfb7d0294 Fix unzoomed width when scrollbars are hidden. 2026-07-30 04:22:51 +00:00
Nicholas Marriott
265fb7b4ea Test for vi keys and padding. 2026-07-29 21:18:38 +01:00
tmux update bot
fa38b36f1f Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Correctly skip padding at end of line, GitHub issue 5411.
2026-07-29 20:04:07 +00:00
nicm
44b8a40b8c Correctly skip padding at end of line, GitHub issue 5411. 2026-07-29 20:04:06 +00:00
tmux update bot
5cd5df1d33 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a flag to turn off scrollbars for display-panes; GitHub issue 5440 from David Marte.
2026-07-29 17:41:02 +00:00
nicm
415d265728 Add a flag to turn off scrollbars for display-panes; GitHub issue 5440
from David Marte.
2026-07-29 17:41:01 +00:00
Nicholas Marriott
b114b7e089 Update tmux.1. 2026-07-29 15:11:38 +01:00
Nicholas Marriott
f85b015dc1 Merge branch 'master' into command_parser 2026-07-29 13:37:36 +01:00
Nicholas Marriott
d6c37ce314 Note SIXEL at end of configure as well. 2026-07-29 11:11:05 +01:00
tmux update bot
c6d02d54d1 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Fix #1 -> #{1} in tmux1, from Rasmus Thystrup Karstensen.
2026-07-29 10:10:07 +00:00
nicm
6f41dac898 Fix #1 -> #{1} in tmux1, from Rasmus Thystrup Karstensen. 2026-07-29 10:10:06 +00:00
Nicholas Marriott
899b4b5471 Merge branch 'master' into command_parser 2026-07-29 10:38:22 +01:00
tmux update bot
5534f1ac32 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Store status position in new mouse event for menus so the position is correct later.
2026-07-28 15:09:08 +00:00
nicm
9f2c535909 Store status position in new mouse event for menus so the position is
correct later.
2026-07-28 15:09:07 +00:00
Nicholas Marriott
7cc0c11cde Add test for mouse menu position. 2026-07-28 14:18:18 +01:00
tmux update bot
31fe1ded5d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Show hook count and time in customize mode.
2026-07-28 12:15:38 +00:00
nicm
6a13d61b48 Show hook count and time in customize mode. 2026-07-28 12:15:37 +00:00
Nicholas Marriott
9c402fa7b7 Test hook time in customize mode. 2026-07-28 11:36:03 +01:00
tmux update bot
a115ca68a5 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a warning about shell metacharacters, from Nikolas Skarlatos.
2026-07-28 10:09:27 +00:00
nicm
a5ad1914b8 Add a warning about shell metacharacters, from Nikolas Skarlatos. 2026-07-28 10:09:26 +00:00
Nicholas Marriott
3c1b2969a8 Tests for hooks time and count. 2026-07-28 08:08:26 +01:00
tmux update bot
e2a5ea2e78 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Store count of how many times a hook is fired and the last time.
2026-07-28 04:30:35 +00:00
nicm
27ab96b284 Store count of how many times a hook is fired and the last time. 2026-07-28 04:30:34 +00:00
tmux update bot
00b5323d5c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  When destroying pane, also close pipe and destroy its event. GitHub issue 5424.
2026-07-27 19:56:21 +00:00
nicm
f751d3f2c1 When destroying pane, also close pipe and destroy its event. GitHub
issue 5424.
2026-07-27 19:56:19 +00:00
Nicholas Marriott
9b97dadc01 Test for pipe-pane. 2026-07-27 15:26:43 +01:00
tmux update bot
8ca512b502 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add after-swap-window hook from basicalllymiloud at gmail dot com.
2026-07-27 14:00:20 +00:00
nicm
5269e14113 Add after-swap-window hook from basicalllymiloud at gmail dot com. 2026-07-27 14:00:18 +00:00
tmux update bot
ff214cf9a1 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Include \n and \t in shell special characters, from Nikolas Skarlatos.
2026-07-26 19:57:58 +00:00
nicm
d57d75deee Include \n and \t in shell special characters, from Nikolas Skarlatos. 2026-07-26 19:57:57 +00:00
tmux update bot
008a3f3f8b Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not try to write to clients which have died, GitHub issue 5431 from Stefan Ladstätter.
2026-07-26 16:16:39 +00:00
nicm
4cc45aa719 Do not try to write to clients which have died, GitHub issue 5431 from
Stefan Ladstätter.
2026-07-26 16:16:37 +00:00
Nicholas Marriott
0255981a54 Test run-shell if killed. 2026-07-26 16:08:58 +01:00
tmux update bot
0a92ff6e76 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Also set ntitles to 0 when creating screen in the first place.
  Reset title count when freeing titles, GitHub issue 5429 from Brett Smith.
  Do not loop forever if the combining character is too long, GitHub issue 5434 from me at qdrs dot dev.
2026-07-26 10:55:47 +00:00
nicm
3068ae06a8 Also set ntitles to 0 when creating screen in the first place. 2026-07-26 10:55:45 +00:00
nicm
55169f1275 Reset title count when freeing titles, GitHub issue 5429 from Brett
Smith.
2026-07-26 10:55:45 +00:00
nicm
afb95f3134 Do not loop forever if the combining character is too long, GitHub issue
5434 from me at qdrs dot dev.
2026-07-26 10:55:45 +00:00
Nicholas Marriott
10726b1b27 Title stack test. 2026-07-26 10:21:01 +01:00
Nicholas Marriott
0768795ce3 Test for overlong runs of combining characters, GitHub issue 5434. 2026-07-26 10:02:31 +01:00
tmux update bot
29bf7fe2f7 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Replace outside as well as empty cells with the border.
2026-07-24 11:46:46 +00:00
nicm
47f210a9d2 Replace outside as well as empty cells with the border. 2026-07-24 11:46:45 +00:00
Nicholas Marriott
e361982acf Fix some tests. 2026-07-24 09:49:32 +01:00
Nicholas Marriott
cc7eea1e34 Merge pull request #5423 from ilyagr/jemalloc-and-asan-mutually-exclusive
configure.ac: don't enable jemalloc if ASAN enabled on macOS, error if both enabled
2026-07-24 07:10:52 +01:00
Ilya Grigoriev
0ec289545a configure.ac: don't enable jemalloc if ASAN enabled on macOS, error if both enabled
ASAN and jemalloc both replace `malloc` with their version.

Previously, configuring with `--enable-asan` on macOS would enable both
jemalloc and ASAN. I'm not sure which one won, but seemed undesired.
2026-07-23 17:43:23 -07:00
tmux update bot
190b5bde16 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Extend fill-character to allow inside and outside to be separate and use a different default (filled characters rather than dots) for inside.
2026-07-23 11:33:53 +00:00
nicm
8862390c34 Extend fill-character to allow inside and outside to be separate and use
a different default (filled characters rather than dots) for inside.
2026-07-23 11:33:52 +00:00
Nicholas Marriott
c59230316b Update CHANGES. 2026-07-23 12:23:12 +01:00
Nicholas Marriott
a2f3bdb601 fill-character test bits. 2026-07-23 10:38:27 +01:00
Nicholas Marriott
689e6f8fac Add jemalloc to regress dependencies. 2026-07-23 09:30:16 +01:00
Nicholas Marriott
6049298140 Show what we have found during configure. 2026-07-23 09:26:57 +01:00
Nicholas Marriott
a10ed3233c Require jemalloc on macOS. 2026-07-23 09:26:42 +01:00
Nicholas Marriott
7568c19276 Log jemalloc version if in use. 2026-07-23 09:12:16 +01:00
Nicholas Marriott
fb88172346 Turn ASAN off again on macOS. 2026-07-23 08:59:49 +01:00
Nicholas Marriott
26bdd2b511 On macOS, look for utf8proc with no arguments and use it if provided. Only
require a flag if it is not available.
2026-07-23 08:59:04 +01:00
Nicholas Marriott
38feebb5d2 Show options tests. 2026-07-23 08:06:09 +01:00
tmux update bot
7428b0431a Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Switch show-options over to using a format and add -F.
  Add a menu on empty areas to allow a new pane to be created.
2026-07-22 22:06:21 +00:00
nicm
7277712ca8 Switch show-options over to using a format and add -F. 2026-07-22 22:06:20 +00:00
nicm
2a5389416a Add a menu on empty areas to allow a new pane to be created. 2026-07-22 22:06:20 +00:00
tmux update bot
597d04ed78 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Show user hooks correctly with show-hooks.
2026-07-22 20:06:33 +00:00
nicm
9625fe3b5f Show user hooks correctly with show-hooks. 2026-07-22 20:06:31 +00:00
Nicholas Marriott
09a2ea190d Update test. 2026-07-22 20:24:21 +01:00
Nicholas Marriott
18fc547b82 Update show hooks tests. 2026-07-22 19:58:55 +01:00
tmux update bot
242239b561 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add A modifier to cycle through a series of values, GitHub issue 5412 from Fernando Daciuk.
2026-07-22 15:11:40 +00:00
nicm
bd4ac02994 Add A modifier to cycle through a series of values, GitHub issue 5412
from Fernando Daciuk.
2026-07-22 15:11:38 +00:00
Nicholas Marriott
0868435e81 Add test for A: modifier, from Fernando Daciuk. 2026-07-22 14:17:22 +01:00
tmux update bot
7abb9af062 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Clip floating panes exactly at window edge, don't let the border creep into the first outside column.
  Invalidate scene when window is resized.
  Add a -C flag to new-pane to have a modal pane close when the mouse is clicked outside it, GitHub issue 5400.
2026-07-21 15:06:24 +00:00
nicm
a57b0f1282 Clip floating panes exactly at window edge, don't let the border creep
into the first outside column.
2026-07-21 15:06:22 +00:00
nicm
c59db2d007 Invalidate scene when window is resized. 2026-07-21 15:06:22 +00:00
nicm
8bf2a4e807 Add a -C flag to new-pane to have a modal pane close when the mouse is
clicked outside it, GitHub issue 5400.
2026-07-21 15:06:22 +00:00
Nicholas Marriott
2848a12562 Update tests for fix to floating panes at window edges. 2026-07-21 14:41:52 +01:00
tmux update bot
92f090c0c0 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  If drawing border but not actually on a border, use the default line style.
  Add copy-mode-current-line-style to set style for current line in copy mode, GitHub issue 5391 from Leo Henon.
2026-07-21 12:55:45 +00:00
nicm
9d583cad5a If drawing border but not actually on a border, use the default line
style.
2026-07-21 12:55:44 +00:00
nicm
abd2f3a9a5 Add copy-mode-current-line-style to set style for current line in copy
mode, GitHub issue 5391 from Leo Henon.
2026-07-21 12:55:44 +00:00
Nicholas Marriott
3e61e63851 Test for outside panes with no border. 2026-07-21 12:56:32 +01:00
tmux update bot
4d1cf62fb4 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Use correct cursor position for range end, GitHub issue 5414.
  Increase escape-time for tmux's own queries (refresh-client -l and window size) as well, GitHub issue 5388 from Jackson Edmonds.
2026-07-21 09:17:33 +00:00
nicm
0e2576a842 Use correct cursor position for range end, GitHub issue 5414. 2026-07-21 09:17:32 +00:00
nicm
418ddada23 Increase escape-time for tmux's own queries (refresh-client -l and
window size) as well, GitHub issue 5388 from Jackson Edmonds.
2026-07-21 09:17:32 +00:00
Nicholas Marriott
16064a410e Merge branch 'master' into command_parser 2026-07-21 08:34:58 +01:00
Nicholas Marriott
8aaae25e56 Fix a merge and add test bits. 2026-07-21 08:00:39 +01:00
Thomas Adam
5ed5e3606e Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Change cellused/size to 16 bits and time to 32 bits in grid_line and add the OSC 133 positions (size stays the same).
2026-07-20 18:17:19 +01:00
nicm
d13b69ece4 Change cellused/size to 16 bits and time to 32 bits in grid_line and add
the OSC 133 positions (size stays the same).
2026-07-20 14:44:19 +00:00
tmux update bot
88a91b9f83 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Correctly include status line in pane height.
2026-07-20 11:14:58 +00:00
nicm
efcfc55535 Correctly include status line in pane height. 2026-07-20 11:14:56 +00:00
tmux update bot
afb3e954bf Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Remove bits to disable removing empty lines since actually we want to do that - zoom added them in the first place.
2026-07-19 22:36:41 +00:00
nicm
aea209b142 Remove bits to disable removing empty lines since actually we want to do
that - zoom added them in the first place.
2026-07-19 22:36:40 +00:00
tmux update bot
8dd0e2129e Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Copy the home directory if it comes from getpwuid.
2026-07-19 20:33:37 +00:00
nicm
8024fab1f2 Copy the home directory if it comes from getpwuid. 2026-07-19 20:33:36 +00:00
Nicholas Marriott
c750c014b2 Extend test. 2026-07-19 20:58:20 +01:00
tmux update bot
1b179582e6 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add unzoomed width and height formats and use them for the sizes shown in panes mode, reported by tb@.
2026-07-19 19:57:33 +00:00
nicm
c766669b3b Add unzoomed width and height formats and use them for the sizes shown
in panes mode, reported by tb@.
2026-07-19 19:57:31 +00:00
tmux update bot
c45bf2e84d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Guard x as well as y to avoid overflow, GitHub issue 5409 from Park Seongjae.
2026-07-19 18:29:04 +00:00
nicm
6ef280fb0f Guard x as well as y to avoid overflow, GitHub issue 5409 from Park
Seongjae.
2026-07-19 18:29:02 +00:00
Nicholas Marriott
2a2bce3dd4 Fix tty-keys.sh test. 2026-07-19 18:34:18 +01:00
tmux update bot
cad1c81c71 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Prevent display-panes from shifting the active pane scroll position by copying the screen from before zoom, also do not let resize consume blank lines.
2026-07-17 19:34:36 +00:00
nicm
496bfdce38 Prevent display-panes from shifting the active pane scroll position by
copying the screen from before zoom, also do not let resize consume
blank lines.
2026-07-17 19:34:34 +00:00
tmux update bot
bacc7952ad Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add some missing calls to invalidate scene.
2026-07-17 16:49:56 +00:00
nicm
377a810168 Add some missing calls to invalidate scene. 2026-07-17 16:49:55 +00:00
Nicholas Marriott
481d48b53b 3.8 changes. 2026-07-17 17:44:43 +01:00
tmux update bot
6dacc024f0 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Remove the active-pane flag for now, there are some gaps in how this works and I don't like it. May come back in a different form (maybe just for windows).
2026-07-17 14:08:10 +00:00
nicm
1ce000062a Remove the active-pane flag for now, there are some gaps in how this
works and I don't like it. May come back in a different form (maybe just
for windows).
2026-07-17 14:08:08 +00:00
Thomas Adam
5bd10a8c2a Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Move per-client window sizes into control.c since the only user is for control mode.
  Change %% to escape single quotes (%1 remains with no quoting and %% with double quotes) to avoid a single quote being able to terminate quoted sections of commands. Fixes unexpected behaviour with session names containing single quotes reported by Aliz Hammond.
2026-07-17 13:24:25 +01:00
nicm
2711780dff Move per-client window sizes into control.c since the only user is for
control mode.
2026-07-17 12:15:52 +00:00
nicm
a177d0f590 Change %% to escape single quotes (%1 remains with no quoting and %%
with double quotes) to avoid a single quote being able to terminate
quoted sections of commands. Fixes unexpected behaviour with session
names containing single quotes reported by Aliz Hammond.
2026-07-17 12:15:52 +00:00
tmux update bot
a8ad3c7d43 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Clear cfg_client if the client is destroyed to prevent UAF found by Ilya Grigoriev.
2026-07-17 09:00:53 +00:00
nicm
d5b1f3ad89 Clear cfg_client if the client is destroyed to prevent UAF found by Ilya
Grigoriev.
2026-07-17 09:00:52 +00:00
Nicholas Marriott
b46684427f Extend control client size test. 2026-07-17 09:37:50 +01:00
Nicholas Marriott
5cd43626ef cmd_template_replace test. 2026-07-17 09:29:59 +01:00
Nicholas Marriott
43092f8d54 Test for client lost during config file. 2026-07-17 09:13:33 +01:00
Your Name
fcc7366e86 Merge branch 'master' into command_parser 2026-07-16 21:26:42 +01:00
tmux update bot
e50545f451 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Fix uninitialized var for sscanf %n.
2026-07-16 16:43:22 +00:00
nicm
1d0d22df22 Fix uninitialized var for sscanf %n. 2026-07-16 16:43:21 +00:00
tmux update bot
f07ea87e20 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not redraw overlays unless actually asked to, GitHub issue 5398 from Noam Stolero.
2026-07-16 12:54:46 +00:00
nicm
824a07290f Do not redraw overlays unless actually asked to, GitHub issue 5398 from
Noam Stolero.
2026-07-16 12:54:45 +00:00
Nicholas Marriott
fba51526af Modal pane regression test. 2026-07-16 13:16:29 +01:00
tmux update bot
15746a1bc7 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Small section missed from previous.
  Add modal panes, created currently with new-pane -O. There is one modal pane per window and it must be a floating pane. These are intended to replace popups. Currently a modal pane will unzoom a zoomed window and rezoom it when it is closed (a bit like modes do), but this is planned to change when we get an always-on-top flag.
2026-07-15 15:53:06 +00:00
nicm
edefce8eb5 Small section missed from previous. 2026-07-15 15:53:04 +00:00
nicm
d98635e1bd Add modal panes, created currently with new-pane -O. There is one modal
pane per window and it must be a floating pane. These are intended to
replace popups. Currently a modal pane will unzoom a zoomed window and
rezoom it when it is closed (a bit like modes do), but this is planned
to change when we get an always-on-top flag.
2026-07-15 15:53:04 +00:00
tmux update bot
9459c7ca64 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Clear the "waiting for editor" area before writing so stray characters aren't visible from underneath.
2026-07-15 13:29:09 +00:00
nicm
75c874c55b Clear the "waiting for editor" area before writing so stray characters
aren't visible from underneath.
2026-07-15 13:29:08 +00:00
tmux update bot
10b2bb76f6 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a mouse binding for empty areas of the window so C-Drag works on them as well.
2026-07-15 12:04:25 +00:00
nicm
cbb97b961c Add a mouse binding for empty areas of the window so C-Drag works on
them as well.
2026-07-15 12:04:24 +00:00
Nicholas Marriott
5350da0be3 Update test. 2026-07-15 11:38:49 +01:00
Nicholas Marriott
5d2b6542b6 Fix control mode error and update regression tests. 2026-07-15 11:05:15 +01:00
Nicholas Marriott
1c7ed8d9ad Remove README.ja from EXTRA_DIST. 2026-07-15 10:40:47 +01:00
Nicholas Marriott
8f2d25024a Remove README.ja which has not been updated since 2018 and is out of date. 2026-07-15 10:39:46 +01:00
Nicholas Marriott
66e6b2e1c3 Merge master into command_parser 2026-07-15 10:34:14 +01:00
tmux update bot
4e586ed0b8 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Switch menus also to be attached to the window instead of an overlay and drawn as part of the client's scene. This means removing the popup menu.
2026-07-14 22:13:52 +00:00
nicm
ad6832e697 Switch menus also to be attached to the window instead of an overlay and
drawn as part of the client's scene. This means removing the popup menu.
2026-07-14 22:13:51 +00:00
Nicholas Marriott
11496010c0 Update test outputs. 2026-07-14 22:15:08 +01:00
Nicholas Marriott
e54785ddd6 Add menu tests. 2026-07-14 22:09:52 +01:00
Thomas Adam
6546fa0982 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Convert display-panes away from an overlay and into a mode. This is another step on the road of getting rid of overlays altogether (they are full of special cases; popups and menus are to go also eventually). We lose some of the styling of borders but gain the ability to run display-panes in another pane (perhaps not hugely useful but one never knows). The -b flag goes away as no longer useful.
2026-07-14 21:20:22 +01:00
nicm
1a02c9957c Convert display-panes away from an overlay and into a mode. This is
another step on the road of getting rid of overlays altogether (they are
full of special cases; popups and menus are to go also eventually). We
lose some of the styling of borders but gain the ability to run
display-panes in another pane (perhaps not hugely useful but one never
knows). The -b flag goes away as no longer useful.
2026-07-14 19:57:30 +00:00
tmux update bot
145f8ef173 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Typos and doc fixes, from Ben Boeckel.
2026-07-14 17:24:20 +00:00
nicm
34a11db6ca Typos and doc fixes, from Ben Boeckel. 2026-07-14 17:24:18 +00:00
Nicholas Marriott
d705471aa5 Regression tests for display-panes changes. 2026-07-14 18:18:52 +01:00
tmux update bot
67f71fe4ac Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Handle padding and tab redraw correctly in copy mode.
2026-07-14 13:12:49 +00:00
nicm
a10252dad2 Handle padding and tab redraw correctly in copy mode. 2026-07-14 13:12:48 +00:00
tmux update bot
c9d6d4b601 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not clear lines when we are just going to overwrite them in copy mode.
2026-07-14 12:17:19 +00:00
nicm
f88189cd59 Do not clear lines when we are just going to overwrite them in copy mode. 2026-07-14 12:17:17 +00:00
Nicholas Marriott
c39d69f961 More copy mode tests. 2026-07-14 12:05:06 +01:00
Nicholas Marriott
cf110d9d1d Copy mode redraw test. 2026-07-14 10:07:51 +01:00
tmux update bot
ed4245a74d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add e key to open editor to customize mode (like buffer mode) and a key to show only changed items.
2026-07-14 04:18:54 +00:00
nicm
09d5977263 Add e key to open editor to customize mode (like buffer mode) and a key
to show only changed items.
2026-07-14 04:18:52 +00:00
tmux update bot
42b3ea0d74 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Apply dragging change to scroll-down as well as scroll-up.
2026-07-13 22:40:19 +00:00
nicm
2a75cc2236 Apply dragging change to scroll-down as well as scroll-up. 2026-07-13 22:40:18 +00:00
Nicholas Marriott
df6ed20271 Add customize mode test. 2026-07-13 23:03:33 +01:00
tmux update bot
de7fdbe85d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add environment to customize mode also
2026-07-13 21:54:09 +00:00
nicm
37da97fbe6 Add environment to customize mode also 2026-07-13 21:54:08 +00:00
Nicholas Marriott
f0f59bc2fd Fix a test. 2026-07-13 22:52:25 +01:00
Nicholas Marriott
9c65d349e8 Tweak times in tests. 2026-07-13 22:08:58 +01:00
Nicholas Marriott
2818069d5f Merge pull request #5389 from YayoRazo/fix/prompt-history-getline
fix(compat): drop fgetln compat shim, migrate prompt-history.c to getline
2026-07-13 21:25:10 +01:00
tmux update bot
2f0308ea4d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Replace last use of fgetln with getline, GitHub issue 5389 from Yayo Razo.
  Add hooks to customize mode.
2026-07-13 20:22:00 +00:00
nicm
b1c347ee0c Replace last use of fgetln with getline, GitHub issue 5389 from Yayo Razo. 2026-07-13 20:21:59 +00:00
nicm
fba5083b58 Add hooks to customize mode. 2026-07-13 20:21:59 +00:00
Thomas Adam
9c9477ae76 Tweak previous; include SYNCING.md 2026-07-13 20:30:47 +01:00
Thomas Adam
e4dbd11d44 doc: rewrite SYNCING and reformat to markdown
Rewrite the SYNCING document to reflect that everything runs on Github.

Add examples on how to deal with merge conflicts.

Also reformat to markdown so any examples are more obvious in different
renderers.
2026-07-13 20:29:35 +01:00
Yayo Razo
c3e38b317d fix(compat): drop fgetln compat shim, migrate prompt-history.c to getline
Following up on the comment on #5365 - this drops fgetln from
prompt-history.c (its only remaining caller) in favor of getline, and
removes the fgetln compat shim entirely since nothing references it
anymore: the compat.h declaration, compat/fgetln.c itself, and its
entry in configure.ac's AC_REPLACE_FUNCS list.

fgetln doesn't null-terminate its returned buffer when the final line
in the file lacks a trailing newline, so the old loop had a second
branch to heap-copy and manually terminate that case. getline always
null-terminates regardless, so that branch goes away and the loop
becomes uniform.

Verified with a real build (ubuntu-24.04 container, same
--enable-utf8proc --enable-asan configure CI uses): crafted a history
file with a normal entry, a blank line, and a final entry with no
trailing newline at EOF (the exact case the removed branch existed
for), loaded it through a real ASan-built tmux via show-prompt-history,
all three entries came back correctly. Full regress suite is otherwise
clean against this change.
2026-07-13 13:03:58 -06:00
tmux update bot
f75ef5ca1c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add keys and menu items for copy mode line numbers, and a menu item for auto refresh.
2026-07-13 18:59:03 +00:00
nicm
8fbbde7035 Add keys and menu items for copy mode line numbers, and a menu item for
auto refresh.
2026-07-13 18:59:02 +00:00
Nicholas Marriott
2dc05ae328 Add to alerts tests. 2026-07-13 19:52:27 +01:00
tmux update bot
511fe53846 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add new-pane -M to create a new pane from a mouse binding and bind to C-MouseDrag1Pane, removing the old ctrl-click-to-swap-pane binding.
2026-07-13 17:43:10 +00:00
nicm
658e087f7a Add new-pane -M to create a new pane from a mouse binding and bind to
C-MouseDrag1Pane, removing the old ctrl-click-to-swap-pane binding.
2026-07-13 17:43:09 +00:00
tmux update bot
dba85ee32e Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add pane-activity event.
2026-07-13 16:21:15 +00:00
nicm
b91086a5c8 Add pane-activity event. 2026-07-13 16:21:14 +00:00
Nicholas Marriott
4cd39ee33e new-pane mouse test. 2026-07-13 17:10:04 +01:00
tmux update bot
e3ef97b6b4 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add some additional simple events.
  fix window_copy_mouse_in_selection() indentation; ok nicm@
  Work out a single layout for the entire prompt so it can all be drawn together, fixes centered prompts. GitHub issue 5328.
  Allow drag on a selection to modify it, from Michael Grant.
  Make split-window inside a floating pane work more nicely and not immediately create an overlapping pane, from Dane Jensen.
  Set other cell when creating layout set, from Dane Jensen.
  Size comparison should be >= not >, from Yayo Razo.
  Remove worflow from master
2026-07-13 14:35:35 +00:00
nicm
e00a1a3e26 Add some additional simple events. 2026-07-13 14:35:33 +00:00
jsg
006cce5e4d fix window_copy_mouse_in_selection() indentation; ok nicm@ 2026-07-13 14:35:33 +00:00
nicm
4a06a1f296 Work out a single layout for the entire prompt so it can all be drawn
together, fixes centered prompts. GitHub issue 5328.
2026-07-13 14:35:33 +00:00
nicm
fe1b877410 Allow drag on a selection to modify it, from Michael Grant. 2026-07-13 14:35:33 +00:00
nicm
2b091d91d3 Make split-window inside a floating pane work more nicely and not
immediately create an overlapping pane, from Dane Jensen.
2026-07-13 14:35:33 +00:00
nicm
6d227a8479 Set other cell when creating layout set, from Dane Jensen. 2026-07-13 14:35:33 +00:00
nicm
447d804a3a Size comparison should be >= not >, from Yayo Razo. 2026-07-13 14:35:33 +00:00
Nicholas Marriott
687c6b9cc0 Regress for more hooks. 2026-07-13 13:57:31 +01:00
Nicholas Marriott
b2e2fe824d Add scroll regress. 2026-07-13 11:32:28 +01:00
Nicholas Marriott
baf71480d0 Merge remote-tracking branch 'origin/master' into command_parser
# Conflicts:
#	cmd-queue.c
#	notify.c
#	options.c
2026-07-13 11:12:46 +01:00
Thomas Adam
1f6385e7bb build: remove openbsd sync job
This only needs to run from the openbsd-cutover repository and will no
longer be merged into thisd repo.
2026-07-13 11:03:46 +01:00
Thomas Adam
309119f659 Remove worflow from master 2026-07-13 10:54:27 +01:00
tmux update bot
884ae3f8da Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  build: run every fifteen minutes
2026-07-13 09:42:03 +00:00
Nicholas Marriott
d9b953d5c6 Create SECURITY.md 2026-07-13 10:29:28 +01:00
Nicholas Marriott
e0014bbc7c Add regress tests for events code. 2026-07-13 10:23:38 +01:00
Thomas Adam
d03de4986a build: run every fifteen minutes 2026-07-13 09:27:05 +01:00
Thomas Adam
6126988781 portable: add Makefile.am 2026-07-13 09:22:53 +01:00
Thomas Adam
25e2e1d671 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  build: don't exit
  build: always attempt portable merge
  build: capture openbsd-base before filtering
  build: fetch cutover master before merge
  Use _exit in child after fork, from Yayo Razo in GitHub issue 5376.
  Add missing calloc check and use fatal. Prompted by a similar change from Yayo Razo.
  actions: use published openbsd-git as base update
  github actions: automate updates
  Do not make pty fds blocking again until all the data has been consumed or control mode clients can get stuck, GitHub issue 5356 from Ben Maurer.
  Add formats and events for OSC 133 commmands, as well as a -T flag to set-hook -B to only fire when the format is true.
  Replace the notification system with events. Events can carry a payload of additional payload (to reduce problems with lifetime of objects) and are delivered to one or more event sinks. This is more powerful and reduces the complex dependencies between control mode and hooks. Events are now used for hooks, control mode notifications and for monitors (set-hook -B).
2026-07-13 09:22:25 +01:00
Thomas Adam
e6eb26518b build: don't exit 2026-07-13 09:13:28 +01:00
Thomas Adam
8b2bc1a384 build: always attempt portable merge 2026-07-13 09:10:24 +01:00
Thomas Adam
0ce72059e6 build: capture openbsd-base before filtering 2026-07-13 09:05:10 +01:00
Thomas Adam
f80d3f6123 build: fetch cutover master before merge 2026-07-13 09:00:47 +01:00
nicm
0925c9846b Use _exit in child after fork, from Yayo Razo in GitHub issue 5376. 2026-07-13 07:57:44 +00:00
nicm
3612a9a605 Add missing calloc check and use fatal. Prompted by a similar change
from Yayo Razo.
2026-07-13 07:57:44 +00:00
Thomas Adam
cc8e0f2f67 actions: use published openbsd-git as base update 2026-07-13 08:50:49 +01:00
Nicholas Marriott
a662981f10 Bump a time limit. 2026-07-13 07:08:05 +01:00
Nicholas Marriott
d77c9dc6aa Tweak BIDI test. 2026-07-12 23:18:47 +01:00
Nicholas Marriott
8ac3fcedca Do not reuse sockets so much to avoid problems with slow server shutdown. 2026-07-12 22:03:16 +01:00
Nicholas Marriott
ea236c12a6 Merge pull request #5382 from YayoRazo/fix/sixel-saved-images-uaf
fix(screen): free alternate-screen images on screen destroy
2026-07-12 21:40:02 +01:00
Nicholas Marriott
a53a0aa0b5 Merge pull request #5365 from YayoRazo/fix/getline-fgetln-overflow
fix(compat): guard buffer size multiplication in getline
2026-07-12 21:18:37 +01:00
Nicholas Marriott
5e3db12c07 No need for ASAN_OPTIONS. 2026-07-12 21:02:30 +01:00
Nicholas Marriott
4f981a2506 Turn on ASAN for macOS regress. 2026-07-12 20:59:11 +01:00
Thomas Adam
b602dc57b1 github actions: automate updates 2026-07-12 17:24:37 +01:00
Nicholas Marriott
330bedfb47 Build with ASAN on macOS by default. 2026-07-12 13:26:25 +01:00
Yayo Razo
3ff24a8eb4 fix(compat): guard buffer size multiplication in getline 2026-07-11 17:07:21 -06:00
Yayo Razo
28a1231100 fix(screen): free alternate-screen images on screen destroy 2026-07-11 15:53:08 -06:00
Nicholas Marriott
6fd9987632 Revert "Add formats and events for OSC 133 commmands, as well as a -T flag to"
This reverts commit f3c6b4f1a3.
2026-07-11 04:37:04 +01:00
Thomas Adam
8f27092756 Merge branch 'obsd-master' 2026-07-10 18:20:43 +01:00
Nicholas Marriott
e7fef1fd64 Test for wait exit, GitHub issue 5356 from Ben Maurer. 2026-07-10 16:45:58 +01:00
nicm
7f2b0ae16e Do not make pty fds blocking again until all the data has been consumed
or control mode clients can get stuck, GitHub issue 5356 from Ben Maurer.
2026-07-10 15:45:11 +00:00
nicm
0cc84b8cae Do not make pty fds blocking again until all the data has been consumed
or control mode clients can get stuck, GitHub issue 5356 from Ben Maurer.
2026-07-10 15:45:11 +00:00
Nicholas Marriott
1329473a4b Merge pull request #5358 from bmaurer/regress-control-client-exit
Add a regression test for output queued to exiting control clients
2026-07-10 16:39:04 +01:00
Thomas Adam
84cc6541f3 Merge branch 'obsd-master' 2026-07-10 16:30:07 +01:00
nicm
512255b9a7 Add formats and events for OSC 133 commmands, as well as a -T flag to
set-hook -B to only fire when the format is true.
2026-07-10 15:20:06 +00:00
nicm
f3c6b4f1a3 Add formats and events for OSC 133 commmands, as well as a -T flag to
set-hook -B to only fire when the format is true.
2026-07-10 15:20:06 +00:00
Ben Maurer
ad662d3926 Add a regression test for output queued to exiting control clients.
Checks that once a control client is marked for exit the server stops
queueing notifications for it (PR 5357). The client's output is drained only
slowly so a backlog holds the exit handshake open; the client is then
detached and the window renamed, and the %window-renamed notifications must
not appear in the client's output stream.
2026-07-10 07:54:21 -07:00
nicm
d29aa12117 Replace the notification system with events. Events can carry a payload of
additional payload (to reduce problems with lifetime of objects) and are
delivered to one or more event sinks. This is more powerful and reduces the
complex dependencies between control mode and hooks. Events are now used for
hooks, control mode notifications and for monitors (set-hook -B).

wait-for can now wait for an event to fire (-E flag, with -F to for filter),
with -v to print the payload, as well as listing (-l) waiting clients on an
event and forcing one to wake (-w).

A few additional hooks are also now available (pane-created, pane-resized, etc)
and some of the existing ones have additional format variables available.
2026-07-10 13:38:45 +00:00
Thomas Adam
ca6dd6ca92 Merge branch 'obsd-master' 2026-07-10 08:30:07 +01:00
nicm
1508bec957 Don't bother to send notifications to clients with CLIENT_EXIT, GitHub
issue 5357 from Ben Maurer.
2026-07-10 07:25:05 +00:00
nicm
bcc73e0db4 Change NOTHING (which means only redraw the indicator) to MOVE. And
redefine NOTHING as /actually/ nothing. Use it for commands which
actually make no change. Fixes flickering due to excessive redrawing of
indicator lines. Mostly from Michael Grant.
2026-07-10 07:13:05 +00:00
Nicholas Marriott
ec31f4566d Tweak test. 2026-07-09 09:39:44 +01:00
Thomas Adam
dfb2a9404f Merge branch 'obsd-master' 2026-07-09 09:30:06 +01:00
Thomas Adam
7ac0d6bead Merge branch 'obsd-master' 2026-07-09 09:00:06 +01:00
Nicholas Marriott
041b6a6bc7 Add test from xiaomublue at gmail dot com. 2026-07-09 08:56:06 +01:00
nicm
3a6c717bb8 Do not exit at bottom if a selection is in progress, GitHub issue 5349
from xiaomublue at gmail dot com.
2026-07-09 07:54:07 +00:00
Thomas Adam
ec74c46d1e Merge branch 'obsd-master' 2026-07-09 08:45:06 +01:00
nicm
161dbaf1ce Cancel pending resizes and resize immediately when entering alternate
screen, fixes flickering with scrollbars. GitHub issue 5351 from Michael
Grant.
2026-07-09 07:35:05 +00:00
nicm
e242da168b Clip the status line and menu against an open popup overlay, GitHub
issue 5350 from Noam Stolero.
2026-07-09 07:32:58 +00:00
nicm
dea309cf8d Allow empty name with new-window again, from Jacob Koziej. 2026-07-09 07:19:24 +00:00
Thomas Adam
ee3944c8fd Merge branch 'obsd-master' 2026-07-09 08:00:06 +01:00
nicm
1fefb80445 Add t/d for time difference in seconds. 2026-07-09 06:33:19 +00:00
Nicholas Marriott
f92cb43ce4 t/d test. 2026-07-08 12:36:19 +01:00
Thomas Adam
cd2ca9ec34 Merge branch 'obsd-master' 2026-07-08 12:15:06 +01:00
nicm
e37a2e54ab Fix regsub with anchor at start of pattern, GitHub issue 5341. 2026-07-08 11:04:51 +00:00
Thomas Adam
53f3b05001 Merge branch 'obsd-master' 2026-07-08 10:30:05 +01:00
nicm
8e76565c9c Keep all modes except cursor during redraw, GitHub issue 5337 from Ayman
Bagabas.
2026-07-08 09:23:10 +00:00
Nicholas Marriott
c830710e11 More sub tests. 2026-07-08 09:55:21 +01:00
Thomas Adam
0f354ac4e0 Merge branch 'obsd-master' 2026-07-08 09:30:06 +01:00
Nicholas Marriott
e601bfb362 Theme report test from Ayman Bagabas. 2026-07-08 09:22:55 +01:00
nicm
dac6514147 Report the terminal's own theme to panes rather than guessing from the
background. GitHub issue 5343 from Ayman Bagabas.
2026-07-08 08:21:53 +00:00
nicm
ac723bf2df Do not crash looking for next or previous session. GitHub issue 5344. 2026-07-08 08:15:49 +00:00
Thomas Adam
c8f6b54da7 Merge branch 'obsd-master' 2026-07-08 09:15:06 +01:00
Nicholas Marriott
27c26e88e5 detach-on-destroy next/previous tests. 2026-07-08 09:16:50 +01:00
Nicholas Marriott
e77d9ad043 -k/empty panes tests. 2026-07-08 09:07:48 +01:00
nicm
25060c3fa5 Fix empty argument to splitw/newp and add -k to copy-mode. 2026-07-08 08:07:42 +00:00
nicm
28c2005a2a Fix a typo (use right var), from Dane Jensen. 2026-07-08 07:55:17 +00:00
Thomas Adam
db115c6b97 Merge branch 'obsd-master' 2026-07-07 14:00:06 +01:00
nicm
7b02cb6d03 Fix . and ; bindings, reported by tb. 2026-07-07 12:30:36 +00:00
Thomas Adam
8beb17df0d Merge branch 'obsd-master' 2026-07-07 11:00:08 +01:00
nicm
76b693aa81 Add C-b g , and C-b g . for move and resize menus, also add to pane menu
(and fix to make them work).
2026-07-07 09:45:09 +00:00
Thomas Adam
d53f1b7458 Merge branch 'obsd-master' 2026-07-07 09:15:06 +01:00
nicm
7188391a22 Add a layout_geometry container struct, from Dane Jensen. 2026-07-07 08:07:26 +00:00
Thomas Adam
869170abd6 Merge branch 'obsd-master' 2026-07-07 09:00:07 +01:00
nicm
1ea574e4ca Set initial scrollbar state, from Michael Grant. 2026-07-07 07:47:03 +00:00
nicm
af35bcb64f Restore { and } to swap-pane and for floating panes and use a separate
key table accessed by g (short for "gove and/or gesize") for floating
panes. So "C-b g Up" moves the pane to top-centre, "C-b g M-Up" moves
and resizes it. This means we not only do not lose the surprisingly
popular { and } bindings, but we can have more than four keys for moving
and resizing panes.
2026-07-07 07:43:07 +00:00
Thomas Adam
55ce48c152 Merge branch 'obsd-master' 2026-07-06 23:00:06 +01:00
nicm
ff09914327 Could of minor tidy ups. 2026-07-06 21:44:26 +00:00
Nicholas Marriott
3946e24eff Add array test. 2026-07-06 18:13:25 +01:00
Thomas Adam
b07424224b Merge branch 'obsd-master' 2026-07-06 17:00:06 +01:00
nicm
d33d5b7de8 When sync is enabled, we need to mark the whole scroll region dirty if
there has been any scrolling. GitHub issue 5330.
2026-07-06 15:41:31 +00:00
Thomas Adam
9180356b13 Merge branch 'obsd-master' 2026-07-06 16:00:06 +01:00
nicm
20ef19b42f Add a key to change an array key in customize mode. 2026-07-06 14:40:57 +00:00
nicm
1117caaf09 Change array item keys to be strings instead of numbers which are
painful to work with.
2026-07-06 14:29:10 +00:00
Thomas Adam
e6505947ec Merge branch 'obsd-master' 2026-07-06 13:30:06 +01:00
nicm
79b83600af NOJOBS is a flag, use correct argument. 2026-07-06 12:10:18 +00:00
nicm
85e3ca5ae2 Check for width/height 0, based on change from Uzair Aftab. 2026-07-06 12:08:52 +00:00
Thomas Adam
e11af53819 Merge branch 'obsd-master' 2026-07-06 13:00:06 +01:00
Thomas Adam
0593cbeb84 Merge branch 'obsd-master' 2026-07-06 12:30:06 +01:00
nicm
d565fd3b54 Do not create zero size panes, from Uzair Aftab. 2026-07-06 11:28:39 +00:00
nicm
699b18414d Do not allow jobs in monitor formats. 2026-07-06 11:17:28 +00:00
Nicholas Marriott
eb82e8527a Shorten all socket names because macOS is stupid. 2026-07-06 11:50:58 +01:00
Nicholas Marriott
069201dd1a Regression test fixes. 2026-07-06 11:22:56 +01:00
Nicholas Marriott
37e4b3cdb6 Fix name. 2026-07-06 10:12:24 +01:00
Nicholas Marriott
501e8da925 Update some tests and improve Makefile. 2026-07-06 10:00:26 +01:00
Nicholas Marriott
6a6822b906 Shorten socket names. 2026-07-06 09:27:17 +01:00
Thomas Adam
e7c1361a10 Merge branch 'obsd-master' 2026-07-06 09:15:06 +01:00
nicm
913e503296 Unzoom before creating a floating pane (for now). 2026-07-06 07:57:45 +00:00
Nicholas Marriott
2c0f06782d Add alerts test. 2026-07-06 08:47:57 +01:00
Nicholas Marriott
fcce73af42 Add another test. 2026-07-05 22:14:48 +01:00
Nicholas Marriott
2b2963859b Add some hooks tests. 2026-07-05 21:56:52 +01:00
Thomas Adam
327c05307a Merge branch 'obsd-master' 2026-07-05 20:00:06 +01:00
nicm
3f065cf789 Add a window pane reference count. 2026-07-05 18:37:57 +00:00
Nicholas Marriott
dc1693d5a1 set-hook -R test. 2026-07-05 18:26:44 +01:00
Nicholas Marriott
29b2b7a875 Mouse on by default. 2026-07-05 15:50:54 +01:00
Thomas Adam
34af038fcc Merge branch 'obsd-master' 2026-07-05 15:01:36 +01:00
Nicholas Marriott
d422621eaa Change a couple of regress configs that are incorret and only work because of
differences in parse-only.
2026-07-05 14:36:47 +01:00
Thomas Adam
f556e4b6fc Merge branch 'obsd-master' 2026-07-05 12:00:06 +01:00
nicm
2fdcb64a79 Add a define for whether mouse is enabled by default (still off). 2026-07-05 10:49:05 +00:00
nicm
468c2942a3 Apply tree mode selection style to the entire line. 2026-07-05 10:28:02 +00:00
Nicholas Marriott
df56ba95fc Update regress for monitor. 2026-07-05 10:21:44 +01:00
Nicholas Marriott
bd39ce0d49 Extend tests. 2026-07-05 10:20:15 +01:00
Thomas Adam
5068e4149a Merge branch 'obsd-master' 2026-07-05 09:30:06 +01:00
nicm
de024e04d7 Add set-hook -B install a subscription like in control mode.
Subscriptions are formats which are checked once a second and if changed
invoke a hook. show-hooks -B lists.
2026-07-05 08:24:00 +00:00
Nicholas Marriott
7ff972773a Bump some times. 2026-07-05 09:06:19 +01:00
Nicholas Marriott
9634538106 Fix test. 2026-07-05 08:17:32 +01:00
Thomas Adam
5356c62ead Merge branch 'obsd-master' 2026-07-04 23:30:06 +01:00
Thomas Adam
9af9226ff9 Merge branch 'obsd-master' 2026-07-04 23:15:06 +01:00
Nicholas Marriott
92028ed6e2 Add a few more tests. 2026-07-04 23:09:16 +01:00
nicm
7a8f8f5a74 Do not use stale pointers in modes. 2026-07-04 22:09:06 +00:00
nicm
415885ecb9 Infer the terminal theme from the background colour correctly, if theme
reporting is not supported.
2026-07-04 22:06:45 +00:00
Thomas Adam
7b833d07d9 Merge branch 'obsd-master' 2026-07-04 21:30:06 +01:00
Nicholas Marriott
2f58804717 Add some basic choose mode tests. 2026-07-04 21:18:48 +01:00
nicm
b05f0abeeb Do not ignore filter when >1 pane. GitHub issue 5326. 2026-07-04 20:17:53 +00:00
Thomas Adam
2d75f79217 Merge branch 'obsd-master' 2026-07-04 20:15:06 +01:00
nicm
898a91903a Add some missing checks which make new pane positions off, from Dane
Jensen.
2026-07-04 18:54:18 +00:00
Nicholas Marriott
6e9574c715 Linux arm64 fails. 2026-07-04 19:40:28 +01:00
Nicholas Marriott
2efd2d4970 Enable macOS regress. 2026-07-04 19:25:39 +01:00
Nicholas Marriott
219dc7f99e Locale breaks tests. 2026-07-04 19:13:53 +01:00
Nicholas Marriott
1b4015226f Use correct counts for this test also. 2026-07-04 19:01:48 +01:00
Nicholas Marriott
0f77d9b30a Fix lengths in test. 2026-07-04 18:52:53 +01:00
Nicholas Marriott
a19b961427 Fix test. 2026-07-04 18:37:53 +01:00
Nicholas Marriott
646637f53a Add control subs test. 2026-07-04 17:18:33 +01:00
Thomas Adam
21e91cfa00 portable: add monitor.c 2026-07-04 17:07:31 +01:00
Thomas Adam
f32b65ac4a Merge branch 'obsd-master' 2026-07-04 17:06:56 +01:00
Nicholas Marriott
a02e25ba01 Do not invoke clear check on respawn. 2026-07-04 17:00:28 +01:00
nicm
c5a7309966 Dirty ED 2 correctly, GiHub issue 5322 from git at twilligon dot com. 2026-07-04 08:34:16 +00:00
Nicholas Marriott
1d7021130d Set ASAN_OPTIONS in regress Makefile. 2026-07-04 09:15:32 +01:00
nicm
8e03310afe Move monitor stuff out of control.c into its own file for reuse. 2026-07-03 22:58:23 +00:00
Thomas Adam
31b0b0c99e Merge branch 'obsd-master' 2026-07-03 20:00:06 +01:00
Thomas Adam
90492aa26d Merge branch 'obsd-master' 2026-07-03 19:30:06 +01:00
nicm
d512a65d4c Add O and V loops in formats for options and environment. 2026-07-03 18:28:57 +00:00
nicm
4378a17fbe Add floating panes key bindings and menu options, from Dane Jensen. 2026-07-03 18:14:45 +00:00
Thomas Adam
8e8fa3d10b Merge branch 'obsd-master' 2026-07-03 19:00:06 +01:00
nicm
b5a6d9bcab Fix some spelling and indentation. 2026-07-03 17:43:14 +00:00
Thomas Adam
929e6b5c9b Merge branch 'obsd-master' 2026-07-03 17:30:06 +01:00
Nicholas Marriott
44304bf9e4 Add pane_start_command_list to tests. 2026-07-03 17:10:51 +01:00
nicm
e03a937c71 Add a quoted pane_start_command_list which does not lose argument
information.
2026-07-03 16:10:25 +00:00
nicm
820067826a Add -E to new-window and respawn-pane and respawn-window to create empty
panes.
2026-07-03 16:09:49 +00:00
Thomas Adam
4a6ac5e209 Merge branch 'obsd-master' 2026-07-03 17:00:06 +01:00
Nicholas Marriott
29f0939a5b Update tests for -E. 2026-07-03 17:01:16 +01:00
Nicholas Marriott
310d0e93d6 Pane sort tests. 2026-07-03 16:37:47 +01:00
nicm
6967f54774 Add additional pane sort orders. 2026-07-03 15:35:51 +00:00
Thomas Adam
8d67f8af24 Merge branch 'obsd-master' 2026-07-03 16:30:06 +01:00
Thomas Adam
0148ad5820 Merge branch 'obsd-master' 2026-07-03 16:15:06 +01:00
nicm
e1fc6b31ee Add manual width and height window formats. 2026-07-03 15:10:07 +00:00
Nicholas Marriott
47b90a4e03 Add single quote test. 2026-07-03 15:48:07 +01:00
nicm
cceca5a158 Add single quote operator. 2026-07-03 14:48:07 +00:00
Thomas Adam
88932f2d09 Merge branch 'obsd-master' 2026-07-03 12:15:06 +01:00
Nicholas Marriott
89a59d4df5 Bump version. 2026-07-03 11:50:24 +01:00
nicm
fa2c9f4551 Allow join-pane to tile floating panes, from Dane Jensen. 2026-07-03 10:47:32 +00:00
Nicholas Marriott
f645f0abe8 A few more tests. 2026-07-03 07:13:31 +01:00
Thomas Adam
f0ec51e78d Merge branch 'obsd-master' 2026-07-03 00:00:06 +01:00
nicm
2b8c3c1362 Enable swap-pane on floating panes again but not -U/-D. From Dane
Jensen.
2026-07-02 22:31:25 +00:00
Nicholas Marriott
dbb70b394e Fix test. 2026-07-02 23:28:15 +01:00
Nicholas Marriott
abcf8dca3a Put ASAN_OPTIONS in the right place. 2026-07-02 23:18:50 +01:00
Nicholas Marriott
351bef58a3 Undo last. 2026-07-02 23:06:55 +01:00
Thomas Adam
8d877421cf Merge branch 'obsd-master' 2026-07-02 23:00:06 +01:00
Nicholas Marriott
f592be00fa Do not require debug for ASAN. 2026-07-02 23:01:19 +01:00
Nicholas Marriott
352b1173c8 Fix. 2026-07-02 22:59:23 +01:00
Nicholas Marriott
bf0eee35f9 Fix. 2026-07-02 22:57:55 +01:00
Nicholas Marriott
228c9c9277 Tweak. 2026-07-02 22:57:14 +01:00
Nicholas Marriott
fa2a3b679a Use ASAN in tests. 2026-07-02 22:56:07 +01:00
Nicholas Marriott
5838b45315 Merge branch 'master' into command_parser 2026-07-02 22:55:04 +01:00
Nicholas Marriott
d718b0eac7 More test fixes. 2026-07-02 22:53:53 +01:00
nicm
93039db58c Use message-style as default for message-format again. GitHub issue 5315. 2026-07-02 21:45:36 +00:00
nicm
6ebef7450a Do not double free or leak pane on failure, from Uzair Aftab in GitHub
issue 5316.
2026-07-02 21:40:05 +00:00
Nicholas Marriott
a82e09d1a3 Test fixes. 2026-07-02 22:12:14 +01:00
Thomas Adam
63a8af0e15 Merge branch 'obsd-master' 2026-07-02 12:00:06 +01:00
nicm
6b9b6c0b0b Copy UTF-8 data correctly. 2026-07-02 10:34:14 +00:00
Nicholas Marriott
390805d8b9 Make output nicer for tests. 2026-07-02 10:46:32 +01:00
Nicholas Marriott
70f37ef815 No vis.h in portable. 2026-07-02 10:28:48 +01:00
Nicholas Marriott
b6fca55447 Add tests for input path. 2026-07-02 10:26:25 +01:00
Thomas Adam
e22c56f02f Merge branch 'obsd-master' 2026-07-02 10:15:06 +01:00
Nicholas Marriott
01c86e64b7 No x flag. 2026-07-02 10:17:04 +01:00
Thomas Adam
ca81c36383 Merge branch 'obsd-master' 2026-07-02 10:00:06 +01:00
Nicholas Marriott
a9a04c7bc0 Targets tests. 2026-07-02 10:03:00 +01:00
nicm
0a81f4d742 Add a raw grid output to capture-pane for debugging and regression
tests.
2026-07-02 08:51:05 +00:00
nicm
0ad5035457 Do not crash if no client. 2026-07-02 08:47:25 +00:00
Nicholas Marriott
d626cef6de Environment tests. 2026-07-02 09:01:49 +01:00
Nicholas Marriott
7713cbebd2 Add options tests. 2026-07-02 08:15:22 +01:00
Nicholas Marriott
baf16f0419 Format tests. 2026-07-02 07:36:26 +01:00
Nicholas Marriott
9dcc2da348 Add format modifiers test. 2026-07-02 07:36:26 +01:00
Thomas Adam
f50a38de23 Merge branch 'obsd-master' 2026-07-01 22:15:06 +01:00
nicm
3ef51a469c move-pane needs -D. 2026-07-01 20:53:38 +00:00
Thomas Adam
d5688bf165 Merge branch 'obsd-master' 2026-07-01 21:30:06 +01:00
nicm
8dc9e24c74 Revert previous, it is not the right change. 2026-07-01 20:26:39 +00:00
nicm
735bbf9da7 Disable time expansion if expanding an inner format or more %s are
needed.
2026-07-01 20:18:05 +00:00
Nicholas Marriott
6ad86ebdf1 Merge branch 'release_3.7b' 2026-07-01 20:06:23 +01:00
Nicholas Marriott
3d58e04c93 no point in having stuff i cannot test 2026-07-01 19:05:24 +01:00
Nicholas Marriott
9fb67a2cdd Missing \. 2026-07-01 19:00:37 +01:00
Nicholas Marriott
d9d12c5c43 More tweaks. 2026-07-01 18:59:34 +01:00
Nicholas Marriott
728ad24d4a utf8proc. 2026-07-01 18:55:28 +01:00
Nicholas Marriott
5b37471db7 Spaces. 2026-07-01 18:53:37 +01:00
Nicholas Marriott
c41e98be3c yaml is poo. 2026-07-01 18:53:16 +01:00
Nicholas Marriott
35f429cb6d macOS too. 2026-07-01 18:52:47 +01:00
Nicholas Marriott
85fc0ea57c Add a test for regress. 2026-07-01 18:43:59 +01:00
Nicholas Marriott
6f588402c3 Remove a char which differs between platforms. 2026-07-01 18:43:01 +01:00
Nicholas Marriott
fada35478b Colours changed in tests. 2026-07-01 18:30:55 +01:00
Nicholas Marriott
bd5d7a8a7a Update prompt test. 2026-07-01 18:15:40 +01:00
Nicholas Marriott
8108356978 Update names test. 2026-07-01 18:02:45 +01:00
Nicholas Marriott
5e32eddb8c Fix up merge. 2026-07-01 18:00:15 +01:00
Thomas Adam
1818b6ac16 Merge branch 'obsd-master' 2026-07-01 17:52:07 +01:00
Nicholas Marriott
e802909de0 Fix so that the end of a synchronized update again triggers a redraw. 2026-07-01 17:49:40 +01:00
nicm
824ef37f5f Change layout sets to build the layout using the new mechanics, from
Dane Jensen.
2026-07-01 16:43:14 +00:00
Nicholas Marriott
cfa755a961 Rm a commment. 2026-07-01 17:37:35 +01:00
nicm
565db46a19 Instead of fully redrawing the pane when sync ends, only redraw lines
that have been changed.
2026-07-01 15:44:13 +00:00
Nicholas Marriott
82683707bc Merge branch 'master' into command_parser 2026-07-01 14:53:15 +01:00
Thomas Adam
87aa44f4bc Merge branch 'obsd-master' 2026-07-01 14:30:07 +01:00
nicm
e9396e59d9 Add a couple of format c/f and c/b to emit colours. 2026-07-01 13:12:17 +00:00
Nicholas Marriott
3ee24bde1a Merge branch 'master' into command_parser 2026-07-01 12:51:34 +01:00
Nicholas Marriott
4f7bef5083 Use only one space with -p. 2026-07-01 12:41:14 +01:00
Nicholas Marriott
80b1f20e7d Use \; at top level also. 2026-07-01 12:32:51 +01:00
Nicholas Marriott
111a11ac6a Expand tabs in print. 2026-07-01 12:25:45 +01:00
Thomas Adam
002ac977f8 Merge branch 'obsd-master' 2026-07-01 09:15:07 +01:00
nicm
724f85d275 Do not draw to tty if PANE_REDRAW has been set since it is just going to
be replaced.
2026-07-01 07:48:48 +00:00
Nicholas Marriott
1853c220a4 Only print one line by default. 2026-07-01 08:22:21 +01:00
Nicholas Marriott
115002748b Add some tests. 2026-07-01 07:35:55 +01:00
Thomas Adam
b3fa1c0b9c Merge branch 'obsd-master' 2026-07-01 07:30:06 +01:00
nicm
e69950a9ca Add overline for ghostty also, GitHub issue 5309. 2026-07-01 06:17:58 +00:00
Nicholas Marriott
db8f9b4c52 Fix various problems. 2026-07-01 07:07:49 +01:00
Nicholas Marriott
4951eac7fa Remove parser test. 2026-07-01 00:19:19 +01:00
Nicholas Marriott
185ae656e0 Merge branch 'master' into command_parser 2026-07-01 00:13:28 +01:00
Nicholas Marriott
ab3da84e34 Handle aliases. 2026-07-01 00:02:10 +01:00
Thomas Adam
41b4743eba Merge branch 'obsd-master' 2026-07-01 00:00:06 +01:00
nicm
1e9a16a748 And some other indentation. 2026-06-30 22:48:12 +00:00
nicm
74a5069c0a Fix some indentation. 2026-06-30 22:44:21 +00:00
Nicholas Marriott
bce30eefd3 Remove unused end_line. 2026-06-30 22:38:35 +01:00
Nicholas Marriott
1302bbde07 Remove stray decl. 2026-06-30 22:37:02 +01:00
Nicholas Marriott
e935342702 More cleanup. 2026-06-30 22:32:36 +01:00
Nicholas Marriott
6d56dd9e7b Move file and flags into parser. 2026-06-30 22:11:25 +01:00
Thomas Adam
8d470017bc Merge branch 'obsd-master' 2026-06-30 22:00:06 +01:00
Nicholas Marriott
8ca7434d65 Remove stray leftovers. 2026-06-30 21:55:01 +01:00
Nicholas Marriott
a9cff084c0 Remove cmd_list and old stuff. 2026-06-30 21:50:26 +01:00
Nicholas Marriott
928c7a9771 More checks for macOS. 2026-06-30 21:28:28 +01:00
nicm
eda7e563d0 Change a malloc to calloc. 2026-06-30 20:27:36 +00:00
Nicholas Marriott
186f51b7ee Do not report errors using client before one is set. 2026-06-30 19:36:38 +01:00
Nicholas Marriott
298b35c8a9 Fail on any error. 2026-06-30 18:59:45 +01:00
Nicholas Marriott
7a1cc1d443 Remove XXX. 2026-06-30 18:54:36 +01:00
Nicholas Marriott
fefb926291 Better way of working out any flag. 2026-06-30 18:48:58 +01:00
Nicholas Marriott
36273fde4f Better version. 2026-06-30 18:33:36 +01:00
Nicholas Marriott
8ac29f6587 Handle ; better. 2026-06-30 18:31:09 +01:00
Nicholas Marriott
db9b54f232 Handle ; better. 2026-06-30 18:30:44 +01:00
Nicholas Marriott
54f5c82500 Convert most of remaining bits. 2026-06-30 18:19:26 +01:00
Nicholas Marriott
b3bb8bbd9f Options and hooks updates. 2026-06-30 17:57:38 +01:00
Nicholas Marriott
c92720e3b0 Preserve file and flags. 2026-06-30 17:37:15 +01:00
Nicholas Marriott
08500f844d Do not use item if NULL. 2026-06-30 17:24:28 +01:00
Nicholas Marriott
a97912a4f8 Simplify args stuff. 2026-06-30 17:19:37 +01:00
Your Name
91ea9e4beb Init. 2026-06-30 16:34:10 +01:00
Your Name
b30f5f82e0 Switch over args stuff. 2026-06-30 16:30:09 +01:00
Your Name
d4eca8d586 Read only bits. 2026-06-30 15:45:16 +01:00
Nicholas Marriott
8908f50524 Allow input to be NULL. 2026-06-30 15:42:25 +01:00
Your Name
eb2e26f4cd Default command tree bits. 2026-06-30 15:33:37 +01:00
Nicholas Marriott
9569c57fc3 Some key bits. 2026-06-30 15:24:20 +01:00
Thomas Adam
90fc51542f Merge branch 'obsd-master' 2026-06-30 11:30:06 +01:00
Nicholas Marriott
b48e2ea8e9 Handle key binding. 2026-06-30 11:20:04 +01:00
nicm
f8674cc993 Add default features for Ghostty, GitHub issue 5305 from Glib Shpychka. 2026-06-30 10:07:05 +00:00
Your Name
f7cd6ab547 No need to escape since logging already does it. 2026-06-30 10:38:39 +01:00
Thomas Adam
843adbf454 Merge branch 'obsd-master' 2026-06-30 10:30:06 +01:00
nicm
abefc3f705 Redraw when sync stops again (accidentally turned off), from Japin Li in
GitHub issue 5304.
2026-06-30 09:20:43 +00:00
Your Name
401793eba6 Fix indentation. 2026-06-30 10:06:54 +01:00
Your Name
fcfa0b6db4 Allow simple commands to be fired. 2026-06-30 10:03:49 +01:00
Your Name
5f7a4b0f52 Remove unused arguments. 2026-06-30 09:47:33 +01:00
Your Name
bcc2b7ad1e Remove pointless debug. 2026-06-30 09:41:23 +01:00
Your Name
9e8cf6f0e2 Stub out all users of old API. 2026-06-30 09:39:45 +01:00
Your Name
b8a62979ac Log func also. 2026-06-30 09:39:34 +01:00
Your Name
45f4000017 Not so much const. 2026-06-30 09:36:26 +01:00
Your Name
572b32766c Remove old bits. 2026-06-30 08:43:02 +01:00
Your Name
e6ad44c8a3 Fix some errors. 2026-06-30 08:40:10 +01:00
Nicholas Marriott
96cd98560b Include Makefile. 2026-06-30 08:34:42 +01:00
Your Name
ac23a47a5f Use correct branch for if. 2026-06-30 08:33:07 +01:00
Your Name
22b6ad6fce Add invoke API. 2026-06-30 08:24:44 +01:00
Thomas Adam
dddd7b44c8 Merge branch 'obsd-master' 2026-06-30 08:15:06 +01:00
Nicholas Marriott
25c874c489 Merge branch 'release_3.7a' 2026-06-30 07:46:06 +01:00
Nicholas Marriott
0e418b62d2 Remove C-r from help. 2026-06-30 07:44:51 +01:00
nicm
a97f643bd3 Remove C-r from help since it does not in fact exist. 2026-06-30 06:44:46 +00:00
Nicholas Marriott
78a2145a47 Update CHANGES. 2026-06-30 07:42:25 +01:00
Nicholas Marriott
dbe50934b1 Add caching of scrollbar options to 3.7a branch. 2026-06-30 07:41:02 +01:00
Nicholas Marriott
efe2453eac New tmux parser and test program. 2026-06-30 07:27:06 +01:00
Thomas Adam
543d104f85 Merge branch 'obsd-master' 2026-06-29 20:15:07 +01:00
nicm
e06207c914 Cache scrollbar options in window to avoid a slow lookup when working
out visible ranges.
2026-06-29 19:03:34 +00:00
nicm
166267c87a Allow :. in names again, forbidding them is overly pernickety. Only
forbid invalid UTF-8 and #(.
2026-06-29 19:53:42 +01:00
Thomas Adam
91e0737b8a Merge branch 'obsd-master' 2026-06-29 19:30:07 +01:00
nicm
65a032b205 Allow :. in names again, forbidding them is overly pernickety. Only
forbid invalid UTF-8 and #(.
2026-06-29 18:17:28 +00:00
Thomas Adam
80c7dcdbd7 Merge branch 'obsd-master' 2026-06-29 18:30:06 +01:00
nicm
bcd54d12d8 Add link=/nolink to styles to specific a hyperlink. GitHub issue 4280
from Moritz Angermann.
2026-06-29 17:08:52 +00:00
Thomas Adam
726e19a886 Merge branch 'obsd-master' 2026-06-29 18:00:06 +01:00
nicm
95719e342d Limit hyperlink URIs to 1024 bytes which seems enough and allows us not
to have to worry about gigantic URIs in styles, part of a change from
Moritz Angermann.
2026-06-29 16:44:06 +00:00
nicm
132a63d1da Allow empty window and session names. 2026-06-29 17:36:40 +01:00
Thomas Adam
822f700b15 Merge branch 'obsd-master' 2026-06-29 17:30:06 +01:00
nicm
5df51a06f5 Allow empty window and session names. 2026-06-29 16:20:45 +00:00
Nicholas Marriott
ad74c5ed7f Add additional grid consistency checks for macOS. 2026-06-29 10:20:38 +01:00
Thomas Adam
d5284be91d Merge branch 'obsd-master' 2026-06-29 09:00:09 +01:00
nicm
c29b41e527 Make pane scrollbars able to auto-hide after a short timeout. This
replaces the previous "modal" behaviour where the pane would be resized,
which tended to make a mess. Instead, the scrollbar appears when
scrolling or when hovered over with the mouse and disappears a
(configurable) short period later. From Michael Grant.
2026-06-29 07:45:09 +00:00
nicm
4e612612dc Only forbid #( in names and titles (styles are #[ and are useful). 2026-06-29 08:11:08 +01:00
Thomas Adam
705fbf92ee Merge branch 'obsd-master' 2026-06-28 21:49:05 +01:00
Nicholas Marriott
2dc704abb6 Always set SIXEL aspect ratio to 1:1, GitHub issue 5291 from James Holderness. 2026-06-28 17:01:52 +01:00
Nicholas Marriott
6c2ef75681 Bump version. 2026-06-28 16:54:51 +01:00
nicm
96899dc527 Only forbid #( in names and titles (styles are #[ and are useful). 2026-06-28 15:53:18 +00:00
Your Name
b0db912bde Update check-names test. 2026-06-28 16:52:28 +01:00
nicm
7cd33ce0dd Set view name immediately when entering mode. 2026-06-27 12:37:36 +00:00
nicm
e0dd40f812 Clear lines before writing in copy mode to avoid leaving stray text when
new line is shorter than old.
2026-06-27 12:27:43 +00:00
nicm
79f0f97495 Make split-window create a new floating pane if used in a floating pane,
rather than just complaining. Also improve documentation. From Dane
Jensen.
2026-06-27 10:23:14 +00:00
nicm
5e0b0a2aa3 Pulled code out of layout_split_pane for reuse and added
layout_insert_tile. From Dane Jensen.
2026-06-27 10:21:26 +00:00
nicm
e24969b422 Add a helper to swap a node into the place of a leaf, from Dane Jensen. 2026-06-27 10:18:32 +00:00
nicm
dd02989e88 Invalidate scene when changing window Z index, from Dane Jensen. 2026-06-27 10:16:15 +00:00
nicm
7383c04205 Preseve prompt flags across line clear, from Aung Myo Kyaw. 2026-06-27 10:05:38 +00:00
Nicholas Marriott
84291b021f Fix check so as to not use NULL name. 2026-06-27 10:58:39 +01:00
Thomas Adam
91e30f4f22 Merge branch 'obsd-master' 2026-06-26 21:15:06 +01:00
nicm
5be44a6c03 Use linked sessions not active. 2026-06-26 19:53:47 +00:00
Thomas Adam
8cc20a5618 Merge branch 'obsd-master' 2026-06-26 19:00:06 +01:00
Nicholas Marriott
81f88f8517 Add a missing {. 2026-06-26 18:55:59 +01:00
Nicholas Marriott
4eafb27b89 tmux 3.7. 2026-06-26 18:50:58 +01:00
Nicholas Marriott
4fc4644652 Tweak. 2026-06-26 18:50:42 +01:00
nicm
c0f213b056 Add info modes to tree mode like for clients. 2026-06-26 17:43:15 +00:00
Nicholas Marriott
a2915cfdef Fuzzy test. 2026-06-26 16:09:55 +01:00
Thomas Adam
4b921f3821 Merge branch 'obsd-master' 2026-06-26 15:56:03 +01:00
nicm
575f84ce0f Add switch-mode a fast switcher with fuzzy searching, bound to Tab (for
windows) or BTab (S-Tab, for sessions) by default.
2026-06-26 14:40:30 +00:00
Thomas Adam
1048796003 Merge branch 'obsd-master' 2026-06-26 15:15:06 +01:00
nicm
973c4ab569 Drop mouse movement events rather than redrawing unnecessarily. 2026-06-26 13:58:00 +00:00
Thomas Adam
445677dc00 Merge branch 'obsd-master' 2026-06-26 14:15:06 +01:00
Thomas Adam
da27529d91 Merge branch 'obsd-master' 2026-06-26 14:00:06 +01:00
nicm
f59921ce18 Use underscore for current in status line. 2026-06-26 12:52:36 +00:00
nicm
ec3c65122f message-command-style should be inverse of message-style. 2026-06-26 12:41:08 +00:00
Thomas Adam
9fa5e81836 Merge branch 'obsd-master' 2026-06-26 13:15:06 +01:00
nicm
4869a5165a Add tree-mode-selection-style to continue the process of moving away
from mode-style.
2026-06-26 12:06:17 +00:00
Thomas Adam
30e84ff687 Merge branch 'obsd-master' 2026-06-26 13:00:09 +01:00
nicm
73fd01c986 Some colour fixes and tweaks (marked pane, cursor colours now work). 2026-06-26 11:36:22 +00:00
Thomas Adam
3729af7438 Merge branch 'obsd-master' 2026-06-26 12:00:06 +01:00
Nicholas Marriott
1e5aef279a Update tests for colour/position changes. 2026-06-26 11:37:36 +01:00
nicm
19db019e0c Do not force theme colours to default when capturing. 2026-06-26 10:29:38 +00:00
Nicholas Marriott
666464d7a9 Add X11 colours script. 2026-06-26 11:28:15 +01:00
Thomas Adam
391f880d07 Merge branch 'obsd-master' 2026-06-26 11:15:06 +01:00
nicm
d0bf3bf06d Add ability to float a tiled pane to break-pane, from Dane Jensen. 2026-06-26 10:02:01 +00:00
Thomas Adam
edb027e910 Merge branch 'obsd-master' 2026-06-26 11:00:07 +01:00
nicm
dd2fef6673 Do not count arguments starting - as optional arguments, makes old
resize-pane syntax work. GitHub issue 5275, based on a changed from Dane
Jensen.
2026-06-26 09:54:56 +00:00
nicm
8e79c4a03d Bump style buffer to 1024, GitHub issue 5279 from Moritz Angermann. 2026-06-26 09:38:44 +00:00
Thomas Adam
19d70a85fc Merge branch 'obsd-master' 2026-06-26 09:30:07 +01:00
nicm
f5e9bcc1fc Another couple of bits look nicer in grey. 2026-06-26 08:19:44 +00:00
nicm
3631f81236 Apply better colours to various builtin bits in modes. 2026-06-26 08:10:48 +00:00
Thomas Adam
1e6f64d5c9 Merge branch 'obsd-master' 2026-06-26 01:00:06 +01:00
nicm
569e9d932f Make the default colours more conservative, its really the greys that
matter.
2026-06-25 23:48:20 +00:00
Thomas Adam
6a7f4a1894 Merge branch 'obsd-master' 2026-06-26 00:30:07 +01:00
nicm
8c55a388cd Add nicer dark and light colour sets (themes) used on terminals with 256
or more colours. Currently based these on emacs but they could change.
Terminals with fewer colours use the ANSI colours. A new "theme" option
overrides the detected theme (set to "terminal" to go back to ANSI
colours).
2026-06-25 23:17:25 +00:00
Thomas Adam
d176f1f89c Merge branch 'obsd-master' 2026-06-25 18:30:06 +01:00
nicm
2d0845860a Fix scrollbar with a format colour. 2026-06-25 17:17:25 +00:00
Thomas Adam
5ae701aab5 Merge branch 'obsd-master' 2026-06-25 18:00:06 +01:00
nicm
0d016195ac Expand colours as formats like styles. 2026-06-25 16:32:42 +00:00
Thomas Adam
fc2de240d2 Merge branch 'obsd-master' 2026-06-25 15:30:06 +01:00
nicm
7c1ef6a387 Format variable for client colours. 2026-06-25 14:27:58 +00:00
Thomas Adam
0b538b6f2a Merge branch 'obsd-master' 2026-06-25 13:29:53 +01:00
Your Name
3d26aa0dfb Prompt regress (will not pass yet). 2026-06-25 12:40:49 +01:00
nicm
51d037e881 Major rework of prompts. The basic prompt mechanics (draw, editing, etc)
are now wrapped up in prompt*.c and do not depend on a client. These
functions are used to provide the original client prompt but also to
allow panes to have their own prompts, which works much much better for
floating panes. The mode prompts for both the tree modes and copy mode
are switched over to be per pane.

There are some visible changes (some of these may be changed if they
don't seem to be working well):

- Prompts in modes now appear in the bottom line, covering whatever
  content was there.

- command-prompt has a -P flag to open a pane prompt.

- Because they cover the content, the default style for prompts in modes
  now does not fill the entire line; the main command prompt stays the
  same.

- The old completion menu has gone, and completions are now shown after
  the text. Builtin aliases are no longer completed.

- Clicking the mouse on the prompt now moves the cursor or selects a
  completion.
2026-06-25 11:39:11 +00:00
Nicholas Marriott
e5fb88fb85 Merge pull request #5273 from daneofmanythings/floating-geometry-regression-test
test(master): Added regression test for floating pane geometry.
2026-06-25 10:23:01 +01:00
Thomas Adam
787234eff4 Merge branch 'obsd-master' 2026-06-24 23:00:06 +01:00
nicm
6b1219b370 Ignore mouse when help is open. 2026-06-24 21:47:06 +00:00
Thomas Adam
ed48555bdd Merge branch 'obsd-master' 2026-06-24 22:30:06 +01:00
nicm
ba9faae8b6 Free modes rather than going through reset when pane is destroyed as the
reset path may try to destroy the pane again.
2026-06-24 21:10:05 +00:00
Dane Jensen
6935dee976 Added regression test for floating pane geometry and how it interacts
with pane borders.
2026-06-24 13:48:17 -07:00
Thomas Adam
12473bb6d3 Merge branch 'obsd-master' 2026-06-24 12:15:06 +01:00
nicm
280a962328 Replace two-value prompt_mode enum with a flag. 2026-06-24 11:05:58 +00:00
nicm
37633ff957 Add some enums for prompt return values to make them a bit less confusing. 2026-06-24 10:55:39 +00:00
Thomas Adam
1c2fc8c3c4 Merge branch 'obsd-master' 2026-06-24 10:15:06 +01:00
Thomas Adam
d3adecd819 Merge branch 'obsd-master' 2026-06-24 10:00:06 +01:00
nicm
0a71af41be Handle different borders in move-pane, from Dane Jensen. 2026-06-24 08:52:15 +00:00
nicm
8fb484da37 Remove target completion which never worked very well, leave only
command completion.
2026-06-24 08:35:46 +00:00
Nicholas Marriott
6e6a434a2d Remove no longer needed test bits. 2026-06-24 09:24:18 +01:00
Thomas Adam
f23ea9e977 Merge branch 'obsd-master' 2026-06-24 01:15:06 +01:00
nicm
55cfdbaa3f Remove fuzzy match operator again for now, this will be done differently. 2026-06-23 23:53:06 +00:00
nicm
1b5a07a740 Rename prompt-input to match the usual format names. 2026-06-23 23:52:39 +00:00
Thomas Adam
00b0af7021 Merge branch 'obsd-master' 2026-06-23 22:15:06 +01:00
nicm
89027bd22c Fix MOVE flag handling and remove key code (not actually needed). 2026-06-23 21:00:20 +00:00
Thomas Adam
ff8de1a106 Merge branch 'obsd-master' 2026-06-23 22:00:05 +01:00
nicm
0c5e6770df Turn off preview if no draw callback and add a function to move to top. 2026-06-23 20:30:10 +00:00
Thomas Adam
c2ad33a04c Merge branch 'obsd-master' 2026-06-23 21:15:06 +01:00
nicm
c0a5e10313 Use flags for input callback instead of a single int done so the
callback can be told about cursor movement in an incremental prompt.
2026-06-23 20:07:58 +00:00
Nicholas Marriott
1c394b8606 Tweak the test. 2026-06-23 20:31:42 +01:00
Nicholas Marriott
505d557fbb Fix SIXEL, from Marc Haesen. 2026-06-23 15:48:02 +01:00
Thomas Adam
a63e429d22 Merge branch 'obsd-master' 2026-06-23 13:00:09 +01:00
nicm
de086f9848 Fix an infinite loop in customize mode when a filter does not match, and
tweak a tmux.1 example.
2026-06-23 11:29:27 +00:00
Thomas Adam
6643acb375 Merge branch 'obsd-master' 2026-06-23 11:00:09 +01:00
Thomas Adam
c25cb107bb Merge branch 'obsd-master' 2026-06-23 10:30:05 +01:00
nicm
9dba08ac8b Allow -p more than 100%, and account for borders when sizing new panes.
From Dane Jensen.
2026-06-23 09:29:26 +00:00
nicm
3ec115115d Do not leak error message, use it instead. From Dane Jensen. 2026-06-23 09:13:30 +00:00
Thomas Adam
09c91b8931 Merge branch 'obsd-master' 2026-06-23 10:00:05 +01:00
Nicholas Marriott
de1940065a Update test. 2026-06-23 10:01:01 +01:00
nicm
748ef8b2ae Extend match to do multiple terms. 2026-06-23 08:35:28 +00:00
Thomas Adam
2ecf0a9053 Merge branch 'obsd-master' 2026-06-23 09:30:05 +01:00
nicm
b82048eb18 Add c key to clear filter. 2026-06-23 08:20:15 +00:00
Nicholas Marriott
9d58ff97fe Update format test. 2026-06-23 09:14:13 +01:00
Nicholas Marriott
51b787e449 Fix SIXEL build. 2026-06-23 09:06:43 +01:00
Nicholas Marriott
7654b518f1 Add to .gitignore. 2026-06-23 08:36:07 +01:00
nicm
e7f414416f Set check callback for menus so they aren't overwritten by sync. 2026-06-23 08:35:09 +01:00
Thomas Adam
52877c9af1 Merge branch 'obsd-master' 2026-06-23 07:35:51 +01:00
nicm
265f22be49 Make cursor movement exit incremental prompt (more like emacs). 2026-06-22 19:43:22 +00:00
nicm
2a26c738bd Add z modifier to m operator for fuzzy match. 2026-06-22 19:39:01 +00:00
nicm
02b37b3c69 Fix scrollbar pad to work even if not set in style. 2026-06-22 13:57:33 +00:00
nicm
d8370864f3 Memory leak from someone in GitHub issue 5259. 2026-06-22 12:39:56 +00:00
Nicholas Marriott
ffe48f00e3 Tests for screen-redraw.c (not all passing right now). 2026-06-22 12:09:44 +01:00
nicm
49dff83a00 Set check callback for menus so they aren't overwritten by sync. 2026-06-22 10:17:08 +00:00
nicm
95afd7549c Rewrite screen-redraw.c to make it tidier and more maintainable. A scene
is generated and cached in the client: it holds positions and sizes of
panes, borders and so on. The scene is invalidated when a pane is moved
or resized or relevant option is changed. This scene is then drawn to
the client as needed and text and colours are filled in. With Michael
Grant.
2026-06-22 08:47:45 +00:00
Thomas Adam
06ee890db3 Merge branch 'obsd-master' 2026-06-22 09:30:06 +01:00
nicm
e680420c89 Place floating cells meaningfully into the layouts, from Dane Jensen. 2026-06-22 08:10:16 +00:00
Thomas Adam
357ee692bd Merge branch 'obsd-master' 2026-06-21 19:00:05 +01:00
nicm
347baa6f3e Do not walk off end or beginning of menu item list if every item is
disabled, GitHub issue 5255 from aizumusheer2 at gmail dot com.
2026-06-21 17:43:47 +00:00
Thomas Adam
cc922de758 Merge branch 'obsd-master' 2026-06-20 10:15:05 +01:00
Thomas Adam
dbca906ef5 Merge branch 'obsd-master' 2026-06-20 10:00:05 +01:00
nicm
1fc4dcb76f Error if key is unknown rather than just carry on, GitHub issue 5248. 2026-06-20 08:51:28 +00:00
nicm
dcd7d1c8e6 Fix {} keys in tmux.1, pointed out by tb. 2026-06-20 08:46:08 +00:00
Thomas Adam
72e768ef14 Merge branch 'obsd-master' 2026-06-19 20:00:06 +01:00
nicm
047f61aad6 popup_write can be deleted now also. 2026-06-19 18:41:36 +00:00
nicm
d04b1ffca5 Use a floating pane for the buffer mode editor instead of a popup. 2026-06-19 18:37:10 +00:00
nicm
c93e2f2332 Do not loop forever if a pane ends up with borders the wrong way round. 2026-06-19 18:28:18 +00:00
Thomas Adam
e7c29d0ceb Merge branch 'obsd-master' 2026-06-19 19:00:05 +01:00
nicm
6c46358be8 Draw help directly instead of using a popup. 2026-06-19 17:37:26 +00:00
Thomas Adam
b0ff8af9ac Merge branch 'obsd-master' 2026-06-19 17:30:05 +01:00
nicm
624db256c6 Add a loop_index variable and use it to show last used 5 sessions on the
session menu instead of next/previous.
2026-06-19 16:25:43 +00:00
Thomas Adam
cfb15d158e Merge branch 'obsd-master' 2026-06-19 17:15:05 +01:00
Thomas Adam
34c7881ddd Merge branch 'obsd-master' 2026-06-19 17:00:05 +01:00
nicm
f06d930445 Correctly report theme changes to panes, based on a fix from someone in
GitHub issue 5246.
2026-06-19 16:00:03 +00:00
nicm
b29f5688bd Make default M-C-Up/Down bound to prompt movement commands in copy mode. 2026-06-19 15:28:26 +00:00
Thomas Adam
333e2f266a Merge branch 'obsd-master' 2026-06-19 12:00:07 +01:00
Nicholas Marriott
af94399473 Add some more bits to test. 2026-06-19 11:50:29 +01:00
Nicholas Marriott
3d35f26031 Add a test for tty_draw_line. 2026-06-19 11:43:43 +01:00
nicm
b1c72ec48c Use a helper and skip some setup for empty cells. 2026-06-19 10:38:29 +00:00
Thomas Adam
8dee34459b Merge branch 'obsd-master' 2026-06-19 11:15:06 +01:00
nicm
e7f649e40a Tidy up a bit in tty_draw_line. 2026-06-19 09:56:53 +00:00
Thomas Adam
5843fbc6ee Merge branch 'obsd-master' 2026-06-19 09:30:06 +01:00
Nicholas Marriott
c8eca3a69d Add command-alias.sh test, from tanushnry at gmail dot com. 2026-06-19 09:29:41 +01:00
Nicholas Marriott
12fdcea8b8 Get use of raster attributes in the right order, from aizumusheer2 at gmail dot
com in GitHub issue 5225.
2026-06-19 09:24:48 +01:00
nicm
035447b08f Do not dereference the current window if it is NULL (can be if the
notification fires after session is destroyed). GitHub issue 5244 from
Ben Maurer.
2026-06-19 08:21:55 +00:00
Thomas Adam
55b2e748e7 Merge branch 'obsd-master' 2026-06-18 13:15:05 +01:00
Thomas Adam
3246331876 Merge branch 'obsd-master' 2026-06-18 13:00:05 +01:00
Nicholas Marriott
6d9fbb0e97 Update regress for format changes. 2026-06-18 12:55:13 +01:00
nicm
14fc4a06a6 Unescape arguments before passing to format_expand1 so that escaping :s
etc actually works.
2026-06-18 11:54:37 +00:00
nicm
cba4ba9cdc Check time periodically in loops rather than every one. 2026-06-18 11:45:08 +00:00
Thomas Adam
937d99150c Merge branch 'obsd-master' 2026-06-18 12:15:05 +01:00
nicm
0a6c846091 Use evbuffer for building S, W, L lists, also add an xmemdup helper
instead of misusing asprintf.
2026-06-18 10:56:22 +00:00
Thomas Adam
c6b8ad6e7e Merge branch 'obsd-master' 2026-06-18 11:15:07 +01:00
nicm
d92a479d2f Add a dim= style attribute to dim the colours (as best as tmux is able
to). GitHub issue 4842.
2026-06-18 09:59:55 +00:00
Thomas Adam
f242182840 Merge branch 'obsd-master' 2026-06-18 10:30:05 +01:00
Thomas Adam
f5871369cc Merge branch 'obsd-master' 2026-06-18 10:15:06 +01:00
nicm
b1b184cdb8 Add functions to work out cell neighbours, and to remove a tiled cell.
From Dane Jensen.
2026-06-18 09:11:24 +00:00
nicm
1a6de01210 Check the panes again if deferred redraw to make sure the flag is always
set. Also add a helper for the loop.
2026-06-18 08:56:07 +00:00
Thomas Adam
1dd2589a42 Merge branch 'obsd-master' 2026-06-17 14:30:05 +01:00
nicm
3485e1c089 Tidy up server_client_check_redraw. Get rid of the bitmask tracking
panes which want redraw for deferred clients - if they are deferred then
they can just have a full redraw instead. Also return earlier if no
redraw is actually needed, and improve the comments.
2026-06-17 13:22:48 +00:00
Thomas Adam
6f7965e4f1 Merge branch 'obsd-master' 2026-06-17 14:00:05 +01:00
nicm
1ba8fa6f04 Fix shifts to 1ULL and check scrollbar even if pane is not being redrawn. 2026-06-17 12:32:54 +00:00
Thomas Adam
6eb67fcc5f Merge branch 'obsd-master' 2026-06-17 09:15:05 +01:00
nicm
3b8e25fef4 Initialize wname so it we are not freeing garbage if it is not used. 2026-06-17 09:11:00 +01:00
nicm
64e83caf04 Be more strict about what names and titles we allow and reject them
immediately when possible, but allow # again for those directly set by
commands (but not escape sequences). From Barrett Ruth in GitHub issue
5175.
2026-06-17 09:10:55 +01:00
Thomas Adam
a8abea8247 Merge branch 'obsd-master' 2026-06-17 09:00:07 +01:00
nicm
d4defd943a Add -M to move-pane and default bindings for M-drag. From Michael Grant. 2026-06-17 07:52:21 +00:00
nicm
eafd2bed5d FIx a couple of minor errors in use of layout_cell_is_tiled. 2026-06-17 07:43:55 +00:00
Thomas Adam
6549765930 Makefile: add window-visible.c 2026-06-16 12:55:41 +01:00
Thomas Adam
6cb2233cd2 Merge branch 'obsd-master' 2026-06-16 12:55:00 +01:00
nicm
9ad75e1dc3 Rename the visible ranges functions since they really relate to windows
and put them nto a new file.
2026-06-16 10:47:35 +00:00
nicm
762e503978 Allow rectangle selection to extend past end of current line to behave
the same as vi with virtualedit=block set. From Mark Kelly in GitHub
issue 5227.
2026-06-16 11:11:47 +01:00
Thomas Adam
bec0f7d975 Merge branch 'obsd-master' 2026-06-16 11:00:05 +01:00
nicm
4b772fd783 Allow rectangle selection to extend past end of current line to behave
the same as vi with virtualedit=block set. From Mark Kelly in GitHub
issue 5227.
2026-06-16 09:28:17 +00:00
Nicholas Marriott
c809aea278 Fix a merge problem. 2026-06-16 10:24:06 +01:00
Nicholas Marriott
1596f9c9f5 Bump version again. 2026-06-16 10:22:51 +01:00
nicm
2f28d9a168 Fix resizing floating pane with a left scrollbar, from Michael Grant. 2026-06-16 10:22:12 +01:00
Nicholas Marriott
baac29debc Prefer libtinfow if it is available, GitHub issue 5224 from Lars Wendler. 2026-06-16 10:21:12 +01:00
nicm
2c4df765c5 Fix missing border when drawing floating panes. From Michael Grant. 2026-06-16 10:21:09 +01:00
Thomas Adam
1d026b921c Merge branch 'obsd-master' 2026-06-16 10:15:14 +01:00
nicm
fcbb6a7384 Initialize wname so it we are not freeing garbage if it is not used. 2026-06-16 09:00:25 +00:00
nicm
bdc01f44de Fix resizing floating pane with a left scrollbar, from Michael Grant. 2026-06-16 08:57:07 +00:00
nicm
d2c23773ff Fix missing border when drawing floating panes. From Michael Grant. 2026-06-16 08:53:14 +00:00
Nicholas Marriott
38dc3ec0da Prefer libtinfow if it is available, GitHub issue 5224 from Lars Wendler. 2026-06-16 09:48:04 +01:00
Thomas Adam
c03acc9572 Merge branch 'obsd-master' 2026-06-16 08:15:07 +01:00
nicm
30e133d36a Fix default window check in break-pane, from Dane Jensen. 2026-06-16 07:07:49 +00:00
nicm
0890df5891 Add layout_cell_is_tiled and layout_cell_has_tiled_child helper
functions, from Dane Jensen.
2026-06-16 07:06:32 +00:00
Thomas Adam
d223a49d18 Merge branch 'obsd-master' 2026-06-16 06:15:05 +01:00
jsg
26285e183e botton -> bottom 2026-06-16 05:01:56 +00:00
Thomas Adam
408e9f1481 Merge branch 'obsd-master' 2026-06-15 23:00:06 +01:00
Nicholas Marriott
7c8344930b Add a test for names. 2026-06-15 22:47:39 +01:00
nicm
482e254a49 Rename window_pane_visible to match other functions, from Dane Jensen. 2026-06-15 21:47:01 +00:00
nicm
eb65331403 Be more strict about what names and titles we allow and reject them
immediately when possible, but allow # again for those directly set by
commands (but not escape sequences). From Barrett Ruth in GitHub issue
5175.
2026-06-15 21:41:39 +00:00
Thomas Adam
9031f59f7b Merge branch 'obsd-master' 2026-06-15 19:00:06 +01:00
nicm
2d9afa6348 Use correct x position when drawing clipped line. 2026-06-15 18:39:43 +01:00
nicm
bae5c14941 Tidy up screen_redraw_get_visible_ranges by using a couple of
temporaries for start and end of range.
2026-06-15 18:39:28 +01:00
nicm
9cf8de099c Convert cursor position back to pane coordinates for tty_cmd_cell. 2026-06-15 18:39:23 +01:00
nicm
b86bd1fcd0 Add pane-border-lines none like popups (-B to new-pane). 2026-06-15 17:34:25 +00:00
Thomas Adam
c156e597dc Merge branch 'obsd-master' 2026-06-15 16:15:07 +01:00
nicm
bb750b07bc If the client is blocked, defer the redraw because it may end up
partially discarded leading to redraw artifacts.
2026-06-15 15:05:12 +00:00
nicm
0e6fe5a097 Convert cursor position back to pane coordinates for tty_cmd_cell. 2026-06-15 14:56:30 +00:00
Thomas Adam
13de7cafb2 Merge branch 'obsd-master' 2026-06-15 13:30:08 +01:00
nicm
057b688fc7 Tidy up screen_redraw_get_visible_ranges by using a couple of
temporaries for start and end of range.
2026-06-15 12:07:49 +00:00
Thomas Adam
45bb3e7ffc Merge branch 'obsd-master' 2026-06-15 13:00:05 +01:00
nicm
8ced9c332c Use correct x position when drawing clipped line. 2026-06-15 11:45:51 +00:00
Thomas Adam
9fcd5ce03d Merge branch 'obsd-master' 2026-06-15 10:30:05 +01:00
nicm
a41e6e2ed2 Add -B to new-pane to select the floating pane border. 2026-06-15 09:21:40 +00:00
Thomas Adam
e54855ef88 Merge branch 'obsd-master' 2026-06-15 09:30:05 +01:00
nicm
7a18fa281d Add top-floating and bottom-floating to pane-border-status to show
status line only on floating panes.
2026-06-15 08:16:05 +00:00
nicm
f98aaf544d When redrawing a whole pane line, fall into tty_draw_line for any
characters that are not plain ASCII. Fixes redraw issues with partial
tabs and other wide characters when a pane is partly outside the window.
2026-06-15 09:07:23 +01:00
nicm
78afc24736 Fix various errors in redrawing:
- Fix the active pane colour when only two panes and scrollbars enabled.

- Clip left and right scrollbars the same for floating panes.

- Do not subtract scrollbar width twice when working out width of status
  line.

- Check if a character is inside a visible range correctly (do not
  include the next position outside the range).
2026-06-15 09:06:58 +01:00
nicm
83e4534387 Skip floating panes when working out the top or bottom cell. Fixes
missing bottom status pane status line when floating panes exist.
2026-06-15 09:06:55 +01:00
Thomas Adam
aed9018807 Merge branch 'obsd-master' 2026-06-15 09:00:05 +01:00
nicm
97b5f22a70 When redrawing a whole pane line, fall into tty_draw_line for any
characters that are not plain ASCII. Fixes redraw issues with partial
tabs and other wide characters when a pane is partly outside the window.
2026-06-15 07:40:45 +00:00
Thomas Adam
748dab2f38 Merge branch 'obsd-master' 2026-06-14 22:15:05 +01:00
Thomas Adam
3c11129b04 Merge branch 'obsd-master' 2026-06-14 22:00:05 +01:00
nicm
6e0e343f23 Fix various errors in redrawing:
- Fix the active pane colour when only two panes and scrollbars enabled.

- Clip left and right scrollbars the same for floating panes.

- Do not subtract scrollbar width twice when working out width of status
  line.

- Check if a character is inside a visible range correctly (do not
  include the next position outside the range).
2026-06-14 20:53:20 +00:00
nicm
cc27470cb6 Skip floating panes when working out the top or bottom cell. Fixes
missing bottom status pane status line when floating panes exist.
2026-06-14 20:37:57 +00:00
Thomas Adam
fb527fa7db Merge branch 'obsd-master' 2026-06-14 21:00:05 +01:00
nicm
87976aa48e Add a helper to get pane-border-status for a window for some other
changes to come.
2026-06-14 19:31:37 +00:00
Thomas Adam
863923472a Merge branch 'obsd-master' 2026-06-14 20:15:07 +01:00
nicm
778a15b64a Take account of borders when resizing floating panes. 2026-06-14 18:59:15 +00:00
Thomas Adam
5eaf5570c4 Merge branch 'obsd-master' 2026-06-14 10:00:06 +01:00
Nicholas Marriott
cbe0f1f1e9 Another const from Jere Viikari. 2026-06-14 09:50:50 +01:00
nicm
97472e374f Return early if connect construct cell, reported by Jere Viikari. 2026-06-14 08:47:44 +00:00
Thomas Adam
7a28aa0dd7 Merge branch 'obsd-master' 2026-06-13 22:00:06 +01:00
nicm
b1054ac227 With mode-keys vi, keep cursor in the same position relative to the text
when scrolling. GitHub issue 5216 from Arseniy Simonov.
2026-06-13 20:39:11 +00:00
Thomas Adam
1618b98533 Merge branch 'obsd-master' 2026-06-13 21:15:06 +01:00
nicm
5b6ed54817 Add some missing const, from Jere Viikari. 2026-06-13 20:07:30 +00:00
nicm
d23a2b7e97 Skip floating cells when moving to previous cell for resize of tiled cells. 2026-06-13 20:03:10 +00:00
nicm
ab92b27226 Make the resize keys always change right and bottom borders for floating
panes which is more intuitive.
2026-06-13 19:57:44 +00:00
Thomas Adam
eb6efdf17a Merge branch 'obsd-master' 2026-06-13 20:00:06 +01:00
nicm
b182791052 Tidy up error messages from split-window. 2026-06-13 18:30:16 +00:00
Thomas Adam
97a7f174ce Merge branch 'obsd-master' 2026-06-13 19:15:06 +01:00
nicm
654758e622 Add move-pane -z to move a pane to a particular the z-index. 2026-06-13 18:06:01 +00:00
Thomas Adam
c8887a5846 Merge branch 'obsd-master' 2026-06-13 19:00:06 +01:00
nicm
56779f9542 Add Z index positions to move-pane -P. 2026-06-13 17:43:20 +00:00
Thomas Adam
f474838e22 Merge branch 'obsd-master' 2026-06-13 18:30:06 +01:00
nicm
19393e13db Add -P to move-pane to move a floating pane to a specific place
(top-left, bottom-right). Get rid of the not-so-useful default { and }
swap-pane bindings and use the keys instead for moving to top-left,
top-right and add M-{ and M-} for bottom-left, bottom-right.
2026-06-13 17:12:02 +00:00
Thomas Adam
301b10de8d Merge branch 'obsd-master' 2026-06-13 17:30:06 +01:00
nicm
aa2fd3a151 Add flags to move-pane to move floating panes around (-U, -D, -L, -R
similar to resize-pane; -X, -Y similar to new-pane).
2026-06-13 16:16:18 +00:00
Thomas Adam
2dd604949f Merge branch 'obsd-master' 2026-06-13 14:15:06 +01:00
nicm
ec481ac82a Missed this one as well. 2026-06-13 13:07:12 +00:00
nicm
7afee45635 Use correct name for activity time. 2026-06-13 13:06:12 +00:00
Thomas Adam
385d18590a Merge branch 'obsd-master' 2026-06-13 13:00:06 +01:00
nicm
bc9252f6e3 Remove some stray code from an old floating panes implementation, from
Dane Jensen.
2026-06-13 11:37:46 +00:00
Thomas Adam
6c76f2a7c7 Merge branch 'obsd-master' 2026-06-13 12:00:06 +01:00
nicm
bf187170b1 Extend client mode so the preview can be changed to a view with a
summary of the client terminal and its features, intended to make
troubleshooting easier. "choose-client -i" or the "i" key in the mode.
2026-06-13 10:32:54 +00:00
Thomas Adam
14837e8e0f Merge branch 'obsd-master' 2026-06-13 10:30:06 +01:00
nicm
b44cdf1006 Add an I format modifier to get some bits of information about a client
(terminal features, capabilities and environment).
2026-06-13 09:17:29 +00:00
Thomas Adam
5d73f90133 Merge branch 'obsd-master' 2026-06-13 10:15:06 +01:00
nicm
34a6a9d3a1 Change relative time for now to only work in the past and not show a
sign which is more useful. Also tidy up some minor style nits.
2026-06-13 08:59:52 +00:00
Thomas Adam
f92a4e611b Merge branch 'obsd-master' 2026-06-12 14:30:06 +01:00
jsg
b3deb9ec86 fix tty_draw_line() indentation; ok nicm@ 2026-06-12 13:16:36 +00:00
nicm
dabaae1534 Make buffer creation time sort oldest first like it used to, but change
windows to match sessions and panes as newest first.
2026-06-12 10:19:05 +01:00
Nicholas Marriott
a92653f2b2 base -> bash from tanushnry at gmail dot com. 2026-06-12 10:18:44 +01:00
Thomas Adam
29cdab650b Merge branch 'obsd-master' 2026-06-12 00:15:06 +01:00
nicm
574a4eccb5 Make buffer creation time sort oldest first like it used to, but change
windows to match sessions and panes as newest first.
2026-06-11 23:01:31 +00:00
Thomas Adam
42c0641daf Merge branch 'obsd-master' 2026-06-11 21:15:06 +01:00
nicm
11b6e7844a When MODE_SYNC is on, all terminal updates need to deferred, not just
scrolling. Check the flag before each one. GitHub issue 4983.
2026-06-11 19:50:23 +00:00
Thomas Adam
f1b67152af Merge branch 'obsd-master' 2026-06-11 20:30:06 +01:00
nicm
b6be437bab Change send-keys -K to insert keys in the right place in the queue, like
we already do for key bindings. GitHub issue 3476.
2026-06-11 19:13:34 +00:00
Thomas Adam
3d7f421809 Merge branch 'obsd-master' 2026-06-11 15:30:06 +01:00
nicm
fa6d33f742 Add a helper to free the list of pending resizes. 2026-06-11 14:19:59 +00:00
Thomas Adam
dc22196394 Merge branch 'obsd-master' 2026-06-11 11:30:06 +01:00
nicm
3ea458b9dc Add support for floating panes to resize-pane, from Dane Jensen. 2026-06-11 10:16:19 +00:00
Thomas Adam
86128a7f06 Merge branch 'obsd-master' 2026-06-11 09:15:09 +01:00
nicm
de60750506 Hide Swap Marked also since floating panes cannot be swapped yet. 2026-06-11 07:52:17 +00:00
Thomas Adam
f07ac3068b Merge branch 'obsd-master' 2026-06-10 20:00:06 +01:00
nicm
7ec94fda08 Make list-keys only use a message if -1 is given, otherwise behave like
other commands (stdout or mode).
2026-06-10 18:32:33 +00:00
Thomas Adam
9231c80cd8 Merge branch 'obsd-master' 2026-06-10 17:15:07 +01:00
nicm
4702e58ee5 Fix text to refer to status-keys not mode-keys, GitHub issue 5191. 2026-06-10 16:08:03 +00:00
nicm
2719549cf2 Rename new-pane -B to -W since -B is best kept to match -B to
display-popup.
2026-06-10 16:03:14 +00:00
Thomas Adam
cd26f29fb8 Merge branch 'obsd-master' 2026-06-10 16:15:11 +01:00
Thomas Adam
9c350af582 Merge branch 'obsd-master' 2026-06-10 16:00:11 +01:00
nicm
c019b3dad9 Use correct size when calculating status line right trim. GitHub issue
5098 from Aung Myo Kyaw.
2026-06-10 14:51:20 +00:00
nicm
8cb4aabb8b Replace refresh-from-pane in copy mode with a way to automatically
update as pane content changes. This is toggled by pressing r. GitHub
issue 5165 from Barrett Ruth.
2026-06-10 14:29:08 +00:00
Thomas Adam
7ad8b3366d Merge branch 'obsd-master' 2026-06-10 15:15:08 +01:00
nicm
c6c8f77bc0 While grouped sessions are being killed, it can leave session which are
not really useful as targets (no active pane or current window) in the
tree. Make cmd_find_best_session skip these. GitHub issue 5167.
2026-06-10 14:06:45 +00:00
Nicholas Marriott
f415a70c0b Merge two SIXEL blocks. 2026-06-10 14:17:10 +01:00
Thomas Adam
da31d94c2a Merge branch 'obsd-master' 2026-06-10 08:50:17 +01:00
nicm
5c0db5a293 Preserve the original text in the first line for display-panes. 2026-06-09 21:31:16 +00:00
nicm
d533d7c97c Add a context for cell/palette/hyperlinks when drawing to tty to avoid
passing so much in parameters.
2026-06-09 21:22:22 +00:00
Thomas Adam
490802099d Merge branch 'obsd-master' 2026-06-09 14:15:07 +01:00
nicm
34dd42dc7f Use name as marker for failure not type (which must be set to be in the error). 2026-06-09 12:58:40 +00:00
nicm
b374868750 Add missing headers. 2026-06-09 12:57:40 +00:00
nicm
538688486e Clarify -a for kill-window, kill-session. 2026-06-09 12:51:57 +00:00
Thomas Adam
27111bd76d Merge branch 'obsd-master' 2026-06-09 13:30:06 +01:00
nicm
4a3f380fd2 Add -f for filter with -a to kill-pane, kill-window, kill-session.
GitHub issue 4782.
2026-06-09 12:24:59 +00:00
nicm
cc87db74c7 Two fixes for RI codepoints. Firstly, do not combine more than two of
them - previously we were ending up with four codepoints in one cell
which tmux believed to be width 2, but terminals considered width 4.
Secondly, invalidate cursor position before redrawing the cell when the
second codepoint is received, terminals vary in how they manage
backspace and cursor movement across these characters, so it is better
to use absolute rather than relative positioning. GitHub issue 4853.
2026-06-09 13:20:14 +01:00
Thomas Adam
4ec7e66e90 Merge branch 'obsd-master' 2026-06-09 13:15:06 +01:00
nicm
a42e425d44 Two fixes for RI codepoints. Firstly, do not combine more than two of
them - previously we were ending up with four codepoints in one cell
which tmux believed to be width 2, but terminals considered width 4.
Secondly, invalidate cursor position before redrawing the cell when the
second codepoint is received, terminals vary in how they manage
backspace and cursor movement across these characters, so it is better
to use absolute rather than relative positioning. GitHub issue 4853.
2026-06-09 11:49:36 +00:00
Thomas Adam
2179c3e420 Merge branch 'obsd-master' 2026-06-09 10:30:06 +01:00
nicm
d5c9196348 Add -B to new-pane/split-window to block until the command exits, like
run-shell -b.
2026-06-09 09:11:05 +00:00
Nicholas Marriott
aa1f0653e9 Bump version to 3.7-rc2. 2026-06-09 09:47:18 +01:00
Thomas Adam
97156f3382 Merge branch 'obsd-master' 2026-06-09 09:30:06 +01:00
nicm
8771b6051f Fix mouse events on tiled pane status line - when panes share a border,
prefer the pane for which the border is the status line. With Dane
Jensen.
2026-06-09 09:12:21 +01:00
nicm
27c58c764d Do not use NULL arguments, bug spotted by Dane Jensen. 2026-06-09 08:11:53 +00:00
Thomas Adam
1564584ba9 Merge branch 'obsd-master' 2026-06-09 00:15:06 +01:00
nicm
fd10db5a62 Fix mouse events on tiled pane status line - when panes share a border,
prefer the pane for which the border is the status line. With Dane
Jensen.
2026-06-08 23:06:21 +00:00
Thomas Adam
6f683da2e9 Merge branch 'obsd-master' 2026-06-08 23:00:06 +01:00
nicm
a6a06c5aa6 Fix squash-groups skipping every session, GitHub issue 5180 from Bryce
Miller.
2026-06-08 21:46:19 +00:00
nicm
4d1ab1ba2a Allow ACLs to use groups as well as users, GitHub issue 4917. 2026-06-08 21:38:19 +00:00
Thomas Adam
d7a6a8e35b Merge branch 'obsd-master' 2026-06-08 22:30:06 +01:00
nicm
bf2e078ecf Add a relative time option for time formatting, GitHub issue 5009. 2026-06-08 21:19:52 +00:00
Thomas Adam
a25ce36925 Merge branch 'obsd-master' 2026-06-08 22:15:06 +01:00
nicm
a0f4038df2 Add a -h flag to choose-tree and choose-client to hide the pane
containing the mode, intended for use with floating panes. From Michael
Grant, GitHub issue 5177.
2026-06-08 21:01:33 +00:00
Thomas Adam
7d8c1ae295 Merge branch 'obsd-master' 2026-06-08 22:00:07 +01:00
nicm
df7c2e605b Add -T to new-pane to set the title; GitHub issue 5176 from Michael Grant. 2026-06-08 20:44:57 +00:00
nicm
ea51cdb3f2 Add display-panes-format to change the top-of-pane text shown with
display-panes.
2026-06-08 20:42:39 +00:00
nicm
fe986a52d6 When entering or leaving the alternate screen, discard any pending
resizes. Improves flicking with scrollbars and programs that leave and
enter the alternate screen on every WINCH like nano. GitHub issue 4772.

Cvs: ----------------------------------------------------------------------
2026-06-08 20:41:21 +00:00
nicm
34fd261a4f Add a -k flag to choose commands to kill the pane when the mode is
exited (useful with floating panes).
2026-06-08 20:38:54 +00:00
Nicholas Marriott
bbd4768bb6 3.7-rc version. 2026-06-08 21:08:37 +01:00
Thomas Adam
cb237b5193 Merge branch 'obsd-master' 2026-06-08 16:30:06 +01:00
nicm
85d72b9952 Return immediately if the list is empty in mode_tree_key instead of
crashing, from Bryce Miller in GitHub issue 5170.
2026-06-08 15:16:21 +00:00
Thomas Adam
a04c33c42d Merge branch 'obsd-master' 2026-06-07 21:15:06 +01:00
nicm
529afada82 Move checking of whether the cursor is visible inside the if so that it
always hits the calculation of the oy offset when the status line is at
the top. From Michael Grant.
2026-06-07 20:05:16 +00:00
nicm
a532f4868f Add -H flag to capture-pane to show hyperlinks. 2026-06-07 20:03:02 +00:00
Thomas Adam
e1d37c37b5 Merge branch 'obsd-master' 2026-06-07 17:30:06 +01:00
nicm
ae5e0e8c05 Add -L to show line numbers with capture-pane and -F to show line flags. 2026-06-07 16:23:05 +00:00
Nicholas Marriott
bbea0ef439 Update copy mode vi test, from Max Vim. 2026-06-07 17:22:14 +01:00
Nicholas Marriott
a134c43f4d Update test result. 2026-06-07 16:15:32 +01:00
Nicholas Marriott
e0ebae4481 Fix a couple of tests and add -f/dev/null to them all. 2026-06-07 16:11:38 +01:00
Thomas Adam
1fbc51b69f Merge branch 'obsd-master' 2026-06-07 16:00:06 +01:00
nicm
42f3e7f7ce Clear entire lines when removing from history or freeing. 2026-06-07 14:41:27 +00:00
Thomas Adam
e163cbacec Merge branch 'obsd-master' 2026-06-07 15:30:06 +01:00
Nicholas Marriott
0ec6870902 Add to CHANGES. 2026-06-07 15:24:53 +01:00
Thomas Adam
55421ede70 Merge branch 'obsd-master' 2026-06-07 15:15:06 +01:00
nicm
3acd247f5d Redraw entire session when making a new pane. 2026-06-07 14:11:20 +00:00
Thomas Adam
59151704b4 Merge branch 'obsd-master' 2026-06-07 15:00:06 +01:00
nicm
343ee5ece1 Add -g flag to kill-session to kill all sessions in a group, GitHub
issue 5157 from github at jiku dot jp.
2026-06-07 13:51:42 +00:00
nicm
0256ee77c2 Add some additional environment variables needed for Wayland, from
shbernal dot 01 at gmail dot com.
2026-06-07 13:37:39 +00:00
Thomas Adam
72f5415fc8 Merge branch 'obsd-master' 2026-06-07 14:30:06 +01:00
nicm
ef8e9df02f Tweak previous. 2026-06-07 13:29:16 +00:00
nicm
306ee0eddb Add -E to split-window to explicitly make an empty pane rather than
requiring an empty command.
2026-06-07 13:15:28 +00:00
Nicholas Marriott
3d9e6bf454 Update CHANGES for 3.7. 2026-06-07 12:01:09 +01:00
Thomas Adam
0fc6cb4099 Merge branch 'obsd-master' 2026-06-07 11:15:06 +01:00
nicm
5744021ac9 Allow floating panes to be created partially off the window, based on a
change from Michael Grant.
2026-06-07 09:54:25 +00:00
Thomas Adam
54be05e5e4 Merge branch 'obsd-master' 2026-06-07 09:30:06 +01:00
nicm
c9e22ab67b Enable floating panes. These are panes which sit above the layout
("tiled panes") like popups but unlike popups are not modal and behave
like panes (so the same escape sequence support). Floating panes are
created with the "new-pane" command, bound to * by default.

Currently floating panes can only be moved and resized using the mouse.
The default second status line (if "status-format" is set to 2) has
changed to show a list of panes.

This is the first step and many obvious features are not yet complete
(notably the ability to swap floating panes, resize them using
resize-pane, change them between floating and tiles, and restore custom
layouts with floating panes).

Mostly written by Michael Grant with help from Dane Jensen.
2026-06-07 08:25:59 +00:00
nicm
4063f968c6 Do not allow swapping floating panes for the moment. 2026-06-07 08:22:58 +00:00
Thomas Adam
8388c53a47 Merge branch 'obsd-master' 2026-06-06 19:00:06 +01:00
nicm
3a72f3beb8 Use correct X position for visible range checks in
screen_write_fast_copy, GitHub issue 5164 from Barrett Ruth.
2026-06-06 17:35:30 +00:00
nicm
35709a30ee Change so that floating panes are always part of the root layout. From
Dane Jensen.
2026-06-06 17:30:48 +00:00
Thomas Adam
f9a683cf39 Merge branch 'obsd-master' 2026-06-05 09:15:07 +01:00
nicm
d7a2b52da8 Add a way to quote for command arguments and use for key in list-keys,
GitHub issue 5153.
2026-06-05 08:04:03 +00:00
Thomas Adam
f43adc364a Merge branch 'obsd-master' 2026-06-04 10:45:09 +01:00
nicm
6beebbe074 Fix scrollbar drag position when window is taller than tty, from Michael
Grant.
2026-06-04 09:24:03 +00:00
Thomas Adam
019f8eb278 Merge branch 'obsd-master' 2026-06-03 21:30:06 +01:00
nicm
580f9ced08 Change run-shell expansion to just be #{1} etc not #1 which interferes
with colours.
2026-06-03 20:18:49 +00:00
nicm
642cc15b99 Change so panes scroll when the cursor is at the top instead of halfway
down, from Michael Grant.
2026-06-03 20:16:14 +00:00
Thomas Adam
cfeade6055 Merge branch 'obsd-master' 2026-06-02 21:00:06 +01:00
nicm
390207cdf1 Use new layout functions for join-pane also, from Dane Jensen. 2026-06-02 19:31:36 +00:00
Thomas Adam
c97b2b95e6 Merge branch 'obsd-master' 2026-06-02 20:30:06 +01:00
nicm
a3bf632276 Move cell creation into layout.c, from Dane Jensen. 2026-06-02 19:25:06 +00:00
Thomas Adam
5368ee32dd github: update lock-threads to v6
Use the latest release of lock-threads.
2026-06-02 16:26:13 +01:00
Thomas Adam
e89f02bd8f Merge branch 'obsd-master' 2026-06-02 09:30:06 +01:00
nicm
ae6a749d3a Move the PANE_FLOATING flag into the layout cell and add an accessor.
From Dane Jensen.
2026-06-02 08:13:50 +00:00
Thomas Adam
0759030ee8 Merge branch 'obsd-master' 2026-06-02 08:30:06 +01:00
nicm
35f4c8b333 Fix border colour bug in the floating panes branch where inactive
floating pane borders incorrectly use the active colour at intersections
with tiled pane borders. From Michael Grant.
2026-06-02 07:11:49 +00:00
Thomas Adam
97ac32f536 Merge branch 'obsd-master' 2026-06-01 21:30:06 +01:00
Thomas Adam
2f3ef88dbf Merge branch 'obsd-master' 2026-06-01 21:15:06 +01:00
nicm
9725a0b9bf Remove the actual LAYOUT_FLOATING enum member. 2026-06-01 20:10:01 +00:00
nicm
b368e89bea Remove LAYOUT_FLOATING and disable custom layouts with floating panes
for now, from Dane Jensen.
2026-06-01 20:09:26 +00:00
nicm
3269833caa Fix vertical position of scrollbar having wrong vertical offset when
status-position top, from Michael Grant.
2026-06-01 20:00:25 +00:00
nicm
922f7d1645 Fix resize of floating panes with pane-border-status top, from Michael
Grant.
2026-06-01 19:59:04 +00:00
nicm
a1873fef61 Only set saved_layout_cell if creating a new cell, avoids crash when
respawning pane. Reported by Mingjie Shen in GitHubb issue 5143.
2026-06-01 19:56:26 +00:00
Thomas Adam
0eff3b6b97 Merge branch 'obsd-master' 2026-06-01 19:30:06 +01:00
nicm
f6557d1ea2 Add a Z sort order in tree mode. 2026-06-01 18:19:51 +00:00
Thomas Adam
718eb2b4cc Merge branch 'obsd-master' 2026-06-01 19:15:06 +01:00
nicm
cd6c01e42b Mark floating panes in tree mode. 2026-06-01 18:00:20 +00:00
Thomas Adam
0b773dd792 Merge branch 'obsd-master' 2026-06-01 15:15:06 +01:00
nicm
b68ab3a4b4 Add pane_x, y, z format variables and show in list-panes. 2026-06-01 14:01:09 +00:00
Thomas Adam
d8c9f88fe7 Merge branch 'obsd-master' 2026-06-01 15:00:06 +01:00
Nicholas Marriott
f6398ba51c Add some more DECRQM responses, from Ayman Bagabas in GitHub issue 5118. 2026-06-01 14:30:33 +01:00
nicm
67dbf01eeb Add some more DECRQM responses, from Ayman Bagabas in GitHub issue 5118. 2026-06-01 13:28:49 +00:00
Thomas Adam
7c71064b64 Merge branch 'obsd-master' 2026-06-01 12:15:06 +01:00
nicm
4d91ff8d8d Do not try to use pane if there isn't one when redrawing wide characters,
GitHub issues 5145 and 5139.
2026-06-01 10:53:28 +00:00
Thomas Adam
e778f7f805 Merge branch 'obsd-master' 2026-06-01 09:45:06 +01:00
nicm
3bff7a9e62 Allow run-shell arguments after a shell command to be expanded as #1, #2
and so on. From Rasmus Thystrup Karstensen in GitHub issue 5121.
2026-06-01 08:27:37 +00:00
Thomas Adam
705c2ac55a Merge branch 'obsd-master' 2026-05-31 23:00:06 +01:00
Thomas Adam
3f3d65e452 Merge branch 'obsd-master' 2026-05-31 22:30:06 +01:00
nicm
a9721ea268 Only draw visible parts of numbers for display-panes. 2026-05-31 21:29:41 +00:00
nicm
d876bb8cf3 When pane-border-indicators is set to "both" or "arrows", only draw
arrows on the active floating pane and no other floating panes. Also
there is no need to loop in screen_redraw_cell_border if only checking
one pane.
2026-05-31 21:08:23 +00:00
Thomas Adam
b17dcc6773 Merge branch 'obsd-master' 2026-05-31 22:00:06 +01:00
nicm
e3ee2b327b Move back to starting position when pane is outside the window at all. 2026-05-31 20:41:07 +00:00
Thomas Adam
74b91d6dec Merge branch 'obsd-master' 2026-05-30 17:45:06 +01:00
nicm
7a3b7ae812 Return empty ranges if line is below window. 2026-05-30 16:10:13 +00:00
Thomas Adam
35df67c893 Merge branch 'obsd-master' 2026-05-30 12:30:06 +01:00
nicm
290303e64f Missed this file from previous. 2026-05-30 11:20:03 +00:00
nicm
f8ab7a5158 Handle ranges for panes which are outside the window to the left. 2026-05-30 11:19:39 +00:00
Thomas Adam
decaaf4b55 Merge branch 'obsd-master' 2026-05-30 11:15:06 +01:00
nicm
b9d228c72f Do not crash when a pane offset is negative. 2026-05-30 09:48:30 +00:00
Thomas Adam
11e48c3fa9 Merge branch 'obsd-master' 2026-05-30 10:15:06 +01:00
nicm
c40302a7ff Check if the range is invalid using start,end rather than length since
it will never be negative.
2026-05-30 08:58:29 +00:00
Thomas Adam
964e22cfef Merge branch 'obsd-master' 2026-05-29 17:15:06 +01:00
nicm
55db69e3b2 Correct offsets for redrawing entire line here too. 2026-05-29 15:51:03 +00:00
Thomas Adam
99b08ecde5 Merge branch 'obsd-master' 2026-05-29 09:15:06 +01:00
nicm
4ecf93487e Use correct X position for redrawing wide characters. 2026-05-29 07:49:01 +00:00
Nicholas Marriott
f066933418 Uninstall the man page too. 2026-05-28 15:20:04 +01:00
Thomas Adam
481d8299ca Merge branch 'obsd-master' 2026-05-28 12:45:06 +01:00
nicm
3b19536df9 Swap panes on z-index list as well. 2026-05-28 11:17:35 +00:00
Thomas Adam
2212157b61 Merge branch 'obsd-master' 2026-05-28 12:00:07 +01:00
nicm
7ebeed1ae7 Do not try to use last pane if there isn't one. 2026-05-28 10:45:17 +00:00
nicm
7769097915 Hide menu options that do not work for floating panes. 2026-05-28 10:34:38 +00:00
Thomas Adam
595b0cb471 Merge branch 'obsd-master' 2026-05-28 10:00:08 +01:00
nicm
c50c3629e6 Use the correct ranges when a pane is covered by a popup in tty_draw_pane. 2026-05-28 08:43:57 +00:00
Thomas Adam
887b6f4da7 Merge branch 'obsd-master' 2026-05-28 09:15:07 +01:00
nicm
9c6cfcd2e9 Obscured check should not be true if pane is same size as window. 2026-05-28 07:58:45 +00:00
Thomas Adam
658c7d1dbf Merge branch 'obsd-master' 2026-05-28 08:45:08 +01:00
nicm
ecfe6e4e7a Check visible ranges for collected cell output. 2026-05-28 07:23:52 +00:00
Thomas Adam
f53c4e7525 Merge branch 'obsd-master' 2026-05-27 22:15:06 +01:00
Nicholas Marriott
3690c64e88 Use -g -ggdb for debug. 2026-05-27 22:07:57 +01:00
Thomas Adam
c489f786b6 Merge branch 'obsd-master' 2026-05-27 22:00:06 +01:00
nicm
5c40cc43b2 Bring in the single-cell redraw parts for floating panes. 2026-05-27 20:57:53 +00:00
nicm
54ae850322 Do not adjust end position when removing partial padding, it does not
change.
2026-05-27 20:47:23 +00:00
Thomas Adam
a9b9d2f6d3 Merge branch 'obsd-master' 2026-05-27 21:00:08 +01:00
nicm
f00f92e7e3 Check visible ranges when copying screens. 2026-05-27 19:43:46 +00:00
nicm
bbe26df83d Add an accessor function needed for floating panes. 2026-05-27 19:36:04 +00:00
Thomas Adam
daf9988b3a Merge branch 'obsd-master' 2026-05-27 20:15:06 +01:00
nicm
ee67452772 Add window-pane-status-format options and adjust the default second
status line to show panes, also change how window-style is checked now
it is a pane option.
2026-05-27 18:57:10 +00:00
Thomas Adam
781364f7f9 Merge branch 'obsd-master' 2026-05-27 17:45:06 +01:00
nicm
84d016ddcf Floating panes clearing bits. 2026-05-27 16:25:01 +00:00
Nicholas Marriott
0241fdac1a Remove extra declaration. 2026-05-27 16:52:09 +01:00
Thomas Adam
d62bae442b Merge branch 'obsd-master' 2026-05-27 16:46:04 +01:00
nicm
9cc8947a43 Cache border and active border style separately, fixes problem reported
by Marcel Partap in GitHub issue 5125.
2026-05-27 13:41:20 +00:00
nicm
bff68fa079 Remove reference to an option that hasn't been added yet. 2026-05-27 12:52:47 +00:00
nicm
00c59041ea Bring in some more floating panes changes - obscured panes need to be
handled specially in screen_write_* rather than dropping into the client
redraw (tty.c).
2026-05-27 11:54:28 +00:00
Thomas Adam
a77b81f0e5 Merge branch 'obsd-master' 2026-05-27 08:15:06 +01:00
jsg
f839588c69 fix sort_get_clients() indentation; ok nicm@ 2026-05-27 07:05:20 +00:00
nicm
27d719cd4d Check error result correctly (*cause not cause), pointed out by jsg. 2026-05-27 07:01:36 +00:00
Thomas Adam
7a15dc6772 Merge branch 'obsd-master' 2026-05-25 16:15:06 +01:00
nicm
bfd7f468ce pane_zoomed_flag went missing somewhere in tmux.1, put it back. 2026-05-25 14:53:09 +00:00
Thomas Adam
cca1e77f95 Merge branch 'obsd-master' 2026-05-25 09:15:07 +01:00
nicm
cc81287665 More of floating panes redraw path, still mostly using flags which are never set. 2026-05-25 08:07:48 +00:00
nicm
ca95153b45 Preserve flags when creating new cells, from Brian Youngs. 2026-05-25 07:55:46 +00:00
Nicholas Marriott
eb330a6df6 Restore SIXEL bit. 2026-05-24 10:05:41 +01:00
Thomas Adam
b498979d4d Merge branch 'obsd-master' 2026-05-24 10:00:06 +01:00
nicm
f047b3f8e7 Do not crash when freeing layout cell, reported by Jere Viikari. 2026-05-24 08:40:43 +00:00
Thomas Adam
c67787536b Merge branch 'obsd-master' 2026-05-23 18:00:06 +01:00
nicm
3cfc96d4de Add missing argument to %unlinked-window-renamed, reported by Andrew
Grangaard.
2026-05-23 16:43:04 +00:00
Thomas Adam
91a8c66537 Merge branch 'obsd-master' 2026-05-23 08:45:06 +01:00
nicm
5c3a4746b7 Some code tidying up. 2026-05-23 07:22:50 +00:00
Nicholas Marriott
b8e0004ff2 Fix missing arguments. 2026-05-22 18:33:04 +01:00
Thomas Adam
4e0aabd8a7 Merge branch 'obsd-master' 2026-05-22 16:54:06 +01:00
nicm
143a177055 Tighten up read-only checks on attach-session, detach-client and
switch-client so that a user should be able to only detach their own
client. Reported by John Walker.
2026-05-22 15:22:43 +00:00
nicm
d45a9cad8c Work out if a pane is obscured by another one when drawing. 2026-05-22 11:55:43 +00:00
Nicholas Marriott
285a3b7522 Fix up SIXEL with recent changes. 2026-05-22 12:44:08 +01:00
Thomas Adam
d4255818c4 Merge branch 'obsd-master' 2026-05-22 12:01:06 +01:00
Thomas Adam
f714f3ee78 Merge branch 'obsd-master' 2026-05-22 10:28:51 +01:00
nicm
36bc351557 Use a union for the data passed around in tty_ctx instead of void *. 2026-05-22 09:21:32 +00:00
nicm
d5ee330d20 Tidy up a load of different flags and special values in the tty_ctx into
a single bitmask.
2026-05-22 09:05:16 +00:00
Thomas Adam
634e9d6213 Merge branch 'obsd-master' 2026-05-21 12:01:06 +01:00
nicm
9aa9a92f72 Pane resizing code for floating panes, mostly by Michael Grant. 2026-05-21 07:28:51 +00:00
Nicholas Marriott
8c51c0fdbd Merge tag '3.6b'
3.6b
2026-05-20 12:53:07 +01:00
Thomas Adam
24e4a545a2 Merge branch 'obsd-master' 2026-05-20 12:19:23 +01:00
Nicholas Marriott
c5fb5e8bb0 Update CHANGES. 2026-05-20 12:17:30 +01:00
Nicholas Marriott
b8434182c9 Track which list (images or saved_images) each image is on so they can be
removed from the correct list when the total image count is reached. Fixes
crash reported by xlabai at tencent dot com.
2026-05-20 12:15:34 +01:00
Nicholas Marriott
e1f942f26a Add a define for max images and bump to 20 for the moment. 2026-05-20 12:14:22 +01:00
Thomas Adam
91b85efabb Merge branch 'obsd-master' 2026-05-20 12:01:06 +01:00
nicm
0057905ca0 Increase escape delay if the buffer contains a partial paste end, fixes
issues with at least Windows Terminal. From jing dot empty at gmail.com
GitHub issue 5088.
2026-05-20 10:56:46 +00:00
nicm
572e26deb8 Some more trivial floating panes bits. 2026-05-20 08:54:40 +00:00
Thomas Adam
caff9a135f Merge branch 'obsd-master' 2026-05-20 08:47:44 +01:00
nicm
ce24b92758 Floating panes full redraw code, by Michael Grant. 2026-05-19 13:12:45 +00:00
nicm
f5a086ac98 Some more easy floating panes bits. 2026-05-19 12:16:25 +00:00
Thomas Adam
cb1025e0e8 Merge branch 'obsd-master' 2026-05-19 12:25:31 +01:00
nicm
a46cdb8bbc More (currently disabled) bits for creating floating panes, from Michael
Grant and Dane Jensen.
2026-05-19 10:26:03 +00:00
nicm
34e2035bad More bits for pane Z index tracking from floating panes, mostly by
Michael Grant.
2026-05-19 09:48:14 +00:00
Thomas Adam
a9ba7b8ecb Merge branch 'obsd-master' 2026-05-18 09:38:02 +01:00
nicm
962d647993 Remove some leftover debug code. 2026-05-17 16:02:33 +00:00
nicm
a8520ba59e Bring over some of layout and positioning code for floating panes, by
Michael Grant.
2026-05-17 16:01:42 +00:00
nicm
f12d7b4e67 When mode-keys is set to vi, do not allow the cursor to go into the
invisible extra cell to the right of the visible text. This is closer to
what vi(1) does. From Max Vim in GitHub issue 5070.
2026-05-17 13:12:21 +00:00
nicm
bbea6e6375 Add a five second limit on pasting for terminals which mysteriously lose
the end sequence if the paste is too big (that is, Terminal.app).
Reported by Garri Djavadyan in GitHub issue 4527.
2026-05-17 13:01:04 +00:00
nicm
b24b8424b1 Switch to use backing grid not copy mode's grid for copy line, GitHub
issue 4995.
2026-05-17 12:38:04 +00:00
nicm
35bd1a4c02 Do not include unattached clients on sorted list, fixes crash reported
by Marcel Partap.
2026-05-17 11:17:16 +00:00
nicm
818352c3cd Add some more explanatory text about targets, GitHub issue 4850. 2026-05-17 11:12:07 +00:00
nicm
742efe989f Report file open errors more sensibly, reported by Meriel Luna
Mittelbach in GitHub issue 5081.
2026-05-17 10:54:01 +00:00
nicm
4cea1c7189 Add new-pane command, currently this is equivalent to split-window but
it will have minor differences for floating panes in future. Also add
-R/-s/-S/-k/-m flags to control border and style and behaviour, like
popups. GitHub issue 5027 from Dane Jensen.
2026-05-17 10:44:53 +00:00
Nicholas Marriott
18ddda48d2 More. 2026-05-16 12:18:41 +01:00
Nicholas Marriott
5651519f5d Try to make Copilot shut up. 2026-05-16 12:07:09 +01:00
nicm
281e8ff766 Fix a couple of bugs in tty_draw_line: do not loop forever if orphan
padding appears, or if a wide character is trimmed at the right of the
region. Much help with testing from qingliu at alauda dot io in GitHub
issue 5024.
2026-05-13 13:12:23 +00:00
nicm
dbc0710bc9 Update supported features for Foot, from Meriel Luna Mittelbach in
GutHub issue 5079.
2026-05-13 10:24:57 +00:00
nicm
8f28f31b78 Do not crash if set progress bar with no pane, from Dane Jensen. 2026-05-13 08:25:05 +00:00
Thomas Adam
3f651d9fa9 Merge branch 'obsd-master' 2026-05-12 16:01:08 +01:00
nicm
18fced7e7f Make pane offsets signed, needed for floating panes. 2026-05-12 12:05:41 +00:00
Thomas Adam
f1f0158741 Merge branch 'obsd-master' 2026-05-12 12:01:11 +01:00
nicm
3d6c542e34 Turn off the "is this a paste" guessing if the terminal supports bracket
pasting instead, GitHub issue 5031.
2026-05-12 10:28:09 +00:00
Nicholas Marriott
c3036116de Update test output. 2026-05-12 10:38:29 +01:00
nicm
9e3653a612 Check FIONREAD for all panes not just piped panes, fixes issues with
tests, GitHub issue 4807.
2026-05-12 09:37:25 +00:00
nicm
27a00d1bfd Fix infinite loop due to underflow when redrawing scrollbar, from Pavel
Lavrukhin in GitHub issue 4932.
2026-05-12 09:32:49 +00:00
nicm
800837ff3f Fix control mode teardown ordering for queued pane output, GitHub issue
5064 from Aaron Campbell.
2026-05-12 09:27:28 +00:00
Nicholas Marriott
bfbfb6dbf3 Avoid overshooting Sixel height in sixel_parse_write, GitHub issue 5071 from
nincsnevem662 at gmail dot com.
2026-05-11 12:00:06 +01:00
Thomas Adam
8fb1b1d412 Merge branch 'obsd-master' 2026-05-08 12:01:09 +01:00
nicm
af98467d0d Do not cache format for status line because it stores various pointers
that might be stale, instead cache the cmd_find_state and rebuild the
formats every time they are needed. Reported by Marcel Partap in GitHub
isue 5065.
2026-05-08 06:57:38 +00:00
Thomas Adam
f9d50d580c Merge branch 'obsd-master' 2026-05-07 12:01:08 +01:00
nicm
496805476e Require layout prefix to be 5 characters, GitHub issue 5067 from
cglosner at gmail dot com.
2026-05-07 09:21:05 +00:00
nicm
962e565591 Add Emacs-style recentre-top-bottom, GitHub issue 5053 from sinyax75 at
gmail dot com.
2026-05-07 09:15:44 +00:00
Thomas Adam
862625a4bf Merge branch 'obsd-master' 2026-05-06 16:01:09 +01:00
nicm
2d8aade74b Add time limit to a couple of other loops. 2026-05-06 13:43:38 +00:00
Nicholas Marriott
2aad2cfc53 Add a configure flag for ASAN. 2026-05-06 08:23:45 +01:00
Thomas Adam
ad94405dac Merge branch 'obsd-master' 2026-05-05 16:01:08 +01:00
nicm
518fcf7e03 Do not sanitize title when popping it from stack, also add a limit to
number of pushed titles.
2026-05-05 13:18:46 +00:00
nicm
1fbd00e1e8 Add missing flags to screen_mode_to_string and do not write before
before if any are missed, second bit from qingliu at alauda dot io.
2026-05-05 12:06:52 +00:00
nicm
cae229cadc Discard queued data and clear offsets when turning pane off to prevent
later read of data that has been removed. From Aaron Campbell in GitHub
issue 5054.
2026-05-05 12:02:12 +00:00
Thomas Adam
9d3dcdbc38 Merge branch 'obsd-master' 2026-05-03 20:01:09 +01:00
nicm
d52fabce72 Allow the indicator in tree mode to be customized by two new options:
tree-mode-preview-format and tree-mode-preview-style.
2026-05-03 15:02:48 +00:00
nicm
637d4c3083 Fix control client hang on exit after toggling no-output, GitHub issue
5049 from Aaron Campbell.
2026-05-03 15:01:21 +00:00
nicm
1e96d9a87f Do not check for NULL after dereferencing, from alexarama at yahoo dot
com in GitHub issue 5051.
2026-05-03 14:57:09 +00:00
nicm
49172b256c Free working stuff when R formats fail. 2026-05-03 14:55:43 +00:00
Thomas Adam
9a94f48663 Merge branch 'obsd-master' 2026-05-01 16:01:09 +01:00
nicm
2e9c6c2586 Add some checks on line size to avoid underflow, from san65384 at gmail
dot com in GitHub issue 4955.
2026-05-01 11:56:41 +00:00
Thomas Adam
92522473b0 Merge branch 'obsd-master' 2026-05-01 12:01:08 +01:00
nicm
56200ca044 Do not leak cached last result from control subs, from Aaron Campbell in
GitHub issue 5047.
2026-05-01 09:59:42 +00:00
nicm
524f283cc6 Add support for line numbers in copy mode. A new copy-mode-line-numbers
option has the following modes: off, default (tmux's normal line
numbering where 0 is the top visible line), absolute (first line in
history is 1), relative (relative to the cursor) and hybrid (current
line is absolute, others relative). Also adds
copy-mode-line-number-style and copy-mode-current-line-number-style to
set the style of the line numbers. When copy mode is entered with the
mouse, line numbers stay off.

From Leo Henon in GitHub issue 5025.
2026-05-01 09:44:42 +00:00
nicm
a2acf0f7cf Check time inside repeat (R:) loop as well. 2026-05-01 09:34:05 +00:00
Thomas Adam
d85f1b942d Merge branch 'obsd-master' 2026-04-28 16:01:08 +01:00
Thomas Adam
8da41bdc2b Merge branch 'obsd-master' 2026-04-28 12:01:08 +01:00
nicm
4a926e489b Make C-[ have the same bindings as Escape for terminals with extended
keys where they are different, GitHub issue 5035 from Eric NICOLAS.
2026-04-28 10:01:07 +00:00
nicm
746dd91e86 Sanitize paste buffer names in paste_set and paste_rename, GitHub issue
5032 from Barrett Ruth.
2026-04-28 08:52:37 +00:00
nicm
763dec881a Do not leak socketpair fds in pipe-pane when fork fails. From Barrett
Ruth.
2026-04-28 08:47:55 +00:00
nicm
2a5715fad5 Do not deref NULL job in window_copy_pipe_run when job_run fails. From
Barrett Ruth in GitHub issue 5036.
2026-04-28 08:35:21 +00:00
nicm
ec58272b95 Do not hang run-shell when job_run fails, from Barrett Ruth in GitHub
issue 5037.
2026-04-28 08:34:15 +00:00
nicm
f90d3af403 Fix -p for for join-pane, from Dane Jensen. 2026-04-28 08:32:44 +00:00
Thomas Adam
c2659401b0 Merge branch 'obsd-master' 2026-04-27 16:01:09 +01:00
nicm
9840d4a4a3 Add a limit on maximum length of environment variable assignment in
configuration files.
2026-04-27 12:31:11 +00:00
Nicholas Marriott
fc6d94a9f8 Track which list (images or saved_images) each image is on so they can be
removed from the correct list when the total image count is reached. Fixes
crash reported by xlabai at tencent dot com.
2026-04-27 13:09:07 +01:00
Nicholas Marriott
61a7be0eab Turn off regular expressions when fuzzing because there are cases that take too
much time or memory that cannot be fixed.
2026-04-26 21:19:16 +01:00
Thomas Adam
e4a439aa0c Merge branch 'obsd-master' 2026-04-26 21:12:23 +01:00
nicm
b844cafb9c No need to stravis the window name twice. 2026-04-24 10:08:52 +00:00
nicm
1d9522a7a6 Kill client rather than fatalx on bad file handling messages, reported
by Tim Zheng.
2026-04-23 12:36:15 +00:00
nicm
ffe80579df Redraw correctly when a popup is present above pane borders, GitHub
issue 4997 from Manuel Einfalt.
2026-04-23 11:29:23 +00:00
Thomas Adam
bcd17cf99a Merge branch 'obsd-master' 2026-04-23 11:58:17 +01:00
nicm
bc15723f7f Add feature for progress bar and pass to outside terminal, GitHu issue
4972 from Eric Dorland.
2026-04-22 07:25:17 +00:00
nicm
7a0cc03532 Translate keypad keys to text in prompt input. From Barrett Ruth in
GitHub issue 4996.
2026-04-22 07:15:34 +00:00
nicm
d36f6783c7 Add a couple of controls (kill, zoom) to default pane-status-format.
Will be more to come with floating panes. From Dane Jensen in GitHub
issue 4981.
2026-04-22 07:13:26 +00:00
nicm
d339ab51eb Sanitize pane titles and window and session names more consistently and
strictly, prevents C0 characters and other nonvisible characters causing
problems. Reported (with a different fix) by Chris Monardo in GitHub
issue 4999.
2026-04-22 07:10:16 +00:00
nicm
fee70031f6 Make clock visible on terminals without colours, from Manuel Einfalt in
GitHub issue 5001.
2026-04-22 07:05:59 +00:00
nicm
303edb71bd Add a fairly low time limit to format evaluation to stop absurdly nested
formats from making tmux appear to hang.
2026-04-22 07:05:03 +00:00
nicm
314f0ae964 Do not leak hyperlinks in copy mode, from Barrett Ruth in GitHub issue
5020.
2026-04-22 07:03:06 +00:00
nicm
0835980ef1 Remove no longer accurate statement from tmux.1, reported by dkuettel at
gmail dot com.
2026-04-22 06:58:59 +00:00
nicm
baf55e4616 Add a default set of features for WezTerm. 2026-04-22 06:57:08 +00:00
Nicholas Marriott
dd62c2f946 Do not need to create a new window each time for input-keys.sh test. 2026-04-15 16:21:35 +01:00
Thomas Adam
638cecf43c Merge branch 'obsd-master' 2026-04-14 16:01:08 +01:00
nicm
a97cfe513d Do not leak old time format if it is replaced in same format. 2026-04-14 11:25:41 +00:00
Thomas Adam
9de3c0495f Merge branch 'obsd-master' 2026-04-14 12:01:08 +01:00
nicm
67e6f8245b If job_run fails, do not crash but instead free the popup. 2026-04-14 08:39:10 +00:00
nicm
31c93c483a Another check for partially initialized control client, from Matt
Koscica in GitHub issue 5004.
2026-04-14 08:32:30 +00:00
nicm
86d4d5689c Add detach to default session menu, suggested by Przemyslaw Sztoch. 2026-04-14 07:35:17 +00:00
nicm
663e82bc19 Fix key binding conflict in session menu, from Dane Jensen. 2026-04-14 07:28:57 +00:00
nicm
51109e3f72 Include window bits for pane notifications, GitHub issue 5007 from Saul
Nogueras.
2026-04-14 07:26:45 +00:00
nicm
a11363e9e7 Limit precision to 100 to stop silly formats from running out of memory,
reported by z1281552865 at gmail dot com.
2026-04-14 07:24:23 +00:00
nicm
cc57913d7a Add WAYLAND_DISPLAY to default update-environment, GitHub issue 4965 from
wgh at torlan dot ru.
2026-04-14 07:16:02 +00:00
Thomas Adam
057d169492 Merge branch 'obsd-master' 2026-04-13 12:01:08 +01:00
nicm
e5a2a25faf Do not notify clients if not fully initialized, from Ben Maurer in
GitHub issue 4980.
2026-04-13 09:35:20 +00:00
nicm
147521b757 Add -C flag to command-prompt to match display-message -C (do not freeze
panes). From Barrett Ruth in GitHub issue 4978.
2026-04-13 09:33:09 +00:00
Thomas Adam
31d77e29b6 Merge branch 'obsd-master' 2026-04-05 20:01:08 +01:00
nicm
4b0ff07bcb When a cell is cleared after having been moved, we cannot reuse its
extended data, because that may still be in use. Add a flag to
grid_clear_cell to indicate this. Fixes irritating problems with ICH
(CSI @) mostly visible in emacs.
2026-04-05 15:43:17 +00:00
Thomas Adam
0ee18d4421 Merge branch 'obsd-master' 2026-04-05 16:01:08 +01:00
nicm
21da45e5c3 Add extkeys feature to tmux itself so nested tmux works, GitHub issue 4960. 2026-04-05 14:29:04 +00:00
nicm
d5ed967f58 Set less crazy limits (than INT_MAX) for pad and trim, makes ossfuzz happier. 2026-04-05 13:24:02 +00:00
Thomas Adam
42dafcc62a Merge branch 'obsd-master' 2026-04-04 20:01:11 +01:00
nicm
cad282ebb7 Cache user from getpwuid because it can be very expensive on some
platforms. From Ben Maurer in GitHub issue 4973.
2026-04-04 17:13:07 +00:00
nicm
7f2ac9c871 Add remain-on-exit key to keep pane around until a key is pressed, from
Michael Grant.
2026-04-04 17:00:33 +00:00
nicm
87aaff5fae Bring some new formats from the floating panes work: pane_zoomed_flag,
pane_flags, pane_floating_flag. By Michael Grant.
2026-04-04 16:40:27 +00:00
Thomas Adam
b411010237 Merge branch 'obsd-master' 2026-04-04 16:01:09 +01:00
nicm
d2d86ac360 Document control ranges, from Dane Jensen. 2026-04-04 11:48:00 +00:00
nicm
a827725a8d Set up default and last cell before clearing padding, reported by
someone on GitHub.
2026-04-04 11:39:49 +00:00
nicm
3badbc50e0 Do not read off end of buffer if it ends in \ when expanding \c, found
by ossfuzz.
2026-04-04 11:32:05 +00:00
nicm
cd60de443e Clamp width to terminal width, also change calculation of end of screen
(it is OK to be outside the screen). Fixes problem reported by Dane
Jensen in GitHub issue 4969.
2026-04-04 11:20:01 +00:00
Nicholas Marriott
95e40115a2 Don't just resize buffer once, repeat until it is big enough. Also use
reallocarray. Reported by tuannguyenduc228 at gmail dot com.
2026-04-04 12:11:25 +01:00
Thomas Adam
66c324d7ef Merge branch 'obsd-master' 2026-04-03 16:01:08 +01:00
nicm
6b056eb53f Add some new mouse ranges called "control0" to "control9", will be used
for controls on floating panes, from Dane Jensen, with some bits from
Michael Grant.
2026-04-03 10:13:20 +00:00
Thomas Adam
8cfdcf9a6f Merge branch 'obsd-master' 2026-04-03 11:04:15 +01:00
nicm
0917cd7010 Missed a line from previous. 2026-04-03 09:18:24 +00:00
nicm
bdd78ce38e Handle OSC 9;4 progress bar sequence and store in format variables, from
Eric Dorland in GitHub issue 4954.
2026-04-03 09:14:27 +00:00
nicm
7497db6e37 Increase b64_pton buffer to allow for Base64 without padding, from
Michal Majchrowicz.
2026-04-03 08:23:10 +00:00
Thomas Adam
43e2942fa0 Merge branch 'obsd-master' 2026-04-02 16:01:08 +01:00
Thomas Adam
51bc2f3b76 Merge branch 'obsd-master' 2026-04-02 12:01:08 +01:00
nicm
483683c197 Do not leak command in list-keys, reported by tb@. Also tidy up some
function names while here.
2026-04-02 09:58:14 +00:00
nicm
c95d341220 Do not leak trimmed string when expanding, found by ossfuzz. 2026-04-02 09:35:46 +00:00
nicm
be2c6f3b5e Use INT_MIN + 1 as strtonum lower limits in formats so -ve works, found
by ossfuzz.
2026-04-02 09:28:22 +00:00
nicm
2d5736f297 Limit argc to between 0 and 1000 to prevent fatal from MSG_COMMAND, from
Michal Majchrowicz.
2026-04-02 09:11:39 +00:00
nicm
1a51193899 Handle empty regular expression in substitution, found by ossfuzz. 2026-04-02 08:45:35 +00:00
nicm
8b51abef08 Check for \0 after skipping # not before in format_expand1, from ossfuzz. 2026-04-02 08:37:14 +00:00
nicm
cbb49e8c84 Fix NULL dereference in sort.c, from Dane Jensen. 2026-04-02 08:01:47 +00:00
Thomas Adam
dc12da4573 Merge branch 'obsd-master' 2026-04-01 12:01:08 +01:00
nicm
7af58f74b0 Fixed missing drag end detection and mouse button detection with
modifiers. From Dane Jensen, reported by Joseph Tyson and Mark Kelly.
2026-04-01 08:10:02 +00:00
Thomas Adam
1081876810 Merge branch 'obsd-master' 2026-03-31 16:01:08 +01:00
nicm
2ff0dd3fef Reorganize structure of key_code so that it can be built directly by
bitshifts rather than a load of huge switches, from Dane Jensen in
GitHub issue 4953.
2026-03-31 11:46:43 +00:00
Thomas Adam
a30fc69f86 Merge branch 'obsd-master' 2026-03-30 12:01:08 +01:00
nicm
022b5cf193 When in copy mode with a large scroll offset and the window is resized
so that history shrinks, data->oy can exceed screen_hsize causing an
unsigned integer underflow in the py computation. Clamp data->oy in
window_copy_resize and window_copy_cmd_refresh_from_pane before the
subtraction. From futpib at gmail dot com in GitHub issue 4958.
2026-03-30 09:23:40 +00:00
Nicholas Marriott
7620c03b72 Add new fuzzers for command parsing, formats and styles, from David Korczynski
in GitHub issue 4957.
2026-03-30 10:20:16 +01:00
Thomas Adam
6324dae114 Merge branch 'obsd-master' 2026-03-27 12:01:10 +00:00
nicm
e0237c6b8c Ql -> Fl, from arza at arza dot us in GitHub issue 4949. 2026-03-27 08:41:54 +00:00
nicm
1cf3e02bfb Add a format flag for bracket paste, from George Nachman in GitHub issue
4951.
2026-03-27 08:40:26 +00:00
Thomas Adam
6ca5405db9 Merge branch 'obsd-master' 2026-03-25 12:01:07 +00:00
Nicholas Marriott
fa31f2a3eb Do not really need both. 2026-03-25 10:39:03 +00:00
Nicholas Marriott
d03f9454b9 Make COPYING the same. 2026-03-25 10:38:03 +00:00
Nicholas Marriott
616fb1cbcf Add a LICENSE file to see if it makes GitHub happy. 2026-03-25 10:26:15 +00:00
nicm
b7d1e6257c Use \- for hyphens in tmux.1 to cause newer groff versions to render
them correctly (ASCII hyphen rather than Unicode) which aids copy and
paste. From Keith Thompson in GitHub issue 4948.
2026-03-25 10:08:50 +00:00
Thomas Adam
b101ffaed6 Merge branch 'obsd-master' 2026-03-23 12:01:10 +00:00
nicm
f7dad4f38f Check lastgc is not NULL before using it, GitHub issue 4935 from Pavel
Lavrukhin.
2026-03-23 09:05:59 +00:00
nicm
b88c08f860 Fix a NULL dereference and use after free, GitHub issue 4936 from Pavel
Lavrukhin.
2026-03-23 09:03:43 +00:00
Nicholas Marriott
cc47f4d1c2 Use window options for cursor-style to avoid crash when no pane, from
Arden Packeer in GitHub issue 4942.
2026-03-23 09:00:42 +00:00
nicm
3909d7e92f Use window options for cursor-style to avoid crash when no pane, from
Arden Packeer in GitHub issue 4942.
2026-03-23 08:58:39 +00:00
nicm
d70edfa0a0 Fix issue where popup window gets overwritten by background updates,
from Conor Taylor in GitHub issue 4920.
2026-03-23 08:48:32 +00:00
nicm
d22ab85b84 Protect against overflow when scrollbar is off screen, from san65384 at
gmail dot com in GitHub issue 4933.
2026-03-23 08:45:30 +00:00
Thomas Adam
5c30b145df Merge branch 'obsd-master' 2026-03-18 12:01:09 +00:00
nicm
e446cba6a9 Copy hyperlinks when redrawing popup so they do not vanish, from Antoine
Gaudreau Simard in GitHub issue 4925.
2026-03-18 08:49:27 +00:00
Nicholas Marriott
8a0b4e0d8c Work around systemd killing panes early during system shutdown by creating
dependencies from the panes to the service which started tmux, GitHub issue
4926 from Dmitry Torokhov.
2026-03-18 08:46:16 +00:00
nicm
02a9cdff57 Fix buffer to both for get-clipboard, from Seth Girvan. 2026-03-18 08:41:20 +00:00
nicm
f516f8a1d4 Allow codepoint-widths to accept ranges, from san65384 at gmail dot com
in GitHub issue 4930.
2026-03-18 08:38:54 +00:00
Thomas Adam
bef4865d91 Merge branch 'obsd-master' 2026-03-12 16:01:08 +00:00
nicm
fd62f419ac Add a short builtin help text for each mode accessible with C-h,
based on code from Patrick Motard in GitHub issue 4751.
2026-03-12 12:40:40 +00:00
Thomas Adam
7fa6f320ed Merge branch 'obsd-master' 2026-03-12 12:01:09 +00:00
nicm
19f3fb131b Draw message as one format, allowing prompts and messages to occupy only
a portion of the status bar, overlaying the normal status content rather
than replacing the entire line. A new message-format option now controls
the entire message (like status-format). From Conor Taylor in GitHub
issue 4861.
2026-03-12 07:25:13 +00:00
nicm
551e8fcd24 Fix comparison of menu keys, reported by Vladimir Bauer in GitHub issue
4891.
2026-03-12 07:15:26 +00:00
Thomas Adam
04b4952f0e Merge branch 'obsd-master' 2026-03-09 20:01:07 +00:00
Thomas Adam
0d77555c97 Merge branch 'obsd-master' 2026-03-09 16:01:09 +00:00
nicm
67141fb4bb Add next/previous variables for windows in W: loop, from Conor Taylor in
GitHub issue 4856.
2026-03-09 15:46:01 +00:00
tb
2efa762c2b tmux: move block assigning to prefix a bit down to avoid a small leak
ok nicm
2026-03-09 14:37:26 +00:00
Nicholas Marriott
6c2dd193cf Fix memory leak in sixel_parse() on error path, from Renaud Allard in GitHub
issue 4916.
2026-03-09 14:34:58 +00:00
nicm
881bec958e Fix server crash when control client session is destroyed, from Renaud
Allard in GitHub issue 4916.
2026-03-09 14:33:55 +00:00
nicm
8899c751e5 Check for NULL value in tilde expansion before dereferencing, from
Renaud Allard in GitHub issue 4916.
2026-03-09 14:31:41 +00:00
Thomas Adam
d0caf0a322 Merge branch 'obsd-master' 2026-03-06 12:01:08 +00:00
tb
91b5108eae tmux: don't leak menu if the tty is too small to display it
ok nicm
2026-03-06 08:19:22 +00:00
nicm
2c7f73f9c4 Do not use recallocarray because the stored size may have changed during
reflow so may not match what it expects, fixes crash reported by Caspar
Schutijser.
2026-03-06 08:09:58 +00:00
Thomas Adam
5310592967 Merge branch 'obsd-master' 2026-03-06 04:01:09 +00:00
nicm
41bddae907 Fix incorrect condition for creating client name, from Vlad Apostol in
GitHub issue 4911.
2026-03-05 22:50:14 +00:00
Thomas Adam
d9d2b2f1ee Merge branch 'obsd-master' 2026-03-05 12:01:09 +00:00
nicm
1e208abd93 Add pane_pipe_pid with pipe fd and call setpgid to make it easier to kill. 2026-03-05 09:22:08 +00:00
Thomas Adam
d32088b58a Merge branch 'obsd-master' 2026-03-04 12:01:09 +00:00
nicm
6d37cc94a7 Make -c work with new-session -A, from Jody Frankowski in GitHub issue
4906.
2026-03-04 08:16:47 +00:00
nicm
55722a7ed7 Another memory leak, from Huihui Huang. 2026-03-04 08:15:24 +00:00
tb
d2fa20a390 tmux: use VIS_NOSLASH to avoid annoying double escaping on paste
from nicm
2026-03-04 07:19:32 +00:00
tb
bcbad050d4 tmux: tc can be NULL, so check before deref to avoid crashing the server
ok nicm
2026-03-04 07:17:01 +00:00
Thomas Adam
27698c468b Merge branch 'obsd-master' 2026-03-03 16:01:09 +00:00
nicm
49bb43047d Allow copy mode to work for readonly clients, except for copy commands,
from Dane Jensen.
2026-03-03 12:26:14 +00:00
nicm
77ce0a837d Check window is not NULL, from Chema Gonzalez in GitHub issue 4908. 2026-03-03 12:24:18 +00:00
Thomas Adam
bbbfa8f360 Merge branch 'obsd-master' 2026-03-02 12:01:09 +00:00
Nicholas Marriott
d781d2eaa1 Add regression test from Ilya Grigoriev, GitHub issue 4818. 2026-03-02 09:32:45 +00:00
nicm
f33b14bda6 Revert r1.343 for the moment since it breaks behaviour (#() in
status-left) that we need to keep.
2026-03-02 08:48:57 +00:00
nicm
e603549563 Do not leak active/all strings in format_loop_windows; from Huihui Huang
in GitHub issue 4898.
2026-03-02 08:41:43 +00:00
nicm
e80e0c761a Fix incorrect placement of It Xo, from Dane Jensen. 2026-03-02 08:39:44 +00:00
nicm
9e804202b6 Fix memory leak, from Emmanuel Ugwu in GitHub issue 4900. 2026-03-02 08:38:23 +00:00
Thomas Adam
0c678b1e1a mailcap: update entry for Thomas Adam 2026-03-01 17:01:08 +00:00
Thomas Adam
4cb29deb93 Merge branch 'obsd-master' 2026-02-27 12:01:08 +00:00
nicm
5413953d30 Validate -O flags, from Dane Jensen in GitHub issue 4889. 2026-02-27 08:25:12 +00:00
nicm
50e51b4513 Fix memory leak, from Chris Lewis, reported by Huihui Huang. 2026-02-27 08:23:02 +00:00
Thomas Adam
ee0647aba6 Merge branch 'obsd-master' 2026-02-26 16:01:09 +00:00
Nicholas Marriott
35be70f1f0 Add DECRPM test from David Turnbull. 2026-02-26 11:32:15 +00:00
nicm
dbb23d2182 Do not free buffer after adding to paste (since it now owns it). 2026-02-26 11:01:48 +00:00
Nicholas Marriott
478eaea982 Update base64 compat. 2026-02-26 10:55:41 +00:00
Thomas Adam
0800e51d41 Merge branch 'obsd-master' 2026-02-26 10:18:15 +00:00
nicm
5ff385f8a9 Tweak previous to reset cursor again as well. 2026-02-25 07:59:45 +00:00
nicm
5b3c642195 Pass paste buffer through vis(3) when pasting to prevent buffers
containing for example the bracket end sequence causing issues. -S flag
disables. Reported by Mason Davis.
2026-02-25 07:53:41 +00:00
nicm
bd6e201926 Do not expand #() in E: and T:, from Mason Davis. 2026-02-24 18:06:41 +00:00
Nicholas Marriott
08779aa2a3 Fix breakage in fuzzer, from David Korczynski, GitHub issue 4871. 2026-02-24 08:27:59 +00:00
nicm
09bd686327 Add sorting (-O flag) and a custom format (-F) to list-keys, from Dane
Jensen in GitHub issue 4845.
2026-02-24 08:20:52 +00:00
nicm
9316476a73 Add commands to turn exit on scroll on, off, toggle. From xcdnlgd at
hotmail dot com in GitHub issue 4884.
2026-02-24 08:00:43 +00:00
nicm
00946007d8 Respond to DECRQM 2026, from David Turnbull in GitHub issue 4887. 2026-02-24 07:50:27 +00:00
Thomas Adam
bc47bc7c22 Merge branch 'obsd-master' 2026-02-23 12:01:08 +00:00
Thomas Adam
dc693601f4 Merge branch 'obsd-master' 2026-02-23 10:01:08 +00:00
nicm
f1f9c63cdb Free history entries properly, from Huihui Huang in GitHub issue 4870. 2026-02-23 09:12:57 +00:00
nicm
0cc4f0fd76 Use buffer size for b64_pton, from someone in GitHub issue 4882. 2026-02-23 09:08:07 +00:00
nicm
0dc1b5adfb Do not leak list on failure, reported by Huihui Huang. 2026-02-23 08:58:40 +00:00
nicm
a76e6eca6b Another memory leak from Huihui Huang. 2026-02-23 08:54:56 +00:00
nicm
8c7278b53a Memory leaks in cmd_display_menu, from Huihui Huang. 2026-02-23 08:50:00 +00:00
nicm
50a3b4c777 Free format on -a, reported by Huihui Huang. 2026-02-23 08:46:57 +00:00
nicm
3094ca1da5 Fix memory leaks, reported by Huihui Huang in GitHub issue 4872. 2026-02-23 08:45:27 +00:00
nicm
23ad10c849 Set cell to default when off screen to avoid crash when logging it. 2026-02-23 08:29:30 +00:00
Thomas Adam
c9fb989a31 Merge branch 'obsd-master' 2026-02-23 08:11:38 +00:00
Nicholas Marriott
8356578a54 Update tmux bash completion file link in README, from Hyun Seungmin. 2026-02-20 08:43:35 +00:00
nicm
0310404155 Reuse the same extended slot when clearing non-RGB cells as well. From
Michael K Darling in GitHub issue 4865.
2026-02-20 08:41:23 +00:00
nicm
03f8690f9c Pass which clipboard is set through to the terminal, from Axel Lindskog
in GitHub issue 4858.
2026-02-18 09:10:31 +00:00
Thomas Adam
4cc3de4f84 Merge branch 'obsd-master' 2026-02-17 08:51:05 +00:00
nicm
fedd4440f0 Reuse extended entry when clearing RGB cell, to prevent memory growth
when cells are repeatedly cleared (they are only compacted when scrolled
off screen). From Michael K Darling in GitHub issue 4862.
2026-02-17 07:42:58 +00:00
nicm
55cedc24fa Format layout change string once per window in control notify, from
Conor Taylor in GitHub issue 4848.
2026-02-16 08:45:38 +00:00
nicm
c9162837a0 Pull format allocation outside of loop for control subs, from Conor
Taylor in GitHub issue 4848.
2026-02-16 08:42:57 +00:00
nicm
f218463976 grid_peek_cell can return NULL, so check for it. From Conor Taylor in
GitHub issue 4848.
2026-02-16 08:02:04 +00:00
nicm
1f0c54f7ea Initialize bufname, reported by Mark Kelly. 2026-02-15 17:43:26 +00:00
Thomas Adam
d3522c328c Merge branch 'obsd-master' 2026-02-12 11:10:01 +00:00
nicm
7e50eb0e83 Make paste_get_top return a copy of the buffer name which is more
sensible and avoids a double free pointed out by DongHan Kim.
2026-02-11 08:30:37 +00:00
nicm
5b455abecc Do not write before buffer when parsing empty clipboard or palette
replies, or try to allocate zero bytes with an empty clipboard sequence.
Reported by DongHan Kim.
2026-02-11 08:23:21 +00:00
nicm
7d41761e84 Fix clients_calculate_size for manual type when window is NULL. From
Elias Oenal in GitHub issue 4849.
2026-02-10 10:02:11 +00:00
nicm
25f6d8b1e9 Implement some obvious missing sort orders, from Dane Jensen in GitHub
issue 4813.
2026-02-10 09:55:53 +00:00
nicm
5a33616e65 Check for no window when updating clients, GitHub issue 4851. 2026-02-10 09:00:30 +00:00
nicm
1cf17b06ae Batch printable output in control mode, from Conor Taylor in GitHub issue 4848. 2026-02-10 08:40:03 +00:00
nicm
aa03706ed0 Remove redundant call to tty_attributes, from Conor Taylor in GitHub
issue 4848.
2026-02-10 08:34:43 +00:00
nicm
d8794e2b30 Check log level before log_debug in tty_draw_line, from Conor Taylor in
GitHub issue 4848.
2026-02-10 08:31:45 +00:00
nicm
b7939eb266 Don't call event_add again if the event is already pending, from Conor
Taylor in GitHub issue 4848.
2026-02-10 08:30:21 +00:00
nicm
19b9a34c48 Only loop over clients if table actually found, from Conor Taylor in
GitHub issue 4848.
2026-02-10 08:28:53 +00:00
nicm
f016e08153 Fix wrong TAILQ member in input_cancel_requests from Conor Taylor in
GitHub issue 4848.
2026-02-10 08:27:17 +00:00
nicm
7b1c503086 Clear search counts when clearing marks in case of repeated search,
reported by Daniel Pereira in GitHub issue 4817.
2026-02-06 10:28:42 +00:00
nicm
5865001e4a Also check PANE_STATUSREADY for pane_dead format to match
pane_dead_status. GitHub issue 4841 from Joshua Pollack.
2026-02-06 10:23:26 +00:00
nicm
62944da74b Make OSC 52 work in popups, from gogongxt at 163 dot com in GitHub issue
4797.
2026-02-03 09:07:44 +00:00
nicm
588013bb44 Refresh copy mode when style changes, from Josh Cooper in GitHub issue 4830. 2026-02-03 08:53:58 +00:00
Thomas Adam
615c27c117 Merge branch 'obsd-master' 2026-02-02 19:06:25 +00:00
nicm
3c3d9ce3a9 Break sorting out into a common file so formats and modes use the same
code. Also add -O for sorting to the list commands. From Dane Jensen in
GitHub issue 4813.
2026-02-02 10:08:30 +00:00
nicm
66011fe48b Do not treat cells as empty unless the background colour stays the same.
Fixes invisible clock in clock mode, reported by tb@.
2026-01-29 09:08:19 +00:00
Thomas Adam
d7f59eccaf Merge branch 'obsd-master' 2026-01-28 17:25:42 +00:00
Thomas Adam
e652213855 Merge branch 'obsd-master' 2026-01-23 10:50:55 +00:00
nicm
f70150a663 Replace overlay_ranges with visible_ranges which can hold more than
three ranges (will be needed for floating panes); move the visible
ranges checks outside of tty_draw_line and rewrite it to fix issues with
partially-obscured wide characters. With Michael Grant.
2026-01-23 10:45:53 +00:00
Nicholas Marriott
fe52f76913 Fiddle with some more warnings. 2026-01-22 15:02:42 +00:00
Nicholas Marriott
0d9c3c895c Stick the pragmas outside the function (make GCC 4 happier) and under __GNUC__. 2026-01-22 14:29:29 +00:00
Nicholas Marriott
6525bb7cef Line length/style nits. 2026-01-22 14:16:39 +00:00
Michael Grant
0790e74f84 Fix strftime warning in gcc. 2026-01-22 14:11:00 +00:00
nicm
195a9cfd88 When history-limit is changed, apply to existing panes, not just new
ones. GitHub issue 4705.
2026-01-22 08:55:01 +00:00
nicm
ecbf8d76d0 Reevaluate menu and popup styles on each draw to allow them to change
when options change, from Josh Cooper in GitHub issues 4828 and 4829.
2026-01-22 07:52:24 +00:00
nicm
818745a605 Set PANE_STYLECHANGED when user options change, from Josh Cooper in
GitHub issue 4825.
2026-01-22 07:42:30 +00:00
nicm
26aacd0e32 Handle theme keys earlier so they are processed even if a popup is open.
From Josh Cooper in GitHub issue 4827.
2026-01-22 07:39:35 +00:00
nicm
8e06739e26 Fix window-size=latest not resizing on switch-client in session groups.
From Ilya Grigoriev in GitHub issue 4818.
2026-01-20 22:50:08 +00:00
nicm
ab1f2ef71c Add a function to convert a screen to a string, from Michael Grant. 2026-01-20 16:32:05 +00:00
Nicholas Marriott
d2e09cb259 Add a --enable-optimizations configure flag. 2026-01-20 15:42:22 +00:00
Thomas Adam
bf8ea85bd7 Merge branch 'obsd-master' 2026-01-19 10:01:08 +00:00
nicm
9c0aeaff40 Now the copy mode indicator can be changed, we need to redraw it when
the cursor is moved. GitHub issue 4774.
2026-01-19 08:20:51 +00:00
Thomas Adam
95b4bb51c6 Merge branch 'obsd-master' 2026-01-14 22:01:08 +00:00
nicm
1e5f93b7b6 Add -e flag to command-prompt to close if empty, from Dane Jensen in
GitHub issue 4812.
2026-01-14 19:43:43 +00:00
Nicholas Marriott
b89d46bb00 Add a regression test from Ilya Grigoriev. 2026-01-13 20:29:21 +00:00
Thomas Adam
97d61131df Merge branch 'obsd-master' 2026-01-12 10:01:08 +00:00
nicm
5721767921 Correctly draw indicators when pane-border-indicators is set to both.
Reported by Ilya Grigoriev in GitHub issue 4780.
2026-01-12 07:50:16 +00:00
nicm
a443531280 Remember last pane or type of location for double and triple clicks and
correctly handle it changes between first and second or second and
third. GitHub issue 4795 from Shaobo Song.
2026-01-12 07:48:36 +00:00
Thomas Adam
ad083677c5 Merge branch 'obsd-master' 2026-01-11 14:01:08 +00:00
nicm
426467856d Add paste to the default pane menu, GitHub issue 4763. 2026-01-11 11:48:46 +00:00
Thomas Adam
844042744e Merge branch 'obsd-master' 2026-01-09 18:01:07 +00:00
nicm
65b3a95be8 Removing padding needs to go through screen_write_collect_trim or there
may end up multiple items covering the same region.
2026-01-09 15:04:00 +00:00
Thomas Adam
ca95950148 Merge branch 'obsd-master' 2026-01-08 14:01:08 +00:00
nicm
6234d79852 Do not set manual size if no window. 2026-01-08 12:05:02 +00:00
Thomas Adam
a5d8b92012 Merge branch 'obsd-master' 2026-01-08 10:01:08 +00:00
nicm
890cfc6dc3 Copy palette responses before processing them since they are not strings. 2026-01-08 07:54:23 +00:00
Thomas Adam
0e6fc2e88b Merge branch 'obsd-master' 2026-01-07 22:01:07 +00:00
nicm
c8ccd420be Reduce request timeout to 500 milliseconds to match the extended
escape-time, and discard palette requests if receiving a reply for a
different index.
2026-01-07 20:16:32 +00:00
nicm
ff4f6b9066 Extend escape timeout if there are active forwarded requests not just
tmux's own requests. GitHub issue 4793.
2026-01-07 20:03:34 +00:00
Thomas Adam
7cffa2de78 Merge branch 'obsd-master' 2026-01-07 20:01:09 +00:00
nicm
e2afaaea75 Correct redrawing of wide characters when overwritten. Reported by Jake
Stewart in GitHub issue 4737.
2026-01-07 18:29:15 +00:00
Thomas Adam
75d3974c12 Merge branch 'obsd-master' 2026-01-07 10:01:08 +00:00
nicm
583f12ea71 Work out the default command from the queue in case it has been set from
the config file, GitHub issue 4791.
2026-01-07 08:16:20 +00:00
Thomas Adam
3de7a8ad8d Merge branch 'obsd-master' 2026-01-06 22:01:07 +00:00
nicm
f6c9052082 If cannot find a terminator for palette responses, treat as a partial
key not complete. GitHub issue 4749.
2026-01-06 20:09:42 +00:00
nicm
f226804149 Do not send theme unless it has changed, and do not send immediately
when updates are enabled. GitHub issue 5787.
2026-01-06 20:05:57 +00:00
Thomas Adam
cf94486e32 Merge branch 'obsd-master' 2026-01-06 16:01:07 +00:00
nicm
035a2f35d4 Clear trimmed lines after moving, from Antony Raj in GitHub issue 4790. 2026-01-06 14:33:05 +00:00
Thomas Adam
9b7732eac4 Merge branch 'obsd-master' 2026-01-06 12:01:07 +00:00
nicm
5f9dac8abc Do not use ;;s in list-keys output as it is confusing and cannot be
parsed on input, from Patrick Motard in GitHub issue 4750.
2026-01-06 10:17:29 +00:00
nicm
ccd4dd7ff2 Do not log theme if pane is NULL. 2026-01-06 09:11:15 +00:00
Thomas Adam
77417b7cbe Merge branch 'obsd-master' 2026-01-05 10:01:08 +00:00
nicm
6a7cd79a63 Redraw pane borders when entering or leaving alternate screen, from Mike
Jonkmans in GitHub issue 4788.
2026-01-05 08:32:19 +00:00
nicm
baa3b51b3e Do not use client if there isn't one, GitHub issue 4789. 2026-01-05 08:30:30 +00:00
Thomas Adam
abeba4910c Merge branch 'obsd-master' 2026-01-04 10:01:08 +00:00
Nicholas Marriott
356d0aedbd Remove an unused declaration (from OpenBSD, same change also sent by Mike
Jonkmans).
2026-01-04 08:41:38 +00:00
nicm
45f23f3a59 Minor tidying of cmd_list_print from Pavel Roskin. 2026-01-04 08:38:26 +00:00
nicm
6ef7375ade Add some missing logging bits for themes. 2026-01-04 08:05:14 +00:00
Thomas Adam
00030aabf7 Merge branch 'obsd-master' 2025-12-25 20:01:08 +00:00
nicm
f72832cc08 Add focus-follows-mouse option, from Barry Wasdell in GitHub issue 4771. 2025-12-25 18:07:00 +00:00
nicm
b5c33ca2b7 Add selection_mode format variable for copy mode, from Mike Jonkmans in
GitHub issue 4773.
2025-12-25 18:05:15 +00:00
Thomas Adam
d607399850 Merge branch 'obsd-master' 2025-12-22 10:01:08 +00:00
nicm
d005803934 Add prompt-command-cursor-style, from Joshua Cooper in GitHub issue 4765. 2025-12-22 08:41:01 +00:00
nicm
a22ec275b4 With status-keys vi, move the cursor left by one when pressing Escape to
enter command mode, like vi. GitHub issue 4767 from Joshua Cooper.
2025-12-22 08:39:35 +00:00
nicm
188f963fe0 Add {current}/{active} for -t for current window or active pane, from
Manuel Einfalt in GitHub issue 4766.
2025-12-22 08:35:04 +00:00
Thomas Adam
9c5df33325 Merge branch 'obsd-master' 2025-12-19 10:01:07 +00:00
nicm
74f60951cc Do not leak items if not using them because of synchronized update. 2025-12-19 08:46:25 +00:00
Nicholas Marriott
54202fcaad Check image size against available screen size properly, GitHub issue 4739. 2025-12-18 13:44:17 +00:00
Nicholas Marriott
fe645c5bcc Fix image scrolling at exact screen height, and add more logging for images. 2025-12-18 09:05:37 +00:00
Thomas Adam
86c15d7ae9 Merge branch 'obsd-master' 2025-12-17 14:01:08 +00:00
Thomas Adam
3c36d5e433 Merge branch 'obsd-master' 2025-12-17 12:01:07 +00:00
nicm
1c7e164c22 Add support for applications to use synchronized output mode (DECSET
2026) to prevent screen tearing during rapid updates. When an
application sends SM ?2026, tmux buffers output until RM ?2026 is received
or a 1-second timeout expires.

From Chris Lloyd with the assistance of Claude Code, GitHub issue 4744.
2025-12-17 11:49:29 +00:00
nicm
ce7eb22e3a Do not have a default prompt cursor colour because some terminals
(urxvt, st) do not support the reset sequence. GitHub issue 4759.
2025-12-17 10:20:21 +00:00
Thomas Adam
739a97140f Merge branch 'obsd-master' 2025-12-17 10:01:07 +00:00
nicm
99ed397e9c Make clock mode seconds synchronized to the second, GitHub issue 4760
from Joao Pedro.
2025-12-17 08:38:12 +00:00
Thomas Adam
d2c7668bc2 Merge branch 'obsd-master' 2025-12-16 00:01:08 +00:00
nicm
7abf3e8bdf Note that switch-client only changes the key table for one key, pointed
out by Jeenu Viswambharan.
2025-12-15 21:21:25 +00:00
Thomas Adam
506b4db762 Merge branch 'obsd-master' 2025-12-15 15:19:29 +00:00
bket
21c27fdcae Simplify argument move using TAILQ_CONCAT()
Replace the manual loop moving each argument from cmd->arguments to
last->arguments with a single TAILQ_CONCAT() call. This makes the code
clearer and more efficient, while preserving identical behavior.

OK nicm@
2025-12-11 04:17:17 +00:00
nicm
672e89a640 Add a scroll-to-mouse command for copy mode to scroll to the mouse
position and bind to the scrollbar, brings the scrollbar keys into line
with the other mouse keys. From Michael Grant, GitHub issue 4731.
2025-12-10 21:24:43 +00:00
Nicholas Marriott
4b810ae493 Add a define for max images and bump to 20 for the moment. 2025-12-09 08:46:00 +00:00
nicm
52e2a7d990 Fix key code for M-BSpace, GitHub issue 4717. 2025-12-09 08:13:59 +00:00
Nicholas Marriott
2e5e9c0298 Fix calculation of scaled SIXEL size, from nincsnevem662 at gmail dot com in
GitHub issue 4739.
2025-12-08 21:41:30 +00:00
nicm
afa05ae15e Use correct style for bottom line when pane status line is on, GitHub
issue 4732.
2025-12-08 21:32:50 +00:00
nicm
f58b8d0d6a Setting working directory after fork means there is a race with
pane_current_path (especially on platforms with systemd which have to
take time to do some additional faffing around). To avoid this, change
it before fork and back in the parent afterwards. GitHub issue 4719.
2025-12-08 08:04:35 +00:00
Nicholas Marriott
5c73d3878d FIx combine test again... 2025-12-07 22:11:45 +00:00
Nicholas Marriott
ef0a7e328c Merge branch 'tmux-3.6a' 2025-12-05 06:20:32 +00:00
Nicholas Marriott
cc117b5048 Update CHANGES. 2025-12-05 05:39:46 +00:00
nicm
faebe7a70a Do not read over buffer if format is a single #, and do not loop forever
if UTF-8 is unfinished in a format. Reported by Giorgi Kobakhia im
GitHub issue 4735.
2025-12-05 05:38:05 +00:00
nicm
01962e25dc Allow drag in alternate screen again, GitHub issue 4743 reported by Brad
King.
2025-12-05 05:37:57 +00:00
nicm
ff207eb583 Fix y offset of mouse if status at top. GitHub issue 4738 from Michael
Grant.
2025-12-05 05:37:48 +00:00
Thomas Adam
8e922ab690 Merge branch 'obsd-master' 2025-12-05 02:01:07 +00:00
Thomas Adam
1a3db6b65e Merge branch 'obsd-master' 2025-12-05 00:01:08 +00:00
nicm
987e05ff31 Allow drag in alternate screen again, GitHub issue 4743 reported by Brad
King.
2025-12-04 22:50:34 +00:00
Nicholas Marriott
2fc123cf4a Update CHANGES. 2025-12-04 20:54:44 +00:00
nicm
3b57077d01 Add a missing skin tone, from Jake Stewart in GitHub issue 4736. 2025-12-04 20:53:01 +00:00
nicm
33c1ba1549 Allow characters to be combined in either order, reported by Jake
Stewart in GitHub issue 4726.
2025-12-04 20:52:55 +00:00
nicm
bd16b22dac Do not read over buffer if format is a single #, and do not loop forever
if UTF-8 is unfinished in a format. Reported by Giorgi Kobakhia im
GitHub issue 4735.
2025-12-04 20:49:57 +00:00
Thomas Adam
0929d8ddfa Merge branch 'obsd-master' 2025-12-04 16:01:08 +00:00
nicm
9d6c69ebde Fix y offset of mouse if status at top. GitHub issue 4738 from Michael
Grant.
2025-12-04 14:45:32 +00:00
Thomas Adam
1bcd360dfe Merge branch 'obsd-master' 2025-12-04 08:01:07 +00:00
nicm
1f2210a3ce Add a missing skin tone, from Jake Stewart in GitHub issue 4736. 2025-12-04 06:04:21 +00:00
nicm
2fe1378d3a Allow characters to be combined in either order, reported by Jake
Stewart in GitHub issue 4726.
2025-12-04 06:02:27 +00:00
Thomas Adam
796539c60b Merge branch 'obsd-master' 2025-12-03 10:01:09 +00:00
Thomas Adam
700936b2ad Merge branch 'obsd-master' 2025-12-03 08:01:07 +00:00
nicm
a28dbe3a59 Improve code readability in colour_palette_get and colour_palette_set.
GitHub issue 4730 from Pavel Roskin.
2025-12-03 07:41:38 +00:00
nicm
820df0f551 Add check that the pane is not in alternate screen mode when in
copy-mode. From Michael Grant in GitHub issue 4728.
2025-12-03 07:38:46 +00:00
nicm
ee9cf1bbaa Fix the size caluation for left-right windows used to spread out cells
horizontally evenly. From Michael Grant in GitHub issue 4724.
2025-12-03 07:35:32 +00:00
Nicholas Marriott
52917abe21 Update CHANGES. 2025-12-02 14:26:30 +00:00
nicm
a40f98df0a Change noattr to be an explicit attribute in the style so that it works
correctly and does not delete attributes set in the style itself, GitHub
issue 4713.
2025-12-02 14:25:05 +00:00
nicm
2c78a5aceb Add horizontal border case to server_client_check_mouse_in_pane to fix
mouse resizing. GitHub issue 4720 from Michael Grant, reported by
someone in GitHub issue 4715.
2025-12-02 14:24:57 +00:00
Thomas Adam
02a8e3fd34 Merge branch 'obsd-master' 2025-12-02 10:01:08 +00:00
nicm
322adfbdde Add a get-clipboard option which when enabled (the default is off) uses
the same mechanism as palette requests to request clipboard from the
terminal and forward to the requesting pane. Remove the now-redundant
forward-to-pane ability from "refresh-client -l". GitHub issue 4275.
2025-12-02 08:20:32 +00:00
Thomas Adam
194d0a0e25 Merge branch 'obsd-master' 2025-12-01 10:01:08 +00:00
Nicholas Marriott
b2d6ebaa10 Fix combine-test.result, GitHub issue 4717. 2025-12-01 08:22:12 +00:00
Nicholas Marriott
bfecbb0685 Fix combine-test.result, GitHub issue 4717. 2025-12-01 08:21:04 +00:00
nicm
e4c552f5a5 Change noattr to be an explicit attribute in the style so that it works
correctly and does not delete attributes set in the style itself, GitHub
issue 4713.
2025-12-01 08:14:29 +00:00
nicm
04f32073c0 Add horizontal border case to server_client_check_mouse_in_pane to fix
mouse resizing. GitHub issue 4720 from Michael Grant, reported by
someone in GitHub issue 4715.
2025-12-01 08:04:26 +00:00
Thomas Adam
e9afd2bb5e Merge branch 'obsd-master' 2025-11-28 12:01:09 +00:00
nicm
55d660a548 Do not remove TERM etc for commands run from config file, reported by
Dennis Eriksen.
2025-11-28 09:42:48 +00:00
nicm
dfaf47d97c session_index was never actually implemented, remove from man page. 2025-11-28 09:14:17 +00:00
Nicholas Marriott
640e1a7643 Update CHANGES. 2025-11-27 07:26:09 +00:00
nicm
2a0b078e15 Place cursor on correct line if message-line is not 0, reported by
Alexis Hildebrandt.
2025-11-27 07:24:50 +00:00
nicm
0af04295f3 Newer libevents do not allow event_del on a zero'd event. 2025-11-27 07:24:47 +00:00
Thomas Adam
3542bfa5b8 Merge branch 'obsd-master' 2025-11-26 22:01:08 +00:00
nicm
e3a54ed0f4 Newer libevents do not allow event_del on a zero'd event. 2025-11-26 19:02:03 +00:00
Nicholas Marriott
ec4b5b52af Version and CHANGES. 2025-11-26 19:00:17 +00:00
nicm
a0dfef3b04 Place cursor on correct line if message-line is not 0, reported by
Alexis Hildebrandt.
2025-11-26 18:57:18 +00:00
Nicholas Marriott
6db6a30ab5 Need signal.h for utempter, from Yasuhiro Kimura. 2025-11-26 18:43:11 +00:00
Nicholas Marriott
bfa2f73335 Need signal.h for utempter, from Yasuhiro Kimura. 2025-11-26 10:41:17 +00:00
Nicholas Marriott
1decccace7 Update version. 2025-11-26 08:44:41 +00:00
Nicholas Marriott
ee53d49059 Merge branch 'tmux-3.6' 2025-11-26 08:44:18 +00:00
Thomas Adam
f949f9a23a Merge branch 'obsd-master' 2025-11-26 00:01:08 +00:00
nicm
f0dec832b8 Add seconds options for clock mode, from augustus7613 dot mail at pm dot
me in GitHub issue 4697.
2025-11-25 21:24:27 +00:00
442 changed files with 58250 additions and 15439 deletions

View File

@@ -63,7 +63,7 @@ tmux -vv -Ltest -f/dev/null new
Or if you need your configuration: Or if you need your configuration:
~~~base ~~~bash
tmux kill-server tmux kill-server
tmux -vv new tmux -vv new
~~~ ~~~

2
.github/README.md vendored
View File

@@ -79,7 +79,7 @@ A small example configuration is in `example_tmux.conf`.
And a bash(1) completion file at: And a bash(1) completion file at:
https://github.com/scop/bash-completion/blob/main/completions/tmux https://github.com/scop/bash-completion/blob/main/completions-core/tmux.bash
For debugging, run tmux with `-v` or `-vv` to generate server and client log For debugging, run tmux with `-v` or `-vv` to generate server and client log
files in the current directory. files in the current directory.

11
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,11 @@
For pull request code reviews in this repository, do not leave any review comments under any circumstances.
If asked to review a pull request, produce no comments.
Do not comment on pull requests in this repository.
For pull request code reviews in this repository, do not leave a pull request overview.
Do not leave inline review comments.
If asked to review a pull request, provide no summary, no overview, and no code comments.

View File

@@ -16,8 +16,10 @@ concurrency:
jobs: jobs:
action: action:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'tmux/tmux'
timeout-minutes: 10
steps: steps:
- uses: dessant/lock-threads@v5 - uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
issue-inactive-days: '30' issue-inactive-days: '30'

90
.github/workflows/regress.yml vendored Normal file
View File

@@ -0,0 +1,90 @@
name: 'Run Tests'
on:
workflow_dispatch:
schedule:
- cron: '33 3 * * *'
permissions:
contents: read
concurrency:
group: tmux-tests
cancel-in-progress: true
jobs:
regress:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
if: github.repository == 'tmux/tmux'
timeout-minutes: 300
strategy:
fail-fast: false
matrix:
include:
- name: ubuntu-24.04-x64
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: macos-26-arm64
runner: macos-26
make: gmake
configure: --enable-utf8proc --enable-asan
steps:
- name: checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
autoconf \
automake \
bison \
build-essential \
libevent-dev \
libncurses-dev \
libutf8proc-dev \
pkg-config
- name: dependencies
if: runner.os == 'macOS'
run: |
brew install \
autoconf \
automake \
bison \
libevent \
jemalloc \
make \
ncurses \
utf8proc \
pkg-config
- name: build
run: |
sh autogen.sh
./configure ${{ matrix.configure }}
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
- name: test
run: |
cd regress
${{ matrix.make }}
- name: logs
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: regress-logs-${{ matrix.name }}
path: regress/logs/*.log
if-no-files-found: ignore

3
.gitignore vendored
View File

@@ -7,6 +7,8 @@
*~ *~
.deps/ .deps/
.dirstamp .dirstamp
.serena/
AGENTS.md
Makefile Makefile
Makefile.in Makefile.in
aclocal.m4 aclocal.m4
@@ -19,6 +21,7 @@ configure
core core
etc/ etc/
fuzz/*-fuzzer fuzz/*-fuzzer
regress/logs/
tags tags
tmux tmux
tmux.1.* tmux.1.*

View File

@@ -29,6 +29,7 @@ Ted Unangst <tedu@openbsd.org> tedu <tedu>
Theo de Raadt <deraadt@openbsd.org> Theo Deraadt <deraadt@openbsd.org> Theo de Raadt <deraadt@openbsd.org> Theo Deraadt <deraadt@openbsd.org>
Theo de Raadt <deraadt@openbsd.org> deraadt <deraadt> Theo de Raadt <deraadt@openbsd.org> deraadt <deraadt>
Thomas Adam <thomas@xteddy.org> Thomas <thomas@xteddy.org> Thomas Adam <thomas@xteddy.org> Thomas <thomas@xteddy.org>
Thomas Adam <thomas@xteddy.org> Thomas Adam <thomas.adam22@gmail.com>
Thomas Adam <thomas@xteddy.org> Thomas Adam <thomas.adam@smoothwall.net> Thomas Adam <thomas@xteddy.org> Thomas Adam <thomas.adam@smoothwall.net>
Thomas Adam <thomas@xteddy.org> n6tadam <n6tadam@xteddy.org> Thomas Adam <thomas@xteddy.org> n6tadam <n6tadam@xteddy.org>
Tim van der Molen <tim@openbsd.org> tim <tim> Tim van der Molen <tim@openbsd.org> tim <tim>

553
CHANGES
View File

@@ -1,3 +1,556 @@
CHANGES FROM 3.7c TO 3.8
* Many improvements to floating panes:
- new-pane and split-window can now set the title with -T and border lines
with -B, and new-pane -W waits for the pane command to exit;
- dragging with Ctrl now creates a new floating pane;
- modal panes may be created with new-pane -O; a window can have one modal
pane and it prevents interaction with the other panes while it is active.
A modal pane is now used for the editor in buffer mode;
- move-pane and resize-pane can now move and resize floating panes, including
mouse dragging;
- split-window in a floating pane creates a new floating pane that does not
overlap the active pane, if possible;
- break-pane can float a tiled pane, join-pane can tile a floating pane, and
swap-pane works with floating panes;
- choose-tree -h and choose-client -h hide the pane containing the mode, and
-k for choose commands kills the pane when the mode exits, which is useful
for modes in floating panes;
- 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;
- floating panes are unzoomed before creation to avoid a crash.
* Add support for themes and improve default colours:
- tmux now has builtin light and dark colour themes for terminals with 256 or
more colours; a new theme option controls whether tmux detects the terminal
theme, uses terminal colours, or forces light or dark (set to terminal for
the old colours);
- styles can use new theme colour names such as themeblack, themewhite and
themegreen, and colours in styles are expanded as formats;
- the terminal's own theme is reported to panes instead of being guessed from
the background, and inferred more accurately when theme reporting is not
supported (Ayman Bagabas, issue 5343);
- tree-mode-selection-style has been added, tree mode selection styling now
covers the whole line, and built-in modes use improved theme colours.
* Improvements to customize mode:
- hooks and environment variables are now shown;
- e opens the selected value in an editor;
- C toggles showing only changed items;
- array item keys can now be changed.
* Hooks (and control mode notifications) are now handled internally by using
events, each of which may carry keys and values as a payload. The control
mode subscriptions mechanism has been extended to allow general-purpose
"monitors" which check a format every second and trigger a hook when it
changes or becomes true:
- set-hook -B adds a monitor hook using the same subscription syntax as
refresh-client -B, and show-hooks -B lists them. -T runs the hook only when
the format is true;
- wait-for -E waits for hooks, notifications or user events, and set-hook -E
fires an event immediately;
- hooks have additional keys available. wait-for -v can be used to see the
keys sent with a hook event;
- new hooks to cover areas including client create and destroy, pane
activity, pane mode and prompt changes, pane movement and resizing, session
group changes and window creation, close and zoom;
- OSC 133 escape sequences now trigger events: pane-command-started,
pane-command-finished and pane-shell-prompt.
* Add switch-mode, a fast switcher bound by default to Tab for windows and BTab
(S-Tab) for sessions.
* Pane scrollbars now support auto-hide. This replaces the previous modal
behaviour where the pane was resized. With auto-hide, the scrollbar appears
while scrolling or while hovered and disappears after pane-scrollbars-timeout
(Michael Grant).
* Extend the fill-character option so both inside and outside the window can be
changed.
* Change set-option and set-hooks to use formats and add a -F flag to each.
* Add a #{A/count:frames} modifier to show a series of frames as an animation
in the status line (issue 5412 from Fernando Daciuk).
* Change command templates so %% is escaped for single quotes while %1 remains
unquoted, preventing single quotes in session or target names from
terminating quoted command sections (reported by Aliz Hammond).
* display-panes is now a mode rather than an overlay, and can be run inside
another pane. The -b flag has been removed.
* 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).
* Menus now belong to the window, so appear on all clients.
* Dragging over an existing copy mode selection now adjusts it (Michael Grant).
* Rectangle selection in copy mode may extend past the end of the current line
to match vi's virtualedit=block behaviour (Mark Kelly, issue 5227).
* With mode-keys set to vi, scrolling in copy mode now keeps the cursor in the
same position relative to the text (Arseniy Simonov, issue 5216).
* Copy mode no longer exits at the bottom while a selection is in progress
(issue 5349).
* Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
nolink) (issues 4842 and 4280 from Moritz Angermann).
* New format modifiers: O: loops over options, V: loops over environments,
the I modifier reports client terminal information, t/d gives a time
difference in seconds, c/f and c/b emit colours, and the m operator supports
multiple terms and fuzzy matching.
* Add or improve format variables including client_colours,
pane_start_command_list, window_manual_width, window_manual_height,
pane_last_output_time, pane_modal_flag and window_modal_pane.
* Add additional pane sort orders, and a z sort order for choose-tree so
floating panes can be sorted by z-index.
* Add -f filters to kill-pane -a, kill-window -a and kill-session -a (issue
4782).
* Allow server ACLs to use groups as well as users (issue 4917).
* The mouse option now defaults to on.
* Fix send-keys -K so keys are inserted in the correct place in the input
queue, like keys from key bindings (issue 3476).
* Fix control mode clients hanging on exit if pty data was still queued (Ben
Maurer, issue 5356), and avoid sending notifications to clients which are
already exiting (Ben Maurer, issue 5357).
* Fix grouped sessions sometimes being left as unusable command targets while
they are being killed (Bryce Miller, issue 5180).
* Fix choose mode filtering when more than one pane exists (issue 5326), and
fix an infinite loop in customize mode when a filter does not match.
* Fix regsub when the pattern is anchored at the start (issue 5341).
* Fix redraw issues around synchronized updates, copy mode, tabs, padding,
status lines, menus, popups, wide characters and floating panes. This
includes fixes for flicker with alternate screen applications and scrollbars
(Michael Grant, issue 5351; Noam Stolero, issue 5350; Aung Myo Kyaw, issue
5098).
* Do not crash looking for the next or previous session (issue 5344), or when
no client is available.
CHANGES FROM 3.7b TO 3.7c
* Build with jemalloc on macOS to avoid what appears to be a bug in calloc
(issue 5385).
* Fix scrollbar initial state so they appear on new windows (issue 5339).
* Check time periodically in loops rather than every one (issue 5367).
* Use message-style again as default for message-format.
* Unzoom before creating floating panes to avoid a crash.
CHANGES FROM 3.7a TO 3.7b
* Fix so that the end of a synchronized update again triggers a redraw.
CHANGES FROM 3.7 TO 3.7a
* Fix crash in break-pane when no name is provided.
* Scrollbar options are now cached rather than being looked up for every redraw
(issue 5298).
* Only forbid #( in names, allow #[, empty names, : and .
CHANGES FROM 3.6b TO 3.7
* Add floating panes. These are panes which sit above the layout ("tiled
panes") like popups but unlike popups are not modal and behave like panes (so
the same escape sequence support). Floating panes are created with the
new-pane command, bound to * by default.
This is an early release of this feature and they are relatively limited.
Currently floating panes can only be moved and resized using the mouse. The
default second status line (if status-format is set to 2) has changed to show
a list of panes. Many obvious features are not yet available for floating
panes (notably the ability to swap floating panes, resize them using
resize-pane, change them between floating and tiles, and restore custom
layouts with floating panes).
Mostly written by Michael Grant with help from Dane Jensen; testing and fixes
from others.
* Allow run-shell arguments after a shell command to be expanded as #{1}, #{2}
and so on (from Rasmus Thystrup Karstensen in issue 5121).
* Add -g to kill-session to kill all sessions in a session group (issue 5157
from github at jiku dot jp).
* Tighten up read-only checks on attach-session, detach-client and
switch-client so that a user should be able to only detach their own client
(reported by John Walker).
* Increase escape delay if the buffer contains a partial paste end, fixes
issues with at least Windows Terminal (from jing dot empty at gmail.com issue
5088).
* When mode-keys is set to vi, do not allow the cursor to go into the invisible
extra cell to the right of the visible text; this is closer to what vi(1)
does (from Max Vim in issue 5070).
* Add a five second limit on pasting for terminals which mysteriously lose the
end sequence if the paste is too big (that is, Terminal.app) (reported by
Garri Djavadyan in issue 4527).
* Show file open errors more sensibly (reported by Meriel Luna Mittelbach in
issue 5081).
* Update supported features list for Foot terminal (from Meriel Luna Mittelbach
in issue 5079).
* Turn off the "is this a paste" guessing if the terminal supports bracket
pasting instead (issue 5031).
* Check FIONREAD for all panes not just piped panes.
* Add emacs-style recentre-top-bottom command to copy mode (issue 5053 from
sinyax75 at gmail dot com).
* Allow the indicator in tree mode to be customized by two new options:
tree-mode-preview-format and tree-mode-preview-style.
* Fix control client hang on exit after toggling no-output (issue 5049 from
Aaron Campbell). Also various other control mode fixes.
* Add support for line numbers in copy mode. There is a new
copy-mode-line-numbers option which may be set to off, default (tmux's normal
line numbering where 0 is the top visible line), absolute (first line in
history is 1), relative (relative to the cursor) and hybrid (current line is
absolute, others relative). Also adds copy-mode-line-number-style and
copy-mode-current-line-number-style to set the style of the line numbers.
When copy mode is entered with the mouse, line numbers stay off. From Leo
Henon in issue 5025.
* Make C-[ have the same bindings as Escape for terminals with extended
keys where they are different (issue 5035 from Eric Nicolas).
* Sanitize paste buffer names in paste_set and paste_rename (issue 5032 from
Barrett Ruth).
* Do not hang in run-shell when job_run fails (from Barrett Ruth in issue
5037).
* Add ability to forward progress bar to outside terminal (issue 4972
from Eric Dorland).
* Translate keypad keys to text in prompt input (from Barrett Ruth in issue
4996).
* Sanitize pane titles and window and session names more consistently and
strictly, prevents C0 characters and other invisible characters causing
problems (reported by Chris Monardo in issue 4999).
* Make clock visible on terminals without colours (from Manuel Einfalt in issue
5001).
* Add detach to default session menu (suggested by Przemyslaw Sztoch).
* Include window format variables for pane notifications (issue 5007 from Saul
Nogueras).
* Limit precision to 100 for formats to stop silly formats from running out of
memory, reported by z1281552865 at gmail dot com. Also various other similar
changes, mostly found by OSS-Fuzz.
* Add WAYLAND_DISPLAY to default update-environment (issue 4965). Also some
additional XDG_* variables (issue 5169).
* Add -C flag to command-prompt to match display-message -C (do not freeze
panes) (from Barrett Ruth in issue 4978).
* Cache user from getpwuid because it can be very expensive on some
platforms (from Ben Maurer in issue 4973).
* Add remain-on-exit key to keep pane around until a key is pressed (from
Michael Grant).
* Add some new mouse ranges called "control0" to "control9" and use to add some
mouse controls to the pane state line (from Dane Jensen with some bits from
Michael Grant).
* Handle OSC 9;4 progress bar sequence and store in format variables (from Eric
Dorland in issue 4954).
* Correctly size buffer used for parsing clipboard sequences (from Michal
Majchrowicz).
* Limit MSG_COMMAND argument to between 0 and 1000 to prevent a misbehaving
client from crashing the server (from Michal Majchrowicz).
* Reorganize host keys are represented internally so they can be built more
easily (from Dane Jensen in issue 4953).
* Add new fuzzers for command parsing, formats and styles (from David
Korczynski in issue 4957). Also fix various issues shown from these.
* Add bracket_paste_flag format flag (from George Nachman in issue 4951).
* Use \- for hyphens in tmux.1 to cause newer groff versions to render them
correctly (from Keith Thompson in issue 4948).
* Various minor code improvements and fixes from Pavel Lavrukhin (issue 4936
and others).
* Use window options for cursor-style to avoid crash when no pane (from Arden
Packeer in issue 4942).
* Fix issue where popup window gets overwritten by background updates (from
Conor Taylor in issue 4920).
* Protect against overflow when scrollbar is off screen (from san65384 at gmail
dot com in issue 4933).
* Copy hyperlinks when redrawing popup so they do not vanish (from Antoine
Gaudreau Simard in issue 4925).
* Work around systemd killing panes early during system shutdown by creating
dependencies from the panes to the service which started tmux (issue 4926
from Dmitry Torokhov).
* Allow codepoint-widths to accept ranges (from san65384 at gmail dot com in
issue 4930).
* Add a short builtin help text for each mode accessible with C-h (based on
code from Patrick Motard in issue 4751).
* Draw message as one format, allowing prompts and messages to occupy only a
portion of the status bar, overlaying the normal status content rather than
replacing the entire line. A new message-format option now controls the
entire message (like status-format). The message-style option now need to
include "fill" in order to cover the whole width (the default has
"fill=yellow"). From Conor Taylor in issue 4861.
* Add next/previous variables for windows in W: loop (from Conor Taylor in
issue 4856).
* Various bug and memory leak fixes from Renaud Allard (issue 4916).
* Add pane_pipe_pid with pipe file descriptor.
* Make -c work with new-session -A (from Jody Frankowski in issue 4906).
* Allow copy mode to work for readonly clients, except for copy commands (from
Dane Jensen).
* Pass paste buffer through vis(3) when pasting to prevent buffers containing
for example the bracket end sequence causing issues, a new -S flag disables
(reported by Mason Davis).
* Add sorting (-O flag) and a custom format (-F) to list-keys (from Dane Jensen
in issue 4845).
* Add scroll-exit-on, scroll-exit-off, scroll-exit-toggle commands to copy mode
(from xcdnlgd at hotmail dot com in issue 4884).
* Respond to DECRQM 2026 (from David Turnbull in issue 4887) and various others
(from Ayman Bagabas in issue 5118).
* Fix various memory leaks reported by Huihui Huang (issue 4872).
* Pass which clipboard is set through to the terminal (from Axel Lindskog in
issue 4858).
* Reuse extended entry when clearing RGB cell, to prevent memory growth when
cells are repeatedly cleared (from Michael K Darling in issue 4862).
* Do not write before buffer when parsing empty clipboard or palette replies,
or try to allocate zero bytes with an empty clipboard sequence (reported by
DongHan Kim).
* Various bug fixes and code improvements from Conor Taylor (issue 4848).
* Clear search counts when clearing marks in case of repeated search (reported
by Daniel Pereira in issue 4817).
* Make OSC 52 work in popups (from gogongxt at 163 dot com in issue 4797).
* Refresh copy mode when style changes (from Josh Cooper in issue 4830).
* Make sorting code common and add -O for sorting to the list commands (from
Dane Jensen in issue 4813).
* Do not treat cells as empty unless the background colour stays the same,
fixes invisible clock in clock mode (reported by Theo Buehler).
* When history-limit is changed, apply to existing panes, not just new
ones (issue 4705).
* Reevaluate menu and popup styles on each draw to allow them to change when
options change (from Josh Cooper in issues 4828 and 4829).
* Handle theme keys earlier so they are processed even if a popup is open (from
Josh Cooper in issue 4827).
* Fix window-size=latest not resizing on switch-client in session groups (from
Ilya Grigoriev in issue 4818).
* Add -e flag to command-prompt to close if empty (from Dane Jensen in issue
4812).
* Correctly draw indicators when pane-border-indicators is set to both
(reported by Ilya Grigoriev in issue 4780).
* Remember last pane or type of location for double and triple clicks and
correctly handle it changes between first and second or second and third
(issue 4795 from Shaobo Song).
* Add paste to the default pane menu (issue 4763).
* Reduce request timeout to 500 milliseconds to match the extended escape-time,
and discard palette requests if receiving a reply for a different index.
* Extend escape timeout if there are active forwarded requests not just
tmux's own requests (issue 4793).
* Correct redrawing of wide characters when overwritten (reported by Jake
Stewart in issue 4737).
* If cannot find a terminator for palette responses, treat as a partial key not
complete (issue 4749).
* Do not send theme unless it has changed, and do not send immediately when
updates are enabled (issue 5787).
* Do not use ;;s in list-keys output as it is confusing and cannot be
parsed on input (from Patrick Motard in issue 4750).
* Redraw pane borders when entering or leaving alternate screen (from Mike
Jonkmans in issue 4788).
* Add focus-follows-mouse option (from Barry Wasdell in issue 4771).
* Add selection_mode format variable for copy mode (from Mike Jonkmans in issue
4773).
* Add prompt-command-cursor-style (from Joshua Cooper in issue 4765).
* With status-keys vi, move the cursor left by one when pressing Escape to
enter command mode, like vi (issue 4767 from Joshua Cooper).
* Add {current}/{active} for -t for current window or active pane (from Manuel
Einfalt, issue 4766).
* Add support for applications to use synchronized output mode (DECSET 2026) to
prevent screen tearing during rapid updates (from Chris Lloyd in issue 4744).
* Do not set a default prompt cursor colour because some terminals (urxvt, st)
do not support the reset sequence (issue 4759).
* Add a scroll-to-mouse command for copy mode to scroll to the mouse position
and bind to the scrollbar, brings the scrollbar keys into line with the other
mouse keys (from Michael Grant in issue 4731).
* Bump the maximum number of SIXEL images to 20.
* Fix key code for M-BSpace (issue 4717).
* Fix calculation of scaled SIXEL size (from nincsnevem662 at gmail dot com in
issue 4739). Also various other fixes and improvements for SIXEL.
* Fix a race between fork and pane_current_path, most noticeable on systems
where starting processes is slow (issue 4719).
* Fix mouse position calculation on scrollbar with pane status line at the top
(issue 4738 from Michael Grant).
* Do not read outside buffer if format is a single #, and do not loop forever
if UTF-8 is unfinished in a format (reported by Giorgi Kobakhia in issue
4735).
* Add a missing skin tone character (from Jake Stewart in issue 4736).
* Allow UTF-8 characters to be combined in either order (reported by Jake
Stewart in issue 4726).
* Do not show scrollbar when entering copy mode from a pane in the alternate
screen (issue 4728 from Michael Grant).
* Fix the size calculation for left-right windows used to spread out cells
horizontally evenly (from Michael Grant in issue 4724).
* Add a get-clipboard option which when enabled (the default is off) and a
clipboard is requested from a pane, requests it from the terminal and
forwards to the requesting pane; also remove the now-redundant
forward-to-pane ability from "refresh-client -l" (issue 4275).
* Fix the noattr attribute in styles, used by the default mode-style (issue
4713).
* Do not remove TERM for commands run from config file (regression reported by
Dennis Eriksen).
* Add seconds options for clock mode (from augustus7613 dot mail at pm dot me,
issue 4697; later improved by Joao Pedro in issue 4760).
CHANGES FROM 3.6a TO 3.6b
* Remove images from the correct list when they are removed while in the
alternate screen (reported by xlabai at tencent dot com).
CHANGES FROM 3.6 TO 3.6a
* Fix a buffer overread and an infinite loop in format processing (reported by
Giorgi Kobakhia, issue 4735).
* Allow drag in alternate screen again (issue 4743 reported by Brad King).
* Fix y offset of mouse if status at top (issue 4738 from Michael Grant).
* Add a missing skin tone (from Jake Stewart, issue 4736).
* Allow characters to be combined in either order (issue 4726, reported by Jake
Stewart).
* Fix horizontal mouse resizing when pane status lines are on (from Michael
Grant, issue 4720).
* Fix noattr so it does not delete attributes set in the style itself (issue
4713).
* Newer libevents do not allow event_del on a zero'd event (issue 4706).
* Place cursor on correct line if message-line is not 0 (issue 4707).
* Fix compile error on FreeBSD (from Yasuhiro Kimura, issue 4701).
CHANGES FROM 3.5a TO 3.6 CHANGES FROM 3.5a TO 3.6
* Add seconds options for clock mode (issue 4697). * Add seconds options for clock mode (issue 4697).

View File

@@ -1,9 +1,4 @@
THIS IS FOR INFORMATION ONLY, CODE IS UNDER THE LICENCE AT THE TOP OF ITS FILE. Copyright (c) Various Authors
The README, CHANGES, FAQ and TODO files are licensed under the ISC license. All
other files have a license and copyright notice at their start, typically:
Copyright (c) <author>
Permission to use, copy, modify, and distribute this software for any Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above

View File

@@ -4,7 +4,7 @@ CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c
# Distribution tarball options. # Distribution tarball options.
EXTRA_DIST = \ EXTRA_DIST = \
CHANGES README README.ja COPYING example_tmux.conf \ CHANGES README COPYING example_tmux.conf \
osdep-*.c mdoc2man.awk tmux.1 osdep-*.c mdoc2man.awk tmux.1
dist_EXTRA_tmux_SOURCES = compat/*.[ch] dist_EXTRA_tmux_SOURCES = compat/*.[ch]
@@ -13,28 +13,40 @@ AM_CPPFLAGS += @XOPEN_DEFINES@ \
-DTMUX_VERSION='"@VERSION@"' \ -DTMUX_VERSION='"@VERSION@"' \
-DTMUX_CONF='"$(sysconfdir)/tmux.conf:~/.tmux.conf:$$XDG_CONFIG_HOME/tmux/tmux.conf:~/.config/tmux/tmux.conf"' \ -DTMUX_CONF='"$(sysconfdir)/tmux.conf:~/.tmux.conf:$$XDG_CONFIG_HOME/tmux/tmux.conf:~/.config/tmux/tmux.conf"' \
-DTMUX_LOCK_CMD='"@DEFAULT_LOCK_CMD@"' \ -DTMUX_LOCK_CMD='"@DEFAULT_LOCK_CMD@"' \
-DTMUX_TERM='"@DEFAULT_TERM@"' -DTMUX_TERM='"@DEFAULT_TERM@"' \
-DTMUX_MOUSE=1
# Additional object files. # Additional object files.
LDADD = $(LIBOBJS) LDADD = $(LIBOBJS)
# Set flags for gcc. # Set flags for gcc.
if IS_GCC if IS_GCC
AM_CFLAGS += -std=gnu99 -O2 AM_CFLAGS += -std=gnu99
if IS_OPTIMIZED
AM_CFLAGS += -O2
else
AM_CFLAGS += -O0
endif
if IS_DEBUG if IS_DEBUG
AM_CFLAGS += -g AM_CFLAGS += -g3 -ggdb
AM_CFLAGS += -Wno-long-long -Wall -W -Wformat=2 AM_CFLAGS += -Wno-long-long -Wall -W -Wformat=2 -Wno-use-after-free
AM_CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations AM_CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations
AM_CFLAGS += -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare AM_CFLAGS += -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare
AM_CFLAGS += -Wundef -Wbad-function-cast -Winline -Wcast-align AM_CFLAGS += -Wundef -Wbad-function-cast -Winline -Wno-cast-align
AM_CFLAGS += -Wdeclaration-after-statement -Wno-pointer-sign -Wno-attributes AM_CFLAGS += -Wdeclaration-after-statement -Wno-pointer-sign -Wno-attributes
AM_CFLAGS += -Wno-unused-result -Wno-format-y2k AM_CFLAGS += -Wno-unused-result -Wno-format-y2k -Wno-unknown-warning-option
AM_CFLAGS += -Wno-maybe-uninitialized
if IS_DARWIN if IS_DARWIN
AM_CFLAGS += -Wno-deprecated-declarations -Wno-cast-align -Wno-macro-redefined AM_CFLAGS += -Wno-deprecated-declarations -Wno-macro-redefined
endif endif
AM_CPPFLAGS += -DDEBUG AM_CPPFLAGS += -DDEBUG
endif endif
AM_CPPFLAGS += -iquote. AM_CPPFLAGS += -iquote.
if IS_ASAN
AM_CPPFLAGS += -DASAN
AM_CFLAGS += -fsanitize=address -fno-omit-frame-pointer
AM_LDFLAGS += -fsanitize=address
endif
endif endif
# Set flags for Solaris. # Set flags for Solaris.
@@ -89,10 +101,10 @@ dist_tmux_SOURCES = \
cmd-detach-client.c \ cmd-detach-client.c \
cmd-display-menu.c \ cmd-display-menu.c \
cmd-display-message.c \ cmd-display-message.c \
cmd-display-panes.c \
cmd-find-window.c \ cmd-find-window.c \
cmd-find.c \ cmd-find.c \
cmd-if-shell.c \ cmd-if-shell.c \
cmd-invoke.c \
cmd-join-pane.c \ cmd-join-pane.c \
cmd-kill-pane.c \ cmd-kill-pane.c \
cmd-kill-server.c \ cmd-kill-server.c \
@@ -100,6 +112,7 @@ dist_tmux_SOURCES = \
cmd-kill-window.c \ cmd-kill-window.c \
cmd-list-buffers.c \ cmd-list-buffers.c \
cmd-list-clients.c \ cmd-list-clients.c \
cmd-list-commands.c \
cmd-list-keys.c \ cmd-list-keys.c \
cmd-list-panes.c \ cmd-list-panes.c \
cmd-list-sessions.c \ cmd-list-sessions.c \
@@ -148,12 +161,16 @@ dist_tmux_SOURCES = \
control-notify.c \ control-notify.c \
control.c \ control.c \
environ.c \ environ.c \
events-payload.c \
events.c \
file.c \ file.c \
format.c \
format-draw.c \ format-draw.c \
format.c \
fuzzy.c \
grid-reader.c \ grid-reader.c \
grid-view.c \ grid-view.c \
grid.c \ grid.c \
hooks.c \
hyperlinks.c \ hyperlinks.c \
input-keys.c \ input-keys.c \
input.c \ input.c \
@@ -166,13 +183,15 @@ dist_tmux_SOURCES = \
log.c \ log.c \
menu.c \ menu.c \
mode-tree.c \ mode-tree.c \
monitor.c \
names.c \ names.c \
notify.c \
options-table.c \ options-table.c \
options.c \ options.c \
paste.c \ paste.c \
popup.c \ popup.c \
proc.c \ proc.c \
prompt-history.c \
prompt.c \
regsub.c \ regsub.c \
resize.c \ resize.c \
screen-redraw.c \ screen-redraw.c \
@@ -183,25 +202,31 @@ dist_tmux_SOURCES = \
server-fn.c \ server-fn.c \
server.c \ server.c \
session.c \ session.c \
sort.c \
spawn.c \ spawn.c \
status.c \ status.c \
style.c \ style.c \
tmux-protocol.h \
tmux.c \ tmux.c \
tmux.h \ tmux.h \
tmux-protocol.h \
tty-acs.c \ tty-acs.c \
tty-draw.c \
tty-features.c \ tty-features.c \
tty-keys.c \ tty-keys.c \
tty-term.c \ tty-term.c \
tty.c \ tty.c \
utf8-combined.c \ utf8-combined.c \
utf8.c \ utf8.c \
window-border.c \
window-buffer.c \ window-buffer.c \
window-client.c \ window-client.c \
window-clock.c \ window-clock.c \
window-copy.c \ window-copy.c \
window-customize.c \ window-customize.c \
window-panes.c \
window-switch.c \
window-tree.c \ window-tree.c \
window-visible.c \
window.c \ window.c \
xmalloc.c \ xmalloc.c \
xmalloc.h xmalloc.h
@@ -227,10 +252,21 @@ if ENABLE_SIXEL
dist_tmux_SOURCES += image.c image-sixel.c dist_tmux_SOURCES += image.c image-sixel.c
endif endif
# Add bits for fuzzing if enabled.
if NEED_FUZZING if NEED_FUZZING
check_PROGRAMS = fuzz/input-fuzzer check_PROGRAMS = \
fuzz/input-fuzzer \
fuzz/cmd-parse-fuzzer \
fuzz/format-fuzzer \
fuzz/style-fuzzer
fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS) fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS)
fuzz_input_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS) fuzz_input_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
fuzz_cmd_parse_fuzzer_LDFLAGS = $(FUZZING_LIBS)
fuzz_cmd_parse_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
fuzz_format_fuzzer_LDFLAGS = $(FUZZING_LIBS)
fuzz_format_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
fuzz_style_fuzzer_LDFLAGS = $(FUZZING_LIBS)
fuzz_style_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
endif endif
# Install tmux.1 in the right format. # Install tmux.1 in the right format.
@@ -245,3 +281,7 @@ install-exec-hook:
$(mkdir_p) $(DESTDIR)$(mandir)/man1 $(mkdir_p) $(DESTDIR)$(mandir)/man1
$(INSTALL_DATA) $(srcdir)/tmux.1.@MANFORMAT@ \ $(INSTALL_DATA) $(srcdir)/tmux.1.@MANFORMAT@ \
$(DESTDIR)$(mandir)/man1/tmux.1 $(DESTDIR)$(mandir)/man1/tmux.1
# And uninstall it.
uninstall-hook:
rm -f $(DESTDIR)$(mandir)/man1/tmux.1

View File

@@ -1,62 +0,0 @@
tmuxへようこそ!
tmuxはターミナルマルチプレクサーです。複数のターミナルを一つのスクリーン内に作成し、操作することができます。
バックグラウンドで処理を実行中に一度スクリーンから離れて後から復帰することも可能です。
OpenBSD、FreeBSD、NetBSD、Linux、macOS、Solarisで実行できます。
tmuxはlibevent 2.x.に依存します。 下記からダウンロードしてください。
http://libevent.org
また、ncursesも必要です。こちらからどうぞ。
http://invisible-island.net/ncurses/
tarballでのtmuxのビルドとインストール方法。
$ ./configure && make
$ sudo make install
tmuxはutmp(5)をアップデートするためにutempterを使うことができます。もしインストール済みであればオプション「--enable-utempter」をつけて実行してください。
リポジトリから最新バージョンを手に入れるためには下記を実行。
$ git clone https://github.com/tmux/tmux.git
$ cd tmux
$ sh autogen.sh
$ ./configure && make
(ビルドのためにはlibevent、ncurses libraries、headersに加えて、C compiler、make、autoconf、automake、pkg-configが必要です。)
詳しい情報はhttp://git-scm.comをご覧ください。修正はメール<tmux-users@googlegroups.com>宛、もしくはhttps://github.com/tmux/tmux/issuesにて受け付けています。
tmuxのドキュメントについてはtmux.1マニュアルをご覧ください。こちらのコマンドで参照可能です。
$ nroff -mdoc tmux.1|less
サンプル設定は本リポジトリのexample_tmux.confに
また、bash-completionファイルは下記にあります。
https://github.com/scop/bash-completion/blob/main/completions/tmux
「-v」や「-vv」を指定することでデバッグモードでの起動が可能です。カレントディレクトリにサーバーやクライアントのログファイルが生成されます。
議論やバグレポート用のメーリングリストにはこちらから参加可能です。
https://groups.google.com/forum/#!forum/tmux-users
gitコミットについての連絡先
https://groups.google.com/forum/#!forum/tmux-git
購読は<tmux-users+subscribe@googlegroups.com>までメールをお願いします。
バグレポートや機能追加(特にコードへの貢献)は大歓迎です。こちらにご連絡ください。
tmux-users@googlegroups.com
本ファイル、CHANGES、 FAQ、SYNCINGそしてTODOはISC licenseで保護されています。
その他のファイルのライセンスや著作権については、ファイルの上部に明記されています。
-- Nicholas Marriott <nicholas.marriott@gmail.com>

1
SECURITY.md Normal file
View File

@@ -0,0 +1 @@
Please report security issues to nicholas.marriott@gmail.com.

173
SYNCING
View File

@@ -1,173 +0,0 @@
Preamble
========
Tmux portable relies on repositories "tmux" and "tmux-obsd".
Here's a description of them:
* "tmux" is the portable version, the one which contains code for other
operating systems, and autotools, etc., which isn't found or needed in the
OpenBSD base system.
* "tmux-obsd" is the version of tmux in OpenBSD base system which provides
the basis of the portable tmux version.
Note: The "tmux-obsd" repository is actually handled by "git cvsimport"
running at 15 minute intervals, so a commit made to OpenBSD's tmux CVS
repository will take at least that long to appear in this git repository.
(It might take longer, depending on the CVS mirror used to import the
OpenBSD code).
If you've never used git before, git tracks meta-data about the committer
and the author, as part of a commit, hence:
% git config [--global] user.name "Your name"
% git config [--global] user.email "you@yourdomain.com"
Note that, if you already have this in the global ~/.gitconfig option, then
this will be used. Setting this per-repository would involve not using the
"--global" flag above. If you wish to use the same credentials always,
pass the "--global" option, as shown.
This is a one-off operation once the repository has been cloned, assuming
this information has ever been set before.
Cloning repositories
====================
This involves having both tmux and tmux-obsd cloned, as in:
% cd /some/where/useful
% git clone https://github.com/tmux/tmux.git
% git clone https://github.com/ThomasAdam/tmux-obsd.git
Note that you do not need additional checkouts to manage the sync -- an
existing clone of either repositories will suffice. So if you already have
these checkouts existing, skip that.
Adding in git-remotes
=====================
Because the portable "tmux" git repository and the "tmux-obsd"
repository do not inherently share any history between each other, the
history has been faked between them. This "faking of history" is something
which has to be told to git for the purposes of comparing the "tmux" and
"tmux-obsd" repositories for syncing. To do this, we must reference the
clone of the "tmux-obsd" repository from the "tmux" repository, as
shown by the following command:
% cd /path/to/tmux
% git remote add obsd-tmux file:///path/to/tmux-obsd
So that now, the remote "obsd-tmux" can be used to reference branches and
commits from the "tmux-obsd" repository, but from the context of the
portable "tmux" repository, which makes sense because it's the "tmux"
repository which will have the updates applied to them.
Fetching updates
================
To ensure the latest commits from "tmux-obsd" can be found from within
"tmux", we have to ensure the "master" branch from "tmux-obsd" is
up-to-date first, and then reference that update in "tmux", as in:
% cd /path/to/tmux-obsd
% git checkout master
% git pull
Then back in "tmux":
% cd /path/to/tmux
% git fetch obsd-tmux
Creating the necessary branches
===============================
Now that "tmux" can see commits and branches from "tmux-obsd" by way
of the remote name "obsd-tmux", we can now create the master branch from
"tmux-obsd" in the "tmux" repository:
% git checkout -b obsd-master obsd-tmux/master
Adding in the fake history points
=================================
To tie both the "master" branch from "tmux" and the "obsd-master"
branch from "tmux-obsd" together, the fake history points added to the
"tmux" repository need to be added. To do this, we must add an
additional refspec line, as in:
% cd /path/to/tmux
% git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*'
% git fetch origin
Performing the Sync
===================
Make sure the "master" branch is checked out:
% git checkout master
The following will show commits on OpenBSD not yet synched with "tmux":
% git log master..obsd-master
From there, merge the result in, fixing up any conflicts which might arise.
% git merge obsd-master
Then ensure things look correct by BUILDING the result of that sync:
% make clean && ./autogen.sh && ./configure && make
Compare the git merge result with what's on origin/master -- that is, check
which commits you're about to push:
% git log origin/master..master
And if happy:
% git push origin master
Keeping an eye on libutil in OpenBSD
====================================
A lot of the compat/ code in tmux comes from libutil, especially imsg.
Sometimes the API can change, etc., which might cause interesting problems
trying to run the portable version of tmux. It's worth checking
periodically for any changes to libutil in OpenBSD and syncing those files
to compat/ as and when appropriate.
Release tmux for next version
=============================
1. Update and commit README and CHANGES. The former should be checked for
anything outdated and updated with a list of things that might break
upgrades and the latter should mention all the major changes since
the last version.
2. Make sure configure.ac has the new version number.
3. Tag with:
% git tag -a 2.X
Where "2.X" is the next version.
Push the tag out with:
% git push --tags
4. Build the tarball with 'make dist'.
5. Check the tarball. If it's good, go here to select the tag just pushed:
https://github.com/tmux/tmux/tags
Click the "Add release notes", upload the tarball and add a link in the
description field to the CHANGES file.
6. Clone the tmux.github.io repository, and change the RELEASE version in the
Makefile. Commit it, and run 'make' to replace %%RELEASE%%. Push the
result out.
7. Change version back to master in configure.ac.

234
SYNCING.md Normal file
View File

@@ -0,0 +1,234 @@
# Preamble
tmux portable is maintained from two repositories:
* `tmux` is the portable repository. It contains the portability layer,
autotools build files, regression tests, documentation, and code needed for
platforms outside OpenBSD.
* `tmux-openbsd-cutover` is the OpenBSD tmux import repository. Its `master`
branch is the OpenBSD tmux history after the OpenBSD Git cutover. Portable
merges this branch when importing OpenBSD changes.
To create the commits in the tmux-openbsd-cutover repo, the source repo is the
Github mirror of OpenBSD's src repo:
```text
https://github.com/openbsd/src.git
```
The update automation filters the OpenBSD source tree to `usr.bin/tmux/`,
publishes that filtered history as `openbsd-git` in the cutover repository,
cherry-picks new filtered commits onto cutover `master`, then merges cutover
`master` into portable `master`.
If you've never used git before, configure your identity before committing:
```sh
git config [--global] user.name "Your name"
git config [--global] user.email "you@yourdomain.com"
```
# Repository layout
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
```
The exact directory names do not matter, but the examples below use:
```text
/path/to/tmux-portable
/path/to/tmux-openbsd-cutover
```
The cutover repository has three important branches:
* `master` is the branch portable consumes. It should contain tmux source
only, not automation files.
* `openbsd-git` is the raw filtered OpenBSD tmux branch generated from
OpenBSD src `master`.
* `automation` is an orphan branch containing the GitHub Actions workflow. It
is separate so that workflow files are not merged into portable.
# Adding the OpenBSD remote to portable
In the portable repository, add the cutover repository as a remote:
```sh
cd /path/to/tmux-portable
git remote add tmux-openbsd /path/to/tmux-openbsd-cutover
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 config remote.tmux-openbsd.tagOpt --no-tags
```
Fetch the cutover master branch explicitly:
```sh
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
`automation` branch of the cutover repository. That workflow:
1. Fetches or clones OpenBSD src using a blobless clone.
2. Filters `usr.bin/tmux/` into a local `tmux-openbsd` branch.
3. Updates `tmux-openbsd-cutover/openbsd-git`.
4. Cherry-picks new `openbsd-git` commits onto cutover `master`.
5. Merges cutover `master` into portable `master`.
6. Pushes the changed repositories.
The workflow deliberately runs from the orphan `automation` branch but checks
out cutover `master` as the branch to update. This keeps `.github/` out of
cutover `master`, so portable does not import workflow files when it merges
OpenBSD changes.
# Manual portable merge
If the workflow fails while merging into portable, do the merge locally and
push the result.
Start from an up-to-date portable master:
```sh
cd /path/to/tmux-portable
git fetch origin
git checkout master
git pull --ff-only origin master
```
Fetch the cutover branch:
```sh
git remote add tmux-openbsd /path/to/tmux-openbsd-cutover 2>/dev/null || true
git config remote.tmux-openbsd.tagOpt --no-tags
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
```
Merge it:
```sh
git merge --no-ff --log refs/remotes/tmux-openbsd/master
```
Resolve conflicts by deciding whether portable or OpenBSD owns the file.
Useful commands:
```sh
git checkout --ours path/to/file
git add path/to/file
```
This keeps the portable version of a conflicted file.
```sh
git checkout --theirs path/to/file
git add path/to/file
```
This takes the OpenBSD/cutover version of a conflicted file.
Before committing, inspect the result:
```sh
git status
git diff --check
git diff --cached --stat
```
Then finish and publish:
```sh
git commit
git push origin master
```
If the merge attempt is wrong, abort it:
```sh
git merge --abort
```
After pushing a manually resolved merge, rerun the workflow. It should either
no-op or continue from the now-merged portable state.
# Manual cutover update
Normally this is handled by the workflow. If it must be done manually, update
`openbsd-git` in the cutover repository from a filtered OpenBSD src checkout,
then cherry-pick the new filtered commits onto cutover `master`.
The important range is:
```text
origin/openbsd-git..openbsd-git
```
where `origin/openbsd-git` is the previously published filtered OpenBSD tmux
branch and `openbsd-git` is the newly generated filtered branch.
In the cutover repository:
```sh
cd /path/to/tmux-openbsd-cutover
git checkout master
git cherry-pick origin/openbsd-git..openbsd-git
git push origin master openbsd-git
```
Do not put workflow files on cutover `master`; keep them on the orphan
`automation` branch.
# Keeping an eye on libutil in OpenBSD
A lot of the `compat/` code in tmux comes from OpenBSD libraries, especially
imsg. Sometimes APIs change in OpenBSD in ways that require corresponding
portable changes. It is worth checking periodically for relevant OpenBSD
libutil changes and syncing those files into `compat/` as appropriate.
# Release tmux for next version
1. Update and commit README and CHANGES. The former should be checked for
anything outdated and updated with a list of things that might break
upgrades and the latter should mention all the major changes since the last
version.
2. Make sure `configure.ac` has the new version number.
3. Tag with:
```sh
% git tag -a 2.X
```
Where `2.X` is the next version.
Push the tag out with:
```sh
% git push --tags
```
4. Build the tarball with `make dist`.
5. Check the tarball. If it is good, go here to select the tag just pushed:
```text
https://github.com/tmux/tmux/tags
```
Click "Add release notes", upload the tarball and add a link in the
description field to the CHANGES file.
6. Clone the tmux.github.io repository, and change the RELEASE version in the
Makefile. Commit it, and run `make` to replace `%%RELEASE%%`. Push the
result out.
7. Change version back to master in `configure.ac`.

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: alerts.c,v 1.35 2026/07/10 13:38:45 nicm Exp $ */
/* /*
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -205,7 +205,7 @@ alerts_check_bell(struct window *w)
} }
if (!alerts_action_applies(wl, "bell-action")) if (!alerts_action_applies(wl, "bell-action"))
continue; continue;
notify_winlink("alert-bell", wl); events_fire_winlink("alert-bell", wl);
if (s->flags & SESSION_ALERTED) if (s->flags & SESSION_ALERTED)
continue; continue;
@@ -241,7 +241,7 @@ alerts_check_activity(struct window *w)
} }
if (!alerts_action_applies(wl, "activity-action")) if (!alerts_action_applies(wl, "activity-action"))
continue; continue;
notify_winlink("alert-activity", wl); events_fire_winlink("alert-activity", wl);
if (s->flags & SESSION_ALERTED) if (s->flags & SESSION_ALERTED)
continue; continue;
@@ -277,7 +277,7 @@ alerts_check_silence(struct window *w)
} }
if (!alerts_action_applies(wl, "silence-action")) if (!alerts_action_applies(wl, "silence-action"))
continue; continue;
notify_winlink("alert-silence", wl); events_fire_winlink("alert-silence", wl);
if (s->flags & SESSION_ALERTED) if (s->flags & SESSION_ALERTED)
continue; continue;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: arguments.c,v 1.67 2026/08/25 06:04:33 nicm Exp $ */
/* /*
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -51,13 +51,6 @@ struct args {
struct args_value *values; struct args_value *values;
}; };
/* Prepared command state. */
struct args_command_state {
struct cmd_list *cmdlist;
char *cmd;
struct cmd_parse_input pi;
};
static struct args_entry *args_find(struct args *, u_char); static struct args_entry *args_find(struct args *, u_char);
static int args_cmp(struct args_entry *, struct args_entry *); static int args_cmp(struct args_entry *, struct args_entry *);
@@ -89,8 +82,7 @@ args_copy_value(struct args_value *to, struct args_value *from)
case ARGS_NONE: case ARGS_NONE:
break; break;
case ARGS_COMMANDS: case ARGS_COMMANDS:
to->cmdlist = from->cmdlist; to->cmd = cmd_parse_add_ref(from->cmd);
to->cmdlist->references++;
break; break;
case ARGS_STRING: case ARGS_STRING:
to->string = xstrdup(from->string); to->string = xstrdup(from->string);
@@ -123,7 +115,7 @@ args_value_as_string(struct args_value *value)
return (""); return ("");
case ARGS_COMMANDS: case ARGS_COMMANDS:
if (value->cached == NULL) if (value->cached == NULL)
value->cached = cmd_list_print(value->cmdlist, 0); value->cached = cmd_parse_print(value->cmd, 0);
return (value->cached); return (value->cached);
case ARGS_STRING: case ARGS_STRING:
return (value->string); return (value->string);
@@ -149,7 +141,7 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause,
int optional_argument) int optional_argument)
{ {
struct args_value *argument, *new; struct args_value *argument, *new;
const char *s; const char *s, *as;
new = xcalloc(1, sizeof *new); new = xcalloc(1, sizeof *new);
if (*string != '\0') { if (*string != '\0') {
@@ -180,12 +172,24 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause,
xasprintf(cause, "-%c expects an argument", flag); xasprintf(cause, "-%c expects an argument", flag);
return (-1); return (-1);
} }
if (optional_argument && argument->type == ARGS_STRING) {
as = argument->string;
if (as[0] == '-' && (as[1] == '-' || isalpha((u_char)as[1]))) {
args_free_value(new);
free(new);
log_debug("%s: -%c (optional)", __func__, flag);
args_set(args, flag, NULL, ARGS_ENTRY_OPTIONAL_VALUE);
return (0);
}
}
args_copy_value(new, argument); args_copy_value(new, argument);
(*i)++; (*i)++;
out: out:
s = args_value_as_string(new); s = args_value_as_string(new);
log_debug("%s: -%c = %s", __func__, flag, s); log_debug("%s: -%c = %s", __func__, flag, s);
args_set(args, flag, new, 0); args_set(args, flag, new, 0);
return (0); return (0);
} }
@@ -355,7 +359,7 @@ args_copy_copy_value(struct args_value *to, struct args_value *from, int argc,
to->string = expanded; to->string = expanded;
break; break;
case ARGS_COMMANDS: case ARGS_COMMANDS:
to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv); to->cmd = cmd_parse_add_ref(from->cmd);
break; break;
} }
} }
@@ -406,7 +410,7 @@ args_free_value(struct args_value *value)
free(value->string); free(value->string);
break; break;
case ARGS_COMMANDS: case ARGS_COMMANDS:
cmd_list_free(value->cmdlist); cmd_parse_free(value->cmd);
break; break;
} }
free(value->cached); free(value->cached);
@@ -465,7 +469,7 @@ args_to_vector(struct args *args, int *argc, char ***argv)
cmd_append_argv(argc, argv, args->values[i].string); cmd_append_argv(argc, argv, args->values[i].string);
break; break;
case ARGS_COMMANDS: case ARGS_COMMANDS:
s = cmd_list_print(args->values[i].cmdlist, 0); s = cmd_parse_print(args->values[i].cmd, 0);
cmd_append_argv(argc, argv, s); cmd_append_argv(argc, argv, s);
free(s); free(s);
break; break;
@@ -520,7 +524,7 @@ args_print_add_value(char **buf, size_t *len, struct args_value *value)
case ARGS_NONE: case ARGS_NONE:
break; break;
case ARGS_COMMANDS: case ARGS_COMMANDS:
expanded = cmd_list_print(value->cmdlist, 0); expanded = cmd_parse_print(value->cmd, 0);
args_print_add(buf, len, "{ %s }", expanded); args_print_add(buf, len, "{ %s }", expanded);
break; break;
case ARGS_STRING: case ARGS_STRING:
@@ -735,144 +739,53 @@ args_string(struct args *args, u_int idx)
return (args_value_as_string(&args->values[idx])); return (args_value_as_string(&args->values[idx]));
} }
/* Make a command now. */ /*
struct cmd_list * * Convert one command argument into a parse tree. A string value is parsed;
args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx, * existing commands are referenced.
int expand) */
{ struct cmd_parse_tree *
struct args_command_state *state; args_to_commands(struct cmd *self, struct cmdq_item *item, u_int idx,
char *error; const char *default_command, int expand, char **cause)
struct cmd_list *cmdlist;
state = args_make_commands_prepare(self, item, idx, NULL, 0, expand);
cmdlist = args_make_commands(state, 0, NULL, &error);
if (cmdlist == NULL) {
cmdq_error(item, "%s", error);
free(error);
}
else
cmdlist->references++;
args_make_commands_free(state);
return (cmdlist);
}
/* Save bits to make a command later. */
struct args_command_state *
args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx,
const char *default_command, int wait, int expand)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct client *tc = cmdq_get_target_client(item);
struct args_value *value; struct args_value *value;
struct args_command_state *state; struct cmd_parse_input pi = { 0 };
const char *cmd; const char *cmd = NULL, *file;
const char *file; char *expanded;
struct cmd_parse_tree *tree;
state = xcalloc(1, sizeof *state); *cause = NULL;
if (idx < args->count) { if (idx >= args->count)
value = &args->values[idx];
if (value->type == ARGS_COMMANDS) {
state->cmdlist = value->cmdlist;
state->cmdlist->references++;
return (state);
}
cmd = value->string;
} else {
if (default_command == NULL)
fatalx("argument out of range");
cmd = default_command; cmd = default_command;
else {
value = &args->values[idx];
switch (value->type) {
case ARGS_COMMANDS:
return (cmd_parse_add_ref(value->cmd));
case ARGS_STRING:
cmd = value->string;
break;
case ARGS_NONE:
break;
} }
}
if (cmd == NULL)
fatalx("argument out of range");
if (expand) if (expand)
state->cmd = format_single_from_target(item, cmd); expanded = format_single_from_target(item, cmd);
else else
state->cmd = xstrdup(cmd); expanded = xstrdup(cmd);
log_debug("%s: %s", __func__, state->cmd); log_debug("%s: %s", __func__, expanded);
if (wait) cmd_get_source(self, &file, &pi.line);
state->pi.item = item;
cmd_get_source(self, &file, &state->pi.line);
if (file != NULL) if (file != NULL)
state->pi.file = xstrdup(file); pi.file = file;
state->pi.c = tc; pi.flags = cmd_get_parse_flags(self);
if (state->pi.c != NULL) tree = cmd_parse_from_string(expanded, &pi, cause);
state->pi.c->references++; free(expanded);
cmd_find_copy_state(&state->pi.fs, target); return (tree);
return (state);
}
/* Return argument as command. */
struct cmd_list *
args_make_commands(struct args_command_state *state, int argc, char **argv,
char **error)
{
struct cmd_parse_result *pr;
char *cmd, *new_cmd;
int i;
if (state->cmdlist != NULL) {
if (argc == 0)
return (state->cmdlist);
return (cmd_list_copy(state->cmdlist, argc, argv));
}
cmd = xstrdup(state->cmd);
log_debug("%s: %s", __func__, cmd);
cmd_log_argv(argc, argv, __func__);
for (i = 0; i < argc; i++) {
new_cmd = cmd_template_replace(cmd, argv[i], i + 1);
log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd);
free(cmd);
cmd = new_cmd;
}
log_debug("%s: %s", __func__, cmd);
pr = cmd_parse_from_string(cmd, &state->pi);
free(cmd);
switch (pr->status) {
case CMD_PARSE_ERROR:
*error = pr->error;
return (NULL);
case CMD_PARSE_SUCCESS:
return (pr->cmdlist);
}
fatalx("invalid parse return state");
}
/* Free commands state. */
void
args_make_commands_free(struct args_command_state *state)
{
if (state->cmdlist != NULL)
cmd_list_free(state->cmdlist);
if (state->pi.c != NULL)
server_client_unref(state->pi.c);
free((void *)state->pi.file);
free(state->cmd);
free(state);
}
/* Get prepared command. */
char *
args_make_commands_get_command(struct args_command_state *state)
{
struct cmd *first;
int n;
char *s;
if (state->cmdlist != NULL) {
first = cmd_list_first(state->cmdlist);
if (first == NULL)
return (xstrdup(""));
return (xstrdup(cmd_get_entry(first)->name));
}
n = strcspn(state->cmd, " ,");
xasprintf(&s, "%.*s", n, state->cmd);
return (s);
} }
/* Get first value in argument. */ /* Get first value in argument. */
@@ -998,7 +911,7 @@ args_string_percentage(const char *value, long long minval, long long maxval,
copy = xstrdup(value); copy = xstrdup(value);
copy[valuelen - 1] = '\0'; copy[valuelen - 1] = '\0';
ll = strtonum(copy, 0, 100, &errstr); ll = strtonum(copy, 0, 1000, &errstr);
free(copy); free(copy);
if (errstr != NULL) { if (errstr != NULL) {
*cause = xstrdup(errstr); *cause = xstrdup(errstr);
@@ -1066,7 +979,7 @@ args_string_percentage_and_expand(const char *value, long long minval,
copy[valuelen - 1] = '\0'; copy[valuelen - 1] = '\0';
f = format_single_from_target(item, copy); f = format_single_from_target(item, copy);
ll = strtonum(f, 0, 100, &errstr); ll = strtonum(f, 0, 1000, &errstr);
free(f); free(f);
free(copy); free(copy);
if (errstr != NULL) { if (errstr != NULL) {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: attributes.c,v 1.13 2026/07/02 08:51:05 nicm Exp $ */
/* /*
* Copyright (c) 2009 Joshua Elsasser <josh@elsasser.org> * Copyright (c) 2009 Joshua Elsasser <josh@elsasser.org>
@@ -31,7 +31,7 @@ attributes_tostring(int attr)
if (attr == 0) if (attr == 0)
return ("none"); return ("none");
len = xsnprintf(buf, sizeof buf, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", len = xsnprintf(buf, sizeof buf, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
(attr & GRID_ATTR_CHARSET) ? "acs," : "", (attr & GRID_ATTR_CHARSET) ? "acs," : "",
(attr & GRID_ATTR_BRIGHT) ? "bright," : "", (attr & GRID_ATTR_BRIGHT) ? "bright," : "",
(attr & GRID_ATTR_DIM) ? "dim," : "", (attr & GRID_ATTR_DIM) ? "dim," : "",
@@ -45,7 +45,8 @@ attributes_tostring(int attr)
(attr & GRID_ATTR_UNDERSCORE_3) ? "curly-underscore," : "", (attr & GRID_ATTR_UNDERSCORE_3) ? "curly-underscore," : "",
(attr & GRID_ATTR_UNDERSCORE_4) ? "dotted-underscore," : "", (attr & GRID_ATTR_UNDERSCORE_4) ? "dotted-underscore," : "",
(attr & GRID_ATTR_UNDERSCORE_5) ? "dashed-underscore," : "", (attr & GRID_ATTR_UNDERSCORE_5) ? "dashed-underscore," : "",
(attr & GRID_ATTR_OVERLINE) ? "overline," : ""); (attr & GRID_ATTR_OVERLINE) ? "overline," : "",
(attr & GRID_ATTR_NOATTR) ? "noattr," : "");
if (len > 0) if (len > 0)
buf[len - 1] = '\0'; buf[len - 1] = '\0';

71
cfg.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cfg.c,v 1.91 2026/08/03 13:38:42 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -56,7 +56,7 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data)
if (cfg_item != NULL) if (cfg_item != NULL)
cmdq_continue(cfg_item); cmdq_continue(cfg_item);
status_prompt_load_history(); prompt_load_history();
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
@@ -87,20 +87,21 @@ start_cfg(void)
if (cfg_quiet) if (cfg_quiet)
flags = CMD_PARSE_QUIET; flags = CMD_PARSE_QUIET;
for (i = 0; i < cfg_nfiles; i++) for (i = 0; i < cfg_nfiles; i++)
load_cfg(cfg_files[i], c, NULL, NULL, flags, NULL); load_cfg(cfg_files[i], NULL, NULL, flags, NULL);
cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL)); cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
} }
int int
load_cfg(const char *path, struct client *c, struct cmdq_item *item, load_cfg(const char *path, struct cmdq_item *item,
struct cmd_find_state *current, int flags, struct cmdq_item **new_item) struct cmd_find_state *current, int flags, struct cmdq_item **new_item)
{ {
FILE *f; FILE *f;
struct cmd_parse_input pi; struct cmd_parse_input pi;
struct cmd_parse_result *pr; struct cmd_parse_tree *tree;
struct cmdq_item *new_item0; struct cmdq_item *new_item0;
struct cmdq_state *state; struct cmdq_state *state;
char *cause = NULL;
if (new_item != NULL) if (new_item != NULL)
*new_item = NULL; *new_item = NULL;
@@ -117,18 +118,16 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
pi.flags = flags; pi.flags = flags;
pi.file = path; pi.file = path;
pi.line = 1; pi.line = 1;
pi.item = item; tree = cmd_parse_from_file(f, &pi, &cause);
pi.c = c;
pr = cmd_parse_from_file(f, &pi);
fclose(f); fclose(f);
if (pr->status == CMD_PARSE_ERROR) { if (tree == NULL) {
cfg_add_cause("%s", pr->error); cfg_add_cause("%s", cause);
free(pr->error); free(cause);
return (-1); return (-1);
} }
cmd_parse_log(__func__, tree);
if (flags & CMD_PARSE_PARSEONLY) { if (flags & CMD_PARSE_PARSEONLY) {
cmd_list_free(pr->cmdlist); cmd_parse_free(tree);
return (0); return (0);
} }
@@ -138,12 +137,12 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
state = cmdq_new_state(NULL, NULL, 0); state = cmdq_new_state(NULL, NULL, 0);
cmdq_add_format(state, "current_file", "%s", pi.file); cmdq_add_format(state, "current_file", "%s", pi.file);
new_item0 = cmdq_get_command(pr->cmdlist, state); new_item0 = cmd_invoke_get(tree, state, NULL);
if (item != NULL) if (item != NULL)
new_item0 = cmdq_insert_after(item, new_item0); new_item0 = cmdq_insert_after(item, new_item0);
else else
new_item0 = cmdq_append(NULL, new_item0); new_item0 = cmdq_append(NULL, new_item0);
cmd_list_free(pr->cmdlist); cmd_parse_free(tree);
cmdq_free_state(state); cmdq_free_state(state);
if (new_item != NULL) if (new_item != NULL)
@@ -153,13 +152,14 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
int int
load_cfg_from_buffer(const void *buf, size_t len, const char *path, load_cfg_from_buffer(const void *buf, size_t len, const char *path,
struct client *c, struct cmdq_item *item, struct cmd_find_state *current, struct cmdq_item *item, struct cmd_find_state *current, int flags,
int flags, struct cmdq_item **new_item) struct cmdq_item **new_item)
{ {
struct cmd_parse_input pi; struct cmd_parse_input pi;
struct cmd_parse_result *pr; struct cmd_parse_tree *tree;
struct cmdq_item *new_item0; struct cmdq_item *new_item0;
struct cmdq_state *state; struct cmdq_state *state;
char *cause = NULL;
if (new_item != NULL) if (new_item != NULL)
*new_item = NULL; *new_item = NULL;
@@ -170,17 +170,15 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
pi.flags = flags; pi.flags = flags;
pi.file = path; pi.file = path;
pi.line = 1; pi.line = 1;
pi.item = item; tree = cmd_parse_from_buffer(buf, len, &pi, &cause);
pi.c = c; if (tree == NULL) {
cfg_add_cause("%s", cause);
pr = cmd_parse_from_buffer(buf, len, &pi); free(cause);
if (pr->status == CMD_PARSE_ERROR) {
cfg_add_cause("%s", pr->error);
free(pr->error);
return (-1); return (-1);
} }
cmd_parse_log(__func__, tree);
if (flags & CMD_PARSE_PARSEONLY) { if (flags & CMD_PARSE_PARSEONLY) {
cmd_list_free(pr->cmdlist); cmd_parse_free(tree);
return (0); return (0);
} }
@@ -190,12 +188,12 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
state = cmdq_new_state(NULL, NULL, 0); state = cmdq_new_state(NULL, NULL, 0);
cmdq_add_format(state, "current_file", "%s", pi.file); cmdq_add_format(state, "current_file", "%s", pi.file);
new_item0 = cmdq_get_command(pr->cmdlist, state); new_item0 = cmd_invoke_get(tree, state, NULL);
if (item != NULL) if (item != NULL)
new_item0 = cmdq_insert_after(item, new_item0); new_item0 = cmdq_insert_after(item, new_item0);
else else
new_item0 = cmdq_append(NULL, new_item0); new_item0 = cmdq_append(NULL, new_item0);
cmd_list_free(pr->cmdlist); cmd_parse_free(tree);
cmdq_free_state(state); cmdq_free_state(state);
if (new_item != NULL) if (new_item != NULL)
@@ -223,13 +221,15 @@ cfg_print_causes(struct cmdq_item *item)
{ {
struct client *c = cmdq_get_client(item); struct client *c = cmdq_get_client(item);
u_int i; u_int i;
char *cause;
for (i = 0; i < cfg_ncauses; i++) { for (i = 0; i < cfg_ncauses; i++) {
cause = cfg_causes[i];
if (c != NULL && (c->flags & CLIENT_CONTROL)) if (c != NULL && (c->flags & CLIENT_CONTROL))
control_write(c, "%%config-error %s", cfg_causes[i]); control_notify_write(c, "%%config-error %s", cause);
else else
cmdq_print(item, "%s", cfg_causes[i]); cmdq_print(item, "%s", cause);
free(cfg_causes[i]); free(cause);
} }
free(cfg_causes); free(cfg_causes);
@@ -244,14 +244,16 @@ cfg_show_causes(struct session *s)
struct window_pane *wp; struct window_pane *wp;
struct window_mode_entry *wme; struct window_mode_entry *wme;
u_int i; u_int i;
char *cause;
if (cfg_ncauses == 0) if (cfg_ncauses == 0)
return; return;
if (c != NULL && (c->flags & CLIENT_CONTROL)) { if (c != NULL && (c->flags & CLIENT_CONTROL)) {
for (i = 0; i < cfg_ncauses; i++) { for (i = 0; i < cfg_ncauses; i++) {
control_write(c, "%%config-error %s", cfg_causes[i]); cause = cfg_causes[i];
free(cfg_causes[i]); control_notify_write(c, "%%config-error %s", cause);
free(cause);
} }
goto out; goto out;
} }
@@ -268,7 +270,8 @@ cfg_show_causes(struct session *s)
wme = TAILQ_FIRST(&wp->modes); wme = TAILQ_FIRST(&wp->modes);
if (wme == NULL || wme->mode != &window_view_mode) if (wme == NULL || wme->mode != &window_view_mode)
window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL,
NULL);
for (i = 0; i < cfg_ncauses; i++) { for (i = 0; i < cfg_ncauses; i++) {
window_copy_add(wp, 0, "%s", cfg_causes[i]); window_copy_add(wp, 0, "%s", cfg_causes[i]);
free(cfg_causes[i]); free(cfg_causes[i]);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: client.c,v 1.168 2026/08/17 20:14:31 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -69,6 +69,25 @@ static void client_dispatch_attached(struct imsg *);
static void client_dispatch_wait(struct imsg *); static void client_dispatch_wait(struct imsg *);
static const char *client_exit_message(void); static const char *client_exit_message(void);
/* Does any command have a flag? */
static int
client_command_has_flag(int argc, char **argv, int flag)
{
struct args_value *values;
struct cmd_parse_tree *tree;
int found;
values = args_from_vector(argc, argv);
tree = cmd_parse_from_arguments(values, argc, NULL);
found = cmd_parse_any_have(tree, flag);
cmd_parse_free(tree);
args_free_values(values, argc);
free(values);
return (found);
}
/* /*
* Get server create lock. If already held then server start is happening in * Get server create lock. If already held then server start is happening in
* another client, so block until the lock is released and return -2 to * another client, so block until the lock is released and return -2 to
@@ -232,18 +251,15 @@ int
client_main(struct event_base *base, int argc, char **argv, uint64_t flags, client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
int feat) int feat)
{ {
struct cmd_parse_result *pr;
struct msg_command *data; struct msg_command *data;
int fd, i; int fd, i;
const char *ttynam, *termname, *cwd; const char *ttynam, *termname, *cwd;
pid_t ppid; pid_t ppid;
enum msgtype msg; enum msgtype msg;
struct termios tio, saved_tio; struct termios tio, saved_tio;
size_t size, linesize = 0; size_t size;
ssize_t linelen; char **caps = NULL, *cause;
char *line = NULL, **caps = NULL, *cause;
u_int ncaps = 0; u_int ncaps = 0;
struct args_value *values;
/* Set up the initial command. */ /* Set up the initial command. */
if (shell_command != NULL) { if (shell_command != NULL) {
@@ -254,22 +270,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
flags |= CLIENT_STARTSERVER; flags |= CLIENT_STARTSERVER;
} else { } else {
msg = MSG_COMMAND; msg = MSG_COMMAND;
if (client_command_has_flag(argc, argv, CMD_STARTSERVER))
/*
* It's annoying parsing the command string twice (in client
* and later in server) but it is necessary to get the start
* server flag.
*/
values = args_from_vector(argc, argv);
pr = cmd_parse_from_arguments(values, argc, NULL);
if (pr->status == CMD_PARSE_SUCCESS) {
if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER))
flags |= CLIENT_STARTSERVER; flags |= CLIENT_STARTSERVER;
cmd_list_free(pr->cmdlist);
} else
free(pr->error);
args_free_values(values, argc);
free(values);
} }
/* Create client process structure (starts logging). */ /* Create client process structure (starts logging). */
@@ -277,7 +279,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
proc_set_signals(client_proc, client_signal); proc_set_signals(client_proc, client_signal);
/* Save the flags. */ /* Save the flags. */
client_flags = flags; client_flags = flags|CLIENT_WRITE_ACK;
log_debug("flags are %#llx", (unsigned long long)client_flags); log_debug("flags are %#llx", (unsigned long long)client_flags);
/* Initialize the client socket and start the server. */ /* Initialize the client socket and start the server. */
@@ -407,11 +409,6 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
client_exec(client_execshell, client_execcmd); client_exec(client_execshell, client_execcmd);
} }
/* Restore streams to blocking. */
setblocking(STDIN_FILENO, 1);
setblocking(STDOUT_FILENO, 1);
setblocking(STDERR_FILENO, 1);
/* Print the exit message, if any, and exit. */ /* Print the exit message, if any, and exit. */
if (client_attached) { if (client_attached) {
if (client_exitreason != CLIENT_EXIT_NONE) if (client_exitreason != CLIENT_EXIT_NONE)
@@ -426,15 +423,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
else else
printf("%%exit\n"); printf("%%exit\n");
fflush(stdout); fflush(stdout);
if (client_flags & CLIENT_CONTROL_WAITEXIT) { if (client_flags & CLIENT_CONTROL_WAITEXIT)
setvbuf(stdin, NULL, _IOLBF, 0); control_wait_exit(STDIN_FILENO);
for (;;) {
linelen = getline(&line, &linesize, stdin);
if (linelen <= 1)
break;
}
free(line);
}
if (client_flags & CLIENT_CONTROLCONTROL) { if (client_flags & CLIENT_CONTROLCONTROL) {
printf("\033\\"); printf("\033\\");
fflush(stdout); fflush(stdout);
@@ -442,6 +432,12 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
} }
} else if (client_exitreason != CLIENT_EXIT_NONE) } else if (client_exitreason != CLIENT_EXIT_NONE)
fprintf(stderr, "%s\n", client_exit_message()); fprintf(stderr, "%s\n", client_exit_message());
/* Restore the streams to blocking. */
setblocking(STDIN_FILENO, 1);
setblocking(STDOUT_FILENO, 1);
setblocking(STDERR_FILENO, 1);
return (client_exitval); return (client_exitval);
} }
@@ -719,6 +715,9 @@ client_dispatch_wait(struct imsg *imsg)
fprintf(stderr, "server version is too old for client\n"); fprintf(stderr, "server version is too old for client\n");
proc_exit(client_proc); proc_exit(client_proc);
break; break;
default:
log_debug("unknown message type %u", imsg->hdr.type);
break;
} }
} }
@@ -805,5 +804,8 @@ client_dispatch_attached(struct imsg *imsg)
system(data); system(data);
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0); proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
break; break;
default:
log_debug("unknown message type %u", imsg->hdr.type);
break;
} }
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-attach-session.c,v 1.91 2026/07/10 13:38:45 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -61,6 +61,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
struct window_pane *wp; struct window_pane *wp;
char *cwd, *cause; char *cwd, *cause;
enum msgtype msgtype; enum msgtype msgtype;
uid_t uid;
if (RB_EMPTY(&sessions)) { if (RB_EMPTY(&sessions)) {
cmdq_error(item, "no sessions"); cmdq_error(item, "no sessions");
@@ -106,8 +107,16 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
} }
if (fflag) if (fflag)
server_client_set_flags(c, fflag); server_client_set_flags(c, fflag);
if (rflag) if (rflag) {
if (c->flags & CLIENT_READONLY) {
uid = proc_get_peer_uid(c->peer);
if (uid != getuid()) {
cmdq_error(item, "client is read-only");
return (CMD_RETURN_ERROR);
}
}
c->flags |= (CLIENT_READONLY|CLIENT_IGNORESIZE); c->flags |= (CLIENT_READONLY|CLIENT_IGNORESIZE);
}
c->last_session = c->session; c->last_session = c->session;
if (c->session != NULL) { if (c->session != NULL) {
@@ -154,7 +163,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
if (~c->flags & CLIENT_CONTROL) if (~c->flags & CLIENT_CONTROL)
proc_send(c->peer, MSG_READY, -1, NULL, 0); proc_send(c->peer, MSG_READY, -1, NULL, 0);
notify_client("client-attached", c); events_fire_client("client-attached", c);
c->flags |= CLIENT_ATTACHED; c->flags |= CLIENT_ATTACHED;
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-bind-key.c,v 1.47 2025/04/09 07:03:04 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -56,11 +56,12 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
key_code key; key_code key;
const char *tablename, *note = args_get(args, 'N'); const char *tablename, *note = args_get(args, 'N'), *file;
struct cmd_parse_result *pr;
int repeat; int repeat;
struct args_value *value; struct args_value *value;
u_int count = args_count(args); u_int count = args_count(args);
struct cmd_parse_tree *cmd;
struct cmd_parse_input pi;
key = key_string_lookup_string(args_string(args, 0)); key = key_string_lookup_string(args_string(args, 0));
if (key == KEYC_NONE || key == KEYC_UNKNOWN) { if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
@@ -83,25 +84,18 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
value = args_value(args, 1); value = args_value(args, 1);
if (count == 2 && value->type == ARGS_COMMANDS) { if (count == 2 && value->type == ARGS_COMMANDS) {
key_bindings_add(tablename, key, note, repeat, value->cmdlist); cmd = cmd_parse_add_ref(value->cmd);
value->cmdlist->references++; key_bindings_add(tablename, key, note, repeat, cmd);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (count == 2) memset(&pi, 0, sizeof pi);
pr = cmd_parse_from_string(args_string(args, 1), NULL); cmd_get_source(self, &file, &pi.line);
else { if (file != NULL)
pr = cmd_parse_from_arguments(args_values(args) + 1, count - 1, pi.file = file;
NULL); pi.flags = cmd_get_parse_flags(self);
}
switch (pr->status) { cmd = cmd_parse_from_arguments(args_values(args) + 1, count - 1, &pi);
case CMD_PARSE_ERROR: key_bindings_add(tablename, key, note, repeat, cmd);
cmdq_error(item, "%s", pr->error);
free(pr->error);
return (CMD_RETURN_ERROR);
case CMD_PARSE_SUCCESS:
break;
}
key_bindings_add(tablename, key, note, repeat, pr->cmdlist);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-break-pane.c,v 1.76 2026/08/17 07:04:45 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -34,9 +34,10 @@ const struct cmd_entry cmd_break_pane_entry = {
.name = "break-pane", .name = "break-pane",
.alias = "breakp", .alias = "breakp",
.args = { "abdPF:n:s:t:", 0, 0, NULL }, .args = { "abdPF:n:s:t:Wx:X:y:Y:", 0, 0, NULL },
.usage = "[-abdP] [-F format] [-n window-name] [-s src-pane] " .usage = "[-abdPW] [-F format] [-n window-name] [-s src-pane] "
"[-t dst-window]", "[-t dst-window] [-x width] [-y height] [-X x-position] "
"[-Y y-position]",
.source = { 's', CMD_FIND_PANE, 0 }, .source = { 's', CMD_FIND_PANE, 0 },
.target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX }, .target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX },
@@ -45,6 +46,46 @@ const struct cmd_entry cmd_break_pane_entry = {
.exec = cmd_break_pane_exec .exec = cmd_break_pane_exec
}; };
static enum cmd_retval
cmd_break_pane_float(struct cmdq_item *item, struct args *args,
struct window *w, struct window_pane *wp)
{
struct layout_cell *lc = wp->layout_cell;
char *cause = NULL;
enum pane_lines lines = window_get_pane_lines(w);
struct layout_geometry *fg = &lc->fg;
if (window_pane_is_floating(wp)) {
cmdq_error(item, "pane is already floating");
return (CMD_RETURN_ERROR);
}
if (w->flags & WINDOW_ZOOMED) {
cmdq_error(item, "can't float a pane while window is zoomed");
return (CMD_RETURN_ERROR);
}
if (layout_floating_args_parse(item, args, lines, w, fg, &cause) != 0) {
cmdq_error(item, "failed to float pane: %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
layout_remove_tile(w, lc);
layout_set_size(lc, fg->sx, fg->sy, fg->xoff, fg->yoff);
lc->flags |= LAYOUT_CELL_FLOATING;
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
if (!args_has(args, 'd'))
window_set_active_pane(w, wp, 1);
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
events_fire_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval static enum cmd_retval
cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
{ {
@@ -57,10 +98,23 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
struct session *src_s = source->s; struct session *src_s = source->s;
struct session *dst_s = target->s; struct session *dst_s = target->s;
struct window_pane *wp = source->wp; struct window_pane *wp = source->wp;
struct window *w = wl->window; struct window *w = wl->window, *old_w = w;
char *name, *cause, *cp; char *cause, *cp;
int idx = target->idx, before; int idx = target->idx, before, old_idx = wl->idx;
const char *template; const char *template, *name = args_get(args, 'n');
if (wp == w->modal) {
cmdq_error(item, "pane is modal");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'W'))
return (cmd_break_pane_float(item, args, w, wp));
if (name != NULL && !check_name(name)) {
cmdq_error(item, "invalid window name: %s", name);
return (CMD_RETURN_ERROR);
}
before = args_has(args, 'b'); before = args_has(args, 'b');
if (args_has(args, 'a') || before) { if (args_has(args, 'a') || before) {
@@ -73,21 +127,22 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
} }
server_unzoom_window(w); server_unzoom_window(w);
if (window_count_panes(w) == 1) { if (window_count_panes(w, 1) == 1) {
if (server_link_window(src_s, wl, dst_s, idx, 0, if (server_link_window(src_s, wl, dst_s, idx, 0,
!args_has(args, 'd'), &cause) != 0) { !args_has(args, 'd'), &cause) != 0) {
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
free(cause); free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (args_has(args, 'n')) { if (name != NULL) {
window_set_name(w, args_get(args, 'n')); window_set_name(w, name, 0);
options_set_number(w->options, "automatic-rename", 0); options_set_number(w->options, "automatic-rename", 0);
} }
server_unlink_window(src_s, wl); server_unlink_window(src_s, wl);
wl = winlink_find_by_window(&dst_s->windows, w); wl = winlink_find_by_window(&dst_s->windows, w);
if (wl == NULL) if (wl == NULL)
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
goto out; goto out;
} }
if (idx != -1 && winlink_find_by_index(&dst_s->windows, idx) != NULL) { if (idx != -1 && winlink_find_by_index(&dst_s->windows, idx) != NULL) {
@@ -96,33 +151,40 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
} }
TAILQ_REMOVE(&w->panes, wp, entry); TAILQ_REMOVE(&w->panes, wp, entry);
TAILQ_REMOVE(&w->z_index, wp, zentry);
server_client_remove_pane(wp); server_client_remove_pane(wp);
window_lost_pane(w, wp); window_lost_pane(w, wp);
layout_close_pane(wp); layout_close_pane(wp);
w = wp->window = window_create(w->sx, w->sy, w->xpixel, w->ypixel); w = wp->window = window_create(w->sx, w->sy, w->xpixel, w->ypixel);
window_add_ref(w, __func__);
options_set_parent(wp->options, w->options); options_set_parent(wp->options, w->options);
wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED);
TAILQ_INSERT_HEAD(&w->panes, wp, entry); TAILQ_INSERT_HEAD(&w->panes, wp, entry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
w->active = wp; w->active = wp;
w->latest = tc; w->latest = tc;
if (!args_has(args, 'n')) { free(w->name);
name = default_window_name(w); if (name == NULL)
window_set_name(w, name); w->name = default_window_name(w);
free(name); else {
} else { w->name = clean_name(name, 0);
window_set_name(w, args_get(args, 'n'));
options_set_number(w->options, "automatic-rename", 0); options_set_number(w->options, "automatic-rename", 0);
} }
window_set_fill_cells(w);
if (idx == -1)
idx = -1 - options_get_number(dst_s->options, "base-index");
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
layout_init(w, wp); layout_init(w, wp);
wp->flags |= PANE_CHANGED; wp->flags |= PANE_CHANGED;
colour_palette_from_option(&wp->palette, wp->options); colour_palette_from_option(&wp->palette, wp->options);
if (idx == -1) window_remove_ref(w, __func__);
idx = -1 - options_get_number(dst_s->options, "base-index"); events_fire_window("window-created", w);
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */ window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
if (!args_has(args, 'd')) { if (!args_has(args, 'd')) {
session_select(dst_s, wl->idx); session_select(dst_s, wl->idx);
cmd_find_from_session(current, dst_s, 0); cmd_find_from_session(current, dst_s, 0);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-capture-pane.c,v 1.68 2026/07/20 11:16:33 nicm Exp $ */
/* /*
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net> * Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
@@ -29,18 +29,21 @@
static enum cmd_retval cmd_capture_pane_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_capture_pane_exec(struct cmd *, struct cmdq_item *);
static char *cmd_capture_pane_append(char *, size_t *, char *, size_t); static char *cmd_capture_pane_append(char *, size_t *, const char *,
size_t);
static char *cmd_capture_pane_pending(struct args *, struct window_pane *, static char *cmd_capture_pane_pending(struct args *, struct window_pane *,
size_t *); size_t *);
static char *cmd_capture_pane_history(struct args *, struct cmdq_item *, static char *cmd_capture_pane_history(struct args *, struct cmdq_item *,
struct window_pane *, size_t *); struct window_pane *, size_t *);
static char *cmd_capture_pane_hyperlinks(struct grid *, struct screen *,
u_int, u_int *, u_int *, size_t *);
const struct cmd_entry cmd_capture_pane_entry = { const struct cmd_entry cmd_capture_pane_entry = {
.name = "capture-pane", .name = "capture-pane",
.alias = "capturep", .alias = "capturep",
.args = { "ab:CeE:JMNpPqS:Tt:", 0, 0, NULL }, .args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL },
.usage = "[-aCeJMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " .usage = "[-aCeFHJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
"[-S start-line] " CMD_TARGET_PANE_USAGE, "[-S start-line] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -63,7 +66,8 @@ const struct cmd_entry cmd_clear_history_entry = {
}; };
static char * static char *
cmd_capture_pane_append(char *buf, size_t *len, char *line, size_t linelen) cmd_capture_pane_append(char *buf, size_t *len, const char *line,
size_t linelen)
{ {
buf = xrealloc(buf, *len + linelen + 1); buf = xrealloc(buf, *len + linelen + 1);
memcpy(buf + *len, line, linelen); memcpy(buf + *len, line, linelen);
@@ -71,6 +75,108 @@ cmd_capture_pane_append(char *buf, size_t *len, char *line, size_t linelen)
return (buf); return (buf);
} }
static char *
cmd_capture_pane_cell(struct screen *s, u_int xx, u_int yy)
{
struct grid *gd = s->grid;
struct hyperlinks *hl = s->hyperlinks;
struct grid_cell gc;
char *line, *data, *link, *linkid, *f, *b, *u;
char c[UTF8_SIZE + 1];
const char *uri, *iid;
u_int flags;
grid_get_cell(gd, xx, yy, &gc);
memcpy(c, gc.data.data, gc.data.size);
c[gc.data.size] = '\0';
utf8_stravis(&data, c, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
if (gc.link != 0 && hyperlinks_get(hl, gc.link, &uri, &iid, NULL)) {
xasprintf(&link, "%s", uri);
if (iid != NULL && *iid != '\0')
xasprintf(&linkid, "%s", iid);
else
xasprintf(&linkid, "NONE");
} else {
xasprintf(&link, "NONE");
xasprintf(&linkid, "NONE");
}
flags = gc.flags;
if (gc.fg & COLOUR_FLAG_256)
flags |= GRID_FLAG_FG256;
if (gc.bg & COLOUR_FLAG_256)
flags |= GRID_FLAG_BG256;
xasprintf(&f, "%s[%x]", colour_tostring(gc.fg), gc.fg);
xasprintf(&b, "%s[%x]", colour_tostring(gc.bg), gc.bg);
xasprintf(&u, "%s[%x]", colour_tostring(gc.us), gc.us);
xasprintf(&line, "\t\tC %u,%u data=(%u,%u,%s) flags=%s[%x] "
"attr=%s[%x] fg=%s bg=%s us=%s link=%s linkid=%s\n",
yy, xx, gc.data.width, gc.data.size, data,
grid_cell_flags_string(flags), flags,
grid_cell_attr_string(gc.attr), gc.attr, f, b, u, link, linkid);
free(f);
free(b);
free(u);
free(link);
free(linkid);
free(data);
return (line);
}
static char *
cmd_capture_pane_grid(struct window_pane *wp, size_t *len)
{
struct screen *s = &wp->base;
struct grid *gd = s->grid;
struct grid_line *gl;
struct osc133_data *od;
char *buf = xstrdup(""), *line;
char p[11];
u_int yy, xx, total = gd->hsize + gd->sy;
xasprintf(&line, "G %ux%u (%u/%u)\n", gd->sx, gd->sy, gd->hsize,
gd->hlimit);
buf = cmd_capture_pane_append(buf, len, line, strlen(line));
free(line);
for (yy = 0; yy < total; yy++) {
gl = grid_get_line(gd, yy);
if (yy < gd->hsize)
snprintf(p, sizeof p, "-");
else
snprintf(p, sizeof p, "%u", yy - gd->hsize);
od = &gl->osc133_data;
if (gl->flags & GRID_LINE_OSC133_FLAGS) {
xasprintf(&line, "\tL %u (%s) flags=%s[%x] "
"%u/%u osc133=%u,%u,%u,%u,%u\n", yy, p,
grid_line_flags_string(gl->flags), gl->flags,
gl->cellused, gl->cellsize, od->prompt_col,
od->cmd_col, od->out_start_col,
od->out_end_col, od->exit_status);
} else {
xasprintf(&line, "\tL %u (%s) flags=%s[%x] "
"%u/%u\n", yy, p,
grid_line_flags_string(gl->flags), gl->flags,
gl->cellused, gl->cellsize);
}
buf = cmd_capture_pane_append(buf, len, line, strlen(line));
free(line);
for (xx = 0; xx < gd->sx; xx++) {
line = cmd_capture_pane_cell(s, xx, yy);
buf = cmd_capture_pane_append(buf, len, line,
strlen(line));
free(line);
}
}
return (buf);
}
static char * static char *
cmd_capture_pane_pending(struct args *args, struct window_pane *wp, cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
size_t *len) size_t *len)
@@ -103,6 +209,46 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
return (buf); return (buf);
} }
static char *
cmd_capture_pane_hyperlinks(struct grid *gd, struct screen *s, u_int py,
u_int *links, u_int *nlinks, size_t *len)
{
const struct grid_line *gl = grid_peek_line(gd, py);
struct grid_cell gc;
const char *uri;
char *line = xstrdup("");
u_int i, j;
*len = 0;
if (s->hyperlinks == NULL || (~gl->flags & GRID_LINE_HYPERLINK))
return (line);
for (i = 0; i < gl->cellused; i++) {
grid_get_cell(gd, i, py, &gc);
if (gc.link == 0)
continue;
for (j = 0; j < *nlinks; j++) {
if (links[j] == gc.link)
break;
}
if (j != *nlinks)
continue;
if (!hyperlinks_get(s->hyperlinks, gc.link, &uri, NULL, NULL))
continue;
if (*nlinks == gd->sx)
break;
links[(*nlinks)++] = gc.link;
if (*len != 0)
line = cmd_capture_pane_append(line, len, " ", 1);
line = cmd_capture_pane_append(line, len, uri, strlen(uri));
}
return (line);
}
static char * static char *
cmd_capture_pane_history(struct args *args, struct cmdq_item *item, cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
struct window_pane *wp, size_t *len) struct window_pane *wp, size_t *len)
@@ -112,9 +258,11 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
struct screen *s; struct screen *s;
struct grid_cell *gc = NULL; struct grid_cell *gc = NULL;
struct window_mode_entry *wme; struct window_mode_entry *wme;
int n, join_lines, flags = 0; int n, join_lines, number_lines, flags = 0;
int show_flags, hyperlinks;
u_int *links = NULL, nlinks = 0;
u_int i, sx, top, bottom, tmp; u_int i, sx, top, bottom, tmp;
char *cause, *buf, *line; char *cause, *buf = NULL, *line, b[64], *cp;
const char *Sflag, *Eflag; const char *Sflag, *Eflag;
size_t linelen; size_t linelen;
@@ -192,12 +340,57 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
flags |= GRID_STRING_EMPTY_CELLS; flags |= GRID_STRING_EMPTY_CELLS;
if (!join_lines && !args_has(args, 'N')) if (!join_lines && !args_has(args, 'N'))
flags |= GRID_STRING_TRIM_SPACES; flags |= GRID_STRING_TRIM_SPACES;
number_lines = args_has(args, 'L');
show_flags = args_has(args, 'F');
hyperlinks = args_has(args, 'H');
if (hyperlinks)
links = xreallocarray(NULL, gd->sx, sizeof *links);
buf = NULL;
for (i = top; i <= bottom; i++) { for (i = top; i <= bottom; i++) {
if (hyperlinks) {
line = cmd_capture_pane_hyperlinks(gd, s, i, links,
&nlinks, &linelen);
} else {
line = grid_string_cells(gd, 0, i, sx, &gc, flags, s); line = grid_string_cells(gd, 0, i, sx, &gc, flags, s);
linelen = strlen(line); linelen = strlen(line);
}
if (hyperlinks && linelen == 0) {
free(line);
continue;
}
if (number_lines) {
if (i >= gd->hsize)
n = i - gd->hsize;
else
n = (int)i - (int)gd->hsize;
n = snprintf(b, sizeof b, "%d ", n);
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)
*cp++ = 'H';
if (gl->flags & GRID_LINE_START_OUTPUT)
*cp++ = 'O';
if (gl->flags & GRID_LINE_START_PROMPT)
*cp++ = 'P';
if (gl->flags & GRID_LINE_WRAPPED)
*cp++ = 'W';
if (gl->flags & GRID_LINE_EXTENDED)
*cp++ = 'X';
if (b == cp)
*cp++ = '-';
*cp++ = ' ';
*cp = '\0';
buf = cmd_capture_pane_append(buf, len, b, strlen (b));
}
buf = cmd_capture_pane_append(buf, len, line, linelen); buf = cmd_capture_pane_append(buf, len, line, linelen);
gl = grid_peek_line(gd, i); gl = grid_peek_line(gd, i);
@@ -206,6 +399,9 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
free(line); free(line);
} }
free(links);
if (buf == NULL)
buf = xstrdup("");
return (buf); return (buf);
} }
@@ -224,11 +420,14 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item)
grid_clear_history(wp->base.grid); grid_clear_history(wp->base.grid);
if (args_has(args, 'H')) if (args_has(args, 'H'))
screen_reset_hyperlinks(wp->screen); screen_reset_hyperlinks(wp->screen);
server_redraw_window(wp->window);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
len = 0; len = 0;
if (args_has(args, 'P')) if (args_has(args, 'R'))
buf = cmd_capture_pane_grid(wp, &len);
else if (args_has(args, 'P') && !args_has(args, 'H'))
buf = cmd_capture_pane_pending(args, wp, &len); buf = cmd_capture_pane_pending(args, wp, &len);
else else
buf = cmd_capture_pane_history(args, item, wp, &len); buf = cmd_capture_pane_history(args, item, wp, &len);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-choose-tree.c,v 1.58 2026/07/14 17:17:17 nicm Exp $ */
/* /*
* Copyright (c) 2012 Thomas Adam <thomas@xteddy.org> * Copyright (c) 2012 Thomas Adam <thomas@xteddy.org>
@@ -33,8 +33,8 @@ const struct cmd_entry cmd_choose_tree_entry = {
.name = "choose-tree", .name = "choose-tree",
.alias = NULL, .alias = NULL,
.args = { "F:f:GK:NO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse }, .args = { "F:f:GhK:kNO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-GNrswZ] [-F format] [-f filter] [-K key-format] " .usage = "[-GhkNrswZ] [-F format] [-f filter] [-K key-format] "
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -47,8 +47,8 @@ const struct cmd_entry cmd_choose_client_entry = {
.name = "choose-client", .name = "choose-client",
.alias = NULL, .alias = NULL,
.args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, .args = { "F:f:hiK:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] " .usage = "[-hikNrZ] [-F format] [-f filter] [-K key-format] "
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -61,8 +61,8 @@ const struct cmd_entry cmd_choose_buffer_entry = {
.name = "choose-buffer", .name = "choose-buffer",
.alias = NULL, .alias = NULL,
.args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, .args = { "F:f:K:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] " .usage = "[-kNrZ] [-F format] [-f filter] [-K key-format] "
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -75,8 +75,8 @@ const struct cmd_entry cmd_customize_mode_entry = {
.name = "customize-mode", .name = "customize-mode",
.alias = NULL, .alias = NULL,
.args = { "F:f:Nt:yZ", 0, 0, NULL }, .args = { "F:f:kNt:yZ", 0, 0, NULL },
.usage = "[-NZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE, .usage = "[-kNZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -84,6 +84,34 @@ const struct cmd_entry cmd_customize_mode_entry = {
.exec = cmd_choose_tree_exec .exec = cmd_choose_tree_exec
}; };
const struct cmd_entry cmd_switch_mode_entry = {
.name = "switch-mode",
.alias = NULL,
.args = { "F:kst:wZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-kswZ] [-F format] " CMD_TARGET_PANE_USAGE " [command]",
.target = { 't', CMD_FIND_PANE, 0 },
.flags = 0,
.exec = cmd_choose_tree_exec
};
const struct cmd_entry cmd_display_panes_entry = {
.name = "display-panes",
.alias = "displayp",
.args = { "d:kNs:t:Z", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-kNZ] [-d duration] [-s source-window] "
CMD_TARGET_PANE_USAGE " [template]",
.source = { 's', CMD_FIND_WINDOW, 0 },
.target = { 't', CMD_FIND_PANE, 0 },
.flags = CMD_AFTERHOOK,
.exec = cmd_choose_tree_exec
};
static enum args_parse_type static enum args_parse_type
cmd_choose_tree_args_parse(__unused struct args *args, __unused u_int idx, cmd_choose_tree_args_parse(__unused struct args *args, __unused u_int idx,
__unused char **cause) __unused char **cause)
@@ -98,6 +126,13 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct window_pane *wp = target->wp; struct window_pane *wp = target->wp;
const struct window_mode *mode; const struct window_mode *mode;
enum sort_order order;
order = sort_order_from_string(args_get(args, 'O'));
if (order == SORT_END && args_has(args, 'O')) {
cmdq_error(item, "invalid sort order");
return (CMD_RETURN_ERROR);
}
if (cmd_get_entry(self) == &cmd_choose_buffer_entry) { if (cmd_get_entry(self) == &cmd_choose_buffer_entry) {
if (paste_is_empty()) if (paste_is_empty())
@@ -109,9 +144,13 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item)
mode = &window_client_mode; mode = &window_client_mode;
} else if (cmd_get_entry(self) == &cmd_customize_mode_entry) } else if (cmd_get_entry(self) == &cmd_customize_mode_entry)
mode = &window_customize_mode; mode = &window_customize_mode;
else if (cmd_get_entry(self) == &cmd_switch_mode_entry)
mode = &window_switch_mode;
else if (cmd_get_entry(self) == &cmd_display_panes_entry)
mode = &window_panes_mode;
else else
mode = &window_tree_mode; mode = &window_tree_mode;
window_pane_set_mode(wp, NULL, mode, target, args); window_pane_set_mode(wp, NULL, mode, item, target, args);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-command-prompt.c,v 1.76 2026/08/25 06:04:33 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -34,16 +34,16 @@ static enum args_parse_type cmd_command_prompt_args_parse(struct args *,
static enum cmd_retval cmd_command_prompt_exec(struct cmd *, static enum cmd_retval cmd_command_prompt_exec(struct cmd *,
struct cmdq_item *); struct cmdq_item *);
static int cmd_command_prompt_callback(struct client *, void *, static enum prompt_result cmd_command_prompt_callback(struct client *, void *,
const char *, int); const char *, enum prompt_key_result);
static void cmd_command_prompt_free(void *); static void cmd_command_prompt_free(void *);
const struct cmd_entry cmd_command_prompt_entry = { const struct cmd_entry cmd_command_prompt_entry = {
.name = "command-prompt", .name = "command-prompt",
.alias = NULL, .alias = NULL,
.args = { "1bFkliI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse }, .args = { "1CbeFiklI:NPp:t:T:", 0, 1, cmd_command_prompt_args_parse },
.usage = "[-1bFkliN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE .usage = "[-1CbeFiklNP] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE
" [-T prompt-type] [template]", " [-T prompt-type] [template]",
.flags = CMD_CLIENT_TFLAG, .flags = CMD_CLIENT_TFLAG,
@@ -57,11 +57,13 @@ struct cmd_command_prompt_prompt {
struct cmd_command_prompt_cdata { struct cmd_command_prompt_cdata {
struct cmdq_item *item; struct cmdq_item *item;
struct args_command_state *state; struct cmd_parse_tree *tree;
int flags; int flags;
enum prompt_type prompt_type; enum prompt_type prompt_type;
struct window_pane *wp;
struct cmd_command_prompt_prompt *prompts; struct cmd_command_prompt_prompt *prompts;
u_int count; u_int count;
u_int current; u_int current;
@@ -83,14 +85,19 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct client *tc = cmdq_get_target_client(item); struct client *tc = cmdq_get_target_client(item);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
const char *type, *s, *input; const char *type, *s, *input, *cmd;
struct cmd_command_prompt_cdata *cdata; struct cmd_command_prompt_cdata *cdata;
char *tmp, *prompts, *prompt, *next_prompt; char *tmp, *prompts, *prompt, *next_prompt;
char *inputs = NULL, *next_input; char *inputs = NULL, *next_input, *cause = NULL;
struct window_pane *wp = target->wp;
u_int count = args_count(args); u_int count = args_count(args);
int wait = !args_has(args, 'b'), space = 1; int wait = !args_has(args, 'b'), space = 1, n;
int pane = args_has(args, 'P');
if (tc->prompt_string != NULL) if (pane) {
if (wp == NULL || window_pane_has_prompt(wp))
return (CMD_RETURN_NORMAL);
} else if (tc->prompt != NULL)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (args_has(args, 'i')) if (args_has(args, 'i'))
wait = 0; wait = 0;
@@ -98,14 +105,24 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
cdata = xcalloc(1, sizeof *cdata); cdata = xcalloc(1, sizeof *cdata);
if (wait) if (wait)
cdata->item = item; cdata->item = item;
cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait, if (pane)
args_has(args, 'F')); cdata->wp = wp;
if (count != 0) {
cdata->tree = args_to_commands(self, item, 0, NULL,
args_has(args, 'F'), &cause);
if (cdata->tree == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
free(cdata);
return (CMD_RETURN_ERROR);
}
}
if ((s = args_get(args, 'p')) == NULL) { if ((s = args_get(args, 'p')) == NULL) {
if (count != 0) { if (count != 0) {
tmp = args_make_commands_get_command(cdata->state); cmd = args_string(args, 0);
xasprintf(&prompts, "(%s)", tmp); n = strcspn(cmd, " ,");
free(tmp); xasprintf(&prompts, "(%.*s)", n, cmd);
} else { } else {
prompts = xstrdup(":"); prompts = xstrdup(":");
space = 0; space = 0;
@@ -146,7 +163,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
} }
if ((type = args_get(args, 'T')) != NULL) { if ((type = args_get(args, 'T')) != NULL) {
cdata->prompt_type = status_prompt_type(type); cdata->prompt_type = prompt_type(type);
if (cdata->prompt_type == PROMPT_TYPE_INVALID) { if (cdata->prompt_type == PROMPT_TYPE_INVALID) {
cmdq_error(item, "unknown type: %s", type); cmdq_error(item, "unknown type: %s", type);
cmd_command_prompt_free(cdata); cmd_command_prompt_free(cdata);
@@ -163,72 +180,118 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
cdata->flags |= PROMPT_INCREMENTAL; cdata->flags |= PROMPT_INCREMENTAL;
else if (args_has(args, 'k')) else if (args_has(args, 'k'))
cdata->flags |= PROMPT_KEY; cdata->flags |= PROMPT_KEY;
else if (args_has(args, 'e'))
cdata->flags |= PROMPT_BSPACE_EXIT;
if (args_has(args, 'C'))
cdata->flags |= PROMPT_NOFREEZE;
if (pane) {
cdata->flags |= PROMPT_ISPANE;
window_pane_set_prompt(wp, tc, target, cdata->prompts[0].prompt,
cdata->prompts[0].input, cmd_command_prompt_callback,
cmd_command_prompt_free, cdata, cdata->flags,
cdata->prompt_type);
} else {
status_prompt_set(tc, target, cdata->prompts[0].prompt, status_prompt_set(tc, target, cdata->prompts[0].prompt,
cdata->prompts[0].input, cmd_command_prompt_callback, cdata->prompts[0].input, cmd_command_prompt_callback,
cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type); cmd_command_prompt_free, cdata, cdata->flags,
cdata->prompt_type);
}
if (!wait) if (!wait)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
return (CMD_RETURN_WAIT); return (CMD_RETURN_WAIT);
} }
static int static enum prompt_result
cmd_command_prompt_callback(struct client *c, void *data, const char *s, cmd_command_prompt_callback(struct client *c, void *data, const char *s,
int done) enum prompt_key_result key)
{ {
struct cmd_command_prompt_cdata *cdata = data; struct cmd_command_prompt_cdata *cdata = data;
char *error; char *error;
struct cmdq_item *item = cdata->item, *new_item; struct cmdq_item *item = cdata->item, *new_item;
struct cmd_list *cmdlist; struct cmdq_state *cs = NULL;
struct cmd_command_prompt_prompt *prompt; struct cmd_command_prompt_prompt *prompt;
struct cmd_invoke_input ci = { 0 };
struct cmd_parse_tree *tree;
struct cmd_parse_input pi = { 0 };
int argc = 0; int argc = 0;
char **argv = NULL; char **argv = NULL;
if (s == NULL) if (s == NULL || key == PROMPT_KEY_MOVE)
goto out; goto out;
if (done) { if (key == PROMPT_KEY_CLOSE) {
if (cdata->flags & PROMPT_INCREMENTAL) if (cdata->flags & PROMPT_INCREMENTAL)
goto out; goto out;
cmd_append_argv(&cdata->argc, &cdata->argv, s); cmd_append_argv(&cdata->argc, &cdata->argv, s);
if (++cdata->current != cdata->count) { if (++cdata->current != cdata->count) {
prompt = &cdata->prompts[cdata->current]; prompt = &cdata->prompts[cdata->current];
status_prompt_update(c, prompt->prompt, prompt->input); if (cdata->wp != NULL) {
return (1); window_pane_update_prompt(cdata->wp,
prompt->prompt, prompt->input);
} else
status_prompt_update(c, prompt->prompt,
prompt->input);
return (PROMPT_CONTINUE);
} }
} }
argc = cdata->argc; argc = cdata->argc;
argv = cmd_copy_argv(cdata->argc, cdata->argv); argv = cmd_copy_argv(cdata->argc, cdata->argv);
if (!done) if (key != PROMPT_KEY_CLOSE)
cmd_append_argv(&argc, &argv, s); cmd_append_argv(&argc, &argv, s);
else {
if (done) {
cmd_free_argv(cdata->argc, cdata->argv); cmd_free_argv(cdata->argc, cdata->argv);
cdata->argc = argc; cdata->argc = argc;
cdata->argv = cmd_copy_argv(argc, argv); cdata->argv = cmd_copy_argv(argc, argv);
} }
cmdlist = args_make_commands(cdata->state, argc, argv, &error); if (item != NULL)
if (cmdlist == NULL) { cs = cmdq_get_state(item);
cmdq_append(c, cmdq_get_error(error));
free(error); if (cdata->tree != NULL) {
} else if (item == NULL) { /* Explicit body: prompt inputs become the template argv. */
new_item = cmdq_get_command(cmdlist, NULL); ci.argc = argc;
ci.argv = argv;
new_item = cmd_invoke_get(cdata->tree, cs, &ci);
if (item == NULL)
cmdq_append(c, new_item); cmdq_append(c, new_item);
} else { else
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item); cmdq_insert_after(item, new_item);
} else if (argc > 0 && argv[0] != NULL) {
/* No body: parse the submitted text as command language. */
tree = cmd_parse_from_string(argv[0], &pi, &error);
if (tree == NULL) {
if (item == NULL)
cmdq_append(c, cmdq_get_error(error));
else
cmdq_error(item, "%s", error);
free(error);
} else {
new_item = cmd_invoke_get(tree, cs, NULL);
cmd_parse_free(tree);
if (item == NULL)
cmdq_append(c, new_item);
else
cmdq_insert_after(item, new_item);
}
} }
cmd_free_argv(argc, argv); cmd_free_argv(argc, argv);
if (c->prompt_inputcb != cmd_command_prompt_callback) /*
return (1); * An incremental prompt fires its callback on every edit but must stay
* open for further typing; only an explicit close (handled above) ends
* it.
*/
if (cdata->flags & PROMPT_INCREMENTAL)
return (PROMPT_CONTINUE);
out: out:
if (item != NULL) if (item != NULL) {
cdata->item = NULL;
cmdq_continue(item); cmdq_continue(item);
return (0); }
return (PROMPT_CLOSE);
} }
static void static void
@@ -237,12 +300,17 @@ cmd_command_prompt_free(void *data)
struct cmd_command_prompt_cdata *cdata = data; struct cmd_command_prompt_cdata *cdata = data;
u_int i; u_int i;
if (cdata->item != NULL) {
cmdq_continue(cdata->item);
cdata->item = NULL;
}
for (i = 0; i < cdata->count; i++) { for (i = 0; i < cdata->count; i++) {
free(cdata->prompts[i].prompt); free(cdata->prompts[i].prompt);
free(cdata->prompts[i].input); free(cdata->prompts[i].input);
} }
free(cdata->prompts); free(cdata->prompts);
cmd_free_argv(cdata->argc, cdata->argv); cmd_free_argv(cdata->argc, cdata->argv);
args_make_commands_free(cdata->state); cmd_parse_free(cdata->tree);
free(cdata); free(cdata);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-confirm-before.c,v 1.61 2026/06/24 10:55:39 nicm Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -33,8 +33,8 @@ static enum args_parse_type cmd_confirm_before_args_parse(struct args *,
static enum cmd_retval cmd_confirm_before_exec(struct cmd *, static enum cmd_retval cmd_confirm_before_exec(struct cmd *,
struct cmdq_item *); struct cmdq_item *);
static int cmd_confirm_before_callback(struct client *, void *, static enum prompt_result cmd_confirm_before_callback(struct client *, void *,
const char *, int); const char *, enum prompt_key_result);
static void cmd_confirm_before_free(void *); static void cmd_confirm_before_free(void *);
const struct cmd_entry cmd_confirm_before_entry = { const struct cmd_entry cmd_confirm_before_entry = {
@@ -51,7 +51,7 @@ const struct cmd_entry cmd_confirm_before_entry = {
struct cmd_confirm_before_data { struct cmd_confirm_before_data {
struct cmdq_item *item; struct cmdq_item *item;
struct cmd_list *cmdlist; struct cmd_parse_tree *tree;
u_char confirm_key; u_char confirm_key;
int default_yes; int default_yes;
}; };
@@ -70,13 +70,15 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_confirm_before_data *cdata; struct cmd_confirm_before_data *cdata;
struct client *tc = cmdq_get_target_client(item); struct client *tc = cmdq_get_target_client(item);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
char *new_prompt; char *new_prompt, *cause = NULL;
const char *confirm_key, *prompt, *cmd; const char *confirm_key, *prompt, *cmd;
int wait = !args_has(args, 'b'); int wait = !args_has(args, 'b'), n;
cdata = xcalloc(1, sizeof *cdata); cdata = xcalloc(1, sizeof *cdata);
cdata->cmdlist = args_make_commands_now(self, item, 0, 1); cdata->tree = args_to_commands(self, item, 0, NULL, 1, &cause);
if (cdata->cmdlist == NULL) { if (cdata->tree == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
free(cdata); free(cdata);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -92,7 +94,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
cdata->confirm_key = confirm_key[0]; cdata->confirm_key = confirm_key[0];
else { else {
cmdq_error(item, "invalid confirm key"); cmdq_error(item, "invalid confirm key");
free(cdata); cmd_confirm_before_free(cdata);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
} }
@@ -102,8 +104,9 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
if ((prompt = args_get(args, 'p')) != NULL) if ((prompt = args_get(args, 'p')) != NULL)
xasprintf(&new_prompt, "%s ", prompt); xasprintf(&new_prompt, "%s ", prompt);
else { else {
cmd = cmd_get_entry(cmd_list_first(cdata->cmdlist))->name; cmd = args_string(args, 0);
xasprintf(&new_prompt, "Confirm '%s'? (%c/n) ", cmd, n = strcspn(cmd, " ,");
xasprintf(&new_prompt, "Confirm '%.*s'? (%c/n) ", n, cmd,
cdata->confirm_key); cdata->confirm_key);
} }
@@ -117,12 +120,13 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_WAIT); return (CMD_RETURN_WAIT);
} }
static int static enum prompt_result
cmd_confirm_before_callback(struct client *c, void *data, const char *s, cmd_confirm_before_callback(struct client *c, void *data, const char *s,
__unused int done) __unused enum prompt_key_result key)
{ {
struct cmd_confirm_before_data *cdata = data; struct cmd_confirm_before_data *cdata = data;
struct cmdq_item *item = cdata->item, *new_item; struct cmdq_item *item = cdata->item, *new_item;
struct cmdq_state *cs;
int retcode = 1; int retcode = 1;
if (c->flags & CLIENT_DEAD) if (c->flags & CLIENT_DEAD)
@@ -135,11 +139,11 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s,
retcode = 0; retcode = 0;
if (item == NULL) { if (item == NULL) {
new_item = cmdq_get_command(cdata->cmdlist, NULL); new_item = cmd_invoke_get(cdata->tree, NULL, NULL);
cmdq_append(c, new_item); cmdq_append(c, new_item);
} else { } else {
new_item = cmdq_get_command(cdata->cmdlist, cs = cmdq_get_state(item);
cmdq_get_state(item)); new_item = cmd_invoke_get(cdata->tree, cs, NULL);
cmdq_insert_after(item, new_item); cmdq_insert_after(item, new_item);
} }
@@ -150,7 +154,7 @@ out:
cmdq_get_client(item)->retval = retcode; cmdq_get_client(item)->retval = retcode;
cmdq_continue(item); cmdq_continue(item);
} }
return (0); return (PROMPT_CLOSE);
} }
static void static void
@@ -158,6 +162,6 @@ cmd_confirm_before_free(void *data)
{ {
struct cmd_confirm_before_data *cdata = data; struct cmd_confirm_before_data *cdata = data;
cmd_list_free(cdata->cmdlist); cmd_parse_free(cdata->tree);
free(cdata); free(cdata);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-copy-mode.c,v 1.54 2026/07/14 17:17:17 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -30,13 +30,13 @@ const struct cmd_entry cmd_copy_mode_entry = {
.name = "copy-mode", .name = "copy-mode",
.alias = NULL, .alias = NULL,
.args = { "deHMqSs:t:u", 0, 0, NULL }, .args = { "dekHMqSs:t:u", 0, 0, NULL },
.usage = "[-deHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE, .usage = "[-dekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
.source = { 's', CMD_FIND_PANE, 0 }, .source = { 's', CMD_FIND_PANE, 0 },
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
.flags = CMD_AFTERHOOK, .flags = CMD_AFTERHOOK|CMD_READONLY,
.exec = cmd_copy_mode_exec .exec = cmd_copy_mode_exec
}; };
@@ -63,6 +63,8 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item)
struct client *c = cmdq_get_client(item); struct client *c = cmdq_get_client(item);
struct session *s; struct session *s;
struct window_pane *wp = target->wp, *swp; struct window_pane *wp = target->wp, *swp;
u_int tty_ox, tty_oy, tty_sx, tty_sy;
int line_numbers;
if (args_has(args, 'q')) { if (args_has(args, 'q')) {
window_pane_reset_mode_all(wp); window_pane_reset_mode_all(wp);
@@ -77,7 +79,8 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item)
} }
if (cmd_get_entry(self) == &cmd_clock_mode_entry) { if (cmd_get_entry(self) == &cmd_clock_mode_entry) {
window_pane_set_mode(wp, NULL, &window_clock_mode, NULL, NULL); window_pane_set_mode(wp, NULL, &window_clock_mode, item, NULL,
NULL);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
@@ -85,17 +88,24 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item)
swp = source->wp; swp = source->wp;
else else
swp = wp; swp = wp;
if (!window_pane_set_mode(wp, swp, &window_copy_mode, NULL, args)) { line_numbers = 1;
if (event != NULL && KEYC_IS_MOUSE(event->key))
line_numbers = 0;
if (!window_pane_set_mode(wp, swp, &window_copy_mode, item, NULL,
args)) {
window_copy_set_line_numbers(wp, line_numbers);
if (args_has(args, 'M')) if (args_has(args, 'M'))
window_copy_start_drag(c, &event->m); window_copy_start_drag(c, &event->m);
} } else
window_copy_set_line_numbers(wp, line_numbers);
if (args_has(args, 'u')) if (args_has(args, 'u'))
window_copy_pageup(wp, 0); window_copy_pageup(wp, 0);
if (args_has(args, 'd')) if (args_has(args, 'd'))
window_copy_pagedown(wp, 0, args_has(args, 'e')); window_copy_pagedown(wp, 0, args_has(args, 'e'));
if (args_has(args, 'S')) { if (args_has(args, 'S')) {
tty_window_offset(&c->tty, &tty_ox, &tty_oy, &tty_sx, &tty_sy);
window_copy_scroll(wp, c->tty.mouse_slider_mpos, event->m.y, window_copy_scroll(wp, c->tty.mouse_slider_mpos, event->m.y,
args_has(args, 'e')); tty_oy, args_has(args, 'e'));
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-detach-client.c,v 1.39 2026/05/22 15:22:43 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -59,6 +59,7 @@ cmd_detach_client_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *source = cmdq_get_source(item); struct cmd_find_state *source = cmdq_get_source(item);
struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item), *loop; struct client *tc = cmdq_get_target_client(item), *loop;
struct session *s; struct session *s;
enum msgtype msgtype; enum msgtype msgtype;
@@ -69,6 +70,13 @@ cmd_detach_client_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (c->flags & CLIENT_READONLY) {
if (args_has(args, 's') || args_has(args, 'a') || c != tc) {
cmdq_error(item, "client is read-only");
return (CMD_RETURN_ERROR);
}
}
if (args_has(args, 'P')) if (args_has(args, 'P'))
msgtype = MSG_DETACHKILL; msgtype = MSG_DETACHKILL;
else else

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-display-menu.c,v 1.53 2026/08/31 07:46:55 nicm Exp $ */
/* /*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -92,7 +92,7 @@ cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause)
} }
static int static int
cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item, 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 args *args, u_int *px, u_int *py, u_int w, u_int h)
{ {
struct tty *tty = &tc->tty; struct tty *tty = &tc->tty;
@@ -126,6 +126,10 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item,
format_add(ft, "popup_mouse_y", "%u", event->m.y); 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 * If there are any status lines, add this window position and the
* status line position. * status line position.
@@ -238,6 +242,8 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item,
xp = "#{popup_pane_left}"; xp = "#{popup_pane_left}";
else if (strcmp(xp, "M") == 0) else if (strcmp(xp, "M") == 0)
xp = "#{popup_mouse_centre_x}"; xp = "#{popup_mouse_centre_x}";
else if (strcmp(xp, "L") == 0)
xp = "#{popup_last_x}";
else if (strcmp(xp, "W") == 0) else if (strcmp(xp, "W") == 0)
xp = "#{popup_window_status_line_x}"; xp = "#{popup_window_status_line_x}";
p = format_expand(ft, xp); p = format_expand(ft, xp);
@@ -258,6 +264,8 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item,
yp = "#{popup_pane_bottom}"; yp = "#{popup_pane_bottom}";
else if (strcmp(yp, "M") == 0) else if (strcmp(yp, "M") == 0)
yp = "#{popup_mouse_top}"; yp = "#{popup_mouse_top}";
else if (strcmp(yp, "L") == 0)
yp = "#{popup_last_y}";
else if (strcmp(yp, "S") == 0) else if (strcmp(yp, "S") == 0)
yp = "#{popup_status_line_y}"; yp = "#{popup_status_line_y}";
else if (strcmp(yp, "W") == 0) else if (strcmp(yp, "W") == 0)
@@ -280,6 +288,184 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item,
return (1); 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)
{
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 *window = target->w;
struct window_pane *wp = target->wp;
struct style_ranges *ranges = NULL;
struct style_range *sr = NULL;
const char *xp, *yp;
char *p;
u_int line, ox, oy, sx, sy, lines, position;
long n, max_x, max_y, mouse_x = 0, mouse_y = 0;
struct format_tree *ft;
max_x = window->sx > w ? window->sx - w : 0;
max_y = window->sy > h ? window->sy - h : 0;
tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy);
ft = format_create_from_target(item);
if (event->m.valid) {
mouse_x = event->m.x + ox;
if (event->m.statusat == 0) {
if (event->m.y >= event->m.statuslines)
mouse_y = event->m.y - event->m.statuslines + oy;
else
mouse_y = oy;
} else if (event->m.statusat > 0 &&
event->m.y >= (u_int)event->m.statusat) {
mouse_y = oy + sy - 1;
} else
mouse_y = event->m.y + oy;
format_add(ft, "popup_mouse_x", "%ld", mouse_x);
format_add(ft, "popup_mouse_y", "%ld", mouse_y);
}
format_add(ft, "popup_last_x", "%u", window->menu_last_px);
format_add(ft, "popup_last_y", "%u", window->menu_last_py + h);
lines = status_line_size(tc);
position = options_get_number(s->options, "status-position");
if (status_at_line(tc) != -1 && lines != 0) {
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 + ox);
if (position == 0) {
format_add(ft, "popup_window_status_line_y",
"%u", h);
} else {
format_add(ft, "popup_window_status_line_y",
"%u", window->sy);
}
}
if (position == 0)
format_add(ft, "popup_status_line_y", "%u", h);
else
format_add(ft, "popup_status_line_y", "%u", window->sy);
}
format_add(ft, "popup_width", "%u", w);
format_add(ft, "popup_height", "%u", h);
n = ((long)window->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 = ((long)window->sy - 1) / 2 + h / 2;
if (n >= window->sy)
format_add(ft, "popup_centre_y", "%ld", max_y);
else
format_add(ft, "popup_centre_y", "%ld", n);
if (event->m.valid) {
n = mouse_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 = mouse_y - h / 2;
if (n + h >= window->sy)
format_add(ft, "popup_mouse_centre_y", "%ld", max_y);
else
format_add(ft, "popup_mouse_centre_y", "%ld", n);
n = mouse_y + h;
if (n >= window->sy)
format_add(ft, "popup_mouse_top", "%u", window->sy - 1);
else
format_add(ft, "popup_mouse_top", "%ld", n);
n = mouse_y - h;
if (n < 0)
format_add(ft, "popup_mouse_bottom", "%u", 0);
else
format_add(ft, "popup_mouse_bottom", "%ld", n);
}
n = wp->yoff + h;
if (n >= window->sy)
format_add(ft, "popup_pane_top", "%ld", max_y);
else
format_add(ft, "popup_pane_top", "%ld", n);
format_add(ft, "popup_pane_bottom", "%u", wp->yoff + wp->sy);
format_add(ft, "popup_pane_left", "%u", wp->xoff);
n = (long)wp->xoff + wp->sx - w;
if (n < 0)
format_add(ft, "popup_pane_right", "%u", 0);
else
format_add(ft, "popup_pane_right", "%ld", n);
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 < 0)
n = 0;
if (n > max_x)
n = max_x;
*px = n;
log_debug("%s: -x: %s = %s = %u (-w %u)", __func__, xp, p, *px, w);
free(p);
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 < 0)
n = 0;
if (n > max_y)
n = max_y;
*py = n;
log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h);
free(p);
format_free(ft);
return (1);
}
static enum cmd_retval static enum cmd_retval
cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
{ {
@@ -294,16 +480,12 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
const char *border_style = args_get(args, 'S'); const char *border_style = args_get(args, 'S');
const char *selected_style = args_get(args, 'H'); const char *selected_style = args_get(args, 'H');
enum box_lines lines = BOX_LINES_DEFAULT; enum box_lines lines = BOX_LINES_DEFAULT;
char *title, *cause; char *title, *cause = NULL;
int flags = 0, starting_choice = 0; int flags = 0, starting_choice = 0;
u_int px, py, i, count = args_count(args); u_int px, py, i, count = args_count(args);
struct options *o = target->s->curw->window->options; struct options *o = target->s->curw->window->options;
struct options_entry *oe; struct options_entry *oe;
if (tc->overlay_draw != NULL)
return (CMD_RETURN_NORMAL);
if (args_has(args, 'C')) { if (args_has(args, 'C')) {
if (strcmp(args_get(args, 'C'), "-") == 0) if (strcmp(args_get(args, 'C'), "-") == 0)
starting_choice = -1; starting_choice = -1;
@@ -312,8 +494,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
&cause); &cause);
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "starting choice %s", cause); cmdq_error(item, "starting choice %s", cause);
free(cause); goto fail;
return (CMD_RETURN_ERROR);
} }
} }
} }
@@ -334,8 +515,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
if (count - i < 2) { if (count - i < 2) {
cmdq_error(item, "not enough arguments"); cmdq_error(item, "not enough arguments");
menu_free(menu); goto fail;
return (CMD_RETURN_ERROR);
} }
key = args_string(args, i++); key = args_string(args, i++);
@@ -347,17 +527,13 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
} }
if (menu == NULL) { if (menu == NULL) {
cmdq_error(item, "invalid menu arguments"); cmdq_error(item, "invalid menu arguments");
return (CMD_RETURN_ERROR); goto fail;
}
if (menu->count == 0) {
menu_free(menu);
return (CMD_RETURN_NORMAL);
}
if (!cmd_display_menu_get_pos(tc, item, args, &px, &py, menu->width + 4,
menu->count + 2)) {
menu_free(menu);
return (CMD_RETURN_NORMAL);
} }
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'); value = args_get(args, 'b');
if (value != NULL) { if (value != NULL) {
@@ -366,8 +542,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
&cause); &cause);
if (lines == -1) { if (lines == -1) {
cmdq_error(item, "menu-border-lines %s", cause); cmdq_error(item, "menu-border-lines %s", cause);
free(cause); goto fail;
return (CMD_RETURN_ERROR);
} }
} }
@@ -377,8 +552,17 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
flags |= MENU_NOMOUSE; flags |= MENU_NOMOUSE;
if (menu_display(menu, flags, starting_choice, item, px, py, tc, lines, if (menu_display(menu, flags, starting_choice, item, px, py, tc, lines,
style, selected_style, border_style, target, NULL, NULL) != 0) style, selected_style, border_style, target, NULL, NULL) != 0)
goto out;
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
return (CMD_RETURN_WAIT);
out:
menu_free(menu);
return (CMD_RETURN_NORMAL);
fail:
free(cause);
menu_free(menu);
return (CMD_RETURN_ERROR);
} }
static enum cmd_retval static enum cmd_retval
@@ -393,7 +577,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
const char *style = args_get(args, 's'); const char *style = args_get(args, 's');
const char *border_style = args_get(args, 'S'); const char *border_style = args_get(args, 'S');
char *cwd = NULL, *cause = NULL, **argv = NULL; char *cwd = NULL, *cause = NULL, **argv = NULL;
char *title; char *title = NULL;
int modify = popup_present(tc); int modify = popup_present(tc);
int flags = -1, argc = 0; int flags = -1, argc = 0;
enum box_lines lines = BOX_LINES_DEFAULT; enum box_lines lines = BOX_LINES_DEFAULT;
@@ -407,6 +591,8 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
server_client_clear_overlay(tc); server_client_clear_overlay(tc);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (tc->flags & CLIENT_CONTROL)
return (CMD_RETURN_NORMAL);
if (!modify && tc->overlay_draw != NULL) if (!modify && tc->overlay_draw != NULL)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
@@ -417,8 +603,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
&cause); &cause);
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "height %s", cause); cmdq_error(item, "height %s", cause);
free(cause); goto fail;
return (CMD_RETURN_ERROR);
} }
} }
@@ -428,8 +613,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
&cause); &cause);
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "width %s", cause); cmdq_error(item, "width %s", cause);
free(cause); goto fail;
return (CMD_RETURN_ERROR);
} }
} }
@@ -437,8 +621,9 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
w = tty->sx; w = tty->sx;
if (h > tty->sy) if (h > tty->sy)
h = tty->sy; h = tty->sy;
if (!cmd_display_menu_get_pos(tc, item, args, &px, &py, w, h)) if (!cmd_display_menu_get_popup_pos(tc, item, args, &px, &py,
return (CMD_RETURN_NORMAL); w, h))
goto out;
value = args_get(args, 'd'); value = args_get(args, 'd');
if (value != NULL) if (value != NULL)
@@ -478,8 +663,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
&cause); &cause);
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "popup-border-lines %s", cause); cmdq_error(item, "popup-border-lines %s", cause);
free(cause); goto fail;
return (CMD_RETURN_ERROR);
} }
} }
@@ -507,22 +691,29 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
if (modify) { if (modify) {
popup_modify(tc, title, style, border_style, lines, flags); popup_modify(tc, title, style, border_style, lines, flags);
free(title); goto out;
return (CMD_RETURN_NORMAL);
} }
if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc, if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0) { argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0)
cmd_free_argv(argc, argv); goto out;
if (env != NULL)
environ_free(env);
free(cwd);
free(title);
return (CMD_RETURN_NORMAL);
}
if (env != NULL)
environ_free(env); environ_free(env);
free(cwd); free(cwd);
free(title); free(title);
cmd_free_argv(argc, argv); cmd_free_argv(argc, argv);
return (CMD_RETURN_WAIT); return (CMD_RETURN_WAIT);
out:
cmd_free_argv(argc, argv);
environ_free(env);
free(cwd);
free(title);
return (CMD_RETURN_NORMAL);
fail:
free(cause);
cmd_free_argv(argc, argv);
environ_free(env);
free(cwd);
free(title);
return (CMD_RETURN_ERROR);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-display-message.c,v 1.65 2026/02/23 08:46:57 nicm Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -131,6 +131,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'a')) { if (args_has(args, 'a')) {
format_each(ft, cmd_display_message_each, item); format_each(ft, cmd_display_message_each, item);
format_free(ft);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
@@ -155,6 +156,5 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
free(msg); free(msg);
format_free(ft); format_free(ft);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,312 +0,0 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2009 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 <stdlib.h>
#include <string.h>
#include "tmux.h"
/*
* Display panes on a client.
*/
static enum args_parse_type cmd_display_panes_args_parse(struct args *,
u_int, char **);
static enum cmd_retval cmd_display_panes_exec(struct cmd *,
struct cmdq_item *);
const struct cmd_entry cmd_display_panes_entry = {
.name = "display-panes",
.alias = "displayp",
.args = { "bd:Nt:", 0, 1, cmd_display_panes_args_parse },
.usage = "[-bN] [-d duration] " CMD_TARGET_CLIENT_USAGE " [template]",
.flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG,
.exec = cmd_display_panes_exec
};
struct cmd_display_panes_data {
struct cmdq_item *item;
struct args_command_state *state;
};
static enum args_parse_type
cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx,
__unused char **cause)
{
return (ARGS_PARSE_COMMANDS_OR_STRING);
}
static void
cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
struct window_pane *wp)
{
struct client *c = ctx->c;
struct tty *tty = &c->tty;
struct session *s = c->session;
struct options *oo = s->options;
struct window *w = wp->window;
struct grid_cell fgc, bgc;
u_int pane, idx, px, py, i, j, xoff, yoff, sx, sy;
int colour, active_colour;
char buf[16], lbuf[16], rbuf[16], *ptr;
size_t len, llen, rlen;
if (wp->xoff + wp->sx <= ctx->ox ||
wp->xoff >= ctx->ox + ctx->sx ||
wp->yoff + wp->sy <= ctx->oy ||
wp->yoff >= ctx->oy + ctx->sy)
return;
if (wp->xoff >= ctx->ox && wp->xoff + wp->sx <= ctx->ox + ctx->sx) {
/* All visible. */
xoff = wp->xoff - ctx->ox;
sx = wp->sx;
} else if (wp->xoff < ctx->ox &&
wp->xoff + wp->sx > ctx->ox + ctx->sx) {
/* Both left and right not visible. */
xoff = 0;
sx = ctx->sx;
} else if (wp->xoff < ctx->ox) {
/* Left not visible. */
xoff = 0;
sx = wp->sx - (ctx->ox - wp->xoff);
} else {
/* Right not visible. */
xoff = wp->xoff - ctx->ox;
sx = wp->sx - xoff;
}
if (wp->yoff >= ctx->oy && wp->yoff + wp->sy <= ctx->oy + ctx->sy) {
/* All visible. */
yoff = wp->yoff - ctx->oy;
sy = wp->sy;
} else if (wp->yoff < ctx->oy &&
wp->yoff + wp->sy > ctx->oy + ctx->sy) {
/* Both top and bottom not visible. */
yoff = 0;
sy = ctx->sy;
} else if (wp->yoff < ctx->oy) {
/* Top not visible. */
yoff = 0;
sy = wp->sy - (ctx->oy - wp->yoff);
} else {
/* Bottom not visible. */
yoff = wp->yoff - ctx->oy;
sy = wp->sy - yoff;
}
if (ctx->statustop)
yoff += ctx->statuslines;
px = sx / 2;
py = sy / 2;
if (window_pane_index(wp, &pane) != 0)
fatalx("index not found");
len = xsnprintf(buf, sizeof buf, "%u", pane);
if (sx < len)
return;
colour = options_get_number(oo, "display-panes-colour");
active_colour = options_get_number(oo, "display-panes-active-colour");
memcpy(&fgc, &grid_default_cell, sizeof fgc);
memcpy(&bgc, &grid_default_cell, sizeof bgc);
if (w->active == wp) {
fgc.fg = active_colour;
bgc.bg = active_colour;
} else {
fgc.fg = colour;
bgc.bg = colour;
}
rlen = xsnprintf(rbuf, sizeof rbuf, "%ux%u", wp->sx, wp->sy);
if (pane > 9 && pane < 35)
llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10));
else
llen = 0;
if (sx < len * 6 || sy < 5) {
tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL);
if (sx >= len + llen + 1) {
len += llen + 1;
tty_cursor(tty, xoff + px - len / 2, yoff + py);
tty_putn(tty, buf, len, len);
tty_putn(tty, " ", 1, 1);
tty_putn(tty, lbuf, llen, llen);
} else {
tty_cursor(tty, xoff + px - len / 2, yoff + py);
tty_putn(tty, buf, len, len);
}
goto out;
}
px -= len * 3;
py -= 2;
tty_attributes(tty, &bgc, &grid_default_cell, NULL, NULL);
for (ptr = buf; *ptr != '\0'; ptr++) {
if (*ptr < '0' || *ptr > '9')
continue;
idx = *ptr - '0';
for (j = 0; j < 5; j++) {
for (i = px; i < px + 5; i++) {
tty_cursor(tty, xoff + i, yoff + py + j);
if (window_clock_table[idx][j][i - px])
tty_putc(tty, ' ');
}
}
px += 6;
}
if (sy <= 6)
goto out;
tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL);
if (rlen != 0 && sx >= rlen) {
tty_cursor(tty, xoff + sx - rlen, yoff);
tty_putn(tty, rbuf, rlen, rlen);
}
if (llen != 0) {
tty_cursor(tty, xoff + sx / 2 + len * 3 - llen - 1,
yoff + py + 5);
tty_putn(tty, lbuf, llen, llen);
}
out:
tty_cursor(tty, 0, 0);
}
static void
cmd_display_panes_draw(struct client *c, __unused void *data,
struct screen_redraw_ctx *ctx)
{
struct window *w = c->session->curw->window;
struct window_pane *wp;
log_debug("%s: %s @%u", __func__, c->name, w->id);
TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_visible(wp))
cmd_display_panes_draw_pane(ctx, wp);
}
}
static void
cmd_display_panes_free(__unused struct client *c, void *data)
{
struct cmd_display_panes_data *cdata = data;
if (cdata->item != NULL)
cmdq_continue(cdata->item);
args_make_commands_free(cdata->state);
free(cdata);
}
static int
cmd_display_panes_key(struct client *c, void *data, struct key_event *event)
{
struct cmd_display_panes_data *cdata = data;
char *expanded, *error;
struct cmdq_item *item = cdata->item, *new_item;
struct cmd_list *cmdlist;
struct window *w = c->session->curw->window;
struct window_pane *wp;
u_int index;
key_code key;
if (event->key >= '0' && event->key <= '9')
index = event->key - '0';
else if ((event->key & KEYC_MASK_MODIFIERS) == 0) {
key = (event->key & KEYC_MASK_KEY);
if (key >= 'a' && key <= 'z')
index = 10 + (key - 'a');
else
return (-1);
} else
return (-1);
wp = window_pane_at_index(w, index);
if (wp == NULL)
return (1);
window_unzoom(w, 1);
xasprintf(&expanded, "%%%u", wp->id);
cmdlist = args_make_commands(cdata->state, 1, &expanded, &error);
if (cmdlist == NULL) {
cmdq_append(c, cmdq_get_error(error));
free(error);
} else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item);
} else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item);
}
free(expanded);
return (1);
}
static enum cmd_retval
cmd_display_panes_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct client *tc = cmdq_get_target_client(item);
struct session *s = tc->session;
u_int delay;
char *cause;
struct cmd_display_panes_data *cdata;
int wait = !args_has(args, 'b');
if (tc->overlay_draw != NULL)
return (CMD_RETURN_NORMAL);
if (args_has(args, 'd')) {
delay = args_strtonum(args, 'd', 0, UINT_MAX, &cause);
if (cause != NULL) {
cmdq_error(item, "delay %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
} else
delay = options_get_number(s->options, "display-panes-time");
cdata = xcalloc(1, sizeof *cdata);
if (wait)
cdata->item = item;
cdata->state = args_make_commands_prepare(self, item, 0,
"select-pane -t \"%%%\"", wait, 0);
if (args_has(args, 'N')) {
server_client_set_overlay(tc, delay, NULL, NULL,
cmd_display_panes_draw, NULL, cmd_display_panes_free, NULL,
cdata);
} else {
server_client_set_overlay(tc, delay, NULL, NULL,
cmd_display_panes_draw, cmd_display_panes_key,
cmd_display_panes_free, NULL, cdata);
}
if (!wait)
return (CMD_RETURN_NORMAL);
return (CMD_RETURN_WAIT);
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-find-window.c,v 1.57 2026/07/14 17:17:17 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -109,7 +109,8 @@ cmd_find_window_exec(struct cmd *self, struct cmdq_item *item)
args_set(new_args, 'Z', NULL, 0); args_set(new_args, 'Z', NULL, 0);
args_set(new_args, 'f', filter, 0); args_set(new_args, 'f', filter, 0);
window_pane_set_mode(wp, NULL, &window_tree_mode, target, new_args); window_pane_set_mode(wp, NULL, &window_tree_mode, item, target,
new_args);
args_free(new_args); args_free(new_args);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-find.c,v 1.87 2026/07/17 12:42:51 nicm Exp $ */
/* /*
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -147,6 +147,18 @@ cmd_find_session_better(struct session *s, struct session *than, int flags)
return (timercmp(&s->activity_time, &than->activity_time, >)); return (timercmp(&s->activity_time, &than->activity_time, >));
} }
/* Can this session be usefully targeted? */
static int
cmd_find_session_valid(struct session *s)
{
if (!session_alive(s) ||
s->curw == NULL ||
s->curw->window == NULL ||
s->curw->window->active == NULL)
return (0);
return (1);
}
/* Find best session from a list, or all if list is NULL. */ /* Find best session from a list, or all if list is NULL. */
static struct session * static struct session *
cmd_find_best_session(struct session **slist, u_int ssize, int flags) cmd_find_best_session(struct session **slist, u_int ssize, int flags)
@@ -159,11 +171,15 @@ cmd_find_best_session(struct session **slist, u_int ssize, int flags)
s = NULL; s = NULL;
if (slist != NULL) { if (slist != NULL) {
for (i = 0; i < ssize; i++) { for (i = 0; i < ssize; i++) {
if (!cmd_find_session_valid(slist[i]))
continue;
if (cmd_find_session_better(slist[i], s, flags)) if (cmd_find_session_better(slist[i], s, flags))
s = slist[i]; s = slist[i];
} }
} else { } else {
RB_FOREACH(s_loop, sessions, &sessions) { RB_FOREACH(s_loop, sessions, &sessions) {
if (!cmd_find_session_valid(s_loop))
continue;
if (cmd_find_session_better(s_loop, s, flags)) if (cmd_find_session_better(s_loop, s, flags))
s = s_loop; s = s_loop;
} }
@@ -868,7 +884,7 @@ cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags)
if (c->session != NULL) { if (c->session != NULL) {
cmd_find_clear_state(fs, flags); cmd_find_clear_state(fs, flags);
fs->wp = server_client_get_pane(c); fs->wp = c->session->curw->window->active;
if (fs->wp == NULL) { if (fs->wp == NULL) {
cmd_find_from_session(fs, c->session, flags); cmd_find_from_session(fs, c->session, flags);
return (0); return (0);
@@ -924,6 +940,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
const char *target, enum cmd_find_type type, int flags) const char *target, enum cmd_find_type type, int flags)
{ {
struct mouse_event *m; struct mouse_event *m;
struct client *c;
struct cmd_find_state current; struct cmd_find_state current;
char *colon, *period, *copy = NULL, tmp[256]; char *colon, *period, *copy = NULL, tmp[256];
const char *session, *window, *pane, *s; const char *session, *window, *pane, *s;
@@ -990,6 +1007,20 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
if (target == NULL || *target == '\0') if (target == NULL || *target == '\0')
goto current; goto current;
if (strcmp(target, "@") == 0 ||
strcmp(target, "{active}") == 0 ||
strcmp(target, "{current}") == 0) {
c = cmdq_get_client(item);
if (c == NULL || c->session == NULL) {
cmdq_error(item, "no current client");
goto error;
}
fs->wl = c->session->curw;
fs->wp = c->session->curw->window->active;
fs->w = c->session->curw->window;
goto found;
}
/* Mouse target is a plain = or {mouse}. */ /* Mouse target is a plain = or {mouse}. */
if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) { if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) {
m = &cmdq_get_event(item)->m; m = &cmdq_get_event(item)->m;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-if-shell.c,v 1.87 2026/08/25 06:04:33 nicm Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -53,8 +53,9 @@ const struct cmd_entry cmd_if_shell_entry = {
}; };
struct cmd_if_shell_data { struct cmd_if_shell_data {
struct args_command_state *cmd_if; struct cmd_parse_tree *cmd_if;
struct args_command_state *cmd_else; struct cmd_parse_tree *cmd_else;
struct client *client; struct client *client;
struct cmdq_item *item; struct cmdq_item *item;
@@ -76,45 +77,58 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct cmd_if_shell_data *cdata; struct cmd_if_shell_data *cdata;
struct cmdq_item *new_item; struct cmdq_item *new_item;
char *shellcmd; struct cmd_parse_tree *tree;
char *shellcmd, *cause = NULL;
struct client *tc = cmdq_get_target_client(item); struct client *tc = cmdq_get_target_client(item);
struct session *s = target->s; struct session *s = target->s;
struct cmd_list *cmdlist; u_int count = args_count(args), idx;
u_int count = args_count(args);
int wait = !args_has(args, 'b'); int wait = !args_has(args, 'b');
shellcmd = format_single_from_target(item, args_string(args, 0)); shellcmd = format_single_from_target(item, args_string(args, 0));
if (args_has(args, 'F')) { if (args_has(args, 'F')) {
if (*shellcmd != '0' && *shellcmd != '\0') if (*shellcmd != '0' && *shellcmd != '\0')
cmdlist = args_make_commands_now(self, item, 1, 0); idx = 1;
else if (count == 3) else if (count == 3)
cmdlist = args_make_commands_now(self, item, 2, 0); idx = 2;
else { else {
free(shellcmd); free(shellcmd);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
free(shellcmd); free(shellcmd);
if (cmdlist == NULL)
tree = args_to_commands(self, item, idx, NULL, 0, &cause);
if (tree == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); }
new_item = cmd_invoke_get(tree, cmdq_get_state(item), NULL);
cmd_parse_free(tree);
cmdq_insert_after(item, new_item); cmdq_insert_after(item, new_item);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
cdata = xcalloc(1, sizeof *cdata); cdata = xcalloc(1, sizeof *cdata);
cdata->cmd_if = args_to_commands(self, item, 1, NULL, 0, &cause);
cdata->cmd_if = args_make_commands_prepare(self, item, 1, NULL, wait, if (cdata->cmd_if == NULL) {
0); cmdq_error(item, "%s", cause);
goto fail;
}
if (count == 3) { if (count == 3) {
cdata->cmd_else = args_make_commands_prepare(self, item, 2, cdata->cmd_else = args_to_commands(self, item, 2, NULL, 0,
NULL, wait, 0); &cause);
if (cdata->cmd_else == NULL) {
cmdq_error(item, "%s", cause);
goto fail;
}
} }
if (wait) { if (!wait)
cdata->client = tc;
else {
cdata->client = cmdq_get_client(item); cdata->client = cmdq_get_client(item);
cdata->item = item; cdata->item = item;
} else }
cdata->client = tc;
if (cdata->client != NULL) if (cdata->client != NULL)
cdata->client->references++; cdata->client->references++;
@@ -123,15 +137,19 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1, cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1,
-1) == NULL) { -1) == NULL) {
cmdq_error(item, "failed to run command: %s", shellcmd); cmdq_error(item, "failed to run command: %s", shellcmd);
free(shellcmd); goto fail;
cmd_if_shell_free(cdata);
return (CMD_RETURN_ERROR);
} }
free(shellcmd); free(shellcmd);
if (!wait) if (!wait)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
return (CMD_RETURN_WAIT); return (CMD_RETURN_WAIT);
fail:
free(cause);
free(shellcmd);
cmd_if_shell_free(cdata);
return (CMD_RETURN_ERROR);
} }
static void static void
@@ -140,32 +158,22 @@ cmd_if_shell_callback(struct job *job)
struct cmd_if_shell_data *cdata = job_get_data(job); struct cmd_if_shell_data *cdata = job_get_data(job);
struct client *c = cdata->client; struct client *c = cdata->client;
struct cmdq_item *item = cdata->item, *new_item; struct cmdq_item *item = cdata->item, *new_item;
struct args_command_state *state; struct cmd_parse_tree *tree;
struct cmd_list *cmdlist;
char *error;
int status; int status;
status = job_get_status(job); status = job_get_status(job);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
state = cdata->cmd_else; tree = cdata->cmd_else;
else else
state = cdata->cmd_if; tree = cdata->cmd_if;
if (state == NULL) if (tree == NULL)
goto out; goto out;
cmdlist = args_make_commands(state, 0, NULL, &error); if (item == NULL) {
if (cmdlist == NULL) { new_item = cmd_invoke_get(tree, NULL, NULL);
if (cdata->item == NULL) {
*error = toupper((u_char)*error);
status_message_set(c, -1, 1, 0, 0, "%s", error);
} else
cmdq_error(cdata->item, "%s", error);
free(error);
} else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item); cmdq_append(c, new_item);
} else { } else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); new_item = cmd_invoke_get(tree, cmdq_get_state(item), NULL);
cmdq_insert_after(item, new_item); cmdq_insert_after(item, new_item);
} }
@@ -183,8 +191,8 @@ cmd_if_shell_free(void *data)
server_client_unref(cdata->client); server_client_unref(cdata->client);
if (cdata->cmd_else != NULL) if (cdata->cmd_else != NULL)
args_make_commands_free(cdata->cmd_else); cmd_parse_free(cdata->cmd_else);
args_make_commands_free(cdata->cmd_if); if (cdata->cmd_if != NULL)
cmd_parse_free(cdata->cmd_if);
free(cdata); free(cdata);
} }

594
cmd-invoke.c Normal file
View File

@@ -0,0 +1,594 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicm@users.sourceforge.net>
*
* 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 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 <pwd.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
#include "tmux-parser.h"
/* One frame in the parse tree stack. */
struct cmd_invoke_frame {
struct cmd_parse_tree *tree;
struct cmd_parse_node *node;
struct cmd_parse_node *next;
struct cmd_parse_node *end;
};
/* Shared state for a parsed command tree invocation. */
struct cmd_invoke_state {
u_int references;
struct cmd_parse_tree *tree;
struct cmd_parse_tree *current_tree;
struct cmd_invoke_frame *stack;
u_int nstack;
enum cmd_retval last;
int have_last;
int argc;
char **argv;
};
static void cmd_invoke_push(struct cmd_invoke_state *,
struct cmd_parse_tree *, struct cmd_parse_node *,
struct cmd_parse_node *, struct cmd_parse_node *);
static struct cmd_parse_tree *cmd_invoke_tree(struct cmd_invoke_state *);
static struct cmd_parse_node *cmd_invoke_next(struct cmd_invoke_state *);
static void cmd_invoke_skip_sequence(struct cmd_invoke_state *);
static int cmd_invoke_expand_string(struct cmdq_item *,
struct cmd_invoke_state *, struct cmd_parse_node *,
char **);
static int cmd_invoke_assignment(struct cmdq_item *,
struct cmd_invoke_state *, struct cmd_parse_node *);
static int cmd_invoke_if(struct cmdq_item *, struct cmd_invoke_state *,
struct cmd_parse_node *);
static struct cmd *cmd_invoke_build_command(struct cmdq_item *,
struct cmd_invoke_state *, struct cmd_parse_node *);
static int cmd_invoke_read_only(struct cmdq_item *, struct cmd *);
/* Push a node's child range onto the traversal stack. */
static void
cmd_invoke_push(struct cmd_invoke_state *is, struct cmd_parse_tree *tree,
struct cmd_parse_node *node, struct cmd_parse_node *first,
struct cmd_parse_node *end)
{
u_int n = is->nstack + 1;
is->stack = xreallocarray(is->stack, n, sizeof *is->stack);
is->stack[is->nstack].tree = cmd_parse_add_ref(tree);
is->stack[is->nstack].node = node;
is->stack[is->nstack].next = first;
is->stack[is->nstack].end = end;
is->nstack = n;
}
static struct cmd_parse_tree *
cmd_invoke_tree(struct cmd_invoke_state *is)
{
if (is->current_tree != NULL)
return (is->current_tree);
return (is->tree);
}
/* Return the next node to execute from the traversal stack. */
static struct cmd_parse_node *
cmd_invoke_next(struct cmd_invoke_state *is)
{
struct cmd_invoke_frame *frame;
struct cmd_parse_node *node;
for (;;) {
if (is->nstack == 0)
return (NULL);
frame = &is->stack[is->nstack - 1];
if (frame->next != NULL && frame->next != frame->end)
break;
cmd_parse_free(frame->tree);
is->nstack--;
}
node = frame->next;
frame->next = cmd_parse_node_next(node);
is->current_tree = frame->tree;
return (node);
}
/*
* Skip the rest of the active sequence after a command failure. Failure scope
* is the active CMD_PARSE_SEQUENCE. Discard nested frames and advance only as
* far as the end of that sequence.
*/
static void
cmd_invoke_skip_sequence(struct cmd_invoke_state *is)
{
enum cmd_parse_node_type type;
u_int i;
for (i = is->nstack; i > 0; i--) {
type = cmd_parse_node_type(is->stack[i - 1].node);
if (type == CMD_PARSE_SEQUENCE) {
is->stack[i - 1].next = is->stack[i - 1].end;
while (is->nstack > i)
cmd_parse_free(is->stack[--is->nstack].tree);
is->nstack = i;
break;
}
}
is->have_last = 0;
}
/* Append a string to a dynamically allocated buffer. */
static void
cmd_invoke_append(char **buf, size_t *len, const char *s)
{
size_t slen;
if (s != NULL) {
slen = strlen(s);
*buf = xrealloc(*buf, *len + slen + 1);
memcpy(*buf + *len, s, slen + 1);
*len += slen;
}
}
/* Look up an environment variable. */
static const char *
cmd_invoke_getenv(struct cmdq_item *item, const char *name)
{
struct client *c = cmdq_get_client(item);
struct environ_entry *envent;
if (c != NULL && c->environ != NULL) {
envent = environ_find(c->environ, name);
if (envent != NULL && envent->value != NULL)
return (envent->value);
}
envent = environ_find(global_environ, name);
if (envent != NULL && envent->value != NULL)
return (envent->value);
return ("");
}
/* Resolve a tilde expansion to a home directory. */
static const char *
cmd_invoke_tilde(const char *name)
{
struct passwd *pw;
if (name == NULL || *name == '\0')
return (find_home());
if ((pw = getpwnam(name)) == NULL)
return ("");
return (pw->pw_dir);
}
/* Expand a parsed string node into an argv string. */
static int
cmd_invoke_expand_string(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node, char **out)
{
struct cmd_parse_node *child;
const char *s, *value;
char *buf = NULL, *new;
size_t len = 0;
int i;
child = cmd_parse_node_first_child(node);
while (child != NULL) {
value = cmd_parse_node_value(child);
switch (cmd_parse_node_type(child)) {
case CMD_PARSE_TEXT:
s = value;
break;
case CMD_PARSE_ENVIRONMENT:
s = cmd_invoke_getenv(item, value);
break;
case CMD_PARSE_TILDE:
s = cmd_invoke_tilde(value);
break;
default:
fatalx("unexpected node type in string");
}
cmd_invoke_append(&buf, &len, s);
child = cmd_parse_node_next(child);
}
if (buf == NULL)
buf = xstrdup("");
for (i = 0; i < is->argc; i++) {
new = cmd_template_replace(buf, is->argv[i], i + 1);
free(buf);
buf = new;
}
*out = buf;
return (0);
}
/* Execute an assignment node by updating the environment. */
static int
cmd_invoke_assignment(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node)
{
struct cmd_parse_node *value;
const char *name;
char *expanded;
int flags = 0;
value = cmd_parse_node_first_child(node);
if (value == NULL)
return (-1);
if (cmd_invoke_expand_string(item, is, value, &expanded) != 0)
return (-1);
name = cmd_parse_node_value(node);
if (cmd_parse_node_type(node) == CMD_PARSE_HIDDEN_ASSIGN)
flags |= ENVIRON_HIDDEN;
environ_set(global_environ, name, flags, "%s", expanded);
free(expanded);
return (0);
}
/* Expand and evaluate a conditional expression. */
static int
cmd_invoke_is_true(struct cmdq_item *item,
struct cmd_invoke_state *is, struct cmd_parse_node *node, int *result)
{
struct format_tree *ft;
char *s, *expanded;
if (cmd_invoke_expand_string(item, is, node, &s) != 0)
return (-1);
ft = format_create_from_target(item);
expanded = format_expand(ft, s);
*result = format_true(expanded);
free(expanded);
format_free(ft);
free(s);
return (0);
}
/* Skip else branches. */
static struct cmd_parse_node *
cmd_invoke_if_branch_end(struct cmd_parse_node *first)
{
struct cmd_parse_node *node;
for (node = first; node != NULL; node = cmd_parse_node_next(node)) {
switch (cmd_parse_node_type(node)) {
case CMD_PARSE_ELIF:
case CMD_PARSE_ELSE:
return (node);
default:
break;
}
}
return (NULL);
}
/* Select and queue the branch for a conditional node. */
static int
cmd_invoke_if(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node)
{
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
struct cmd_parse_node *child, *first, *next;
int r;
next = cmd_parse_node_first_child(node);
if (next == NULL)
return (0);
first = cmd_parse_node_next(next);
if (cmd_invoke_is_true(item, is, next, &r) != 0)
return (-1);
if (r) {
next = cmd_invoke_if_branch_end(first);
cmd_invoke_push(is, tree, node, first, next);
return (0);
}
for (child = first; child != NULL; child = cmd_parse_node_next(child)) {
switch (cmd_parse_node_type(child)) {
case CMD_PARSE_ELIF:
next = cmd_parse_node_first_child(child);
if (next == NULL)
break;
if (cmd_invoke_is_true(item, is, next, &r) != 0)
return (-1);
if (r) {
next = cmd_parse_node_next(next);
cmd_invoke_push(is, tree, child, next, NULL);
return (0);
}
break;
case CMD_PARSE_ELSE:
next = cmd_parse_node_first_child(child);
cmd_invoke_push(is, tree, child, next, NULL);
return (0);
default:
break;
}
}
return (0);
}
/* Report an error from an invoke item. */
static void
cmd_invoke_error(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node, const char *cause)
{
struct client *c = cmdq_get_client(item);
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
const char *file = cmd_parse_file(tree);
u_int line = cmd_parse_node_line(node);
if (c != NULL && (~c->flags & CLIENT_CONTROL || file == NULL)) {
cmdq_error(item, "%s", cause);
return;
}
if (file != NULL)
cfg_add_cause("%s:%u: %s", file, line, cause);
else
cfg_add_cause("%s", cause);
}
/* Build one command from a parsed command node. */
static struct cmd *
cmd_invoke_build_command(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node)
{
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
struct cmd_parse_node *child;
struct args_value *values = NULL;
struct cmd *cmd;
const char *file = cmd_parse_file(tree);
u_int line = cmd_parse_node_line(node);
int flags = cmd_parse_flags(tree);
char *cause = NULL;
u_int count = 0;
child = cmd_parse_node_first_child(node);
while (child != NULL) {
values = xreallocarray(values, count + 1, sizeof *values);
memset(&values[count], 0, sizeof values[count]);
switch (cmd_parse_node_type(child)) {
case CMD_PARSE_STRING:
values[count].type = ARGS_STRING;
if (cmd_invoke_expand_string(item, is, child,
&values[count].string) != 0)
goto fail;
break;
case CMD_PARSE_COMMANDS:
values[count].type = ARGS_COMMANDS;
values[count].cmd = cmd_parse_from_node(tree, child);
break;
default:
fatalx("unexpected node type in command");
}
count++;
child = cmd_parse_node_next(child);
}
cmd = cmd_parse(values, count, file, line, flags, &cause);
if (cmd == NULL) {
cmd_invoke_error(item, is, node, cause);
free(cause);
goto fail;
}
args_free_values(values, count);
free(values);
return (cmd);
fail:
args_free_values(values, count);
free(values);
return (NULL);
}
static int
cmd_invoke_read_only(struct cmdq_item *item, struct cmd *cmd)
{
struct client *c = cmdq_get_client(item);
const struct cmd_entry *entry = cmd_get_entry(cmd);
if (c == NULL || (~c->flags & CLIENT_READONLY))
return (0);
if (entry->flags & CMD_READONLY)
return (0);
cmdq_error(item, "client is read-only");
return (-1);
}
/* Create the first invoke queue item for a parsed tree. */
struct cmdq_item *
cmd_invoke_get(struct cmd_parse_tree *tree, struct cmdq_state *state,
const struct cmd_invoke_input *input)
{
struct cmd_invoke_state *is;
struct cmd_parse_node *root = cmd_parse_root(tree), *first;
struct cmdq_item *item;
struct cmd_invoke_input new_input = { 0 };
int i;
if (input == NULL)
input = &new_input;
is = xcalloc(1, sizeof *is);
is->references = 1;
is->tree = cmd_parse_add_ref(tree);
is->argc = input->argc;
if (input->argc != 0) {
is->argv = xreallocarray(NULL, input->argc, sizeof *is->argv);
for (i = 0; i < input->argc; i++)
is->argv[i] = xstrdup(input->argv[i]);
}
first = cmd_parse_node_first_child(root);
cmd_invoke_push(is, tree, root, first, NULL);
item = cmdq_get_invoke(is, state);
cmd_invoke_state_free(is);
return (item);
}
/* Add a reference to shared invoke state. */
struct cmd_invoke_state *
cmd_invoke_state_add_ref(struct cmd_invoke_state *is)
{
is->references++;
return (is);
}
/* Release a reference to shared invoke state. */
void
cmd_invoke_state_free(struct cmd_invoke_state *is)
{
int i;
u_int j;
if (is == NULL)
return;
if (--is->references != 0)
return;
for (i = 0; i < is->argc; i++)
free(is->argv[i]);
free(is->argv);
for (j = 0; j < is->nstack; j++)
cmd_parse_free(is->stack[j].tree);
cmd_parse_free(is->tree);
free(is->stack);
free(is);
}
/* Record the result from the last command item. */
void
cmd_invoke_result(struct cmd_invoke_state *is, enum cmd_retval retval)
{
is->last = retval;
is->have_last = 1;
}
/* Fire an invoke item and queue the next command plus continuation. */
enum cmd_retval
cmd_invoke_fire(struct cmdq_item *item, struct cmd_invoke_state *is)
{
struct cmd_parse_node *node, *first;
struct cmd_parse_tree *tree, *alias;
struct cmdq_item *new_item, *next;
struct cmdq_state *state;
struct client *c;
struct cmd *cmd;
const char *file;
char *cause;
int r;
if (is->have_last && is->last == CMD_RETURN_ERROR)
cmd_invoke_skip_sequence(is);
else
is->have_last = 0;
for (;;) {
node = cmd_invoke_next(is);
if (node == NULL)
return (CMD_RETURN_NORMAL);
cmd_parse_log_node(__func__, node);
tree = cmd_invoke_tree(is);
switch (cmd_parse_node_type(node)) {
case CMD_PARSE_ROOT:
case CMD_PARSE_SEQUENCE:
first = cmd_parse_node_first_child(node);
cmd_invoke_push(is, tree, node, first, NULL);
break;
case CMD_PARSE_ASSIGN:
case CMD_PARSE_HIDDEN_ASSIGN:
if (cmd_invoke_assignment(item, is, node) == 0)
break;
cmd_invoke_error(item, is, node, "bad assignment");
cmd_invoke_skip_sequence(is);
break;
case CMD_PARSE_IF:
if (cmd_invoke_if(item, is, node) == 0)
break;
cmd_invoke_error(item, is, node, "bad condition");
cmd_invoke_skip_sequence(is);
break;
case CMD_PARSE_ELIF:
case CMD_PARSE_ELSE:
break;
case CMD_PARSE_COMMAND:
r = cmd_parse_expand_alias(tree, node, &alias, &cause);
if (r == 1) {
node = cmd_parse_root(alias);
first = cmd_parse_node_first_child(node);
cmd_invoke_push(is, alias, node, first, NULL);
cmd_parse_free(alias);
break;
}
if (r == -1) {
c = cmdq_get_client(item);
file = cmd_parse_file(tree);
if (c != NULL &&
(~c->flags & CLIENT_CONTROL || file == NULL))
cmdq_error(item, "%s", cause);
else
cfg_add_cause("%s", cause);
free(cause);
cmd_invoke_skip_sequence(is);
break;
}
cmd = cmd_invoke_build_command(item, is, node);
if (cmd == NULL) {
cmd_invoke_skip_sequence(is);
break;
}
if (cmd_invoke_read_only(item, cmd) != 0) {
cmd_free(cmd);
cmd_invoke_skip_sequence(is);
break;
}
/*
* Queue one command followed by this walker. WAIT and
* command-inserted items therefore run before resume.
*/
state = cmdq_get_state(item);
new_item = cmdq_get_one_command(cmd, state, is);
next = cmdq_get_invoke(is, state);
cmdq_insert_after(item, next);
cmdq_insert_after(item, new_item);
return (CMD_RETURN_NORMAL);
case CMD_PARSE_STRING:
case CMD_PARSE_COMMANDS:
case CMD_PARSE_TEXT:
case CMD_PARSE_ENVIRONMENT:
case CMD_PARSE_TILDE:
fatalx("unexpected node type");
}
}
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-join-pane.c,v 1.74 2026/08/03 20:29:52 nicm Exp $ */
/* /*
* Copyright (c) 2011 George Nachman <tmux@georgester.com> * Copyright (c) 2011 George Nachman <tmux@georgester.com>
@@ -30,6 +30,9 @@
*/ */
static enum cmd_retval cmd_join_pane_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_join_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_join_pane_mouse_update(struct cmdq_item *);
static void cmd_join_pane_mouse_move(struct client *,
struct mouse_event *);
const struct cmd_entry cmd_join_pane_entry = { const struct cmd_entry cmd_join_pane_entry = {
.name = "join-pane", .name = "join-pane",
@@ -49,8 +52,10 @@ const struct cmd_entry cmd_move_pane_entry = {
.name = "move-pane", .name = "move-pane",
.alias = "movep", .alias = "movep",
.args = { "bdfhvp:l:s:t:", 0, 0, NULL }, .args = { "bdD::fhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL },
.usage = "[-bdfhv] [-l size] " CMD_SRCDST_PANE_USAGE, .usage = "[-bdfhMv] [-D lines] [-l size] [-L columns] [-P position] "
"[-R columns] " CMD_SRCDST_PANE_USAGE " [-U lines] "
"[-X x-position] [-Y y-position] [-z z-index]",
.source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED }, .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED },
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -59,6 +64,348 @@ const struct cmd_entry cmd_move_pane_entry = {
.exec = cmd_join_pane_exec .exec = cmd_join_pane_exec
}; };
static enum cmd_retval
cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
struct window_pane *wp, const char *position)
{
struct window *w = wl->window;
struct layout_cell *lc = wp->layout_cell;
struct window_pane *owp;
int wx = w->sx, wy = w->sy;
int px = lc->g.sx, py = lc->g.sy;
int xoff = lc->g.xoff, yoff = lc->g.yoff;
int border = 1;
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
border = 0;
if (strcmp(position, "top-left") == 0) {
xoff = border;
yoff = border;
} else if (strcmp(position, "top-centre") == 0 ||
strcmp(position, "top-center") == 0) {
xoff = (wx - px) / 2;
yoff = border;
} else if (strcmp(position, "top-right") == 0) {
xoff = wx - px - border;
yoff = border;
} else if (strcmp(position, "centre-left") == 0 ||
strcmp(position, "center-left") == 0) {
xoff = border;
yoff = (wy - py) / 2;
} else if (strcmp(position, "centre") == 0 ||
strcmp(position, "center") == 0) {
xoff = (wx - px) / 2;
yoff = (wy - py) / 2;
} else if (strcmp(position, "centre-right") == 0 ||
strcmp(position, "center-right") == 0) {
xoff = wx - px - border;
yoff = (wy - py) / 2;
} else if (strcmp(position, "bottom-left") == 0) {
xoff = border;
yoff = wy - py - border;
} else if (strcmp(position, "bottom-centre") == 0 ||
strcmp(position, "bottom-center") == 0) {
xoff = (wx - px) / 2;
yoff = wy - py - border;
} else if (strcmp(position, "bottom-right") == 0) {
xoff = wx - px - border;
yoff = wy - py - border;
} else if (strcmp(position, "top-left-centre") == 0 ||
strcmp(position, "top-left-center") == 0) {
xoff = wx / 4 - px / 2;
yoff = wy / 4 - py / 2;
} else if (strcmp(position, "top-right-centre") == 0 ||
strcmp(position, "top-right-center") == 0) {
xoff = (3 * wx) / 4 - px / 2;
yoff = wy / 4 - py / 2;
} else if (strcmp(position, "bottom-left-centre") == 0 ||
strcmp(position, "bottom-left-center") == 0) {
xoff = wx / 4 - px / 2;
yoff = (3 * wy) / 4 - py / 2;
} else if (strcmp(position, "bottom-right-centre") == 0 ||
strcmp(position, "bottom-right-center") == 0) {
xoff = (3 * wx) / 4 - px / 2;
yoff = (3 * wy) / 4 - py / 2;
} else if (strcmp(position, "front") == 0) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
} 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))
break;
}
if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry);
else
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
} else if (strcmp(position, "forward") == 0) {
owp = TAILQ_PREV(wp, 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);
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);
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))
break;
}
if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry);
else
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
}
} else if (strcmp(position, "backward-loop") == 0) {
owp = TAILQ_NEXT(wp, 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 {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
}
} else {
cmdq_error(item, "unknown position: %s", position);
return (CMD_RETURN_ERROR);
}
if (xoff != lc->g.xoff || yoff != lc->g.yoff) {
lc->g.xoff = xoff;
lc->g.yoff = yoff;
layout_fix_panes(w, NULL);
}
redraw_invalidate_scene(w);
events_fire_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_join_pane_move(struct cmdq_item *item, struct args *args,
struct winlink *wl, struct window_pane *wp)
{
struct window *w = wl->window;
struct layout_cell *lc = wp->layout_cell;
const char *errstr, *argval;
const char flags[] = { 'U', 'D', 'L', 'R' };
char *cause = NULL, flag;
int xoff = lc->g.xoff, yoff = lc->g.yoff, adjust;
u_int i;
enum pane_lines lines = window_pane_get_pane_lines(wp);
if (args_has(args, 'X')) {
xoff = args_percentage_and_expand(args, 'X', -(int)w->sx,
w->sx, w->sx, item, &cause);
if (cause != NULL) {
cmdq_error(item, "position %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
if (lines != PANE_LINES_NONE)
xoff += 1;
}
if (args_has(args, 'Y')) {
yoff = args_percentage_and_expand(args, 'Y', -(int)w->sy,
w->sy, w->sy, item, &cause);
if (cause != NULL) {
cmdq_error(item, "position %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
if (lines != PANE_LINES_NONE)
yoff += 1;
}
for (i = 0; i < nitems(flags); i++) {
flag = flags[i];
if (!args_has(args, flag))
continue;
argval = args_get(args, flag);
if (argval == NULL)
argval = "1";
adjust = strtonum(argval, INT_MIN, INT_MAX, &errstr);
if (errstr != NULL) {
cmdq_error(item, "offset %s", errstr);
return (CMD_RETURN_ERROR);
}
if (flag == 'U')
yoff -= adjust;
else if (flag == 'D')
yoff += adjust;
else if (flag == 'L')
xoff -= adjust;
else
xoff += adjust;
}
if (xoff != lc->g.xoff || yoff != lc->g.yoff) {
lc->g.xoff = xoff;
lc->g.yoff = yoff;
layout_fix_panes(w, NULL);
events_fire_window("window-layout-changed", w);
server_redraw_window(w);
}
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_join_pane_mouse_update(struct cmdq_item *item)
{
struct cmd_find_state *target = cmdq_get_target(item);
struct key_event *event = cmdq_get_event(item);
struct client *c = cmdq_get_client(item);
struct session *s = target->s;
struct winlink *wl;
struct window *w;
struct window_pane *wp;
if (!event->m.valid)
return (CMD_RETURN_NORMAL);
wp = cmd_mouse_pane(&event->m, &s, &wl);
if (wp == NULL || c == NULL || c->session != s)
return (CMD_RETURN_NORMAL);
if (!window_pane_is_floating(wp))
return (CMD_RETURN_NORMAL);
w = wl->window;
window_redraw_active_switch(w, wp);
window_set_active_pane(w, wp, 1);
c->tty.mouse_drag_update = cmd_join_pane_mouse_move;
cmd_join_pane_mouse_move(c, &event->m);
return (CMD_RETURN_NORMAL);
}
static void
cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
{
struct winlink *wl;
struct window *w;
struct window_pane *wp;
struct layout_cell *lc;
int y, ly, x, lx;
wp = cmd_mouse_pane(m, NULL, &wl);
if (wp == NULL) {
c->tty.mouse_drag_update = NULL;
return;
}
w = wl->window;
lc = wp->layout_cell;
y = m->y + m->oy; x = m->x + m->ox;
if (m->statusat == 0 && y >= (int)m->statuslines)
y -= m->statuslines;
else if (m->statusat > 0 && y >= m->statusat)
y = m->statusat - 1;
ly = m->ly + m->oy; lx = m->lx + m->ox;
if (m->statusat == 0 && ly >= (int)m->statuslines)
ly -= m->statuslines;
else if (m->statusat > 0 && ly >= m->statusat)
ly = m->statusat - 1;
if (x != lx || y != ly) {
lc->g.xoff += x - lx;
lc->g.yoff += y - ly;
layout_fix_panes(w, NULL);
server_redraw_window(w);
server_redraw_window_borders(w);
}
}
static enum cmd_retval
cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl,
struct window_pane *wp, const char *s)
{
struct window *w = wl->window;
struct window_pane *owp;
const char *errstr;
u_int n, z;
z = strtonum(s, 0, UINT_MAX, &errstr);
if (errstr != NULL) {
cmdq_error(item, "z-index %s", errstr);
return (CMD_RETURN_ERROR);
}
TAILQ_REMOVE(&w->z_index, wp, zentry);
n = 0;
TAILQ_FOREACH(owp, &w->z_index, zentry) {
if (!window_pane_is_floating(owp))
break;
if (n >= z)
break;
n++;
}
if (owp != NULL)
TAILQ_INSERT_BEFORE(owp, wp, zentry);
else
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
redraw_invalidate_scene(w);
events_fire_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_join_pane_tile(struct cmdq_item *item, struct args *args, struct window *w,
struct window_pane *wp)
{
struct layout_cell *lc = wp->layout_cell;
if (!window_pane_is_floating(wp)) {
cmdq_error(item, "pane is not floating");
return (CMD_RETURN_ERROR);
}
if (w->flags & WINDOW_ZOOMED) {
cmdq_error(item, "can't tile a pane while window is zoomed");
return (CMD_RETURN_ERROR);
}
lc->fg.sx = lc->g.sx;
lc->fg.sy = lc->g.sy;
lc->fg.xoff = lc->g.xoff;
lc->fg.yoff = lc->g.yoff;
if (layout_insert_tile(w, lc) != 0) {
cmdq_error(item, "no space for a new pane");
return (CMD_RETURN_ERROR);
}
lc->flags &= ~LAYOUT_CELL_FLOATING;
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
if (!args_has(args, 'd'))
window_set_active_pane(w, wp, 1);
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
redraw_invalidate_scene(w);
events_fire_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval static enum cmd_retval
cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
{ {
@@ -70,74 +417,71 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
struct winlink *src_wl, *dst_wl; struct winlink *src_wl, *dst_wl;
struct window *src_w, *dst_w; struct window *src_w, *dst_w;
struct window_pane *src_wp, *dst_wp; struct window_pane *src_wp, *dst_wp;
const char *s;
char *cause = NULL; char *cause = NULL;
int size, dst_idx; int flags = 0, dst_idx;
int flags;
enum layout_type type;
struct layout_cell *lc; struct layout_cell *lc;
u_int curval = 0;
dst_s = target->s; dst_s = target->s;
dst_wl = target->wl; dst_wl = target->wl;
dst_wp = target->wp; dst_wp = target->wp;
dst_w = dst_wl->window; dst_w = dst_wl->window;
dst_idx = dst_wl->idx; dst_idx = dst_wl->idx;
if (cmd_get_entry(self) == &cmd_move_pane_entry) {
if (args_has(args, 'M'))
return (cmd_join_pane_mouse_update(item));
if (args_has(args, 'P') ||
args_has(args, 'z') ||
args_has(args, 'X') ||
args_has(args, 'Y') ||
args_has(args, 'U') ||
args_has(args, 'D') ||
args_has(args, 'L') ||
args_has(args, 'R')) {
if (!window_pane_is_floating(dst_wp)) {
cmdq_error(item, "pane is not floating");
return (CMD_RETURN_ERROR);
}
server_unzoom_window(dst_w); 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)
return (cmd_join_pane_zindex(item, dst_wl, dst_wp, s));
return (cmd_join_pane_move(item, args, dst_wl, dst_wp));
}
}
src_wl = source->wl; src_wl = source->wl;
src_wp = source->wp; src_wp = source->wp;
src_w = src_wl->window; src_w = src_wl->window;
if (src_wp == src_w->modal || dst_wp == dst_w->modal) {
cmdq_error(item, "pane is modal");
return (CMD_RETURN_ERROR);
}
server_unzoom_window(dst_w);
server_unzoom_window(src_w); server_unzoom_window(src_w);
if (src_wp == dst_wp) { if (src_wp == dst_wp) {
if (window_pane_is_floating(src_wp))
return (cmd_join_pane_tile(item, args, src_w, src_wp));
cmdq_error(item, "source and target panes must be different"); cmdq_error(item, "source and target panes must be different");
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
type = LAYOUT_TOPBOTTOM;
if (args_has(args, 'h')) if (args_has(args, 'h'))
type = LAYOUT_LEFTRIGHT; flags |= SPAWN_HORIZONTAL;
/* If the 'p' flag is dropped then this bit can be moved into 'l'. */
if (args_has(args, 'l') || args_has(args, 'p')) {
if (args_has(args, 'f')) {
if (type == LAYOUT_TOPBOTTOM)
curval = dst_w->sy;
else
curval = dst_w->sx;
} else {
if (type == LAYOUT_TOPBOTTOM)
curval = dst_wp->sy;
else
curval = dst_wp->sx;
}
}
size = -1;
if (args_has(args, 'l')) {
size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval,
item, &cause);
} else if (args_has(args, 'p')) {
size = args_strtonum_and_expand(args, 'l', 0, 100, item,
&cause);
if (cause == NULL)
size = curval * size / 100;
}
if (cause != NULL) {
cmdq_error(item, "size %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
flags = 0;
if (args_has(args, 'b')) if (args_has(args, 'b'))
flags |= SPAWN_BEFORE; flags |= SPAWN_BEFORE;
if (args_has(args, 'f')) if (args_has(args, 'f'))
flags |= SPAWN_FULLSIZE; flags |= SPAWN_FULLSIZE;
lc = layout_split_pane(dst_wp, type, size, flags); lc = layout_get_tiled_cell(item, args, dst_w, dst_wp, flags, &cause);
if (lc == NULL) { if (cause != NULL) {
cmdq_error(item, "create pane failed: pane too small"); cmdq_error(item, "size or position %s", cause);
free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -146,14 +490,18 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
server_client_remove_pane(src_wp); server_client_remove_pane(src_wp);
window_lost_pane(src_w, src_wp); window_lost_pane(src_w, src_wp);
TAILQ_REMOVE(&src_w->panes, src_wp, entry); TAILQ_REMOVE(&src_w->panes, src_wp, entry);
TAILQ_REMOVE(&src_w->z_index, src_wp, zentry);
src_wp->window = dst_w; src_wp->window = dst_w;
options_set_parent(src_wp->options, dst_w->options); options_set_parent(src_wp->options, dst_w->options);
src_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); src_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED);
if (flags & SPAWN_BEFORE) if (flags & SPAWN_BEFORE) {
TAILQ_INSERT_BEFORE(dst_wp, src_wp, entry); TAILQ_INSERT_BEFORE(dst_wp, src_wp, entry);
else TAILQ_INSERT_BEFORE(dst_wp, src_wp, zentry);
} else {
TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry); TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry);
TAILQ_INSERT_AFTER(&dst_w->z_index, dst_wp, src_wp, zentry);
}
layout_assign_pane(lc, src_wp, 0); layout_assign_pane(lc, src_wp, 0);
colour_palette_from_option(&src_wp->palette, src_wp->options); colour_palette_from_option(&src_wp->palette, src_wp->options);
@@ -170,11 +518,12 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
} else } else
server_status_session(dst_s); server_status_session(dst_s);
if (window_count_panes(src_w) == 0) window_fire_pane_moved(src_wp, src_w, src_wl->idx, dst_w, dst_idx);
if (window_count_panes(src_w, 1) == 0)
server_kill_window(src_w, 1); server_kill_window(src_w, 1);
else else
notify_window("window-layout-changed", src_w); events_fire_window("window-layout-changed", src_w);
notify_window("window-layout-changed", dst_w); events_fire_window("window-layout-changed", dst_w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-kill-pane.c,v 1.34 2026/06/09 21:22:22 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -27,13 +27,17 @@
*/ */
static enum cmd_retval cmd_kill_pane_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_kill_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_kill_pane_all(struct cmdq_item *, const char *);
static int cmd_kill_pane_filter(struct cmdq_item *,
struct session *, struct winlink *,
struct window_pane *, const char *);
const struct cmd_entry cmd_kill_pane_entry = { const struct cmd_entry cmd_kill_pane_entry = {
.name = "kill-pane", .name = "kill-pane",
.alias = "killp", .alias = "killp",
.args = { "at:", 0, 0, NULL }, .args = { "af:t:", 0, 0, NULL },
.usage = "[-a] " CMD_TARGET_PANE_USAGE, .usage = "[-a] [-f filter] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -46,14 +50,40 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct winlink *wl = target->wl; struct window_pane *wp = target->wp;
struct window_pane *loopwp, *tmpwp, *wp = target->wp; const char *filter = args_get(args, 'f');
if (filter != NULL && !args_has(args, 'a')) {
cmdq_error(item, "-f only valid with -a");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'a'))
return (cmd_kill_pane_all(item, filter));
if (wp == NULL) {
cmdq_error(item, "no active pane to kill");
return (CMD_RETURN_ERROR);
}
server_kill_pane(wp);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_kill_pane_all(struct cmdq_item *item, const char *filter)
{
struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s;
struct winlink *wl = target->wl;
struct window_pane *wp = target->wp;
struct window_pane *loopwp, *tmpwp;
if (args_has(args, 'a')) {
server_unzoom_window(wl->window); server_unzoom_window(wl->window);
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) { TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
if (loopwp == wp) if (loopwp == wp)
continue; continue;
if (!cmd_kill_pane_filter(item, s, wl, loopwp, filter))
continue;
server_client_remove_pane(loopwp); server_client_remove_pane(loopwp);
layout_close_pane(loopwp); layout_close_pane(loopwp);
window_remove_pane(wl->window, loopwp); window_remove_pane(wl->window, loopwp);
@@ -62,6 +92,24 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
server_kill_pane(wp); static int
return (CMD_RETURN_NORMAL); cmd_kill_pane_filter(struct cmdq_item *item, struct session *s,
struct winlink *wl, struct window_pane *wp, const char *filter)
{
struct format_tree *ft;
char *expanded;
int flag;
if (filter == NULL)
return (1);
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, s, wl, wp);
expanded = format_expand(ft, filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
return (flag);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-kill-server.c,v 1.20 2021/08/21 10:22:39 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-kill-session.c,v 1.31 2026/06/09 12:57:40 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -18,6 +18,8 @@
#include <sys/types.h> #include <sys/types.h>
#include <stdlib.h>
#include "tmux.h" #include "tmux.h"
/* /*
@@ -28,13 +30,16 @@
*/ */
static enum cmd_retval cmd_kill_session_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_kill_session_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_kill_session_all(struct cmdq_item *, const char *);
static int cmd_kill_session_filter(struct cmdq_item *,
struct session *, const char *);
const struct cmd_entry cmd_kill_session_entry = { const struct cmd_entry cmd_kill_session_entry = {
.name = "kill-session", .name = "kill-session",
.alias = NULL, .alias = NULL,
.args = { "aCt:", 0, 0, NULL }, .args = { "aCgf:t:", 0, 0, NULL },
.usage = "[-aC] " CMD_TARGET_SESSION_USAGE, .usage = "[-aCg] [-f filter] " CMD_TARGET_SESSION_USAGE,
.target = { 't', CMD_FIND_SESSION, 0 }, .target = { 't', CMD_FIND_SESSION, 0 },
@@ -48,7 +53,14 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s, *sloop, *stmp; struct session *s = target->s, *sloop, *stmp;
struct session_group *sg;
struct winlink *wl; struct winlink *wl;
const char *filter = args_get(args, 'f');
if (filter != NULL && (!args_has(args, 'a') || args_has(args, 'C'))) {
cmdq_error(item, "-f only valid with -a");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'C')) { if (args_has(args, 'C')) {
RB_FOREACH(wl, winlinks, &s->windows) { RB_FOREACH(wl, winlinks, &s->windows) {
@@ -56,16 +68,56 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
wl->flags &= ~WINLINK_ALERTFLAGS; wl->flags &= ~WINLINK_ALERTFLAGS;
} }
server_redraw_session(s); server_redraw_session(s);
} else if (args_has(args, 'a')) { } else if (args_has(args, 'a'))
RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) { return (cmd_kill_session_all(item, filter));
if (sloop != s) { else if (args_has(args, 'g') &&
(sg = session_group_contains(s)) != NULL) {
TAILQ_FOREACH_SAFE(sloop, &sg->sessions, gentry, stmp) {
server_destroy_session(sloop); server_destroy_session(sloop);
session_destroy(sloop, 1, __func__); session_destroy(sloop, 1, __func__);
} }
}
} else { } else {
server_destroy_session(s); server_destroy_session(s);
session_destroy(s, 1, __func__); session_destroy(s, 1, __func__);
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
static enum cmd_retval
cmd_kill_session_all(struct cmdq_item *item, const char *filter)
{
struct session *s = cmdq_get_target(item)->s;
struct session *sloop, *stmp;
RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) {
if (sloop == s)
continue;
if (!cmd_kill_session_filter(item, sloop, filter))
continue;
server_destroy_session(sloop);
session_destroy(sloop, 1, __func__);
}
return (CMD_RETURN_NORMAL);
}
static int
cmd_kill_session_filter(struct cmdq_item *item, struct session *s,
const char *filter)
{
struct format_tree *ft;
char *expanded;
int flag;
if (filter == NULL)
return (1);
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, s, NULL, NULL);
expanded = format_expand(ft, filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
return (flag);
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-kill-window.c,v 1.30 2026/06/09 12:57:40 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -18,6 +18,8 @@
#include <sys/types.h> #include <sys/types.h>
#include <stdlib.h>
#include "tmux.h" #include "tmux.h"
/* /*
@@ -25,13 +27,16 @@
*/ */
static enum cmd_retval cmd_kill_window_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_kill_window_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_kill_window_all(struct cmdq_item *, const char *);
static int cmd_kill_window_filter(struct cmdq_item *,
struct session *, struct winlink *, const char *);
const struct cmd_entry cmd_kill_window_entry = { const struct cmd_entry cmd_kill_window_entry = {
.name = "kill-window", .name = "kill-window",
.alias = "killw", .alias = "killw",
.args = { "at:", 0, 0, NULL }, .args = { "af:t:", 0, 0, NULL },
.usage = "[-a] " CMD_TARGET_WINDOW_USAGE, .usage = "[-a] [-f filter] " CMD_TARGET_WINDOW_USAGE,
.target = { 't', CMD_FIND_WINDOW, 0 }, .target = { 't', CMD_FIND_WINDOW, 0 },
@@ -57,10 +62,15 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct winlink *wl = target->wl, *loop; struct winlink *wl = target->wl;
struct window *w = wl->window; struct window *w = wl->window;
struct session *s = target->s; struct session *s = target->s;
u_int found; const char *filter = args_get(args, 'f');
if (filter != NULL && !args_has(args, 'a')) {
cmdq_error(item, "-f only valid with -a");
return (CMD_RETURN_ERROR);
}
if (cmd_get_entry(self) == &cmd_unlink_window_entry) { if (cmd_get_entry(self) == &cmd_unlink_window_entry) {
if (!args_has(args, 'k') && !session_is_linked(s, w)) { if (!args_has(args, 'k') && !session_is_linked(s, w)) {
@@ -72,7 +82,22 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_has(args, 'a')) { if (args_has(args, 'a'))
return (cmd_kill_window_all(item, filter));
server_kill_window(wl->window, 1);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_kill_window_all(struct cmdq_item *item, const char *filter)
{
struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s;
struct winlink *wl = target->wl;
struct winlink *loop;
u_int found, kill_current;
if (RB_PREV(winlinks, &s->windows, wl) == NULL && if (RB_PREV(winlinks, &s->windows, wl) == NULL &&
RB_NEXT(winlinks, &s->windows, wl) == NULL) RB_NEXT(winlinks, &s->windows, wl) == NULL)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
@@ -81,7 +106,8 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
do { do {
found = 0; found = 0;
RB_FOREACH(loop, winlinks, &s->windows) { RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window != wl->window) { if (loop->window != wl->window &&
cmd_kill_window_filter(item, s, loop, filter)) {
server_kill_window(loop->window, 0); server_kill_window(loop->window, 0);
found++; found++;
break; break;
@@ -90,21 +116,42 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
} while (found != 0); } while (found != 0);
/* /*
* If the current window appears in the session more than once, * If the current window appears in the session more than once, kill it
* kill it as well. * as well if it matches the filter.
*/ */
found = 0; found = kill_current = 0;
RB_FOREACH(loop, winlinks, &s->windows) { RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window == wl->window) if (loop->window == wl->window) {
found++; found++;
if (cmd_kill_window_filter(item, s, loop, filter))
kill_current = 1;
} }
if (found > 1) }
if (kill_current && found > 1)
server_kill_window(wl->window, 0); server_kill_window(wl->window, 0);
server_renumber_all(); server_renumber_all();
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
server_kill_window(wl->window, 1); static int
return (CMD_RETURN_NORMAL); cmd_kill_window_filter(struct cmdq_item *item, struct session *s,
struct winlink *wl, const char *filter)
{
struct format_tree *ft;
char *expanded;
int flag;
if (filter == NULL)
return (1);
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, s, wl, NULL);
expanded = format_expand(ft, filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
return (flag);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-list-buffers.c,v 1.41 2026/02/27 08:25:12 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -36,8 +36,8 @@ const struct cmd_entry cmd_list_buffers_entry = {
.name = "list-buffers", .name = "list-buffers",
.alias = "lsb", .alias = "lsb",
.args = { "F:f:", 0, 0, NULL }, .args = { "F:f:O:r", 0, 0, NULL },
.usage = "[-F format] [-f filter]", .usage = "[-F format] [-f filter] [-O order]",
.flags = CMD_AFTERHOOK, .flags = CMD_AFTERHOOK,
.exec = cmd_list_buffers_exec .exec = cmd_list_buffers_exec
@@ -47,20 +47,29 @@ static enum cmd_retval
cmd_list_buffers_exec(struct cmd *self, struct cmdq_item *item) cmd_list_buffers_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct paste_buffer *pb; struct paste_buffer **l;
struct format_tree *ft; struct format_tree *ft;
const char *template, *filter; const char *template, *filter;
char *line, *expanded; char *line, *expanded;
int flag; int flag;
u_int i, n;
struct sort_criteria sort_crit;
if ((template = args_get(args, 'F')) == NULL) if ((template = args_get(args, 'F')) == NULL)
template = LIST_BUFFERS_TEMPLATE; template = LIST_BUFFERS_TEMPLATE;
filter = args_get(args, 'f'); filter = args_get(args, 'f');
pb = NULL; sort_crit.order = sort_order_from_string(args_get(args, 'O'));
while ((pb = paste_walk(pb)) != NULL) { if (sort_crit.order == SORT_END && args_has(args, 'O')) {
cmdq_error(item, "invalid sort order");
return (CMD_RETURN_ERROR);
}
sort_crit.reversed = args_has(args, 'r');
l = sort_get_buffers(&n, &sort_crit);
for (i = 0; i < n; i++) {
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults_paste_buffer(ft, pb); format_defaults_paste_buffer(ft, l[i]);
if (filter != NULL) { if (filter != NULL) {
expanded = format_expand(ft, filter); expanded = format_expand(ft, filter);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-list-clients.c,v 1.42 2026/02/27 08:25:12 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -41,8 +41,8 @@ const struct cmd_entry cmd_list_clients_entry = {
.name = "list-clients", .name = "list-clients",
.alias = "lsc", .alias = "lsc",
.args = { "F:f:t:", 0, 0, NULL }, .args = { "F:f:O:rt:", 0, 0, NULL },
.usage = "[-F format] [-f filter] " CMD_TARGET_SESSION_USAGE, .usage = "[-F format] [-f filter] [-O order]" CMD_TARGET_SESSION_USAGE,
.target = { 't', CMD_FIND_SESSION, 0 }, .target = { 't', CMD_FIND_SESSION, 0 },
@@ -55,13 +55,14 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct client *c; struct client **l;
struct session *s; struct session *s;
struct format_tree *ft; struct format_tree *ft;
const char *template, *filter; const char *template, *filter;
u_int idx; u_int i, n;
char *line, *expanded; char *line, *expanded;
int flag; int flag;
struct sort_criteria sort_crit;
if (args_has(args, 't')) if (args_has(args, 't'))
s = target->s; s = target->s;
@@ -72,14 +73,21 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item)
template = LIST_CLIENTS_TEMPLATE; template = LIST_CLIENTS_TEMPLATE;
filter = args_get(args, 'f'); filter = args_get(args, 'f');
idx = 0; sort_crit.order = sort_order_from_string(args_get(args, 'O'));
TAILQ_FOREACH(c, &clients, entry) { if (sort_crit.order == SORT_END && args_has(args, 'O')) {
if (c->session == NULL || (s != NULL && s != c->session)) cmdq_error(item, "invalid sort order");
return (CMD_RETURN_ERROR);
}
sort_crit.reversed = args_has(args, 'r');
l = sort_get_clients(&n, &sort_crit);
for (i = 0; i < n; i++) {
if (l[i]->session == NULL || (s != NULL && s != l[i]->session))
continue; continue;
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", idx); format_add(ft, "line", "%u", i);
format_defaults(ft, c, NULL, NULL, NULL); format_defaults(ft, l[i], NULL, NULL, NULL);
if (filter != NULL) { if (filter != NULL) {
expanded = format_expand(ft, filter); expanded = format_expand(ft, filter);
@@ -94,8 +102,6 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item)
} }
format_free(ft); format_free(ft);
idx++;
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);

107
cmd-list-commands.c Normal file
View File

@@ -0,0 +1,107 @@
/* $OpenBSD: cmd-list-commands.c,v 1.15 2026/02/24 08:22:13 nicm Exp $ */
/*
* Copyright (c) 2007 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 <stdlib.h>
#include <string.h>
#include "tmux.h"
/*
* List all commands.
*/
#define LIST_COMMANDS_TEMPLATE \
"#{command_list_name}" \
"#{?command_list_alias, (#{command_list_alias}),} " \
"#{command_list_usage}"
static enum cmd_retval cmd_list_commands(struct cmd *, struct cmdq_item *);
const struct cmd_entry cmd_list_commands_entry = {
.name = "list-commands",
.alias = "lscm",
.args = { "F:", 0, 1, NULL },
.usage = "[-F format] [command]",
.flags = CMD_STARTSERVER|CMD_AFTERHOOK,
.exec = cmd_list_commands
};
static void
cmd_list_single_command(const struct cmd_entry *entry, struct format_tree *ft,
const char *template, struct cmdq_item *item)
{
const char *s;
char *line;
format_add(ft, "command_list_name", "%s", entry->name);
if (entry->alias != NULL)
s = entry->alias;
else
s = "";
format_add(ft, "command_list_alias", "%s", s);
if (entry->usage != NULL)
s = entry->usage;
else
s = "";
format_add(ft, "command_list_usage", "%s", s);
line = format_expand(ft, template);
if (*line != '\0')
cmdq_print(item, "%s", line);
free(line);
}
static enum cmd_retval
cmd_list_commands(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
const struct cmd_entry **entryp;
const struct cmd_entry *entry;
struct format_tree *ft;
const char *template, *command;
char *cause;
if ((template = args_get(args, 'F')) == NULL)
template = LIST_COMMANDS_TEMPLATE;
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, NULL, NULL, NULL);
command = args_string(args, 0);
if (command == NULL) {
for (entryp = cmd_table; *entryp != NULL; entryp++)
cmd_list_single_command(*entryp, ft, template, item);
} else {
entry = cmd_find(command, &cause);
if (entry != NULL)
cmd_list_single_command(entry, ft, template, item);
else {
cmdq_error(item, "%s", cause);
free(cause);
format_free(ft);
return (CMD_RETURN_ERROR);
}
}
format_free(ft);
return (CMD_RETURN_NORMAL);
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-list-keys.c,v 1.78 2026/07/01 13:12:17 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -27,121 +27,149 @@
* List key bindings. * List key bindings.
*/ */
static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *); #define LIST_KEYS_TEMPLATE \
"#{?notes_only," \
"#{key_prefix} " \
"#{p|#{key_string_width}:key_string} " \
"#{?key_note,#{key_note},#{key_command}}" \
"," \
"bind-key #{?key_has_repeat,#{?key_repeat,-r, },} " \
"-T #{p|#{key_table_width}:key_table} " \
"#{p|#{key_string_width}:#{q|a:key_string}} " \
"#{key_command}}"
#define LIST_KEYS_TEMPLATE_PRETTY \
"#{?notes_only," \
"#{key_prefix} " \
"#{key_string} " \
"#{?key_note,#{key_note},#{key_command}}" \
"," \
"bind-key #{?key_repeat,-r ,}" \
"-T #{key_table} " \
"#{q|a:key_string} " \
"#{key_command}}"
static enum cmd_retval cmd_list_keys_commands(struct cmd *, static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *);
struct cmdq_item *);
const struct cmd_entry cmd_list_keys_entry = { const struct cmd_entry cmd_list_keys_entry = {
.name = "list-keys", .name = "list-keys",
.alias = "lsk", .alias = "lsk",
.args = { "1aNP:T:", 0, 1, NULL }, .args = { "1aF:NO:P:prT:", 0, 1, NULL },
.usage = "[-1aN] [-P prefix-string] [-T key-table] [key]", .usage = "[-1aNpr] [-F format] [-O order] [-P prefix-string]"
"[-T key-table] [key]",
.flags = CMD_STARTSERVER|CMD_AFTERHOOK, .flags = CMD_STARTSERVER|CMD_AFTERHOOK,
.exec = cmd_list_keys_exec .exec = cmd_list_keys_exec
}; };
const struct cmd_entry cmd_list_commands_entry = { static char *
.name = "list-commands", cmd_list_keys_get_prefix(struct args *args)
.alias = "lscm", {
key_code prefix;
.args = { "F:", 0, 1, NULL }, if (args_has(args, 'P'))
.usage = "[-F format] [command]", return (xstrdup(args_get(args, 'P')));
.flags = CMD_STARTSERVER|CMD_AFTERHOOK, prefix = options_get_number(global_s_options, "prefix");
.exec = cmd_list_keys_exec if (prefix == KEYC_NONE)
}; return (xstrdup(""));
return (xstrdup(key_string_lookup_key(prefix, 0)));
}
static u_int static u_int
cmd_list_keys_get_width(const char *tablename, key_code only) cmd_list_keys_get_width(struct key_binding **l, u_int n)
{ {
struct key_table *table; u_int i, width, keywidth = 0;
struct key_binding *bd;
u_int width, keywidth = 0;
table = key_bindings_get_table(tablename, 0); for (i = 0; i < n; i++) {
if (table == NULL) width = utf8_cstrwidth(key_string_lookup_key(l[i]->key, 0));
return (0);
bd = key_bindings_first(table);
while (bd != NULL) {
if ((only != KEYC_UNKNOWN && bd->key != only) ||
KEYC_IS_MOUSE(bd->key) ||
bd->note == NULL ||
*bd->note == '\0') {
bd = key_bindings_next(table, bd);
continue;
}
width = utf8_cstrwidth(key_string_lookup_key(bd->key, 0));
if (width > keywidth) if (width > keywidth)
keywidth = width; keywidth = width;
bd = key_bindings_next(table, bd);
} }
return (keywidth); return (keywidth);
} }
static int static u_int
cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, cmd_list_keys_get_table_width(struct key_binding **l, u_int n)
const char *tablename, u_int keywidth, key_code only, const char *prefix)
{ {
struct client *tc = cmdq_get_target_client(item); u_int i, width, tablewidth = 0;
struct key_table *table;
struct key_binding *bd;
const char *key;
char *tmp, *note;
int found = 0;
table = key_bindings_get_table(tablename, 0); for (i = 0; i < n; i++) {
if (table == NULL) width = utf8_cstrwidth(l[i]->tablename);
return (0); if (width > tablewidth)
bd = key_bindings_first(table); tablewidth = width;
while (bd != NULL) { }
if ((only != KEYC_UNKNOWN && bd->key != only) || return (tablewidth);
KEYC_IS_MOUSE(bd->key) || }
((bd->note == NULL || *bd->note == '\0') &&
!args_has(args, 'a'))) { static struct key_binding **
bd = key_bindings_next(table, bd); cmd_list_keys_get_root_and_prefix(u_int *n, struct sort_criteria *sort_crit)
{
const char *tables[] = { "prefix", "root" };
struct key_table *t;
struct key_binding **lt;
u_int i, ltsz, len = 0, offset = 0;
static struct key_binding **l = NULL;
static u_int lsz = 0;
for (i = 0; i < nitems(tables); i++) {
t = key_bindings_get_table(tables[i], 0);
lt = sort_get_key_bindings_table(t, &ltsz, sort_crit);
len += ltsz;
if (lsz <= len) {
lsz = len + 100;
l = xreallocarray(l, lsz, sizeof *l);
}
memcpy(l + offset, lt, ltsz * sizeof *l);
offset += ltsz;
}
*n = len;
return (l);
}
static void
cmd_list_keys_do_filter(int filter_notes, int filter_key, key_code only,
struct key_binding **l, u_int *n)
{
key_code key;
u_int i, j = 0;
for (i = 0; i < *n; i++) {
key = l[i]->key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS);
if (filter_key && only != key)
continue; continue;
if (filter_notes && l[i]->note == NULL)
continue;
l[j++] = l[i];
} }
found = 1; *n = j;
key = key_string_lookup_key(bd->key, 0);
if (bd->note == NULL || *bd->note == '\0')
note = cmd_list_print(bd->cmdlist, 1);
else
note = xstrdup(bd->note);
tmp = utf8_padcstr(key, keywidth + 1);
if (args_has(args, '1') && tc != NULL) {
status_message_set(tc, -1, 1, 0, 0, "%s%s%s", prefix,
tmp, note);
} else
cmdq_print(item, "%s%s%s", prefix, tmp, note);
free(tmp);
free(note);
if (args_has(args, '1'))
break;
bd = key_bindings_next(table, bd);
}
return (found);
} }
static char * static void
cmd_list_keys_get_prefix(struct args *args, key_code *prefix) cmd_list_keys_format_key_binding(struct format_tree *ft,
const struct key_binding *bd, const char *prefix, int flags)
{ {
char *s; char *s;
*prefix = options_get_number(global_s_options, "prefix"); if (bd->flags & KEY_BINDING_REPEAT)
if (!args_has(args, 'P')) { format_add(ft, "key_repeat", "1");
if (*prefix != KEYC_NONE)
xasprintf(&s, "%s ", key_string_lookup_key(*prefix, 0));
else else
s = xstrdup(""); format_add(ft, "key_repeat", "0");
} else
s = xstrdup(args_get(args, 'P')); if (bd->note != NULL)
return (s); format_add(ft, "key_note", "%s", bd->note);
else
format_add(ft, "key_note", "%s", "");
format_add(ft, "key_prefix", "%s", prefix);
format_add(ft, "key_table", "%s", bd->tablename);
format_add(ft, "key_string", "%s", key_string_lookup_key(bd->key, 0));
s = cmd_parse_print(bd->cmd, flags);
format_add(ft, "key_command", "%s", s);
free(s);
} }
static enum cmd_retval static enum cmd_retval
@@ -149,16 +177,17 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct client *tc = cmdq_get_target_client(item); struct client *tc = cmdq_get_target_client(item);
struct key_table *table; struct format_tree *ft;
struct key_binding *bd; struct key_table *table = NULL;
const char *tablename, *r, *keystr; struct key_binding **l;
char *key, *cp, *tmp, *start, *empty; key_code only = KEYC_UNKNOWN;
key_code prefix, only = KEYC_UNKNOWN; const char *template, *tablename, *keystr;
int repeat, width, tablewidth, keywidth, found = 0; char *line;
size_t tmpsize, tmpused, cplen; char *prefix = NULL;
u_int i, n;
if (cmd_get_entry(self) == &cmd_list_commands_entry) int single, notes_only, filter_notes, filter_key;
return (cmd_list_keys_commands(self, item)); int print_flags = 0;
struct sort_criteria sort_crit;
if ((keystr = args_string(args, 0)) != NULL) { if ((keystr = args_string(args, 0)) != NULL) {
only = key_string_lookup_string(keystr); only = key_string_lookup_string(keystr);
@@ -169,218 +198,75 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
only &= (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS); only &= (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS);
} }
tablename = args_get(args, 'T'); sort_crit.order = sort_order_from_string(args_get(args, 'O'));
if (tablename != NULL && key_bindings_get_table(tablename, 0) == NULL) { if (sort_crit.order == SORT_END && args_has(args, 'O')) {
cmdq_error(item, "invalid sort order");
return (CMD_RETURN_ERROR);
}
sort_crit.reversed = args_has(args, 'r');
if ((tablename = args_get(args, 'T')) != NULL) {
table = key_bindings_get_table(tablename, 0);
if (table == NULL) {
cmdq_error(item, "table %s doesn't exist", tablename); cmdq_error(item, "table %s doesn't exist", tablename);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (args_has(args, 'N')) {
if (tablename == NULL) {
start = cmd_list_keys_get_prefix(args, &prefix);
keywidth = cmd_list_keys_get_width("root", only);
if (prefix != KEYC_NONE) {
width = cmd_list_keys_get_width("prefix", only);
if (width == 0)
prefix = KEYC_NONE;
else if (width > keywidth)
keywidth = width;
}
empty = utf8_padcstr("", utf8_cstrwidth(start));
found = cmd_list_keys_print_notes(item, args, "root",
keywidth, only, empty);
if (prefix != KEYC_NONE) {
if (cmd_list_keys_print_notes(item, args,
"prefix", keywidth, only, start))
found = 1;
}
free(empty);
} else {
if (args_has(args, 'P'))
start = xstrdup(args_get(args, 'P'));
else
start = xstrdup("");
keywidth = cmd_list_keys_get_width(tablename, only);
found = cmd_list_keys_print_notes(item, args, tablename,
keywidth, only, start);
}
free(start);
goto out;
} }
repeat = 0; prefix = cmd_list_keys_get_prefix(args);
tablewidth = keywidth = 0; single = args_has(args, '1');
table = key_bindings_first_table(); notes_only = args_has(args, 'N');
while (table != NULL) { if (args_has(args, 'p'))
if (tablename != NULL && strcmp(table->name, tablename) != 0) { print_flags |= CMD_PARSE_PRINT_MULTILINE;
table = key_bindings_next_table(table);
continue;
}
bd = key_bindings_first(table);
while (bd != NULL) {
if (only != KEYC_UNKNOWN && bd->key != only) {
bd = key_bindings_next(table, bd);
continue;
}
key = args_escape(key_string_lookup_key(bd->key, 0));
if (bd->flags & KEY_BINDING_REPEAT)
repeat = 1;
width = utf8_cstrwidth(table->name);
if (width > tablewidth)
tablewidth = width;
width = utf8_cstrwidth(key);
if (width > keywidth)
keywidth = width;
free(key);
bd = key_bindings_next(table, bd);
}
table = key_bindings_next_table(table);
}
tmpsize = 256;
tmp = xmalloc(tmpsize);
table = key_bindings_first_table();
while (table != NULL) {
if (tablename != NULL && strcmp(table->name, tablename) != 0) {
table = key_bindings_next_table(table);
continue;
}
bd = key_bindings_first(table);
while (bd != NULL) {
if (only != KEYC_UNKNOWN && bd->key != only) {
bd = key_bindings_next(table, bd);
continue;
}
found = 1;
key = args_escape(key_string_lookup_key(bd->key, 0));
if (!repeat)
r = "";
else if (bd->flags & KEY_BINDING_REPEAT)
r = "-r ";
else
r = " ";
tmpused = xsnprintf(tmp, tmpsize, "%s-T ", r);
cp = utf8_padcstr(table->name, tablewidth);
cplen = strlen(cp) + 1;
while (tmpused + cplen + 1 >= tmpsize) {
tmpsize *= 2;
tmp = xrealloc(tmp, tmpsize);
}
strlcat(tmp, cp, tmpsize);
tmpused = strlcat(tmp, " ", tmpsize);
free(cp);
cp = utf8_padcstr(key, keywidth);
cplen = strlen(cp) + 1;
while (tmpused + cplen + 1 >= tmpsize) {
tmpsize *= 2;
tmp = xrealloc(tmp, tmpsize);
}
strlcat(tmp, cp, tmpsize);
tmpused = strlcat(tmp, " ", tmpsize);
free(cp);
cp = cmd_list_print(bd->cmdlist, 1);
cplen = strlen(cp);
while (tmpused + cplen + 1 >= tmpsize) {
tmpsize *= 2;
tmp = xrealloc(tmp, tmpsize);
}
strlcat(tmp, cp, tmpsize);
free(cp);
if (args_has(args, '1') && tc != NULL) {
status_message_set(tc, -1, 1, 0, 0,
"bind-key %s", tmp);
} else
cmdq_print(item, "bind-key %s", tmp);
free(key);
if (args_has(args, '1'))
break;
bd = key_bindings_next(table, bd);
}
table = key_bindings_next_table(table);
}
free(tmp);
out:
if (only != KEYC_UNKNOWN && !found) {
cmdq_error(item, "unknown key: %s", args_string(args, 0));
return (CMD_RETURN_ERROR);
}
return (CMD_RETURN_NORMAL);
}
static void
cmd_list_single_command(const struct cmd_entry *entry, struct format_tree *ft,
const char *template, struct cmdq_item *item)
{
const char *s;
char *line;
format_add(ft, "command_list_name", "%s", entry->name);
if (entry->alias != NULL)
s = entry->alias;
else
s = "";
format_add(ft, "command_list_alias", "%s", s);
if (entry->usage != NULL)
s = entry->usage;
else
s = "";
format_add(ft, "command_list_usage", "%s", s);
line = format_expand(ft, template);
if (*line != '\0')
cmdq_print(item, "%s", line);
free(line);
}
static enum cmd_retval
cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
const struct cmd_entry **entryp;
const struct cmd_entry *entry;
struct format_tree *ft;
const char *template, *command;
char *cause;
if ((template = args_get(args, 'F')) == NULL) { if ((template = args_get(args, 'F')) == NULL) {
template = "#{command_list_name}" if (print_flags & CMD_PARSE_PRINT_MULTILINE)
"#{?command_list_alias, (#{command_list_alias}),} " template = LIST_KEYS_TEMPLATE_PRETTY;
"#{command_list_usage}"; else
template = LIST_KEYS_TEMPLATE;
} }
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); if (table)
format_defaults(ft, NULL, NULL, NULL, NULL); l = sort_get_key_bindings_table(table, &n, &sort_crit);
else if (notes_only)
l = cmd_list_keys_get_root_and_prefix(&n, &sort_crit);
else
l = sort_get_key_bindings(&n, &sort_crit);
command = args_string(args, 0); filter_notes = notes_only && !args_has(args, 'a');
if (command == NULL) { filter_key = only != KEYC_UNKNOWN;
for (entryp = cmd_table; *entryp != NULL; entryp++) if (filter_notes || filter_key)
cmd_list_single_command(*entryp, ft, template, item); cmd_list_keys_do_filter(filter_notes, filter_key, only, l, &n);
} else { if (filter_key && n == 0) {
entry = cmd_find(command, &cause); cmdq_error(item, "unknown key: %s", keystr);
if (entry != NULL) free(prefix);
cmd_list_single_command(entry, ft, template, item);
else {
cmdq_error(item, "%s", cause);
free(cause);
format_free(ft);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
} if (single && n > 1)
n = 1;
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, tc, NULL, NULL, NULL);
format_add(ft, "notes_only", "%d", notes_only);
format_add(ft, "key_has_repeat", "%d", key_bindings_has_repeat(l, n));
format_add(ft, "key_string_width", "%u", cmd_list_keys_get_width(l, n));
format_add(ft, "key_table_width", "%u",
cmd_list_keys_get_table_width(l, n));
for (i = 0; i < n; i++) {
cmd_list_keys_format_key_binding(ft, l[i], prefix, print_flags);
line = format_expand(ft, template);
if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL))
status_message_set(tc, -1, 1, 0, 0, "%s", line);
else if (*line != '\0')
cmdq_print(item, "%s", line);
free(line);
if (single)
break;
}
format_free(ft); format_free(ft);
free(prefix);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-list-panes.c,v 1.40 2026/06/01 14:01:09 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -38,8 +38,9 @@ const struct cmd_entry cmd_list_panes_entry = {
.name = "list-panes", .name = "list-panes",
.alias = "lsp", .alias = "lsp",
.args = { "asF:f:t:", 0, 0, NULL }, .args = { "aF:f:O:rst:", 0, 0, NULL },
.usage = "[-as] [-F format] [-f filter] " CMD_TARGET_WINDOW_USAGE, .usage = "[-asr] [-F format] [-f filter] [-O order]"
CMD_TARGET_WINDOW_USAGE,
.target = { 't', CMD_FIND_WINDOW, 0 }, .target = { 't', CMD_FIND_WINDOW, 0 },
@@ -54,6 +55,13 @@ cmd_list_panes_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s; struct session *s = target->s;
struct winlink *wl = target->wl; struct winlink *wl = target->wl;
enum sort_order order;
order = sort_order_from_string(args_get(args, 'O'));
if (order == SORT_END && args_has(args, 'O')) {
cmdq_error(item, "invalid sort order");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'a')) if (args_has(args, 'a'))
cmd_list_panes_server(self, item); cmd_list_panes_server(self, item);
@@ -89,34 +97,41 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
struct cmdq_item *item, int type) struct cmdq_item *item, int type)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct window_pane *wp; struct window_pane *wp, **l;
u_int n; u_int i, n;
struct format_tree *ft; struct format_tree *ft;
const char *template, *filter; const char *template, *filter;
char *line, *expanded; char *line, *expanded;
int flag; int flag;
struct sort_criteria sort_crit;
template = args_get(args, 'F'); template = args_get(args, 'F');
if (template == NULL) { if (template == NULL) {
switch (type) { switch (type) {
case 0: case 0:
template = "#{pane_index}: " template = "#{pane_index}: "
"[#{pane_width}x#{pane_height}] [history " "[#{pane_width}x#{pane_height}"
"#{?pane_floating_flag, "
"#{pane_x}#,#{pane_y}#,#{pane_z}}] [history "
"#{history_size}/#{history_limit}, " "#{history_size}/#{history_limit}, "
"#{history_bytes} bytes] #{pane_id}" "#{history_bytes} bytes] #{pane_id}"
"#{?pane_active, (active),}#{?pane_dead, (dead),}"; "#{?pane_active, (active),}#{?pane_dead, (dead),}";
break; break;
case 1: case 1:
template = "#{window_index}.#{pane_index}: " template = "#{window_index}.#{pane_index}: "
"[#{pane_width}x#{pane_height}] [history " "[#{pane_width}x#{pane_height}"
"#{?pane_floating_flag, "
"#{pane_x}#,#{pane_y}#,#{pane_z}}] [history "
"#{history_size}/#{history_limit}, " "#{history_size}/#{history_limit}, "
"#{history_bytes} bytes] #{pane_id}" "#{history_bytes} bytes] #{pane_id}"
"#{?pane_active, (active),}#{?pane_dead, (dead),}"; "#{?pane_active, (active),}#{?pane_dead, (dead),}";
break; break;
case 2: case 2:
template = "#{session_name}:#{window_index}." template = "#{session_name}:#{window_index}."
"#{pane_index}: [#{pane_width}x#{pane_height}] " "#{pane_index}: [#{pane_width}x#{pane_height}"
"[history #{history_size}/#{history_limit}, " "#{?pane_floating_flag, "
"#{pane_x}#,#{pane_y}#,#{pane_z}}] [history "
"#{history_size}/#{history_limit}, "
"#{history_bytes} bytes] #{pane_id}" "#{history_bytes} bytes] #{pane_id}"
"#{?pane_active, (active),}#{?pane_dead, (dead),}"; "#{?pane_active, (active),}#{?pane_dead, (dead),}";
break; break;
@@ -124,8 +139,12 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
} }
filter = args_get(args, 'f'); filter = args_get(args, 'f');
n = 0; sort_crit.order = sort_order_from_string(args_get(args, 'O'));
TAILQ_FOREACH(wp, &wl->window->panes, entry) { sort_crit.reversed = args_has(args, 'r');
l = sort_get_panes_window(wl->window, &n, &sort_crit);
for (i = 0; i < n; i++) {
wp = l[i];
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", n); format_add(ft, "line", "%u", n);
format_defaults(ft, NULL, s, wl, wp); format_defaults(ft, NULL, s, wl, wp);
@@ -143,6 +162,5 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
} }
format_free(ft); format_free(ft);
n++;
} }
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-list-sessions.c,v 1.36 2026/02/27 08:25:12 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_list_sessions_entry = {
.name = "list-sessions", .name = "list-sessions",
.alias = "ls", .alias = "ls",
.args = { "F:f:", 0, 0, NULL }, .args = { "F:f:O:r", 0, 0, NULL },
.usage = "[-F format] [-f filter]", .usage = "[-r] [-F format] [-f filter] [-O order]",
.flags = CMD_AFTERHOOK, .flags = CMD_AFTERHOOK,
.exec = cmd_list_sessions_exec .exec = cmd_list_sessions_exec
@@ -53,22 +53,30 @@ static enum cmd_retval
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item) cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct session *s; struct session **l;
u_int n; u_int n, i;
struct format_tree *ft; struct format_tree *ft;
const char *template, *filter; const char *template, *filter;
char *line, *expanded; char *line, *expanded;
int flag; int flag;
struct sort_criteria sort_crit;
if ((template = args_get(args, 'F')) == NULL) if ((template = args_get(args, 'F')) == NULL)
template = LIST_SESSIONS_TEMPLATE; template = LIST_SESSIONS_TEMPLATE;
filter = args_get(args, 'f'); filter = args_get(args, 'f');
n = 0; sort_crit.order = sort_order_from_string(args_get(args, 'O'));
RB_FOREACH(s, sessions, &sessions) { if (sort_crit.order == SORT_END && args_has(args, 'O')) {
cmdq_error(item, "invalid sort order");
return (CMD_RETURN_ERROR);
}
sort_crit.reversed = args_has(args, 'r');
l = sort_get_sessions(&n, &sort_crit);
for (i = 0; i < n; i++) {
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", n); format_add(ft, "line", "%u", i);
format_defaults(ft, NULL, s, NULL, NULL); format_defaults(ft, NULL, l[i], NULL, NULL);
if (filter != NULL) { if (filter != NULL) {
expanded = format_expand(ft, filter); expanded = format_expand(ft, filter);
@@ -83,7 +91,6 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
} }
format_free(ft); format_free(ft);
n++;
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-list-windows.c,v 1.50 2026/02/27 08:25:12 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -41,16 +41,13 @@
static enum cmd_retval cmd_list_windows_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_list_windows_exec(struct cmd *, struct cmdq_item *);
static void cmd_list_windows_server(struct cmd *, struct cmdq_item *);
static void cmd_list_windows_session(struct cmd *, struct session *,
struct cmdq_item *, int);
const struct cmd_entry cmd_list_windows_entry = { const struct cmd_entry cmd_list_windows_entry = {
.name = "list-windows", .name = "list-windows",
.alias = "lsw", .alias = "lsw",
.args = { "F:f:at:", 0, 0, NULL }, .args = { "aF:f:O:rt:", 0, 0, NULL },
.usage = "[-a] [-F format] [-f filter] " CMD_TARGET_SESSION_USAGE, .usage = "[-ar] [-F format] [-f filter] [-O order]"
CMD_TARGET_SESSION_USAGE,
.target = { 't', CMD_FIND_SESSION, 0 }, .target = { 't', CMD_FIND_SESSION, 0 },
@@ -63,51 +60,38 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct winlink *wl, **l;
if (args_has(args, 'a'))
cmd_list_windows_server(self, item);
else
cmd_list_windows_session(self, target->s, item, 0);
return (CMD_RETURN_NORMAL);
}
static void
cmd_list_windows_server(struct cmd *self, struct cmdq_item *item)
{
struct session *s; struct session *s;
u_int i, n;
RB_FOREACH(s, sessions, &sessions)
cmd_list_windows_session(self, s, item, 1);
}
static void
cmd_list_windows_session(struct cmd *self, struct session *s,
struct cmdq_item *item, int type)
{
struct args *args = cmd_get_args(self);
struct winlink *wl;
u_int n;
struct format_tree *ft; struct format_tree *ft;
const char *template, *filter; const char *template, *filter;
char *line, *expanded; char *line, *expanded;
int flag; int flag;
struct sort_criteria sort_crit;
template = args_get(args, 'F'); template = args_get(args, 'F');
if (template == NULL) {
switch (type) {
case 0:
template = LIST_WINDOWS_TEMPLATE;
break;
case 1:
template = LIST_WINDOWS_WITH_SESSION_TEMPLATE;
break;
}
}
filter = args_get(args, 'f'); filter = args_get(args, 'f');
n = 0; sort_crit.order = sort_order_from_string(args_get(args, 'O'));
RB_FOREACH(wl, winlinks, &s->windows) { if (sort_crit.order == SORT_END && args_has(args, 'O')) {
cmdq_error(item, "invalid sort order");
return (CMD_RETURN_ERROR);
}
sort_crit.reversed = args_has(args, 'r');
if (args_has(args, 'a')) {
l = sort_get_winlinks(&n, &sort_crit);
if (template == NULL)
template = LIST_WINDOWS_WITH_SESSION_TEMPLATE;
} else {
l = sort_get_winlinks_session(target->s, &n, &sort_crit);
if (template == NULL)
template = LIST_WINDOWS_TEMPLATE;
}
for (i = 0; i < n; i++) {
wl = l[i];
s = wl->session;
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", n); format_add(ft, "line", "%u", n);
format_defaults(ft, NULL, s, wl, NULL); format_defaults(ft, NULL, s, wl, NULL);
@@ -125,6 +109,7 @@ cmd_list_windows_session(struct cmd *self, struct session *s,
} }
format_free(ft); format_free(ft);
n++;
} }
return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-load-buffer.c,v 1.66 2025/10/28 07:32:26 nicm Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-lock-server.c,v 1.31 2021/08/21 10:22:39 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-move-window.c,v 1.34 2021/08/21 10:22:39 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-new-session.c,v 1.152 2026/07/10 13:38:45 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -77,8 +77,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
struct termios tio, *tiop; struct termios tio, *tiop;
struct session_group *sg = NULL; struct session_group *sg = NULL;
const char *errstr, *template, *group, *tmp; const char *errstr, *template, *group, *tmp;
char *cause, *cwd = NULL, *cp, *newname = NULL; char *cause, *cwd = NULL, *cp, *ename;
char *name, *prefix = NULL; char *wname = NULL, *sname = NULL, *prefix = NULL;
int detached, already_attached, is_control = 0; int detached, already_attached, is_control = 0;
u_int sx, sy, dsx, dsy, count = args_count(args); u_int sx, sy, dsx, dsy, count = args_count(args);
struct spawn_context sc = { 0 }; struct spawn_context sc = { 0 };
@@ -99,32 +99,43 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
tmp = args_get(args, 's'); if ((tmp = args_get(args, 'n')) != NULL) {
if (tmp != NULL) { ename = format_single(item, tmp, c, NULL, NULL, NULL);
name = format_single(item, tmp, c, NULL, NULL, NULL); if (!check_name(ename)) {
newname = session_check_name(name); cmdq_error(item, "invalid window name: %s", ename);
if (newname == NULL) { free(ename);
cmdq_error(item, "invalid session: %s", name);
free(name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
free(name); wname = clean_name(ename, 0);
free(ename);
}
if ((tmp = args_get(args, 's')) != NULL) {
ename = format_single(item, tmp, c, NULL, NULL, NULL);
if (!check_name(ename)) {
cmdq_error(item, "invalid session name: %s", ename);
free(ename);
goto fail;
}
sname = clean_name(ename, 0);
free(ename);
} }
if (args_has(args, 'A')) { if (args_has(args, 'A')) {
if (newname != NULL) if (sname != NULL)
as = session_find(newname); as = session_find(sname);
else else
as = target->s; as = target->s;
if (as != NULL) { if (as != NULL) {
retval = cmd_attach_session(item, as->name, retval = cmd_attach_session(item, as->name,
args_has(args, 'D'), args_has(args, 'X'), 0, NULL, args_has(args, 'D'), args_has(args, 'X'), 0,
args_has(args, 'E'), args_get(args, 'f')); args_get(args, 'c'), args_has(args, 'E'),
free(newname); args_get(args, 'f'));
free(wname);
free(sname);
return (retval); return (retval);
} }
} }
if (newname != NULL && session_find(newname) != NULL) { if (sname != NULL && session_find(sname) != NULL) {
cmdq_error(item, "duplicate session: %s", newname); cmdq_error(item, "duplicate session: %s", sname);
goto fail; goto fail;
} }
@@ -141,12 +152,12 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
else if (groupwith != NULL) else if (groupwith != NULL)
prefix = xstrdup(groupwith->name); prefix = xstrdup(groupwith->name);
else { else {
prefix = session_check_name(group); if (!check_name(group)) {
if (prefix == NULL) { cmdq_error(item,
cmdq_error(item, "invalid session group: %s", "invalid session group name: %s", group);
group);
goto fail; goto fail;
} }
prefix = clean_name(group, 0);
} }
} }
@@ -275,7 +286,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
environ_put(env, av->string, 0); environ_put(env, av->string, 0);
av = args_next_value(av); av = args_next_value(av);
} }
s = session_create(prefix, newname, cwd, env, oo, tiop); s = session_create(prefix, sname, cwd, env, oo, tiop);
/* Spawn the initial window. */ /* Spawn the initial window. */
sc.item = item; sc.item = item;
@@ -283,7 +294,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
if (!detached) if (!detached)
sc.tc = c; sc.tc = c;
sc.name = args_get(args, 'n'); sc.name = wname;
args_to_vector(args, &sc.argc, &sc.argv); args_to_vector(args, &sc.argc, &sc.argv);
sc.idx = -1; sc.idx = -1;
@@ -314,7 +325,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
session_group_synchronize_to(s); session_group_synchronize_to(s);
session_select(s, RB_MIN(winlinks, &s->windows)->idx); session_select(s, RB_MIN(winlinks, &s->windows)->idx);
} }
notify_session("session-created", s); events_fire_session("session-created", s);
/* /*
* Set the client to the new session. If a command client exists, it is * Set the client to the new session. If a command client exists, it is
@@ -356,7 +367,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
if (sc.argv != NULL) if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv); cmd_free_argv(sc.argc, sc.argv);
free(cwd); free(cwd);
free(newname); free(wname);
free(sname);
free(prefix); free(prefix);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
@@ -364,7 +376,8 @@ fail:
if (sc.argv != NULL) if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv); cmd_free_argv(sc.argc, sc.argv);
free(cwd); free(cwd);
free(newname); free(wname);
free(sname);
free(prefix); free(prefix);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-new-window.c,v 1.103 2026/07/08 08:07:42 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -38,8 +38,8 @@ const struct cmd_entry cmd_new_window_entry = {
.name = "new-window", .name = "new-window",
.alias = "neww", .alias = "neww",
.args = { "abc:de:F:kn:PSt:", 0, -1, NULL }, .args = { "abc:de:EF:kn:PSt:", 0, -1, NULL },
.usage = "[-abdkPS] [-c start-directory] [-e environment] [-F format] " .usage = "[-abdEkPS] [-c start-directory] [-e environment] [-F format] "
"[-n window-name] " CMD_TARGET_WINDOW_USAGE "[-n window-name] " CMD_TARGET_WINDOW_USAGE
" [shell-command [argument ...]]", " [shell-command [argument ...]]",
@@ -60,19 +60,36 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
struct client *tc = cmdq_get_target_client(item); struct client *tc = cmdq_get_target_client(item);
struct session *s = target->s; struct session *s = target->s;
struct winlink *wl = target->wl, *new_wl = NULL; struct winlink *wl = target->wl, *new_wl = NULL;
int idx = target->idx, before; int idx = target->idx, before, count = args_count(args);
char *cause = NULL, *cp, *expanded; char *cause = NULL, *cp, *expanded, *wname = NULL;
const char *template, *name; const char *template, *name;
struct cmd_find_state fs; struct cmd_find_state fs;
struct args_value *av; struct args_value *av;
if (args_has(args, 'E') &&
count != 0 &&
(count != 1 || *args_string(args, 0) != '\0')) {
cmdq_error(item, "command cannot be given for empty pane");
return (CMD_RETURN_ERROR);
}
/* /*
* If -S and -n are given and -t is not and a single window with this * If -S and -n are given and -t is not and a single window with this
* name already exists, select it. * name already exists, select it.
*/ */
name = args_get(args, 'n'); name = args_get(args, 'n');
if (args_has(args, 'S') && name != NULL && target->idx == -1) { if (name != NULL) {
expanded = format_single(item, name, c, s, NULL, NULL); expanded = format_single(item, name, c, s, NULL, NULL);
if (!check_name(expanded)) {
cmdq_error(item, "invalid window name: %s", expanded);
free(expanded);
return (CMD_RETURN_ERROR);
}
wname = clean_name(expanded, 0);
free(expanded);
}
if (args_has(args, 'S') && wname != NULL && target->idx == -1) {
expanded = format_single(item, wname, c, s, NULL, NULL);
RB_FOREACH(wl, winlinks, &s->windows) { RB_FOREACH(wl, winlinks, &s->windows) {
if (strcmp(wl->window->name, expanded) != 0) if (strcmp(wl->window->name, expanded) != 0)
continue; continue;
@@ -80,12 +97,14 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
new_wl = wl; new_wl = wl;
continue; continue;
} }
cmdq_error(item, "multiple windows named %s", name); cmdq_error(item, "multiple windows named %s", wname);
free(wname);
free(expanded); free(expanded);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
free(expanded); free(expanded);
if (new_wl != NULL) { if (new_wl != NULL) {
free(wname);
if (args_has(args, 'd')) if (args_has(args, 'd'))
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (session_set_current(s, new_wl) == 0) if (session_set_current(s, new_wl) == 0)
@@ -108,7 +127,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
sc.s = s; sc.s = s;
sc.tc = tc; sc.tc = tc;
sc.name = args_get(args, 'n'); sc.name = wname;
args_to_vector(args, &sc.argc, &sc.argv); args_to_vector(args, &sc.argc, &sc.argv);
sc.environ = environ_create(); sc.environ = environ_create();
@@ -122,6 +141,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
sc.cwd = args_get(args, 'c'); sc.cwd = args_get(args, 'c');
sc.flags = 0; sc.flags = 0;
if (args_has(args, 'E') || (count == 1 && *args_string(args, 0) == '\0'))
sc.flags |= SPAWN_EMPTY;
if (args_has(args, 'd')) if (args_has(args, 'd'))
sc.flags |= SPAWN_DETACHED; sc.flags |= SPAWN_DETACHED;
if (args_has(args, 'k')) if (args_has(args, 'k'))
@@ -130,10 +151,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
if ((new_wl = spawn_window(&sc, &cause)) == NULL) { if ((new_wl = spawn_window(&sc, &cause)) == NULL) {
cmdq_error(item, "create window failed: %s", cause); cmdq_error(item, "create window failed: %s", cause);
free(cause); free(cause);
if (sc.argv != NULL) goto fail;
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
return (CMD_RETURN_ERROR);
} }
if (!args_has(args, 'd') || new_wl == s->curw) { if (!args_has(args, 'd') || new_wl == s->curw) {
cmd_find_from_winlink(current, new_wl, 0); cmd_find_from_winlink(current, new_wl, 0);
@@ -156,5 +174,13 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
if (sc.argv != NULL) if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv); cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ); environ_free(sc.environ);
free(wname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
fail:
if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
free(wname);
return (CMD_RETURN_ERROR);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-paste-buffer.c,v 1.44 2026/03/04 07:19:32 tb Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -33,8 +33,8 @@ const struct cmd_entry cmd_paste_buffer_entry = {
.name = "paste-buffer", .name = "paste-buffer",
.alias = "pasteb", .alias = "pasteb",
.args = { "db:prs:t:", 0, 0, NULL }, .args = { "db:prSs:t:", 0, 0, NULL },
.usage = "[-dpr] [-s separator] " CMD_BUFFER_USAGE " " .usage = "[-dprS] [-s separator] " CMD_BUFFER_USAGE " "
CMD_TARGET_PANE_USAGE, CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -43,6 +43,17 @@ const struct cmd_entry cmd_paste_buffer_entry = {
.exec = cmd_paste_buffer_exec .exec = cmd_paste_buffer_exec
}; };
static void
cmd_paste_buffer_paste(struct window_pane *wp, const char *buf, size_t len)
{
char *cp;
size_t n;
n = utf8_stravisx(&cp, buf, len, VIS_SAFE|VIS_NOSLASH);
bufferevent_write(wp->event, cp, n);
free(cp);
}
static enum cmd_retval static enum cmd_retval
cmd_paste_buffer_exec(struct cmd *self, struct cmdq_item *item) cmd_paste_buffer_exec(struct cmd *self, struct cmdq_item *item)
{ {
@@ -51,7 +62,7 @@ cmd_paste_buffer_exec(struct cmd *self, struct cmdq_item *item)
struct window_pane *wp = target->wp; struct window_pane *wp = target->wp;
struct paste_buffer *pb; struct paste_buffer *pb;
const char *sepstr, *bufname, *bufdata, *bufend, *line; const char *sepstr, *bufname, *bufdata, *bufend, *line;
size_t seplen, bufsize; size_t seplen, bufsize, len;
int bracket = args_has(args, 'p'); int bracket = args_has(args, 'p');
if (window_pane_exited(wp)) { if (window_pane_exited(wp)) {
@@ -93,14 +104,22 @@ cmd_paste_buffer_exec(struct cmd *self, struct cmdq_item *item)
line = memchr(bufdata, '\n', bufend - bufdata); line = memchr(bufdata, '\n', bufend - bufdata);
if (line == NULL) if (line == NULL)
break; break;
len = line - bufdata;
bufferevent_write(wp->event, bufdata, line - bufdata); if (args_has(args, 'S'))
bufferevent_write(wp->event, bufdata, len);
else
cmd_paste_buffer_paste(wp, bufdata, len);
bufferevent_write(wp->event, sepstr, seplen); bufferevent_write(wp->event, sepstr, seplen);
bufdata = line + 1; bufdata = line + 1;
} }
if (bufdata != bufend) if (bufdata != bufend) {
bufferevent_write(wp->event, bufdata, bufend - bufdata); len = bufend - bufdata;
if (args_has(args, 'S'))
bufferevent_write(wp->event, bufdata, len);
else
cmd_paste_buffer_paste(wp, bufdata, len);
}
if (bracket && (wp->screen->mode & MODE_BRACKETPASTE)) if (bracket && (wp->screen->mode & MODE_BRACKETPASTE))
bufferevent_write(wp->event, "\033[201~", 6); bufferevent_write(wp->event, "\033[201~", 6);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-pipe-pane.c,v 1.64 2026/07/27 14:25:46 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -123,11 +123,13 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item)
/* Fork the child. */ /* Fork the child. */
sigfillset(&set); sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, &oldset); sigprocmask(SIG_BLOCK, &set, &oldset);
switch (fork()) { switch ((wp->pipe_pid = fork())) {
case -1: case -1:
sigprocmask(SIG_SETMASK, &oldset, NULL); sigprocmask(SIG_SETMASK, &oldset, NULL);
cmdq_error(item, "fork error: %s", strerror(errno)); cmdq_error(item, "fork error: %s", strerror(errno));
close(pipe_fd[0]);
close(pipe_fd[1]);
free(cmd); free(cmd);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
case 0: case 0:
@@ -136,6 +138,9 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item)
sigprocmask(SIG_SETMASK, &oldset, NULL); sigprocmask(SIG_SETMASK, &oldset, NULL);
close(pipe_fd[0]); close(pipe_fd[0]);
if (setpgid(0, 0) == -1)
_exit(1);
null_fd = open(_PATH_DEVNULL, O_WRONLY); null_fd = open(_PATH_DEVNULL, O_WRONLY);
if (out) { if (out) {
if (dup2(pipe_fd[1], STDIN_FILENO) == -1) if (dup2(pipe_fd[1], STDIN_FILENO) == -1)
@@ -189,9 +194,13 @@ static void
cmd_pipe_pane_read_callback(__unused struct bufferevent *bufev, void *data) cmd_pipe_pane_read_callback(__unused struct bufferevent *bufev, void *data)
{ {
struct window_pane *wp = data; struct window_pane *wp = data;
struct evbuffer *evb = wp->pipe_event->input; struct evbuffer *evb;
size_t available; size_t available;
if (wp->pipe_event == NULL)
return;
evb = wp->pipe_event->input;
available = EVBUFFER_LENGTH(evb); available = EVBUFFER_LENGTH(evb);
log_debug("%%%u pipe read %zu", wp->id, available); log_debug("%%%u pipe read %zu", wp->id, available);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-queue.c,v 1.123 2026/08/24 20:34:26 nicm Exp $ */
/* /*
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -35,6 +35,7 @@
enum cmdq_type { enum cmdq_type {
CMDQ_COMMAND, CMDQ_COMMAND,
CMDQ_CALLBACK, CMDQ_CALLBACK,
CMDQ_INVOKE,
}; };
/* Command queue item. */ /* Command queue item. */
@@ -47,7 +48,6 @@ struct cmdq_item {
struct client *target_client; struct client *target_client;
enum cmdq_type type; enum cmdq_type type;
u_int group;
u_int number; u_int number;
time_t time; time_t time;
@@ -58,8 +58,8 @@ struct cmdq_item {
struct cmd_find_state source; struct cmd_find_state source;
struct cmd_find_state target; struct cmd_find_state target;
struct cmd_list *cmdlist;
struct cmd *cmd; struct cmd *cmd;
struct cmd_invoke_state *invoke_state;
cmdq_cb cb; cmdq_cb cb;
void *data; void *data;
@@ -147,6 +147,13 @@ cmdq_get_name(struct cmdq_item *item)
return (item->name); return (item->name);
} }
/* Get item command. */
struct cmd *
cmdq_get_cmd(struct cmdq_item *item)
{
return (item->cmd);
}
/* Get item client. */ /* Get item client. */
struct client * struct client *
cmdq_get_client(struct cmdq_item *item) cmdq_get_client(struct cmdq_item *item)
@@ -350,74 +357,53 @@ cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item)
/* Insert a hook. */ /* Insert a hook. */
void void
cmdq_insert_hook(struct session *s, struct cmdq_item *item, cmdq_insert_hook(__unused struct session *s, struct cmdq_item *item,
struct cmd_find_state *current, const char *fmt, ...) struct cmd_find_state *current, const char *fmt, ...)
{ {
struct cmdq_state *state = item->state;
struct cmd *cmd = item->cmd; struct cmd *cmd = item->cmd;
struct args *args = cmd_get_args(cmd); struct args *args = cmd_get_args(cmd);
struct args_entry *ae; struct args_entry *ae;
struct args_value *av; struct args_value *av;
struct options *oo; struct event_payload *ep;
va_list ap; va_list ap;
char *name, tmp[32], flag, *arguments; char *name, tmp[32], flag, *arguments;
u_int i; u_int i;
const char *value; const char *value;
struct cmdq_item *new_item;
struct cmdq_state *new_state;
struct options_entry *o;
struct options_array_item *a;
struct cmd_list *cmdlist;
if (item->state->flags & CMDQ_STATE_NOHOOKS) if (item->state->flags & CMDQ_STATE_NOHOOKS)
return; return;
if (s == NULL)
oo = global_s_options;
else
oo = s->options;
va_start(ap, fmt); va_start(ap, fmt);
xvasprintf(&name, fmt, ap); xvasprintf(&name, fmt, ap);
va_end(ap); va_end(ap);
o = options_get(oo, name); ep = event_payload_create();
if (o == NULL) { if (current != NULL)
free(name); event_payload_set_target(ep, current);
return; event_payload_set_pointer(ep, "_cmdq_item", item, NULL, NULL);
}
log_debug("running hook %s (parent %p)", name, item);
/*
* The hooks get a new state because they should not update the current
* target or formats for any subsequent commands.
*/
new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS);
cmdq_add_format(new_state, "hook", "%s", name);
arguments = args_print(args); arguments = args_print(args);
cmdq_add_format(new_state, "hook_arguments", "%s", arguments); event_payload_set_string(ep, "arguments", "%s", arguments);
free(arguments); free(arguments);
for (i = 0; i < args_count(args); i++) { for (i = 0; i < args_count(args); i++) {
xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i); xsnprintf(tmp, sizeof tmp, "argument_%u", i);
cmdq_add_format(new_state, tmp, "%s", args_string(args, i)); event_payload_set_string(ep, tmp, "%s", args_string(args, i));
} }
flag = args_first(args, &ae); flag = args_first(args, &ae);
while (flag != 0) { while (flag != 0) {
value = args_get(args, flag); value = args_get(args, flag);
if (value == NULL) { xsnprintf(tmp, sizeof tmp, "flag_%c", flag);
xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); if (value == NULL)
cmdq_add_format(new_state, tmp, "1"); event_payload_set_string(ep, tmp, "1");
} else { else
xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); event_payload_set_string(ep, tmp, "%s", value);
cmdq_add_format(new_state, tmp, "%s", value);
}
i = 0; i = 0;
av = args_first_value(args, flag); av = args_first_value(args, flag);
while (av != NULL) { while (av != NULL) {
xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i); xsnprintf(tmp, sizeof tmp, "flag_%c_%u", flag, i);
cmdq_add_format(new_state, tmp, "%s", av->string); event_payload_set_string(ep, tmp, "%s", av->string);
i++; i++;
av = args_next_value(av); av = args_next_value(av);
} }
@@ -425,20 +411,7 @@ cmdq_insert_hook(struct session *s, struct cmdq_item *item,
flag = args_next(&ae); flag = args_next(&ae);
} }
a = options_array_first(o); events_fire(name, ep);
while (a != NULL) {
cmdlist = options_array_item_value(a)->cmdlist;
if (cmdlist != NULL) {
new_item = cmdq_get_command(cmdlist, new_state);
if (item != NULL)
item = cmdq_insert_after(item, new_item);
else
item = cmdq_append(NULL, new_item);
}
a = options_array_next(a);
}
cmdq_free_state(new_state);
free(name); free(name);
} }
@@ -455,8 +428,10 @@ cmdq_remove(struct cmdq_item *item)
{ {
if (item->client != NULL) if (item->client != NULL)
server_client_unref(item->client); server_client_unref(item->client);
if (item->cmdlist != NULL) if (item->cmd != NULL)
cmd_list_free(item->cmdlist); cmd_free(item->cmd);
if (item->invoke_state != NULL)
cmd_invoke_state_free(item->invoke_state);
cmdq_free_state(item->state); cmdq_free_state(item->state);
TAILQ_REMOVE(&item->queue->list, item, entry); TAILQ_REMOVE(&item->queue->list, item, entry);
@@ -465,75 +440,46 @@ cmdq_remove(struct cmdq_item *item)
free(item); free(item);
} }
/* Remove all subsequent items that match this item's group. */ /* Get a single command for command queue with invoke state. */
static void
cmdq_remove_group(struct cmdq_item *item)
{
struct cmdq_item *this, *next;
if (item->group == 0)
return;
this = TAILQ_NEXT(item, entry);
while (this != NULL) {
next = TAILQ_NEXT(this, entry);
if (this->group == item->group)
cmdq_remove(this);
this = next;
}
}
/* Empty command callback. */
static enum cmd_retval
cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data)
{
return (CMD_RETURN_NORMAL);
}
/* Get a command for the command queue. */
struct cmdq_item * struct cmdq_item *
cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state) cmdq_get_one_command(struct cmd *cmd, struct cmdq_state *state,
struct cmd_invoke_state *invoke_state)
{ {
struct cmdq_item *item, *first = NULL, *last = NULL; struct cmdq_item *item;
struct cmd *cmd; const struct cmd_entry *entry = cmd_get_entry(cmd);
const struct cmd_entry *entry;
int created = 0;
if ((cmd = cmd_list_first(cmdlist)) == NULL)
return (cmdq_get_callback(cmdq_empty_command, NULL));
if (state == NULL) {
state = cmdq_new_state(NULL, NULL, 0);
created = 1;
}
while (cmd != NULL) {
entry = cmd_get_entry(cmd);
item = xcalloc(1, sizeof *item); item = xcalloc(1, sizeof *item);
xasprintf(&item->name, "[%s/%p]", entry->name, item); xasprintf(&item->name, "[%s/%p]", entry->name, item);
item->type = CMDQ_COMMAND; item->type = CMDQ_COMMAND;
item->group = cmd_get_group(cmd);
item->state = cmdq_link_state(state);
item->cmdlist = cmdlist;
item->cmd = cmd; item->cmd = cmd;
cmdlist->references++; if (state == NULL)
log_debug("%s: %s group %u", __func__, item->name, item->group); item->state = cmdq_new_state(NULL, NULL, 0);
else
item->state = cmdq_link_state(state);
if (first == NULL) if (invoke_state != NULL)
first = item; item->invoke_state = cmd_invoke_state_add_ref(invoke_state);
if (last != NULL)
last->next = item;
last = item;
cmd = cmd_list_next(cmd); log_debug("%s: %s", __func__, item->name);
return (item);
} }
if (created) /* Get an invoke continuation for command queue. */
cmdq_free_state(state); struct cmdq_item *
return (first); cmdq_get_invoke(struct cmd_invoke_state *invoke_state, struct cmdq_state *state)
{
struct cmdq_item *item;
item = xcalloc(1, sizeof *item);
xasprintf(&item->name, "[invoke/%p]", item);
item->type = CMDQ_INVOKE;
if (state == NULL)
item->state = cmdq_new_state(NULL, NULL, 0);
else
item->state = cmdq_link_state(state);
item->invoke_state = cmd_invoke_state_add_ref(invoke_state);
return (item);
} }
/* Fill in flag for a command. */ /* Fill in flag for a command. */
@@ -611,7 +557,7 @@ cmdq_fire_command(struct cmdq_item *item)
cmdq_add_message(item); cmdq_add_message(item);
if (log_get_level() > 1) { if (log_get_level() > 1) {
tmp = cmd_print(cmd); tmp = cmd_print(cmd);
log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); log_debug("%s %s: %s", __func__, name, tmp);
free(tmp); free(tmp);
} }
@@ -690,7 +636,6 @@ cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
xasprintf(&item->name, "[%s/%p]", name, item); xasprintf(&item->name, "[%s/%p]", name, item);
item->type = CMDQ_CALLBACK; item->type = CMDQ_CALLBACK;
item->group = 0;
item->state = cmdq_new_state(NULL, NULL, 0); item->state = cmdq_new_state(NULL, NULL, 0);
item->cb = cb; item->cb = cb;
@@ -772,17 +717,16 @@ cmdq_next(struct client *c)
switch (item->type) { switch (item->type) {
case CMDQ_COMMAND: case CMDQ_COMMAND:
retval = cmdq_fire_command(item); retval = cmdq_fire_command(item);
if (item->invoke_state != NULL)
cmd_invoke_result(item->invoke_state, retval);
/*
* If a command returns an error, remove any
* subsequent commands in the same group.
*/
if (retval == CMD_RETURN_ERROR)
cmdq_remove_group(item);
break; break;
case CMDQ_CALLBACK: case CMDQ_CALLBACK:
retval = cmdq_fire_callback(item); retval = cmdq_fire_callback(item);
break; break;
case CMDQ_INVOKE:
retval = cmd_invoke_fire(item, item->invoke_state);
break;
default: default:
retval = CMD_RETURN_ERROR; retval = CMD_RETURN_ERROR;
break; break;
@@ -829,7 +773,7 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
u_int number = item->number; u_int number = item->number;
if (c != NULL && (c->flags & CLIENT_CONTROL)) if (c != NULL && (c->flags & CLIENT_CONTROL))
control_write(c, "%%%s %ld %u %d", guard, t, number, flags); control_write_guard(c, guard, t, number, flags);
} }
/* Show message from command. */ /* Show message from command. */
@@ -877,7 +821,18 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...)
if (c == NULL) { if (c == NULL) {
cmd_get_source(cmd, &file, &line); cmd_get_source(cmd, &file, &line);
if (!cfg_finished) {
if (file != NULL)
cfg_add_cause("%s:%u: %s", file, line, msg); cfg_add_cause("%s:%u: %s", file, line, msg);
else
cfg_add_cause("%s", msg);
} else {
if (file != NULL) {
server_add_message("message: %s:%u: %s", file,
line, msg);
} else
server_add_message("message: %s", msg);
}
} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
server_add_message("%s message: %s", c->name, msg); server_add_message("%s message: %s", c->name, msg);
if (~c->flags & CLIENT_UTF8) { if (~c->flags & CLIENT_UTF8) {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-refresh-client.c,v 1.55 2026/07/17 08:37:29 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -34,7 +34,7 @@ const struct cmd_entry cmd_refresh_client_entry = {
.name = "refresh-client", .name = "refresh-client",
.alias = "refresh", .alias = "refresh",
.args = { "A:B:cC:Df:r:F:l::LRSt:U", 0, 1, NULL }, .args = { "A:B:cC:Df:r:F:lLRSt:U", 0, 1, NULL },
.usage = "[-cDlLRSU] [-A pane:state] [-B name:what:format] " .usage = "[-cDlLRSU] [-A pane:state] [-B name:what:format] "
"[-C XxY] [-f flags] [-r pane:report] " CMD_TARGET_CLIENT_USAGE "[-C XxY] [-f flags] [-r pane:report] " CMD_TARGET_CLIENT_USAGE
" [adjustment]", " [adjustment]",
@@ -46,36 +46,17 @@ const struct cmd_entry cmd_refresh_client_entry = {
static void static void
cmd_refresh_client_update_subscription(struct client *tc, const char *value) cmd_refresh_client_update_subscription(struct client *tc, const char *value)
{ {
char *copy, *split, *name, *what; char *name, *format;
enum control_sub_type subtype; enum monitor_type type;
int subid = -1; int id;
copy = name = xstrdup(value); if (monitor_parse(value, &name, &type, &id, &format) != 0) {
if ((split = strchr(copy, ':')) == NULL) { control_remove_sub(tc, value);
control_remove_sub(tc, copy); return;
goto out;
} }
*split++ = '\0'; control_add_sub(tc, name, type, id, format);
free(name);
what = split; free(format);
if ((split = strchr(what, ':')) == NULL)
goto out;
*split++ = '\0';
if (strcmp(what, "%*") == 0)
subtype = CONTROL_SUB_ALL_PANES;
else if (sscanf(what, "%%%d", &subid) == 1 && subid >= 0)
subtype = CONTROL_SUB_PANE;
else if (strcmp(what, "@*") == 0)
subtype = CONTROL_SUB_ALL_WINDOWS;
else if (sscanf(what, "@%d", &subid) == 1 && subid >= 0)
subtype = CONTROL_SUB_WINDOW;
else
subtype = CONTROL_SUB_SESSION;
control_add_sub(tc, name, subtype, subid, split);
out:
free(copy);
} }
static enum cmd_retval static enum cmd_retval
@@ -85,7 +66,6 @@ cmd_refresh_client_control_client_size(struct cmd *self, struct cmdq_item *item)
struct client *tc = cmdq_get_target_client(item); struct client *tc = cmdq_get_target_client(item);
const char *size = args_get(args, 'C'); const char *size = args_get(args, 'C');
u_int w, x, y; u_int w, x, y;
struct client_window *cw;
if (sscanf(size, "@%u:%ux%u", &w, &x, &y) == 3) { if (sscanf(size, "@%u:%ux%u", &w, &x, &y) == 3) {
if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM || if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM ||
@@ -95,22 +75,16 @@ cmd_refresh_client_control_client_size(struct cmd *self, struct cmdq_item *item)
} }
log_debug("%s: client %s window @%u: size %ux%u", __func__, log_debug("%s: client %s window @%u: size %ux%u", __func__,
tc->name, w, x, y); tc->name, w, x, y);
cw = server_client_add_client_window(tc, w); control_set_window_size(tc, w, x, y);
cw->sx = x;
cw->sy = y;
tc->flags |= CLIENT_WINDOWSIZECHANGED; tc->flags |= CLIENT_WINDOWSIZECHANGED;
recalculate_sizes_now(1); recalculate_sizes_now(1);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (sscanf(size, "@%u:", &w) == 1) { if (sscanf(size, "@%u:", &w) == 1) {
cw = server_client_get_client_window(tc, w);
if (cw != NULL) {
log_debug("%s: client %s window @%u: no size", __func__, log_debug("%s: client %s window @%u: no size", __func__,
tc->name, w); tc->name, w);
cw->sx = 0; control_clear_window_size(tc, w);
cw->sy = 0;
recalculate_sizes_now(1); recalculate_sizes_now(1);
}
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
@@ -163,42 +137,12 @@ out:
free(copy); free(copy);
} }
static enum cmd_retval
cmd_refresh_client_clipboard(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct client *tc = cmdq_get_target_client(item);
const char *p;
u_int i;
struct cmd_find_state fs;
p = args_get(args, 'l');
if (p == NULL) {
if (tc->flags & CLIENT_CLIPBOARDBUFFER)
return (CMD_RETURN_NORMAL);
tc->flags |= CLIENT_CLIPBOARDBUFFER;
} else {
if (cmd_find_target(&fs, item, p, CMD_FIND_PANE, 0) != 0)
return (CMD_RETURN_ERROR);
for (i = 0; i < tc->clipboard_npanes; i++) {
if (tc->clipboard_panes[i] == fs.wp->id)
break;
}
if (i != tc->clipboard_npanes)
return (CMD_RETURN_NORMAL);
tc->clipboard_panes = xreallocarray(tc->clipboard_panes,
tc->clipboard_npanes + 1, sizeof *tc->clipboard_panes);
tc->clipboard_panes[tc->clipboard_npanes++] = fs.wp->id;
}
tty_clipboard_query(&tc->tty);
return (CMD_RETURN_NORMAL);
}
static void static void
cmd_refresh_report(struct tty *tty, const char *value) cmd_refresh_report(struct tty *tty, const char *value)
{ {
struct window_pane *wp; struct window_pane *wp;
u_int pane; u_int pane;
int fg, bg;
size_t size = 0; size_t size = 0;
char *copy, *split; char *copy, *split;
@@ -215,8 +159,14 @@ cmd_refresh_report(struct tty *tty, const char *value)
if (wp == NULL) if (wp == NULL)
goto out; goto out;
tty_keys_colours(tty, split, strlen(split), &size, &wp->control_fg, fg = wp->control_fg;
&wp->control_bg); bg = wp->control_bg;
if (tty_keys_colours(tty, split, strlen(split), &size, &fg, &bg) == 0) {
if (bg != wp->control_bg)
wp->flags |= PANE_THEMECHANGED;
wp->control_fg = fg;
wp->control_bg = bg;
}
out: out:
free(copy); free(copy);
@@ -284,8 +234,10 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_has(args, 'l')) if (args_has(args, 'l')) {
return (cmd_refresh_client_clipboard(self, item)); tty_clipboard_query(&tc->tty);
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'F')) /* -F is an alias for -f */ if (args_has(args, 'F')) /* -F is an alias for -f */
server_client_set_flags(tc, args_get(args, 'F')); server_client_set_flags(tc, args_get(args, 'F'));

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-rename-session.c,v 1.39 2026/07/10 13:38:45 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -49,15 +49,17 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct session *s = target->s; struct session *s = target->s;
struct event_payload *ep;
struct cmd_find_state fs;
char *newname, *tmp; char *newname, *tmp;
tmp = format_single_from_target(item, args_string(args, 0)); tmp = format_single_from_target(item, args_string(args, 0));
newname = session_check_name(tmp); if (!check_name(tmp)) {
if (newname == NULL) { cmdq_error(item, "invalid session name: %s", tmp);
cmdq_error(item, "invalid session: %s", tmp);
free(tmp); free(tmp);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
newname = clean_name(tmp, 0);
free(tmp); free(tmp);
if (strcmp(newname, s->name) == 0) { if (strcmp(newname, s->name) == 0) {
free(newname); free(newname);
@@ -69,13 +71,20 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
ep = event_payload_create();
cmd_find_from_session(&fs, s, 0);
event_payload_set_target(ep, &fs);
event_payload_set_session(ep, "session", s);
event_payload_set_string(ep, "old_name", "%s", s->name);
event_payload_set_string(ep, "new_name", "%s", newname);
RB_REMOVE(sessions, &sessions, s); RB_REMOVE(sessions, &sessions, s);
free(s->name); free(s->name);
s->name = newname; s->name = newname;
RB_INSERT(sessions, &sessions, s); RB_INSERT(sessions, &sessions, s);
server_status_session(s); server_status_session(s);
notify_session("session-renamed", s); events_fire("session-renamed", ep);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-rename-window.c,v 1.30 2026/06/29 18:17:28 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -48,15 +48,21 @@ cmd_rename_window_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct winlink *wl = target->wl; struct winlink *wl = target->wl;
char *newname; char *name;
newname = format_single_from_target(item, args_string(args, 0)); name = format_single_from_target(item, args_string(args, 0));
window_set_name(wl->window, newname); if (!check_name(name)) {
cmdq_error(item, "invalid window name: %s", name);
free(name);
return (CMD_RETURN_ERROR);
}
window_set_name(wl->window, name, 0);
options_set_number(wl->window->options, "automatic-rename", 0); options_set_number(wl->window->options, "automatic-rename", 0);
free(name);
server_redraw_window_borders(wl->window); server_redraw_window_borders(wl->window);
server_status_window(wl->window); server_status_window(wl->window);
free(newname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-resize-pane.c,v 1.68 2026/08/31 07:44:39 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -18,6 +18,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <ctype.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -29,16 +30,20 @@
static enum cmd_retval cmd_resize_pane_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_resize_pane_exec(struct cmd *, struct cmdq_item *);
static void cmd_resize_pane_mouse_update(struct client *, static enum cmd_retval cmd_resize_pane_mouse_update(struct cmd *,
struct cmdq_item *);
static void cmd_resize_pane_mouse_resize_move_floating(
struct client *, struct mouse_event *);
static void cmd_resize_pane_mouse_resize_tiled(struct client *,
struct mouse_event *); struct mouse_event *);
const struct cmd_entry cmd_resize_pane_entry = { const struct cmd_entry cmd_resize_pane_entry = {
.name = "resize-pane", .name = "resize-pane",
.alias = "resizep", .alias = "resizep",
.args = { "DLMRTt:Ux:y:Z", 0, 1, NULL }, .args = { "D::L::MR::Tt:U::x:y:Z", 0, 1, NULL },
.usage = "[-DLMRTUZ] [-x width] [-y height] " CMD_TARGET_PANE_USAGE " " .usage = "[-MTZ] [-D lines] [-L columns] [-R columns] [-U lines] "
"[adjustment]", "[-x width] [-y height] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -51,23 +56,23 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct key_event *event = cmdq_get_event(item);
struct window_pane *wp = target->wp; struct window_pane *wp = target->wp;
struct winlink *wl = target->wl; struct winlink *wl = target->wl;
struct window *w = wl->window; struct window *w = wl->window;
struct client *c = cmdq_get_client(item); struct layout_cell *lc = wp->layout_cell;
struct session *s = target->s; enum layout_type type;
const char *errstr; const char *errstr, *argval;
char *cause; const char flags[4] = { 'U', 'D', 'L', 'R' };
u_int adjust; char *cause = NULL, flag;
int x, y, status; int adjust, x, y, status, opposite = 0;
long unsigned i;
struct grid *gd = wp->base.grid; struct grid *gd = wp->base.grid;
if (args_has(args, 'T')) { if (args_has(args, 'T')) {
if (!TAILQ_EMPTY(&wp->modes)) if (!TAILQ_EMPTY(&wp->modes))
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
adjust = screen_size_y(&wp->base) - 1 - wp->base.cy; adjust = screen_size_y(&wp->base) - 1 - wp->base.cy;
if (adjust > gd->hsize) if (adjust > (int)gd->hsize)
adjust = gd->hsize; adjust = gd->hsize;
grid_remove_history(gd, adjust); grid_remove_history(gd, adjust);
wp->base.cy += adjust; wp->base.cy += adjust;
@@ -75,15 +80,8 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_has(args, 'M')) { if (args_has(args, 'M'))
if (!event->m.valid || cmd_mouse_window(&event->m, &s) == NULL) return (cmd_resize_pane_mouse_update(self, item));
return (CMD_RETURN_NORMAL);
if (c == NULL || c->session != s)
return (CMD_RETURN_NORMAL);
c->tty.mouse_drag_update = cmd_resize_pane_mouse_update;
cmd_resize_pane_mouse_update(c, &event->m);
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'Z')) { if (args_has(args, 'Z')) {
if (w->flags & WINDOW_ZOOMED) if (w->flags & WINDOW_ZOOMED)
@@ -94,34 +92,33 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
server_unzoom_window(w); server_unzoom_window(w);
lc = wp->layout_cell; /* may have been replaced by unzoom */
if (args_count(args) == 0)
adjust = 1;
else {
adjust = strtonum(args_string(args, 0), 1, INT_MAX, &errstr);
if (errstr != NULL) {
cmdq_error(item, "adjustment %s", errstr);
return (CMD_RETURN_ERROR);
}
}
if (args_has(args, 'x')) { if (args_has(args, 'x')) {
x = args_percentage(args, 'x', 0, INT_MAX, w->sx, &cause); x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "width %s", cause); cmdq_error(item, "width %s", cause);
free(cause); free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (window_pane_is_floating(wp)) {
if (layout_resize_floating_pane_to(wp, LAYOUT_LEFTRIGHT,
x, &cause) != 0) {
cmdq_error(item, "size %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
} else
layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x); layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x);
} }
if (args_has(args, 'y')) { if (args_has(args, 'y')) {
y = args_percentage(args, 'y', 0, INT_MAX, w->sy, &cause); y = args_percentage(args, 'y', 0, PANE_MAXIMUM, w->sy, &cause);
if (cause != NULL) { if (cause != NULL) {
cmdq_error(item, "height %s", cause); cmdq_error(item, "height %s", cause);
free(cause); free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
status = options_get_number(w->options, "pane-border-status"); status = window_get_pane_status(w);
switch (status) { switch (status) {
case PANE_STATUS_TOP: case PANE_STATUS_TOP:
if (y != INT_MAX && wp->yoff == 1) if (y != INT_MAX && wp->yoff == 1)
@@ -132,24 +129,231 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
y++; y++;
break; break;
} }
if (window_pane_is_floating(wp)) {
if (layout_resize_floating_pane_to(wp, LAYOUT_TOPBOTTOM,
y, &cause) != 0) {
cmdq_error(item, "size %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
} else
layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y); layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y);
} }
if (args_has(args, 'L')) for (i = 0; i < nitems(flags); i++) {
layout_resize_pane(wp, LAYOUT_LEFTRIGHT, -adjust, 1); flag = flags[i];
else if (args_has(args, 'R')) if (!args_has(args, flag))
layout_resize_pane(wp, LAYOUT_LEFTRIGHT, adjust, 1); continue;
else if (args_has(args, 'U'))
layout_resize_pane(wp, LAYOUT_TOPBOTTOM, -adjust, 1); argval = args_get(args, flag);
else if (args_has(args, 'D')) if (argval == NULL) {
layout_resize_pane(wp, LAYOUT_TOPBOTTOM, adjust, 1); if (args_count(args) == 0)
server_redraw_window(wl->window); argval = "1";
else
argval = args_string(args, 0);
}
adjust = strtonum(argval, INT_MIN, INT_MAX, &errstr);
if (errstr != NULL) {
cmdq_error(item, "adjustment %s", errstr);
return (CMD_RETURN_ERROR);
}
type = LAYOUT_TOPBOTTOM;
if (flag == 'L' || flag == 'R')
type = LAYOUT_LEFTRIGHT;
if (window_pane_is_floating(wp)) {
if (flag == 'L' || flag == 'U')
opposite = 1;
if (layout_resize_floating_pane(wp, type, adjust,
opposite, &cause) != 0) {
cmdq_error(item, "adjustment %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
} else {
if (flag == 'L' || flag == 'U')
adjust = -adjust;
layout_resize_pane(wp, type, adjust, 1);
}
}
if (lc->parent != NULL)
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
events_fire_window("window-layout-changed", w);
server_redraw_window(w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
static enum cmd_retval
cmd_resize_pane_mouse_update(__unused struct cmd *self, struct cmdq_item *item)
{
struct cmd_find_state *target = cmdq_get_target(item);
struct key_event *event = cmdq_get_event(item);
struct window_pane *wp = target->wp;
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct client *c = cmdq_get_client(item);
struct session *s = target->s;
if (!event->m.valid)
return (CMD_RETURN_NORMAL);
wp = cmd_mouse_pane(&event->m, &s, NULL);
if (wp == NULL || c == NULL || c->session != s)
return (CMD_RETURN_NORMAL);
if (!window_pane_is_floating(wp)) {
c->tty.mouse_drag_update = cmd_resize_pane_mouse_resize_tiled;
cmd_resize_pane_mouse_resize_tiled(c, &event->m);
return (CMD_RETURN_NORMAL);
}
window_redraw_active_switch(w, wp);
window_set_active_pane(w, wp, 1);
c->tty.mouse_drag_update = cmd_resize_pane_mouse_resize_move_floating;
cmd_resize_pane_mouse_resize_move_floating(c, &event->m);
return (CMD_RETURN_NORMAL);
}
/*
* Resizes or moves the pane by dragging. Resize a floating pane by dragging
* the borders or corners. Grabbing an edge only resizes that axis (special
* case). Moves the pane if dragging the top border. Since characters are
* generally rectangular, to make it easier to grab the corner, the character
* next to the corner is also considered the corner.
*/
static void static void
cmd_resize_pane_mouse_update(struct client *c, struct mouse_event *m) cmd_resize_pane_mouse_resize_move_floating(struct client *c,
struct mouse_event *m)
{
struct winlink *wl;
struct window *w;
struct window_pane *wp;
struct layout_cell *lc;
int y, ly, x, lx, sx, sy, new_sx, new_sy;
int left, right;
int new_xoff, new_yoff, resizes = 0;
wp = cmd_mouse_pane(m, NULL, &wl);
if (wp == NULL) {
c->tty.mouse_drag_update = NULL;
return;
}
w = wl->window;
lc = wp->layout_cell;
sx = wp->sx;
sy = wp->sy;
left = wp->xoff - 1;
right = wp->xoff + sx;
if (window_pane_scrollbar_reserve(wp) &&
w->sb_pos == PANE_SCROLLBARS_LEFT) {
left -= wp->scrollbar_style.width + wp->scrollbar_style.pad;
} else if (window_pane_scrollbar_reserve(wp) &&
w->sb_pos == PANE_SCROLLBARS_RIGHT) {
right += wp->scrollbar_style.width + wp->scrollbar_style.pad;
}
y = m->y + m->oy; x = m->x + m->ox;
if (m->statusat == 0 && y >= (int)m->statuslines)
y -= m->statuslines;
else if (m->statusat > 0 && y >= m->statusat)
y = m->statusat - 1;
ly = m->ly + m->oy; lx = m->lx + m->ox;
if (m->statusat == 0 && ly >= (int)m->statuslines)
ly -= m->statuslines;
else if (m->statusat > 0 && ly >= m->statusat)
ly = m->statusat - 1;
if ((lx == left || lx == left + 1) && ly == wp->yoff - 1) {
/* Top left corner. */
new_sx = lc->g.sx + (lx - x);
if (new_sx < PANE_MINIMUM)
new_sx = PANE_MINIMUM;
new_sy = lc->g.sy + (ly - y);
if (new_sy < PANE_MINIMUM)
new_sy = PANE_MINIMUM;
new_xoff = x + 1; /* because mouse is on border at xoff - 1 */
new_yoff = y + 1;
layout_set_size(lc, new_sx, new_sy, new_xoff, new_yoff);
resizes++;
} else if ((lx == right + 1 || lx == right) &&
ly == wp->yoff - 1) {
/* Top right corner. */
new_sx = x - lc->g.xoff;
if (new_sx < PANE_MINIMUM)
new_sx = PANE_MINIMUM;
new_sy = lc->g.sy + (ly - y);
if (new_sy < PANE_MINIMUM)
new_sy = PANE_MINIMUM;
new_yoff = y + 1;
layout_set_size(lc, new_sx, new_sy, lc->g.xoff, new_yoff);
resizes++;
} else if ((lx == left || lx == left + 1) &&
ly == wp->yoff + sy) {
/* Bottom left corner. */
new_sx = lc->g.sx + (lx - x);
if (new_sx < PANE_MINIMUM)
new_sx = PANE_MINIMUM;
new_sy = y - lc->g.yoff;
if (new_sy < PANE_MINIMUM)
return;
new_xoff = x + 1;
layout_set_size(lc, new_sx, new_sy, new_xoff, lc->g.yoff);
resizes++;
} else if ((lx == right + 1 || lx == right) &&
ly == wp->yoff + sy) {
/* Bottom right corner. */
new_sx = x - lc->g.xoff;
if (new_sx < PANE_MINIMUM)
new_sx = PANE_MINIMUM;
new_sy = y - lc->g.yoff;
if (new_sy < PANE_MINIMUM)
new_sy = PANE_MINIMUM;
layout_set_size(lc, new_sx, new_sy, lc->g.xoff, lc->g.yoff);
resizes++;
} else if (lx == right) {
/* Right border. */
new_sx = x - lc->g.xoff;
if (new_sx < PANE_MINIMUM)
return;
layout_set_size(lc, new_sx, lc->g.sy, lc->g.xoff, lc->g.yoff);
resizes++;
} else if (lx == left) {
/* Left border. */
new_sx = lc->g.sx + (lx - x);
if (new_sx < PANE_MINIMUM)
return;
new_xoff = x + 1;
layout_set_size(lc, new_sx, lc->g.sy, new_xoff, lc->g.yoff);
resizes++;
} else if (ly == wp->yoff + sy) {
/* Bottom border. */
new_sy = y - lc->g.yoff;
if (new_sy < PANE_MINIMUM)
return;
layout_set_size(lc, lc->g.sx, new_sy, lc->g.xoff, lc->g.yoff);
resizes++;
} else if (ly == wp->yoff - 1) {
/* Top border (move instead of resize). */
new_xoff = lc->g.xoff + (x - lx);
new_yoff = y + 1;
layout_set_size(lc, lc->g.sx, lc->g.sy, new_xoff, new_yoff);
resizes++;
}
if (resizes != 0) {
layout_fix_panes(w, NULL);
server_redraw_window(w);
server_redraw_window_borders(w);
}
}
static void
cmd_resize_pane_mouse_resize_tiled(struct client *c, struct mouse_event *m)
{ {
struct winlink *wl; struct winlink *wl;
struct window *w; struct window *w;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-resize-window.c,v 1.10 2023/06/30 13:19:32 nicm Exp $ */
/* /*
* Copyright (c) 2018 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2018 Nicholas Marriott <nicholas.marriott@gmail.com>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-respawn-pane.c,v 1.40 2026/07/03 16:09:49 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -34,8 +34,8 @@ const struct cmd_entry cmd_respawn_pane_entry = {
.name = "respawn-pane", .name = "respawn-pane",
.alias = "respawnp", .alias = "respawnp",
.args = { "c:e:kt:", 0, -1, NULL }, .args = { "c:e:Ekt:", 0, -1, NULL },
.usage = "[-k] [-c start-directory] [-e environment] " .usage = "[-Ek] [-c start-directory] [-e environment] "
CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]", CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -75,6 +75,8 @@ cmd_respawn_pane_exec(struct cmd *self, struct cmdq_item *item)
sc.cwd = args_get(args, 'c'); sc.cwd = args_get(args, 'c');
sc.flags = SPAWN_RESPAWN; sc.flags = SPAWN_RESPAWN;
if (args_has(args, 'E'))
sc.flags |= SPAWN_EMPTY;
if (args_has(args, 'k')) if (args_has(args, 'k'))
sc.flags |= SPAWN_KILL; sc.flags |= SPAWN_KILL;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-respawn-window.c,v 1.51 2026/07/03 16:09:49 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -34,8 +34,8 @@ const struct cmd_entry cmd_respawn_window_entry = {
.name = "respawn-window", .name = "respawn-window",
.alias = "respawnw", .alias = "respawnw",
.args = { "c:e:kt:", 0, -1, NULL }, .args = { "c:e:Ekt:", 0, -1, NULL },
.usage = "[-k] [-c start-directory] [-e environment] " .usage = "[-Ek] [-c start-directory] [-e environment] "
CMD_TARGET_WINDOW_USAGE " [shell-command [argument ...]]", CMD_TARGET_WINDOW_USAGE " [shell-command [argument ...]]",
.target = { 't', CMD_FIND_WINDOW, 0 }, .target = { 't', CMD_FIND_WINDOW, 0 },
@@ -74,6 +74,8 @@ cmd_respawn_window_exec(struct cmd *self, struct cmdq_item *item)
sc.cwd = args_get(args, 'c'); sc.cwd = args_get(args, 'c');
sc.flags = SPAWN_RESPAWN; sc.flags = SPAWN_RESPAWN;
if (args_has(args, 'E'))
sc.flags |= SPAWN_EMPTY;
if (args_has(args, 'k')) if (args_has(args, 'k'))
sc.flags |= SPAWN_KILL; sc.flags |= SPAWN_KILL;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-rotate-window.c,v 1.34 2026/06/22 08:47:45 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -109,6 +109,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item)
window_set_active_pane(w, wp, 1); window_set_active_pane(w, wp, 1);
cmd_find_from_winlink_pane(current, wl, wp, 0); cmd_find_from_winlink_pane(current, wl, wp, 0);
window_pop_zoom(w); window_pop_zoom(w);
redraw_invalidate_scene(w);
server_redraw_window(w); server_redraw_window(w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-run-shell.c,v 1.94 2026/08/25 06:04:33 nicm Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -44,9 +44,9 @@ const struct cmd_entry cmd_run_shell_entry = {
.name = "run-shell", .name = "run-shell",
.alias = "run", .alias = "run",
.args = { "bd:Ct:Es:c:", 0, 1, cmd_run_shell_args_parse }, .args = { "bd:Ct:Es:c:", 0, -1, cmd_run_shell_args_parse },
.usage = "[-bCE] [-c start-directory] [-d delay] " CMD_TARGET_PANE_USAGE .usage = "[-bCE] [-c start-directory] [-d delay] " CMD_TARGET_PANE_USAGE
" [shell-command]", " [shell-command [argument ...]]",
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
@@ -57,7 +57,7 @@ const struct cmd_entry cmd_run_shell_entry = {
struct cmd_run_shell_data { struct cmd_run_shell_data {
struct client *client; struct client *client;
char *cmd; char *cmd;
struct args_command_state *state; struct cmd_parse_tree *tree;
char *cwd; char *cwd;
struct cmdq_item *item; struct cmdq_item *item;
struct session *s; struct session *s;
@@ -90,8 +90,8 @@ cmd_run_shell_print(struct job *job, const char *msg)
cmdq_print(cdata->item, "%s", msg); cmdq_print(cdata->item, "%s", msg);
return; return;
} }
if (cdata->item != NULL && cdata->client != NULL) if (cdata->client != NULL && cdata->client->session != NULL)
wp = server_client_get_pane(cdata->client); wp = cdata->client->session->curw->window->active;
if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0) if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0)
wp = fs.wp; wp = fs.wp;
if (wp == NULL) if (wp == NULL)
@@ -100,7 +100,8 @@ cmd_run_shell_print(struct job *job, const char *msg)
wme = TAILQ_FIRST(&wp->modes); wme = TAILQ_FIRST(&wp->modes);
if (wme == NULL || wme->mode != &window_view_mode) if (wme == NULL || wme->mode != &window_view_mode)
window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL,
NULL);
window_copy_add(wp, 1, "%s", msg); window_copy_add(wp, 1, "%s", msg);
} }
@@ -115,9 +116,11 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
struct session *s = target->s; struct session *s = target->s;
struct window_pane *wp = target->wp; struct window_pane *wp = target->wp;
const char *delay, *cmd; const char *delay, *cmd;
struct format_tree *ft;
double d; double d;
struct timeval tv; struct timeval tv;
char *end; char *end, key[16], *cause = NULL;
u_int i;
int wait = !args_has(args, 'b'); int wait = !args_has(args, 'b');
if ((delay = args_get(args, 'd')) != NULL) { if ((delay = args_get(args, 'd')) != NULL) {
@@ -126,17 +129,31 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
cmdq_error(item, "invalid delay time: %s", delay); cmdq_error(item, "invalid delay time: %s", delay);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
} else if (args_count(args) == 0) } else {
if (args_count(args) == 0)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
}
cdata = xcalloc(1, sizeof *cdata); cdata = xcalloc(1, sizeof *cdata);
if (!args_has(args, 'C')) { if (!args_has(args, 'C')) {
cmd = args_string(args, 0); cmd = args_string(args, 0);
if (cmd != NULL) if (cmd != NULL) {
cdata->cmd = format_single_from_target(item, cmd); ft = format_create_from_target(item);
for (i = 1; i < args_count(args); i++) {
xsnprintf(key, sizeof key, "%u", i);
format_add(ft, key, "%s", args_string(args, i));
}
cdata->cmd = format_expand(ft, cmd);
format_free(ft);
}
} else { } else {
cdata->state = args_make_commands_prepare(self, item, 0, NULL, cdata->tree = args_to_commands(self, item, 0, NULL, 1, &cause);
wait, 1); if (cdata->tree == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
cmd_run_shell_free(cdata);
return (CMD_RETURN_ERROR);
}
} }
if (args_has(args, 't') && wp != NULL) if (args_has(args, 't') && wp != NULL)
@@ -153,6 +170,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
} }
if (cdata->client != NULL) if (cdata->client != NULL)
cdata->client->references++; cdata->client->references++;
if (args_has(args, 'c')) if (args_has(args, 'c'))
cdata->cwd = xstrdup(args_get(args, 'c')); cdata->cwd = xstrdup(args_get(args, 'c'));
else else
@@ -161,18 +179,20 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'E')) if (args_has(args, 'E'))
cdata->flags |= JOB_SHOWSTDERR; cdata->flags |= JOB_SHOWSTDERR;
if (s != NULL) {
cdata->s = s; cdata->s = s;
if (s != NULL)
session_add_ref(s, __func__); session_add_ref(s, __func__);
}
evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata); evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
if (delay != NULL) { if (delay == NULL)
event_active(&cdata->timer, EV_TIMEOUT, 1);
else {
timerclear(&tv); timerclear(&tv);
tv.tv_sec = (time_t)d; tv.tv_sec = (time_t)d;
tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U; tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
evtimer_add(&cdata->timer, &tv); evtimer_add(&cdata->timer, &tv);
} else }
event_active(&cdata->timer, EV_TIMEOUT, 1);
if (!wait) if (!wait)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
@@ -186,10 +206,9 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
struct client *c = cdata->client; struct client *c = cdata->client;
const char *cmd = cdata->cmd; const char *cmd = cdata->cmd;
struct cmdq_item *item = cdata->item, *new_item; struct cmdq_item *item = cdata->item, *new_item;
struct cmd_list *cmdlist; struct cmdq_state *cs = NULL;
char *error;
if (cdata->state == NULL) { if (cdata->tree == NULL) {
if (cmd == NULL) { if (cmd == NULL) {
if (cdata->item != NULL) if (cdata->item != NULL)
cmdq_continue(cdata->item); cmdq_continue(cdata->item);
@@ -198,26 +217,27 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
} }
if (job_run(cmd, 0, NULL, NULL, cdata->s, cdata->cwd, NULL, if (job_run(cmd, 0, NULL, NULL, cdata->s, cdata->cwd, NULL,
cmd_run_shell_callback, cmd_run_shell_free, cdata, cmd_run_shell_callback, cmd_run_shell_free, cdata,
cdata->flags, -1, -1) == NULL) cdata->flags, -1, -1) == NULL) {
if (cdata->item == NULL)
status_message_set(c, -1, 1, 0, 0,
"failed to run command: %s", cmd);
else {
cmdq_error(cdata->item,
"failed to run command: %s", cmd);
cmdq_continue(cdata->item);
}
cmd_run_shell_free(cdata); cmd_run_shell_free(cdata);
}
return; return;
} }
cmdlist = args_make_commands(cdata->state, 0, NULL, &error); if (item != NULL)
if (cmdlist == NULL) { cs = cmdq_get_state(item);
if (cdata->item == NULL) { new_item = cmd_invoke_get(cdata->tree, cs, NULL);
*error = toupper((u_char)*error); if (item == NULL)
status_message_set(c, -1, 1, 0, 0, "%s", error);
} else
cmdq_error(cdata->item, "%s", error);
free(error);
} else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item); cmdq_append(c, new_item);
} else { else
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item); cmdq_insert_after(item, new_item);
}
if (cdata->item != NULL) if (cdata->item != NULL)
cmdq_continue(cdata->item); cmdq_continue(cdata->item);
@@ -285,8 +305,7 @@ cmd_run_shell_free(void *data)
session_remove_ref(cdata->s, __func__); session_remove_ref(cdata->s, __func__);
if (cdata->client != NULL) if (cdata->client != NULL)
server_client_unref(cdata->client); server_client_unref(cdata->client);
if (cdata->state != NULL) cmd_parse_free(cdata->tree);
args_make_commands_free(cdata->state);
free(cdata->cwd); free(cdata->cwd);
free(cdata->cmd); free(cdata->cmd);
free(cdata); free(cdata);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-save-buffer.c,v 1.57 2025/10/28 07:32:26 nicm Exp $ */
/* /*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-select-layout.c,v 1.43 2026/07/10 13:38:45 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -90,7 +90,7 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
previous = 1; previous = 1;
oldlayout = w->old_layout; oldlayout = w->old_layout;
w->old_layout = layout_dump(w->layout_root); w->old_layout = layout_dump(w, w->layout_root);
if (next || previous) { if (next || previous) {
if (next) if (next)
@@ -139,7 +139,7 @@ changed:
free(oldlayout); free(oldlayout);
recalculate_sizes(); recalculate_sizes();
server_redraw_window(w); server_redraw_window(w);
notify_window("window-layout-changed", w); events_fire_window("window-layout-changed", w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
error: error:

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-select-pane.c,v 1.78 2026/08/20 09:19:24 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -80,6 +80,69 @@ cmd_select_pane_redraw(struct window *w)
} }
} }
static enum cmd_retval
cmd_select_pane_marked_pane(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct event_payload *ep;
struct cmd_find_state fs;
struct winlink *wl = target->wl;
struct window_pane *wp = target->wp, *lwp = NULL, *mwp;
struct session *s = target->s;
if (args_has(args, 'm') && !window_pane_is_visible(wp))
return (CMD_RETURN_NORMAL);
if (server_check_marked())
lwp = marked_pane.wp;
if (args_has(args, 'M') || server_is_marked(s, wl, wp))
server_clear_marked();
else
server_set_marked(s, wl, wp);
mwp = marked_pane.wp;
ep = event_payload_create();
if (mwp != NULL)
cmd_find_from_pane(&fs, mwp, 0);
else if (lwp != NULL)
cmd_find_from_pane(&fs, lwp, 0);
else
cmd_find_from_pane(&fs, wp, 0);
event_payload_set_target(ep, &fs);
if (mwp != NULL) {
event_payload_set_pane(ep, "pane", mwp);
event_payload_set_pane(ep, "new_pane", mwp);
event_payload_set_window(ep, "window", mwp->window);
} else if (lwp != NULL) {
event_payload_set_pane(ep, "pane", lwp);
event_payload_set_window(ep, "window", lwp->window);
} else {
event_payload_set_pane(ep, "pane", wp);
event_payload_set_window(ep, "window", wp->window);
}
if (lwp != NULL)
event_payload_set_pane(ep, "old_pane", lwp);
event_payload_set_int(ep, "marked", mwp != NULL);
events_fire("marked-pane-changed", ep);
if (lwp != NULL) {
lwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|PANE_THEMECHANGED);
server_redraw_window_borders(lwp->window);
server_status_window(lwp->window);
}
if (mwp != NULL) {
mwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|PANE_THEMECHANGED);
server_redraw_window_borders(mwp->window);
server_status_window(mwp->window);
}
if (window_pane_is_floating(wp)) {
window_redraw_active_switch(wp->window, wp);
window_set_active_pane(wp->window, wp, 1);
}
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval static enum cmd_retval
cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
{ {
@@ -87,15 +150,17 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
const struct cmd_entry *entry = cmd_get_entry(self); const struct cmd_entry *entry = cmd_get_entry(self);
struct cmd_find_state *current = cmdq_get_current(item); struct cmd_find_state *current = cmdq_get_current(item);
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct client *c = cmdq_get_client(item); struct event_payload *ep;
struct cmd_find_state fs;
struct winlink *wl = target->wl; struct winlink *wl = target->wl;
struct window *w = wl->window; struct window *w = wl->window;
struct session *s = target->s; struct session *s = target->s;
struct window_pane *wp = target->wp, *activewp, *lastwp, *markedwp; struct window_pane *wp = target->wp, *lastwp;
struct options *oo = wp->options; struct options *oo = wp->options;
char *title; char *title;
const char *style; const char *style;
struct options_entry *o; struct options_entry *o;
int visible, Zflag = args_has(args, 'Z');
if (entry == &cmd_last_pane_entry || args_has(args, 'l')) { if (entry == &cmd_last_pane_entry || args_has(args, 'l')) {
/* /*
@@ -103,7 +168,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
* spawned without being visited (for example split-window -d). * spawned without being visited (for example split-window -d).
*/ */
lastwp = TAILQ_FIRST(&w->last_panes); lastwp = TAILQ_FIRST(&w->last_panes);
if (lastwp == NULL && window_count_panes(w) == 2) { if (lastwp == NULL && window_count_panes(w, 1) == 2) {
lastwp = TAILQ_PREV(w->active, window_panes, entry); lastwp = TAILQ_PREV(w->active, window_panes, entry);
if (lastwp == NULL) if (lastwp == NULL)
lastwp = TAILQ_NEXT(w->active, entry); lastwp = TAILQ_NEXT(w->active, entry);
@@ -121,47 +186,25 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
server_redraw_window_borders(lastwp->window); server_redraw_window_borders(lastwp->window);
server_status_window(lastwp->window); server_status_window(lastwp->window);
} else { } else {
if (window_push_zoom(w, 0, args_has(args, 'Z'))) if (w->modal != NULL && lastwp != w->modal)
visible = 1;
else
visible = window_pane_is_visible(lastwp);
if (!visible && window_push_zoom(w, 0, Zflag))
server_redraw_window(w); server_redraw_window(w);
window_redraw_active_switch(w, lastwp); window_redraw_active_switch(w, lastwp);
if (window_set_active_pane(w, lastwp, 1)) { if (window_set_active_pane(w, lastwp, 1)) {
cmd_find_from_winlink(current, wl, 0); cmd_find_from_winlink(current, wl, 0);
cmd_select_pane_redraw(w); cmd_select_pane_redraw(w);
} }
if (window_pop_zoom(w)) if (!visible && window_pop_zoom(w))
server_redraw_window(w); server_redraw_window(w);
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_has(args, 'm') || args_has(args, 'M')) { if (args_has(args, 'm') || args_has(args, 'M'))
if (args_has(args, 'm') && !window_pane_visible(wp)) return (cmd_select_pane_marked_pane(self, item));
return (CMD_RETURN_NORMAL);
if (server_check_marked())
lastwp = marked_pane.wp;
else
lastwp = NULL;
if (args_has(args, 'M') || server_is_marked(s, wl, wp))
server_clear_marked();
else
server_set_marked(s, wl, wp);
markedwp = marked_pane.wp;
if (lastwp != NULL) {
lastwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|
PANE_THEMECHANGED);
server_redraw_window_borders(lastwp->window);
server_status_window(lastwp->window);
}
if (markedwp != NULL) {
markedwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|
PANE_THEMECHANGED);
server_redraw_window_borders(markedwp->window);
server_status_window(markedwp->window);
}
return (CMD_RETURN_NORMAL);
}
style = args_get(args, 'P'); style = args_get(args, 'P');
if (style != NULL) { if (style != NULL) {
@@ -213,8 +256,14 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'T')) { if (args_has(args, 'T')) {
title = format_single_from_target(item, args_get(args, 'T')); title = format_single_from_target(item, args_get(args, 'T'));
if (screen_set_title(&wp->base, title)) { if (screen_set_title(&wp->base, title, 0)) {
notify_pane("pane-title-changed", wp); ep = event_payload_create();
cmd_find_from_pane(&fs, wp, 0);
event_payload_set_target(ep, &fs);
event_payload_set_pane(ep, "pane", wp);
event_payload_set_window(ep, "window", wp->window);
event_payload_set_string(ep, "new_title", "%s", title);
events_fire("pane-title-changed", ep);
server_redraw_window_borders(wp->window); server_redraw_window_borders(wp->window);
server_status_window(wp->window); server_status_window(wp->window);
} }
@@ -222,22 +271,20 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (c != NULL && c->session != NULL && (c->flags & CLIENT_ACTIVEPANE)) if (wp == w->active)
activewp = server_client_get_pane(c);
else
activewp = w->active;
if (wp == activewp)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (window_push_zoom(w, 0, args_has(args, 'Z'))) if (w->modal != NULL && wp != w->modal)
visible = 1;
else
visible = window_pane_is_visible(wp);
if (!visible && window_push_zoom(w, 0, Zflag))
server_redraw_window(w); server_redraw_window(w);
window_redraw_active_switch(w, wp); window_redraw_active_switch(w, wp);
if (c != NULL && c->session != NULL && (c->flags & CLIENT_ACTIVEPANE)) if (window_set_active_pane(w, wp, 1))
server_client_set_pane(c, wp);
else if (window_set_active_pane(w, wp, 1))
cmd_find_from_winlink_pane(current, wl, wp, 0); cmd_find_from_winlink_pane(current, wl, wp, 0);
cmdq_insert_hook(s, item, current, "after-select-pane"); cmdq_insert_hook(s, item, current, "after-select-pane");
cmd_select_pane_redraw(w); cmd_select_pane_redraw(w);
if (window_pop_zoom(w)) if (!visible && window_pop_zoom(w))
server_redraw_window(w); server_redraw_window(w);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-select-window.c,v 1.30 2021/08/21 10:22:39 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-send-keys.c,v 1.81 2026/06/11 19:13:34 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -39,7 +39,8 @@ const struct cmd_entry cmd_send_keys_entry = {
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
.flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG|CMD_CLIENT_CANFAIL, .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG|CMD_CLIENT_CANFAIL|
CMD_READONLY,
.exec = cmd_send_keys_exec .exec = cmd_send_keys_exec
}; };
@@ -69,6 +70,7 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after,
struct key_table *table = NULL; struct key_table *table = NULL;
struct key_binding *bd; struct key_binding *bd;
struct key_event *event; struct key_event *event;
struct cmdq_item *new_after = after;
if (args_has(args, 'K')) { if (args_has(args, 'K')) {
if (tc == NULL) if (tc == NULL)
@@ -76,10 +78,16 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after,
event = xcalloc(1, sizeof *event); event = xcalloc(1, sizeof *event);
event->key = key|KEYC_SENT; event->key = key|KEYC_SENT;
memset(&event->m, 0, sizeof event->m); memset(&event->m, 0, sizeof event->m);
if (server_client_handle_key(tc, event) == 0) { if (after == NULL) {
if (server_client_handle_key(tc, event) != 0)
return (item);
} else {
if (server_client_handle_key_after(tc, event, after,
&new_after) != 0)
return (new_after);
}
free(event->buf); free(event->buf);
free(event); free(event);
}
return (item); return (item);
} }
@@ -167,6 +175,11 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item)
u_int count = args_count(args); u_int count = args_count(args);
char *cause = NULL; char *cause = NULL;
if (tc != NULL && tc->flags & CLIENT_READONLY && !args_has(args, 'X')) {
cmdq_error(item, "client is read-only");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'N')) { if (args_has(args, 'N')) {
np = args_strtonum_and_expand(args, 'N', 1, UINT_MAX, item, np = args_strtonum_and_expand(args, 'N', 1, UINT_MAX, item,
&cause); &cause);
@@ -223,8 +236,10 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item)
if (count == 0) { if (count == 0) {
if (args_has(args, 'N') || args_has(args, 'R')) if (args_has(args, 'N') || args_has(args, 'R'))
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
after = args_has(args, 'K') ? item : NULL;
for (; np != 0; np--) for (; np != 0; np--)
cmd_send_keys_inject_key(item, NULL, args, event->key); after = cmd_send_keys_inject_key(item, after, args,
event->key);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-server-access.c,v 1.6 2026/06/09 12:58:40 nicm Exp $ */
/* /*
* Copyright (c) 2021 Dallas Lyons <dallasdlyons@gmail.com> * Copyright (c) 2021 Dallas Lyons <dallasdlyons@gmail.com>
@@ -19,6 +19,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <grp.h>
#include <pwd.h> #include <pwd.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -37,66 +38,71 @@ const struct cmd_entry cmd_server_access_entry = {
.name = "server-access", .name = "server-access",
.alias = NULL, .alias = NULL,
.args = { "adlrw", 0, 1, NULL }, .args = { "adglrw", 0, 1, NULL },
.usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]", .usage = "[-adglrw] " CMD_TARGET_PANE_USAGE " [user|group]",
.flags = CMD_CLIENT_CANFAIL, .flags = CMD_CLIENT_CANFAIL,
.exec = cmd_server_access_exec .exec = cmd_server_access_exec
}; };
static enum cmd_retval static enum cmd_retval
cmd_server_access_deny(struct cmdq_item *item, struct passwd *pw) cmd_server_access_deny(struct cmdq_item *item, id_t id, int flags,
const char *type, const char *name)
{ {
struct client *loop; if (!server_acl_find(id, flags)) {
struct server_acl_user *user; cmdq_error(item, "%s %s not found", type, name);
uid_t uid;
if ((user = server_acl_user_find(pw->pw_uid)) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
TAILQ_FOREACH(loop, &clients, entry) { server_acl_deny(id, flags);
uid = proc_get_peer_uid(loop->peer);
if (uid == server_acl_get_uid(user)) {
loop->exit_message = xstrdup("access not allowed");
loop->flags |= CLIENT_EXIT;
}
}
server_acl_user_deny(pw->pw_uid);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
static enum cmd_retval static enum cmd_retval
cmd_server_access_exec(struct cmd *self, struct cmdq_item *item) cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_target_client(item); struct client *c = cmdq_get_target_client(item);
char *name; char *arg;
struct passwd *pw = NULL; const char *name = NULL, *type;
struct passwd *pw;
struct group *gr;
id_t id;
int flags = 0;
if (args_has(args, 'l')) { if (args_has(args, 'l')) {
server_acl_display(item); server_acl_display(item);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_count(args) == 0) { if (args_count(args) == 0) {
cmdq_error(item, "missing user argument"); cmdq_error(item, "missing user or group argument");
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL); arg = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
if (*name != '\0') if (args_has(args, 'g')) {
pw = getpwnam(name); type = "group";
if (pw == NULL) { if ((gr = getgrnam(arg)) != NULL) {
cmdq_error(item, "unknown user: %s", name); id = gr->gr_gid;
name = gr->gr_name;
flags |= SERVER_ACL_IS_GROUP;
}
} else {
type = "user";
if ((pw = getpwnam(arg)) != NULL) {
id = pw->pw_uid;
name = pw->pw_name;
}
}
if (name == NULL) {
cmdq_error(item, "unknown %s: %s", type, arg);
free(arg);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
free(name); free(arg);
if (pw->pw_uid == 0 || pw->pw_uid == getuid()) { if ((~flags & SERVER_ACL_IS_GROUP) && (id == 0 || id == getuid())) {
cmdq_error(item, "%s owns the server, can't change access", cmdq_error(item, "%s owns the server, can't change access",
pw->pw_name); name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -110,36 +116,35 @@ cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
} }
if (args_has(args, 'd')) if (args_has(args, 'd'))
return (cmd_server_access_deny(item, pw)); return (cmd_server_access_deny(item, id, flags, type, name));
if (args_has(args, 'a')) { if (args_has(args, 'a')) {
if (server_acl_user_find(pw->pw_uid) != NULL) { if (server_acl_find(id, flags)) {
cmdq_error(item, "user %s is already added", cmdq_error(item, "%s %s is already added", type, name);
pw->pw_name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
server_acl_user_allow(pw->pw_uid); server_acl_allow(id, flags);
/* Do not return - allow -r or -w with -a. */ /* Do not return - allow -r or -w with -a. */
} else if (args_has(args, 'r') || args_has(args, 'w')) { } else if (args_has(args, 'r') || args_has(args, 'w')) {
/* -r or -w implies -a if user does not exist. */ /* -r or -w implies -a if the entry does not exist. */
if (server_acl_user_find(pw->pw_uid) == NULL) if (!server_acl_find(id, flags))
server_acl_user_allow(pw->pw_uid); server_acl_allow(id, flags);
} }
if (args_has(args, 'w')) { if (args_has(args, 'w')) {
if (server_acl_user_find(pw->pw_uid) == NULL) { if (!server_acl_find(id, flags)) {
cmdq_error(item, "user %s not found", pw->pw_name); cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
server_acl_user_allow_write(pw->pw_uid); server_acl_allow_write(id, flags);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_has(args, 'r')) { if (args_has(args, 'r')) {
if (server_acl_user_find(pw->pw_uid) == NULL) { if (!server_acl_find(id, flags)) {
cmdq_error(item, "user %s not found", pw->pw_name); cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
server_acl_user_deny_write(pw->pw_uid); server_acl_deny_write(id, flags);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-set-buffer.c,v 1.38 2026/08/24 07:05:23 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -57,30 +57,30 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct client *tc = cmdq_get_target_client(item); struct client *tc = cmdq_get_target_client(item);
struct paste_buffer *pb; struct paste_buffer *pb = NULL;
char *bufdata, *cause; char *bufname = NULL, *bufdata = NULL, *cause = NULL;
const char *bufname, *olddata; const char *olddata;
size_t bufsize, newsize; size_t bufsize = 0, newsize;
bufname = args_get(args, 'b'); if (args_get(args, 'b') != NULL) {
if (bufname == NULL) bufname = xstrdup(args_get(args, 'b'));
pb = NULL;
else
pb = paste_get_name(bufname); pb = paste_get_name(bufname);
}
if (cmd_get_entry(self) == &cmd_delete_buffer_entry) { if (cmd_get_entry(self) == &cmd_delete_buffer_entry) {
if (pb == NULL) { if (pb == NULL) {
if (bufname != NULL) { if (bufname != NULL) {
cmdq_error(item, "unknown buffer: %s", bufname); cmdq_error(item, "unknown buffer: %s", bufname);
return (CMD_RETURN_ERROR); goto fail;
} }
pb = paste_get_top(&bufname); pb = paste_get_top(&bufname);
} }
if (pb == NULL) { if (pb == NULL) {
cmdq_error(item, "no buffer"); cmdq_error(item, "no buffer");
return (CMD_RETURN_ERROR); goto fail;
} }
paste_free(pb); paste_free(pb);
free(bufname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
@@ -88,31 +88,30 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
if (pb == NULL) { if (pb == NULL) {
if (bufname != NULL) { if (bufname != NULL) {
cmdq_error(item, "unknown buffer: %s", bufname); cmdq_error(item, "unknown buffer: %s", bufname);
return (CMD_RETURN_ERROR); goto fail;
} }
pb = paste_get_top(&bufname); pb = paste_get_top(&bufname);
} }
if (pb == NULL) { if (pb == NULL) {
cmdq_error(item, "no buffer"); cmdq_error(item, "no buffer");
return (CMD_RETURN_ERROR); goto fail;
} }
if (paste_rename(bufname, args_get(args, 'n'), &cause) != 0) { if (paste_rename(bufname, args_get(args, 'n'), &cause) != 0) {
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
free(cause); goto fail;
return (CMD_RETURN_ERROR);
} }
free(bufname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (args_count(args) != 1) { if (args_count(args) != 1) {
cmdq_error(item, "no data specified"); cmdq_error(item, "no data specified");
return (CMD_RETURN_ERROR); goto fail;
} }
if ((newsize = strlen(args_string(args, 0))) == 0) if ((newsize = strlen(args_string(args, 0))) == 0) {
free(bufname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
}
bufsize = 0;
bufdata = NULL;
if (args_has(args, 'a') && pb != NULL) { if (args_has(args, 'a') && pb != NULL) {
olddata = paste_buffer_data(pb, &bufsize); olddata = paste_buffer_data(pb, &bufsize);
@@ -126,12 +125,17 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
if (paste_set(bufdata, bufsize, bufname, &cause) != 0) { if (paste_set(bufdata, bufsize, bufname, &cause) != 0) {
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
free(bufdata); goto fail;
free(cause);
return (CMD_RETURN_ERROR);
} }
if (args_has(args, 'w') && tc != NULL) if (args_has(args, 'w') && tc != NULL)
tty_set_selection(&tc->tty, "", bufdata, bufsize); tty_set_selection(&tc->tty, "", bufdata, bufsize);
free(bufname);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
fail:
free(bufdata);
free(bufname);
free(cause);
return (CMD_RETURN_ERROR);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-set-environment.c,v 1.29 2025/04/09 06:27:43 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-set-option.c,v 1.146 2026/07/10 15:20:06 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -31,6 +31,10 @@ static enum args_parse_type cmd_set_option_args_parse(struct args *,
u_int, char **); u_int, char **);
static enum cmd_retval cmd_set_option_exec(struct cmd *, static enum cmd_retval cmd_set_option_exec(struct cmd *,
struct cmdq_item *); struct cmdq_item *);
static enum cmd_retval cmd_set_hook_event_exec(struct cmd *,
struct cmdq_item *);
static enum cmd_retval cmd_set_hook_monitor_exec(struct cmdq_item *,
struct args *, int);
const struct cmd_entry cmd_set_option_entry = { const struct cmd_entry cmd_set_option_entry = {
.name = "set-option", .name = "set-option",
@@ -62,8 +66,9 @@ const struct cmd_entry cmd_set_hook_entry = {
.name = "set-hook", .name = "set-hook",
.alias = NULL, .alias = NULL,
.args = { "agpRt:uw", 1, 2, cmd_set_option_args_parse }, .args = { "agpERTt:uB:w", 0, 2, cmd_set_option_args_parse },
.usage = "[-agpRuw] " CMD_TARGET_PANE_USAGE " hook [command]", .usage = "[-agpERTuw] [-B name:what:format] " CMD_TARGET_PANE_USAGE " "
"[hook] [command]",
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
@@ -72,14 +77,152 @@ const struct cmd_entry cmd_set_hook_entry = {
}; };
static enum args_parse_type static enum args_parse_type
cmd_set_option_args_parse(__unused struct args *args, u_int idx, cmd_set_option_args_parse(struct args *args, u_int idx,
__unused char **cause) __unused char **cause)
{ {
if (args_has(args, 'B'))
return (ARGS_PARSE_COMMANDS_OR_STRING);
if (idx == 1) if (idx == 1)
return (ARGS_PARSE_COMMANDS_OR_STRING); return (ARGS_PARSE_COMMANDS_OR_STRING);
return (ARGS_PARSE_STRING); return (ARGS_PARSE_STRING);
} }
static enum cmd_retval
cmd_set_hook_event_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 event_payload *ep;
struct client *c;
char *argument;
if (args_count(args) == 0) {
cmdq_error(item, "missing argument");
return (CMD_RETURN_ERROR);
}
if (args_count(args) != 1) {
cmdq_error(item, "too many arguments");
return (CMD_RETURN_ERROR);
}
argument = format_single_from_target(item, args_string(args, 0));
if (*argument != '@') {
cmdq_error(item, "event name must start with @");
free(argument);
return (CMD_RETURN_ERROR);
}
ep = event_payload_create();
event_payload_set_target(ep, target);
c = cmdq_get_client(item);
if (c != NULL)
event_payload_set_client(ep, "client", c);
if (target->s != NULL)
event_payload_set_session(ep, "session", target->s);
if (target->w != NULL)
event_payload_set_window(ep, "window", target->w);
if (target->wl != NULL)
event_payload_set_int(ep, "window_index", target->wl->idx);
else if (target->idx != -1)
event_payload_set_int(ep, "window_index", target->idx);
if (target->wp != NULL)
event_payload_set_pane(ep, "pane", target->wp);
events_fire(argument, ep);
free(argument);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_set_hook_monitor_exec(struct cmdq_item *item, struct args *args, int window)
{
struct cmd_find_state *target = cmdq_get_target(item), fs;
struct options *oo;
struct options_entry *o;
struct session *s = NULL;
char *cause = NULL, *name = NULL, *format = NULL;
char *expanded = NULL, *newvalue = NULL;
const char *value, *old;
enum monitor_type type;
int id, scope, flags = 0;
if (args_count(args) > 1) {
cmdq_error(item, "too many arguments");
return (CMD_RETURN_ERROR);
}
value = args_get(args, 'B');
if (args_has(args, 'u')) {
if (monitor_parse(value, &name, &type, &id, &format) != 0)
name = xstrdup(value);
free(format);
format = NULL;
} else {
if (monitor_parse(value, &name, &type, &id, &format) != 0) {
cmdq_error(item, "invalid subscription: %s", value);
return (CMD_RETURN_ERROR);
}
}
if (*name != '@') {
cmdq_error(item, "monitor hook name must start with @");
goto fail;
}
scope = options_scope_from_name(args, window, name, target, &oo,
&cause);
if (scope == OPTIONS_TABLE_NONE) {
cmdq_error(item, "%s", cause);
free(cause);
goto fail;
}
cmd_find_copy_state(&fs, target);
if (args_has(args, 'u')) {
hooks_monitor_remove(oo, name);
goto out;
}
if (args_count(args) != 0) {
value = args_string(args, 0);
if (args_has(args, 'F')) {
expanded = format_single_from_target(item, value);
value = expanded;
}
o = options_get_only(oo, name);
if (!args_has(args, 'o') || o == NULL) {
if (args_has(args, 'a') && o != NULL) {
old = options_get_string(oo, name);
xasprintf(&newvalue, "%s%s", old, value);
value = newvalue;
}
options_set_string(oo, name, 0, "%s", value);
options_push_changes(name);
}
}
if (oo != global_options &&
oo != global_s_options &&
oo != global_w_options)
s = target->s;
if (args_has(args, 'T'))
flags |= MONITOR_NOTIFY_TRUE;
hooks_monitor_add(item, oo, name, type, id, format, flags, &fs, s);
out:
free(newvalue);
free(expanded);
free(name);
free(format);
return (CMD_RETURN_NORMAL);
fail:
free(newvalue);
free(expanded);
free(name);
free(format);
return (CMD_RETURN_ERROR);
}
static enum cmd_retval static enum cmd_retval
cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
{ {
@@ -89,26 +232,34 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
struct window_pane *loop; struct window_pane *loop;
struct options *oo; struct options *oo;
struct options_entry *parent, *o, *po; struct options_entry *parent, *o, *po;
char *name, *argument, *expanded = NULL; char *name, *argument, *cause;
char *cause; char *expanded = NULL, *array_key = NULL;
const char *value; const char *value;
int window, idx, already, error, ambiguous; int window, already, error, ambiguous;
int scope; int scope;
window = (cmd_get_entry(self) == &cmd_set_window_option_entry); window = (cmd_get_entry(self) == &cmd_set_window_option_entry);
if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'E'))
return (cmd_set_hook_event_exec(self, item));
if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'B'))
return (cmd_set_hook_monitor_exec(item, args, window));
if (args_count(args) == 0) {
cmdq_error(item, "missing argument");
return (CMD_RETURN_ERROR);
}
/* Expand argument. */ /* Expand argument. */
argument = format_single_from_target(item, args_string(args, 0)); argument = format_single_from_target(item, args_string(args, 0));
/* If set-hook -R, fire the hook straight away. */ /* If set-hook -R, fire the hook straight away. */
if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'R')) { if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'R')) {
notify_hook(item, argument); hooks_run(item, argument);
free(argument); free(argument);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
/* Parse option name and index. */ /* Parse option name and array key. */
name = options_match(argument, &idx, &ambiguous); name = options_match(argument, &array_key, &ambiguous);
if (name == NULL) { if (name == NULL) {
if (args_has(args, 'q')) if (args_has(args, 'q'))
goto out; goto out;
@@ -140,21 +291,23 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
o = options_get_only(oo, name); o = options_get_only(oo, name);
parent = options_get(oo, name); parent = options_get(oo, name);
/* Check that array options and indexes match up. */ /* Check that array options and keys match up. */
if (idx != -1 && (*name == '@' || !options_is_array(parent))) { if (array_key != NULL && (*name == '@' || !options_is_array(parent))) {
cmdq_error(item, "not an array: %s", argument); cmdq_error(item, "not an array: %s", argument);
goto fail; goto fail;
} }
/* With -o, check this option is not already set. */ /* With -o, check this option is not already set. */
if (!args_has(args, 'u') && args_has(args, 'o')) { if (!args_has(args, 'u') && args_has(args, 'o')) {
if (idx == -1) if (array_key == NULL)
already = (o != NULL); already = (o != NULL);
else { else {
if (o == NULL) if (o == NULL)
already = 0; already = 0;
else if (options_array_get(o, array_key) != NULL)
already = 1;
else else
already = (options_array_get(o, idx) != NULL); already = 0;
} }
if (already) { if (already) {
if (args_has(args, 'q')) if (args_has(args, 'q'))
@@ -170,7 +323,8 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
po = options_get_only(loop->options, name); po = options_get_only(loop->options, name);
if (po == NULL) if (po == NULL)
continue; continue;
if (options_remove_or_default(po, idx, &cause) != 0) { if (options_remove_or_default(po, array_key,
&cause) != 0) {
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
free(cause); free(cause);
goto fail; goto fail;
@@ -180,7 +334,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'u') || args_has(args, 'U')) { if (args_has(args, 'u') || args_has(args, 'U')) {
if (o == NULL) if (o == NULL)
goto out; goto out;
if (options_remove_or_default(o, idx, &cause) != 0) { if (options_remove_or_default(o, array_key, &cause) != 0) {
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
free(cause); free(cause);
goto fail; goto fail;
@@ -191,7 +345,9 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
goto fail; goto fail;
} }
options_set_string(oo, name, append, "%s", value); options_set_string(oo, name, append, "%s", value);
} else if (idx == -1 && !options_is_array(parent)) { if (cmd_get_entry(self) == &cmd_set_hook_entry)
hooks_add_event(name);
} else if (array_key == NULL && !options_is_array(parent)) {
error = options_from_string(oo, options_table_entry(parent), error = options_from_string(oo, options_table_entry(parent),
options_table_entry(parent)->name, value, options_table_entry(parent)->name, value,
args_has(args, 'a'), &cause); args_has(args, 'a'), &cause);
@@ -207,7 +363,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
} }
if (o == NULL) if (o == NULL)
o = options_empty(oo, options_table_entry(parent)); o = options_empty(oo, options_table_entry(parent));
if (idx == -1) { if (array_key == NULL) {
if (!append) if (!append)
options_array_clear(o); options_array_clear(o);
if (options_array_assign(o, value, &cause) != 0) { if (options_array_assign(o, value, &cause) != 0) {
@@ -215,7 +371,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
free(cause); free(cause);
goto fail; goto fail;
} }
} else if (options_array_set(o, idx, value, append, } else if (options_array_set(o, array_key, value, append,
&cause) != 0) { &cause) != 0) {
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
free(cause); free(cause);
@@ -229,11 +385,13 @@ out:
free(argument); free(argument);
free(expanded); free(expanded);
free(name); free(name);
free(array_key);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
fail: fail:
free(argument); free(argument);
free(expanded); free(expanded);
free(name); free(name);
free(array_key);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-show-environment.c,v 1.29 2025/04/09 06:27:43 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-show-messages.c,v 1.37 2025/11/18 08:37:54 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-show-options.c,v 1.76 2026/07/27 19:15:58 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -27,10 +27,23 @@
* Show options. * Show options.
*/ */
#define SHOW_OPTIONS_TEMPLATE \
"#{?option_value_only," \
"#{option_value}," \
"#{option_name}#{?option_has_array_key," \
"[#{option_array_key}],}" \
"#{?option_is_parent,*,}" \
"#{?option_has_value, " \
"#{?option_is_string,#{q/a:option_value},#{option_value}},}}"
#define SHOW_HOOKS_MONITOR_TEMPLATE \
"#{option_name}:#{hook_monitor_target}:#{hook_monitor_format}"
static enum cmd_retval cmd_show_options_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_show_options_exec(struct cmd *, struct cmdq_item *);
static void cmd_show_options_print(struct cmd *, struct cmdq_item *, static void cmd_show_options_print(struct cmd *, struct cmdq_item *,
struct options_entry *, int, int); struct options_entry *, const char *, int);
static void cmd_show_hooks_print_monitor(struct cmd *,
struct cmdq_item *, struct options_entry *);
static enum cmd_retval cmd_show_options_all(struct cmd *, struct cmdq_item *, static enum cmd_retval cmd_show_options_all(struct cmd *, struct cmdq_item *,
int, struct options *); int, struct options *);
@@ -38,8 +51,8 @@ const struct cmd_entry cmd_show_options_entry = {
.name = "show-options", .name = "show-options",
.alias = "show", .alias = "show",
.args = { "AgHpqst:vw", 0, 1, NULL }, .args = { "AgF:Hpqst:vw", 0, 1, NULL },
.usage = "[-AgHpqsvw] " CMD_TARGET_PANE_USAGE " [option]", .usage = "[-AgHpqsvw] [-F format] " CMD_TARGET_PANE_USAGE " [option]",
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
@@ -51,8 +64,8 @@ const struct cmd_entry cmd_show_window_options_entry = {
.name = "show-window-options", .name = "show-window-options",
.alias = "showw", .alias = "showw",
.args = { "gvt:", 0, 1, NULL }, .args = { "F:gvt:", 0, 1, NULL },
.usage = "[-gv] " CMD_TARGET_WINDOW_USAGE " [option]", .usage = "[-gv] [-F format] " CMD_TARGET_WINDOW_USAGE " [option]",
.target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL }, .target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL },
@@ -64,8 +77,8 @@ const struct cmd_entry cmd_show_hooks_entry = {
.name = "show-hooks", .name = "show-hooks",
.alias = NULL, .alias = NULL,
.args = { "gpt:w", 0, 1, NULL }, .args = { "BF:gpt:w", 0, 1, NULL },
.usage = "[-gpw] " CMD_TARGET_PANE_USAGE " [hook]", .usage = "[-Bgpw] [-F format] " CMD_TARGET_PANE_USAGE " [hook]",
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
@@ -80,7 +93,8 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item); struct cmd_find_state *target = cmdq_get_target(item);
struct options *oo; struct options *oo;
char *argument, *name = NULL, *cause; char *argument, *name = NULL, *cause;
int window, idx, ambiguous, parent, scope; char *array_key = NULL;
int window, ambiguous, parent, print_parent, scope;
struct options_entry *o; struct options_entry *o;
window = (cmd_get_entry(self) == &cmd_show_window_options_entry); window = (cmd_get_entry(self) == &cmd_show_window_options_entry);
@@ -95,11 +109,20 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
free(cause); free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (cmd_get_entry(self) == &cmd_show_hooks_entry &&
args_has(args, 'B')) {
o = options_first(oo);
while (o != NULL) {
cmd_show_hooks_print_monitor(self, item, o);
o = options_next(o);
}
return (CMD_RETURN_NORMAL);
}
return (cmd_show_options_all(self, item, scope, oo)); return (cmd_show_options_all(self, item, scope, oo));
} }
argument = format_single_from_target(item, args_string(args, 0)); argument = format_single_from_target(item, args_string(args, 0));
name = options_match(argument, &idx, &ambiguous); name = options_match(argument, &array_key, &ambiguous);
if (name == NULL) { if (name == NULL) {
if (args_has(args, 'q')) if (args_has(args, 'q'))
goto out; goto out;
@@ -124,9 +147,20 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
parent = 1; parent = 1;
} else } else
parent = 0; parent = 0;
if (o != NULL) if (o != NULL) {
cmd_show_options_print(self, item, o, idx, parent); if (cmd_get_entry(self) == &cmd_show_hooks_entry &&
else if (*name == '@') { args_has(args, 'B'))
cmd_show_hooks_print_monitor(self, item, o);
else {
print_parent = parent;
if (array_key == NULL && options_is_array(o) &&
options_array_first(o) == NULL) {
print_parent = 0;
}
cmd_show_options_print(self, item, o, array_key,
print_parent);
}
} else if (*name == '@') {
if (args_has(args, 'q')) if (args_has(args, 'q'))
goto out; goto out;
cmdq_error(item, "invalid option: %s", argument); cmdq_error(item, "invalid option: %s", argument);
@@ -135,64 +169,171 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
out: out:
free(name); free(name);
free(array_key);
free(argument); free(argument);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
fail: fail:
free(name); free(name);
free(array_key);
free(argument); free(argument);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
static void static void
cmd_show_options_print(struct cmd *self, struct cmdq_item *item, cmd_show_options_print(struct cmd *self, struct cmdq_item *item,
struct options_entry *o, int idx, int parent) struct options_entry *o, const char *array_key, int parent)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
struct options_array_item *a; struct options_array_item *a;
struct format_tree *ft;
const char *name = options_name(o); const char *name = options_name(o);
char *value, *tmp = NULL, *escaped; const char *template = args_get(args, 'F');
char *value, *line;
struct timeval tv = { 0 };
u_int fire_count;
time_t fire_time;
int is_hook = 0, is_user = 0;
int has_value = 1;
const struct options_table_entry *oe = options_table_entry(o);
if (idx != -1) { if (array_key != NULL)
xasprintf(&tmp, "%s[%d]", name, idx); value = options_to_string(o, array_key, 0);
name = tmp; else if (options_is_array(o)) {
} else {
if (options_is_array(o)) {
a = options_array_first(o); a = options_array_first(o);
if (a == NULL) { if (a != NULL) {
if (!args_has(args, 'v'))
cmdq_print(item, "%s", name);
return;
}
while (a != NULL) { while (a != NULL) {
idx = options_array_item_index(a); array_key = options_array_item_key(a);
cmd_show_options_print(self, item, o, idx, cmd_show_options_print(self, item, o, array_key,
parent); parent);
a = options_array_next(a); a = options_array_next(a);
} }
return; return;
} }
} if (template == NULL && args_has(args, 'v'))
return;
value = xstrdup("");
has_value = 0;
} else
value = options_to_string(o, NULL, 0);
value = options_to_string(o, idx, 0); if (template == NULL)
if (args_has(args, 'v')) template = SHOW_OPTIONS_TEMPLATE;
cmdq_print(item, "%s", value);
else if (options_is_string(o)) { if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK))
escaped = args_escape(value); is_hook = 1;
if (parent) else if (oe == NULL)
cmdq_print(item, "%s* %s", name, escaped); is_user = 1;
else
cmdq_print(item, "%s %s", name, escaped); ft = format_create_from_target(item);
free(escaped); format_add(ft, "option_name", "%s", name);
format_add(ft, "option_value", "%s", value);
format_add(ft, "option_value_only", "%d", args_has(args, 'v'));
format_add(ft, "option_is_parent", "%d", parent);
format_add(ft, "option_is_array", "%d", options_is_array(o));
format_add(ft, "option_is_string", "%d", options_is_string(o));
format_add(ft, "option_is_hook", "%d", is_hook);
format_add(ft, "option_is_user", "%d", is_user);
format_add(ft, "option_has_value", "%d", has_value);
if (cmd_get_entry(self) == &cmd_show_hooks_entry) {
fire_count = options_get_fire_count(o);
format_add(ft, "hook_fire_count", "%u", fire_count);
fire_time = options_get_fire_time(o);
if (fire_time != 0) {
tv.tv_sec = fire_time;
format_add_tv(ft, "hook_fire_time", &tv);
}
}
if (array_key != NULL) {
format_add(ft, "option_array_key", "%s", array_key);
format_add(ft, "option_has_array_key", "1");
} else { } else {
if (parent) format_add(ft, "option_array_key", "%s", "");
cmdq_print(item, "%s* %s", name, value); format_add(ft, "option_has_array_key", "0");
else
cmdq_print(item, "%s %s", name, value);
} }
free(value); line = format_expand(ft, template);
format_free(ft);
free(tmp); cmdq_print(item, "%s", line);
free(line);
free(value);
}
static void
cmd_show_hooks_print_monitor(struct cmd *self, struct cmdq_item *item,
struct options_entry *o)
{
struct args *args = cmd_get_args(self);
struct format_tree *ft;
enum monitor_type type;
const char *template = args_get(args, 'F'), *format;
char *value, *target, *line;
struct timeval tv = { 0 };
u_int fire_count;
time_t fire_time;
int id;
value = hooks_monitor_to_string(o);
if (value == NULL)
return;
if (!hooks_monitor_get(o, &type, &id, &format)) {
free(value);
return;
}
if (template == NULL)
template = SHOW_HOOKS_MONITOR_TEMPLATE;
switch (type) {
case MONITOR_SESSION:
target = xstrdup("");
break;
case MONITOR_PANE:
xasprintf(&target, "%%%d", id);
break;
case MONITOR_ALL_PANES:
target = xstrdup("%*");
break;
case MONITOR_WINDOW:
xasprintf(&target, "@%d", id);
break;
case MONITOR_ALL_WINDOWS:
target = xstrdup("@*");
break;
}
ft = format_create_from_target(item);
format_add(ft, "option_name", "%s", options_name(o));
format_add(ft, "option_value", "%s", value);
format_add(ft, "option_value_only", "%d", 0);
format_add(ft, "option_is_parent", "%d", 0);
format_add(ft, "option_is_array", "%d", 0);
format_add(ft, "option_is_string", "%d", 1);
format_add(ft, "option_is_hook", "%d", 1);
format_add(ft, "option_is_user", "%d", 1);
format_add(ft, "option_has_value", "%d", 1);
format_add(ft, "option_array_key", "%s", "");
format_add(ft, "option_has_array_key", "0");
format_add(ft, "hook_monitor_target", "%s", target);
format_add(ft, "hook_monitor_format", "%s", format);
fire_count = hooks_monitor_get_fire_count(o);
format_add(ft, "hook_fire_count", "%u", fire_count);
fire_time = hooks_monitor_get_fire_time(o);
if (fire_time != 0) {
tv.tv_sec = fire_time;
format_add_tv(ft, "hook_fire_time", &tv);
}
line = format_expand(ft, template);
format_free(ft);
cmdq_print(item, "%s", line);
free(line);
free(target);
free(value);
} }
static enum cmd_retval static enum cmd_retval
@@ -202,19 +343,30 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope,
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
const struct options_table_entry *oe; const struct options_table_entry *oe;
struct options_entry *o; struct options_entry *o;
struct options_array_item *a;
const char *name; const char *name;
u_int idx; int parent, is_user_hook;
int parent;
if (cmd_get_entry(self) != &cmd_show_hooks_entry) {
o = options_first(oo); o = options_first(oo);
while (o != NULL) { while (o != NULL) {
if (options_table_entry(o) == NULL) if (options_table_entry(o) == NULL) {
cmd_show_options_print(self, item, o, -1, 0); name = options_name(o);
o = options_next(o); is_user_hook = 0;
if (*name == '@') {
if (hooks_is_event(name) ||
options_get_monitor_data(o) != NULL) {
is_user_hook = 1;
} }
} }
if (cmd_get_entry(self) != &cmd_show_hooks_entry) {
if (!is_user_hook || args_has(args, 'H')) {
cmd_show_options_print(self, item, o,
NULL, 0);
}
} else if (is_user_hook)
cmd_show_options_print(self, item, o, NULL, 0);
}
o = options_next(o);
}
for (oe = options_table; oe->name != NULL; oe++) { for (oe = options_table; oe->name != NULL; oe++) {
if (~oe->scope & scope) if (~oe->scope & scope)
continue; continue;
@@ -237,24 +389,7 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope,
} else } else
parent = 0; parent = 0;
if (!options_is_array(o)) cmd_show_options_print(self, item, o, NULL, parent);
cmd_show_options_print(self, item, o, -1, parent);
else if ((a = options_array_first(o)) == NULL) {
if (!args_has(args, 'v')) {
name = options_name(o);
if (parent)
cmdq_print(item, "%s*", name);
else
cmdq_print(item, "%s", name);
}
} else {
while (a != NULL) {
idx = options_array_item_index(a);
cmd_show_options_print(self, item, o, idx,
parent);
a = options_array_next(a);
}
}
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-show-prompt-history.c,v 1.5 2026/06/25 11:39:11 nicm Exp $ */
/* /*
* Copyright (c) 2021 Anindya Mukherjee <anindya49@hotmail.com> * Copyright (c) 2021 Anindya Mukherjee <anindya49@hotmail.com>
@@ -55,51 +55,44 @@ cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
const char *typestr = args_get(args, 'T'); const char *typestr = args_get(args, 'T');
enum prompt_type type; enum prompt_type type;
u_int tidx, hidx; u_int t, h;
const char *v;
if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) { if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) {
if (typestr == NULL) { if (typestr == NULL) {
for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { for (t = 0; t < PROMPT_NTYPES; t++)
free(status_prompt_hlist[tidx]); prompt_history_clear(t);
status_prompt_hlist[tidx] = NULL;
status_prompt_hsize[tidx] = 0;
}
} else { } else {
type = status_prompt_type(typestr); type = prompt_type(typestr);
if (type == PROMPT_TYPE_INVALID) { if (type == PROMPT_TYPE_INVALID) {
cmdq_error(item, "invalid type: %s", typestr); cmdq_error(item, "invalid type: %s", typestr);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
free(status_prompt_hlist[type]); prompt_history_clear(type);
status_prompt_hlist[type] = NULL;
status_prompt_hsize[type] = 0;
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (typestr == NULL) { if (typestr == NULL) {
for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { for (t = 0; t < PROMPT_NTYPES; t++) {
cmdq_print(item, "History for %s:\n", typestr = prompt_type_string(t);
status_prompt_type_string(tidx)); cmdq_print(item, "History for %s:\n", typestr);
for (hidx = 0; hidx < status_prompt_hsize[tidx]; for (h = 0; h < prompt_history_size(t); h++) {
hidx++) { v = prompt_history_get(t, h);
cmdq_print(item, "%d: %s", hidx + 1, cmdq_print(item, "%d: %s", h + 1, v);
status_prompt_hlist[tidx][hidx]);
} }
cmdq_print(item, "%s", ""); cmdq_print(item, "%s", "");
} }
} else { } else {
type = status_prompt_type(typestr); type = prompt_type(typestr);
if (type == PROMPT_TYPE_INVALID) { if (type == PROMPT_TYPE_INVALID) {
cmdq_error(item, "invalid type: %s", typestr); cmdq_error(item, "invalid type: %s", typestr);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
cmdq_print(item, "History for %s:\n", cmdq_print(item, "History for %s:\n", prompt_type_string(type));
status_prompt_type_string(type)); for (h = 0; h < prompt_history_size(type); h++) {
for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) { v = prompt_history_get(type, h);
cmdq_print(item, "%d: %s", hidx + 1, cmdq_print(item, "%d: %s", h + 1, v);
status_prompt_hlist[type][hidx]);
} }
cmdq_print(item, "%s", ""); cmdq_print(item, "%s", "");
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-source-file.c,v 1.63 2026/08/18 08:05:05 nicm Exp $ */
/* /*
* Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org> * Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org>
@@ -50,6 +50,7 @@ const struct cmd_entry cmd_source_file_entry = {
struct cmd_source_file_data { struct cmd_source_file_data {
struct cmdq_item *item; struct cmdq_item *item;
struct client *client;
int flags; int flags;
struct cmdq_item *after; struct cmdq_item *after;
@@ -60,10 +61,24 @@ struct cmd_source_file_data {
u_int nfiles; u_int nfiles;
}; };
static enum cmd_retval static void
cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data) cmd_source_file_free_data(struct cmd_source_file_data *cdata)
{ {
struct client *c = cmdq_get_client(item); u_int i;
for (i = 0; i < cdata->nfiles; i++)
free(cdata->files[i]);
free(cdata->files);
if (cdata->client != NULL)
server_client_unref(cdata->client);
free(cdata);
}
static enum cmd_retval
cmd_source_file_complete_cb(struct cmdq_item *item, void *data)
{
struct cmd_source_file_data *cdata = data;
struct client *c = cdata->client;
if (c == NULL) { if (c == NULL) {
cmd_source_file_depth--; cmd_source_file_depth--;
@@ -74,36 +89,36 @@ cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
} }
cfg_print_causes(item); cfg_print_causes(item);
cmd_source_file_free_data(cdata);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
static void static void
cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata) cmd_source_file_complete(struct cmd_source_file_data *cdata)
{ {
struct client *c = cdata->client;
struct cmdq_item *new_item; struct cmdq_item *new_item;
u_int i;
if (cfg_finished) { if (!cfg_finished) {
cmd_source_file_free_data(cdata);
return;
}
if (cdata->retval == CMD_RETURN_ERROR && if (cdata->retval == CMD_RETURN_ERROR &&
c != NULL && c != NULL &&
c->session == NULL) c->session == NULL)
c->retval = 1; c->retval = 1;
new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL); new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
cmdq_insert_after(cdata->after, new_item); cmdq_insert_after(cdata->after, new_item);
} }
for (i = 0; i < cdata->nfiles; i++)
free(cdata->files[i]);
free(cdata->files);
free(cdata);
}
static void static void
cmd_source_file_done(struct client *c, const char *path, int error, cmd_source_file_done(__unused struct client *oc, const char *path,
int closed, struct evbuffer *buffer, void *data) int error, int closed, struct evbuffer *buffer, void *data)
{ {
struct cmd_source_file_data *cdata = data; struct cmd_source_file_data *cdata = data;
struct cmdq_item *item = cdata->item; struct cmdq_item *item = cdata->item;
struct client *c = cdata->client;
void *bdata = EVBUFFER_DATA(buffer); void *bdata = EVBUFFER_DATA(buffer);
size_t bsize = EVBUFFER_LENGTH(buffer); size_t bsize = EVBUFFER_LENGTH(buffer);
u_int n; u_int n;
@@ -116,7 +131,7 @@ cmd_source_file_done(struct client *c, const char *path, int error,
if (error != 0) if (error != 0)
cmdq_error(item, "%s: %s", strerror(error), path); cmdq_error(item, "%s: %s", strerror(error), path);
else if (bsize != 0) { else if (bsize != 0) {
if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after, if (load_cfg_from_buffer(bdata, bsize, path, cdata->after,
target, cdata->flags, &new_item) < 0) target, cdata->flags, &new_item) < 0)
cdata->retval = CMD_RETURN_ERROR; cdata->retval = CMD_RETURN_ERROR;
else if (new_item != NULL) else if (new_item != NULL)
@@ -127,7 +142,7 @@ cmd_source_file_done(struct client *c, const char *path, int error,
if (n < cdata->nfiles) if (n < cdata->nfiles)
file_read(c, cdata->files[n], cmd_source_file_done, cdata); file_read(c, cdata->files[n], cmd_source_file_done, cdata);
else { else {
cmd_source_file_complete(c, cdata); cmd_source_file_complete(cdata);
cmdq_continue(item); cmdq_continue(item);
} }
} }
@@ -187,6 +202,9 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
cdata = xcalloc(1, sizeof *cdata); cdata = xcalloc(1, sizeof *cdata);
cdata->item = item; cdata->item = item;
cdata->client = c;
if (c != NULL)
c->references++;
if (args_has(args, 'q')) if (args_has(args, 'q'))
cdata->flags |= CMD_PARSE_QUIET; cdata->flags |= CMD_PARSE_QUIET;
@@ -249,7 +267,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
file_read(c, cdata->files[0], cmd_source_file_done, cdata); file_read(c, cdata->files[0], cmd_source_file_done, cdata);
retval = CMD_RETURN_WAIT; retval = CMD_RETURN_WAIT;
} else } else
cmd_source_file_complete(c, cdata); cmd_source_file_complete(cdata);
free(cwd); free(cwd);
return (retval); return (retval);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-split-window.c,v 1.150 2026/08/20 09:19:24 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -27,22 +27,44 @@
#include "tmux.h" #include "tmux.h"
/* /*
* Split a window (add a new pane). * Create a new pane.
*/ */
#define SPLIT_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}" #define SPLIT_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}"
static enum cmd_retval cmd_split_window_exec(struct cmd *, static enum cmd_retval cmd_split_window_exec(struct cmd *, struct cmdq_item *);
struct cmdq_item *); static void cmd_split_window_mouse_resize(struct client *,
struct mouse_event *);
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] "
"[-c start-directory] [-e environment] "
"[-F format] [-l size] [-m message] [-p percentage] "
"[-s style] [-S active-border-style] "
"[-R inactive-border-style] [-T title] [-x width] [-y height] "
"[-X x-position] [-Y y-position] " CMD_TARGET_PANE_USAGE " "
"[shell-command [argument ...]]",
.target = { 't', CMD_FIND_PANE, 0 },
.flags = 0,
.exec = cmd_split_window_exec
};
const struct cmd_entry cmd_split_window_entry = { const struct cmd_entry cmd_split_window_entry = {
.name = "split-window", .name = "split-window",
.alias = "splitw", .alias = "splitw",
.args = { "bc:de:fF:hIl:p:Pt:vZ", 0, -1, NULL }, .args = { "bB:c:de:EfF:hIkl:m:p:PR:s:S:t:T:vWZ", 0, -1, NULL },
.usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " .usage = "[-bdefhIklPvWZ] [-B border-lines] [-c start-directory] "
"[-F format] [-l size] " CMD_TARGET_PANE_USAGE "[-e environment] [-F format] [-l size] [-m message] "
" [shell-command [argument ...]]", "[-p percentage] [-s style] [-S active-border-style] "
"[-R inactive-border-style] [-T title] "
CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]",
.target = { 't', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 },
@@ -61,65 +83,105 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
struct session *s = target->s; struct session *s = target->s;
struct winlink *wl = target->wl; struct winlink *wl = target->wl;
struct window *w = wl->window; struct window *w = wl->window;
struct window_pane *wp = target->wp, *new_wp; struct window_pane *wp = target->wp, *new_wp = NULL;
enum layout_type type; struct layout_cell *lc = NULL;
struct layout_cell *lc; struct event_payload *ep;
struct cmd_find_state fs; struct cmd_find_state fs;
int size, flags, input; struct key_event *event = cmdq_get_event(item);
const char *template; int input, empty, is_floating, flags = 0;
char *cause = NULL, *cp; int restore_zoom = 0;
const char *template, *style, *value;
char *cause = NULL, *cp, *title;
const struct options_table_entry *oe;
struct args_value *av; struct args_value *av;
u_int count = args_count(args), curval = 0; enum pane_lines lines;
u_int count = args_count(args);
type = LAYOUT_TOPBOTTOM; if (window_active_pane_is_over_zoom(w))
if (args_has(args, 'h')) restore_zoom = 1;
type = LAYOUT_LEFTRIGHT;
/* If the 'p' flag is dropped then this bit can be moved into 'l'. */ if (cmd_get_entry(self) == &cmd_new_pane_entry)
if (args_has(args, 'l') || args_has(args, 'p')) { is_floating = !args_has(args, 'L');
if (args_has(args, 'f')) { else {
if (type == LAYOUT_TOPBOTTOM) if (!window_pane_is_visible(wp))
curval = w->sy; restore_zoom = 0;
else if (!restore_zoom)
curval = w->sx; window_unzoom(w, 1);
} else { is_floating = window_pane_is_floating(wp);
if (type == LAYOUT_TOPBOTTOM) flags |= SPAWN_SPLIT;
curval = wp->sy;
else
curval = wp->sx;
}
} }
size = -1; if (args_has(args, 'O')) {
if (args_has(args, 'l')) { if (!is_floating) {
size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, cmdq_error(item, "modal pane must be floating");
item, &cause);
} else if (args_has(args, 'p')) {
size = args_strtonum_and_expand(args, 'p', 0, 100, item,
&cause);
if (cause == NULL)
size = curval * size / 100;
}
if (cause != NULL) {
cmdq_error(item, "size %s", cause);
free(cause);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (w->modal != NULL) {
cmdq_error(item, "window already has a modal pane");
return (CMD_RETURN_ERROR);
}
}
window_push_zoom(wp->window, 1, args_has(args, 'Z')); if (args_has(args, 'M') && is_floating) {
input = (args_has(args, 'I') && count == 0); if (event == NULL || !event->m.valid || tc == NULL)
return (CMD_RETURN_NORMAL);
}
flags = 0; if (is_floating)
flags |= SPAWN_FLOATING;
if (args_has(args, 'h'))
flags |= SPAWN_HORIZONTAL;
if (args_has(args, 'b')) if (args_has(args, 'b'))
flags |= SPAWN_BEFORE; flags |= SPAWN_BEFORE;
if (args_has(args, 'f')) if (args_has(args, 'f'))
flags |= SPAWN_FULLSIZE; flags |= SPAWN_FULLSIZE;
if (args_has(args, 'd'))
flags |= SPAWN_DETACHED;
if (args_has(args, 'Z'))
flags |= SPAWN_ZOOM;
if (args_has(args, 'O'))
flags |= SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
if (is_floating && args_has(args, 'A'))
flags |= SPAWN_FLOATOVERZOOM;
if ((w->flags & WINDOW_ZOOMED) && (flags & SPAWN_FLOATOVERZOOM))
restore_zoom = 1;
input = args_has(args, 'I');
if (input || (count == 1 && *args_string(args, 0) == '\0')) if (input || (count == 1 && *args_string(args, 0) == '\0'))
empty = 1;
else
empty = args_has(args, 'E');
if (empty &&
count != 0 &&
(count != 1 || *args_string(args, 0) != '\0')) {
cmdq_error(item, "command cannot be given for empty pane");
return (CMD_RETURN_ERROR);
}
if (empty)
flags |= SPAWN_EMPTY; flags |= SPAWN_EMPTY;
lc = layout_split_pane(wp, type, size, flags); if ((value = args_get(args, 'B')) == NULL)
if (lc == NULL) { lines = window_get_pane_lines(w);
cmdq_error(item, "no space for new pane"); else {
oe = options_search("pane-border-lines");
lines = options_find_choice(oe, value, &cause);
if (cause != NULL) {
cmdq_error(item, "pane-border-lines %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
}
if (flags & SPAWN_FLOATING) {
lc = layout_get_floating_cell(item, args, lines, w, wp, flags,
&cause);
} else
lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause);
if (cause != NULL) {
cmdq_error(item, "%s", cause);
free(cause);
if (restore_zoom)
window_pop_zoom(w);
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -141,43 +203,103 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
sc.idx = -1; sc.idx = -1;
sc.cwd = args_get(args, 'c'); sc.cwd = args_get(args, 'c');
sc.flags = flags; sc.flags = flags;
if (args_has(args, 'd'))
sc.flags |= SPAWN_DETACHED;
if (args_has(args, 'Z'))
sc.flags |= SPAWN_ZOOM;
if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { if ((new_wp = spawn_pane(&sc, &cause)) == NULL) {
cmdq_error(item, "create pane failed: %s", cause); cmdq_error(item, "create pane failed: %s", cause);
free(cause); free(cause);
if (sc.argv != NULL) /*
cmd_free_argv(sc.argc, sc.argv); * spawn_pane has already torn the half-built pane down (its
environ_free(sc.environ); * fork-failure path removes the pane and destroys the layout
return (CMD_RETURN_ERROR); * cell), so new_wp is NULL and there is nothing for fail to do.
*/
goto fail;
} }
if (args_has(args, 'K') && args_has(args, 'O'))
new_wp->flags |= PANE_CAPTUREALLKEYS;
if (args_has(args, 'C') && args_has(args, 'O'))
new_wp->flags |= PANE_CLOSEONCLICK;
style = args_get(args, '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);
}
style = args_get(args, 'S');
if (style != NULL) {
if (options_set_string(new_wp->options,
"pane-active-border-style", 0, "%s", style) == NULL) {
cmdq_error(item, "bad active border style: %s", style);
goto fail;
}
}
style = args_get(args, 'R');
if (style != NULL) {
if (options_set_string(new_wp->options, "pane-border-style", 0,
"%s", style) == NULL) {
cmdq_error(item, "bad inactive border style: %s",
style);
goto fail;
}
}
if (args_has(args, 'B'))
options_set_number(new_wp->options, "pane-border-lines", lines);
if (args_has(args, 'k') || args_has(args, 'm')) {
options_set_number(new_wp->options, "remain-on-exit", 3);
if (args_has(args, 'm')) {
options_set_string(new_wp->options,
"remain-on-exit-format", 0, "%s",
args_get(args, 'm'));
}
}
if (args_has(args, 'T')) {
title = format_single_from_target(item, args_get(args, 'T'));
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);
free(title);
}
if (input) { if (input) {
switch (window_pane_start_input(new_wp, item, &cause)) { switch (window_pane_start_input(new_wp, item, &cause)) {
case -1: case -1:
server_client_remove_pane(new_wp);
layout_close_pane(new_wp);
window_remove_pane(wp->window, new_wp);
cmdq_error(item, "%s", cause); cmdq_error(item, "%s", cause);
free(cause); free(cause);
if (sc.argv != NULL) goto fail;
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
return (CMD_RETURN_ERROR);
case 1: case 1:
input = 0; input = 0;
break; break;
} }
} }
if (!args_has(args, 'd')) if (~flags & SPAWN_DETACHED)
cmd_find_from_winlink_pane(current, wl, new_wp, 0); cmd_find_from_winlink_pane(current, wl, new_wp, 0);
if (restore_zoom) {
window_pop_zoom(wp->window); window_pop_zoom(wp->window);
server_redraw_window(wp->window); server_redraw_window(wp->window);
server_status_session(s); } else if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) {
window_pop_zoom(wp->window);
server_redraw_window(wp->window);
}
server_redraw_session(s);
if (args_has(args, 'M') && is_floating) {
tc->tty.mouse_last_pane = new_wp->id;
tc->tty.mouse_drag_update = cmd_split_window_mouse_resize;
cmd_split_window_mouse_resize(tc, &event->m);
}
if (args_has(args, 'P')) { if (args_has(args, 'P')) {
if ((template = args_get(args, 'F')) == NULL) if ((template = args_get(args, 'F')) == NULL)
@@ -195,5 +317,106 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
environ_free(sc.environ); environ_free(sc.environ);
if (input) if (input)
return (CMD_RETURN_WAIT); return (CMD_RETURN_WAIT);
return (CMD_RETURN_NORMAL);
if (args_has(args, 'W')) {
/*
* With -W, block this command queue item until the pane's
* command exits; window_pane_wait_finish will be called to
* continue it.
*/
new_wp->wait_item = item;
return (CMD_RETURN_WAIT);
}
return (CMD_RETURN_NORMAL);
fail:
/*
* If the pane was spawned before we failed, tear it down here; this
* also destroys its layout cell. spawn_pane's own failure path has
* already done this, so new_wp is NULL in that case.
*/
if (new_wp != NULL) {
server_client_remove_pane(new_wp);
if (!is_floating)
layout_close_pane(new_wp);
window_remove_pane(wp->window, new_wp);
}
if (restore_zoom || (~flags & SPAWN_FLOATING))
window_pop_zoom(wp->window);
if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
return (CMD_RETURN_ERROR);
}
static void
cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
{
struct window_pane *wp;
struct window *w;
struct layout_cell *lc;
enum pane_lines lines;
u_int sx, sy;
int x, y, xoff, yoff, border;
if (c->tty.mouse_last_pane == -1)
return;
wp = window_pane_find_by_id(c->tty.mouse_last_pane);
if (wp == NULL || !window_pane_is_floating(wp)) {
c->tty.mouse_drag_update = NULL;
return;
}
w = wp->window;
lc = wp->layout_cell;
x = m->x + m->ox;
y = m->y + m->oy;
if (m->statusat == 0 && y >= (int)m->statuslines)
y -= m->statuslines;
else if (m->statusat > 0 && y >= m->statusat)
y = m->statusat - 1;
lines = window_pane_get_pane_lines(wp);
border = (lines != PANE_LINES_NONE);
if (x >= (int)c->tty.mouse_drag_x) {
xoff = c->tty.mouse_drag_x + border;
sx = x - c->tty.mouse_drag_x + 1;
} else {
sx = c->tty.mouse_drag_x - x + 1;
xoff = c->tty.mouse_drag_x - sx + 1;
if (border)
xoff++;
}
if (y >= (int)c->tty.mouse_drag_y) {
yoff = c->tty.mouse_drag_y + border;
sy = y - c->tty.mouse_drag_y + 1;
} else {
sy = c->tty.mouse_drag_y - y + 1;
yoff = c->tty.mouse_drag_y - sy + 1;
if (border)
yoff++;
}
if (border) {
if (sx <= 2)
sx = PANE_MINIMUM;
else
sx -= 2;
if (sy <= 2)
sy = PANE_MINIMUM;
else
sy -= 2;
}
if (sx < PANE_MINIMUM)
sx = PANE_MINIMUM;
if (sy < PANE_MINIMUM)
sy = PANE_MINIMUM;
layout_set_size(lc, sx, sy, xoff, yoff);
layout_fix_panes(w, NULL);
server_redraw_window(w);
server_redraw_window_borders(w);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-swap-pane.c,v 1.55 2026/07/15 13:02:33 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -42,6 +42,22 @@ const struct cmd_entry cmd_swap_pane_entry = {
.exec = cmd_swap_pane_exec .exec = cmd_swap_pane_exec
}; };
static struct window_pane *
cmd_swap_pane_next_tiled_pane(struct window_pane *wp)
{
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
wp = TAILQ_NEXT(wp, entry);
return (wp);
}
static struct window_pane *
cmd_swap_pane_prev_tiled_pane(struct window_pane *wp)
{
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
wp = TAILQ_PREV(wp, window_panes, entry);
return (wp);
}
static enum cmd_retval static enum cmd_retval
cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
{ {
@@ -52,25 +68,47 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
struct window_pane *tmp_wp, *src_wp, *dst_wp; struct window_pane *tmp_wp, *src_wp, *dst_wp;
struct layout_cell *src_lc, *dst_lc; struct layout_cell *src_lc, *dst_lc;
u_int sx, sy, xoff, yoff; u_int sx, sy, xoff, yoff;
int src_idx, dst_idx;
dst_w = target->wl->window; dst_w = target->wl->window;
dst_wp = target->wp; dst_wp = target->wp;
dst_idx = target->wl->idx;
src_w = source->wl->window; src_w = source->wl->window;
src_wp = source->wp; src_wp = source->wp;
src_idx = source->wl->idx;
if (src_wp == src_w->modal || dst_wp == dst_w->modal) {
cmdq_error(item, "pane is modal");
return (CMD_RETURN_ERROR);
}
if (window_push_zoom(dst_w, 0, args_has(args, 'Z'))) if (window_push_zoom(dst_w, 0, args_has(args, 'Z')))
server_redraw_window(dst_w); server_redraw_window(dst_w);
if (args_has(args, 'D')) { if (args_has(args, 'D')) {
if (window_pane_is_floating(dst_wp)) {
cmdq_error(item, "cannot swap down on floating pane");
return (CMD_RETURN_ERROR);
}
src_w = dst_w; src_w = dst_w;
src_wp = TAILQ_NEXT(dst_wp, entry); src_wp = TAILQ_NEXT(dst_wp, entry);
if (src_wp == NULL) src_wp = cmd_swap_pane_next_tiled_pane(src_wp);
if (src_wp == NULL) {
src_wp = TAILQ_FIRST(&dst_w->panes); src_wp = TAILQ_FIRST(&dst_w->panes);
src_wp = cmd_swap_pane_next_tiled_pane(src_wp);
}
} else if (args_has(args, 'U')) { } else if (args_has(args, 'U')) {
if (window_pane_is_floating(dst_wp)) {
cmdq_error(item, "cannot swap up on floating pane");
return (CMD_RETURN_ERROR);
}
src_w = dst_w; src_w = dst_w;
src_wp = TAILQ_PREV(dst_wp, window_panes, entry); src_wp = TAILQ_PREV(dst_wp, window_panes, entry);
if (src_wp == NULL) src_wp = cmd_swap_pane_prev_tiled_pane(src_wp);
if (src_wp == NULL) {
src_wp = TAILQ_LAST(&dst_w->panes, window_panes); src_wp = TAILQ_LAST(&dst_w->panes, window_panes);
src_wp = cmd_swap_pane_prev_tiled_pane(src_wp);
}
} }
if (src_w != dst_w && window_push_zoom(src_w, 0, args_has(args, 'Z'))) if (src_w != dst_w && window_push_zoom(src_w, 0, args_has(args, 'Z')))
@@ -92,6 +130,16 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
else else
TAILQ_INSERT_AFTER(&dst_w->panes, tmp_wp, src_wp, entry); TAILQ_INSERT_AFTER(&dst_w->panes, tmp_wp, src_wp, entry);
tmp_wp = TAILQ_PREV(dst_wp, window_panes, zentry);
TAILQ_REMOVE(&dst_w->z_index, dst_wp, zentry);
TAILQ_REPLACE(&src_w->z_index, src_wp, dst_wp, zentry);
if (tmp_wp == src_wp)
tmp_wp = dst_wp;
if (tmp_wp == NULL)
TAILQ_INSERT_HEAD(&dst_w->z_index, src_wp, zentry);
else
TAILQ_INSERT_AFTER(&dst_w->z_index, tmp_wp, src_wp, zentry);
src_lc = src_wp->layout_cell; src_lc = src_wp->layout_cell;
dst_lc = dst_wp->layout_cell; dst_lc = dst_wp->layout_cell;
src_lc->wp = dst_wp; src_lc->wp = dst_wp;
@@ -133,14 +181,20 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
colour_palette_from_option(&src_wp->palette, src_wp->options); colour_palette_from_option(&src_wp->palette, src_wp->options);
colour_palette_from_option(&dst_wp->palette, dst_wp->options); colour_palette_from_option(&dst_wp->palette, dst_wp->options);
layout_fix_panes(src_w, NULL); layout_fix_panes(src_w, NULL);
redraw_invalidate_scene(src_w);
server_redraw_window(src_w); server_redraw_window(src_w);
} }
layout_fix_panes(dst_w, NULL); layout_fix_panes(dst_w, NULL);
redraw_invalidate_scene(dst_w);
server_redraw_window(dst_w); server_redraw_window(dst_w);
notify_window("window-layout-changed", src_w); if (src_w != dst_w) {
window_fire_pane_moved(src_wp, src_w, src_idx, dst_w, dst_idx);
window_fire_pane_moved(dst_wp, dst_w, dst_idx, src_w, src_idx);
}
events_fire_window("window-layout-changed", src_w);
if (src_w != dst_w) if (src_w != dst_w)
notify_window("window-layout-changed", dst_w); events_fire_window("window-layout-changed", dst_w);
out: out:
if (window_pop_zoom(src_w)) if (window_pop_zoom(src_w))

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-swap-window.c,v 1.30 2026/07/27 08:03:01 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -38,7 +38,7 @@ const struct cmd_entry cmd_swap_window_entry = {
.source = { 's', CMD_FIND_WINDOW, CMD_FIND_DEFAULT_MARKED }, .source = { 's', CMD_FIND_WINDOW, CMD_FIND_DEFAULT_MARKED },
.target = { 't', CMD_FIND_WINDOW, 0 }, .target = { 't', CMD_FIND_WINDOW, 0 },
.flags = 0, .flags = CMD_AFTERHOOK,
.exec = cmd_swap_window_exec .exec = cmd_swap_window_exec
}; };

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-switch-client.c,v 1.75 2026/08/20 09:19:24 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -20,6 +20,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include "tmux.h" #include "tmux.h"
@@ -34,9 +35,9 @@ const struct cmd_entry cmd_switch_client_entry = {
.name = "switch-client", .name = "switch-client",
.alias = "switchc", .alias = "switchc",
.args = { "lc:EFnpt:rT:Z", 0, 0, NULL }, .args = { "c:EFlnO:pt:rT:Z", 0, 0, NULL },
.usage = "[-ElnprZ] [-c target-client] [-t target-session] " .usage = "[-ElnprZ] [-c target-client] [-t target-session] "
"[-T key-table]", "[-T key-table] [-O order]",
/* -t is special */ /* -t is special */
@@ -52,7 +53,8 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state target; struct cmd_find_state target;
const char *tflag = args_get(args, 't'); const char *tflag = args_get(args, 't');
enum cmd_find_type type; enum cmd_find_type type;
int flags; int flags, visible, Zflag = args_has(args, 'Z');
struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item); struct client *tc = cmdq_get_target_client(item);
struct session *s; struct session *s;
struct winlink *wl; struct winlink *wl;
@@ -60,6 +62,8 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
struct window_pane *wp; struct window_pane *wp;
const char *tablename; const char *tablename;
struct key_table *table; struct key_table *table;
struct sort_criteria sort_crit;
uid_t uid;
if (tflag != NULL && if (tflag != NULL &&
(tflag[strcspn(tflag, ":.%")] != '\0' || strcmp(tflag, "=") == 0)) { (tflag[strcspn(tflag, ":.%")] != '\0' || strcmp(tflag, "=") == 0)) {
@@ -76,6 +80,13 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
wp = target.wp; wp = target.wp;
if (args_has(args, 'r')) { if (args_has(args, 'r')) {
if (tc->flags & CLIENT_READONLY) {
uid = proc_get_peer_uid(c->peer);
if (uid != getuid()) {
cmdq_error(item, "client is read-only");
return (CMD_RETURN_ERROR);
}
}
if (tc->flags & CLIENT_READONLY) if (tc->flags & CLIENT_READONLY)
tc->flags &= ~(CLIENT_READONLY|CLIENT_IGNORESIZE); tc->flags &= ~(CLIENT_READONLY|CLIENT_IGNORESIZE);
else else
@@ -95,13 +106,22 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
sort_crit.order = sort_order_from_string(args_get(args, 'O'));
if (sort_crit.order == SORT_END && args_has(args, 'O')) {
cmdq_error(item, "invalid sort order");
return (CMD_RETURN_ERROR);
}
sort_crit.reversed = args_has(args, 'r');
if (args_has(args, 'n')) { if (args_has(args, 'n')) {
if ((s = session_next_session(tc->session)) == NULL) { s = session_next_session(tc->session, &sort_crit);
if (s == NULL) {
cmdq_error(item, "can't find next session"); cmdq_error(item, "can't find next session");
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
} else if (args_has(args, 'p')) { } else if (args_has(args, 'p')) {
if ((s = session_previous_session(tc->session)) == NULL) { s = session_previous_session(tc->session, &sort_crit);
if (s == NULL) {
cmdq_error(item, "can't find previous session"); cmdq_error(item, "can't find previous session");
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
@@ -119,11 +139,15 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
if (wl != NULL && wp != NULL && wp != wl->window->active) { if (wl != NULL && wp != NULL && wp != wl->window->active) {
w = wl->window; w = wl->window;
if (window_push_zoom(w, 0, args_has(args, 'Z'))) if (w->modal != NULL && wp != w->modal)
visible = 1;
else
visible = window_pane_is_visible(wp);
if (!visible && window_push_zoom(w, 0, Zflag))
server_redraw_window(w); server_redraw_window(w);
window_redraw_active_switch(w, wp); window_redraw_active_switch(w, wp);
window_set_active_pane(w, wp, 1); window_set_active_pane(w, wp, 1);
if (window_pop_zoom(w)) if (!visible && window_pop_zoom(w))
server_redraw_window(w); server_redraw_window(w);
} }
if (wl != NULL) { if (wl != NULL) {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-unbind-key.c,v 1.34 2021/08/21 10:22:39 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd-wait-for.c,v 1.23 2026/07/10 13:38:45 nicm Exp $ */
/* /*
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -34,8 +34,8 @@ const struct cmd_entry cmd_wait_for_entry = {
.name = "wait-for", .name = "wait-for",
.alias = "wait", .alias = "wait",
.args = { "LSU", 1, 1, NULL }, .args = { "EF:LSUlvw:", 1, 1, NULL },
.usage = "[-L|-S|-U] channel", .usage = "[-ELSUlv] [-F format] [-w waiter] name",
.flags = 0, .flags = 0,
.exec = cmd_wait_for_exec .exec = cmd_wait_for_exec
@@ -46,6 +46,17 @@ struct wait_item {
TAILQ_ENTRY(wait_item) entry; TAILQ_ENTRY(wait_item) entry;
}; };
struct wait_event_item {
struct cmdq_item *item;
struct events_sink *sink;
char *name;
char *filter;
int verbose;
TAILQ_ENTRY(wait_event_item) entry;
};
static TAILQ_HEAD(, wait_event_item) wait_event_items =
TAILQ_HEAD_INITIALIZER(wait_event_items);
struct wait_channel { struct wait_channel {
const char *name; const char *name;
int locked; int locked;
@@ -76,9 +87,28 @@ static enum cmd_retval cmd_wait_for_lock(struct cmdq_item *, const char *,
struct wait_channel *); struct wait_channel *);
static enum cmd_retval cmd_wait_for_unlock(struct cmdq_item *, const char *, static enum cmd_retval cmd_wait_for_unlock(struct cmdq_item *, const char *,
struct wait_channel *); struct wait_channel *);
static enum cmd_retval cmd_wait_for_event(struct cmdq_item *, const char *,
struct args *);
static void cmd_wait_for_event_cb(const char *,
struct event_payload *, void *);
static void cmd_wait_for_event_free(struct wait_event_item *);
static enum cmd_retval cmd_wait_for_event_list(struct cmdq_item *,
const char *);
static enum cmd_retval cmd_wait_for_event_wake(struct cmdq_item *,
const char *, struct args *);
static enum cmd_retval cmd_wait_for_list(struct cmdq_item *,
struct wait_channel *);
static enum cmd_retval cmd_wait_for_wake(struct cmdq_item *, const char *,
struct args *, struct wait_channel *);
static struct wait_channel *cmd_wait_for_add(const char *); static struct wait_channel *cmd_wait_for_add(const char *);
static void cmd_wait_for_remove(struct wait_channel *); static void cmd_wait_for_remove(struct wait_channel *);
static void cmd_wait_for_remove_empty(
struct wait_channel *);
static const char *cmd_wait_for_item_client_name(
struct cmdq_item *);
static const char *cmd_wait_for_client_name(
struct wait_event_item *);
static struct wait_channel * static struct wait_channel *
cmd_wait_for_add(const char *name) cmd_wait_for_add(const char *name)
@@ -117,16 +147,53 @@ cmd_wait_for_remove(struct wait_channel *wc)
free(wc); free(wc);
} }
static void
cmd_wait_for_remove_empty(struct wait_channel *wc)
{
if (wc->locked || wc->woken)
return;
if (!TAILQ_EMPTY(&wc->waiters) || !TAILQ_EMPTY(&wc->lockers))
return;
log_debug("remove empty wait channel %s", wc->name);
RB_REMOVE(wait_channels, &wait_channels, wc);
free((void *)wc->name);
free(wc);
}
static const char *
cmd_wait_for_item_client_name(struct cmdq_item *item)
{
struct client *c = cmdq_get_client(item);
if (c == NULL || c->name == NULL)
return ("");
return (c->name);
}
static const char *
cmd_wait_for_client_name(struct wait_event_item *wei)
{
return (cmd_wait_for_item_client_name(wei->item));
}
static enum cmd_retval static enum cmd_retval
cmd_wait_for_exec(struct cmd *self, struct cmdq_item *item) cmd_wait_for_exec(struct cmd *self, struct cmdq_item *item)
{ {
struct args *args = cmd_get_args(self); struct args *args = cmd_get_args(self);
const char *name = args_string(args, 0); const char *name = args_string(args, 0);
struct wait_channel *wc, find; struct wait_channel *wc, find = { .name = name };
if (args_has(args, 'E'))
return (cmd_wait_for_event(item, name, args));
find.name = name;
wc = RB_FIND(wait_channels, &wait_channels, &find); wc = RB_FIND(wait_channels, &wait_channels, &find);
if (args_has(args, 'l'))
return (cmd_wait_for_list(item, wc));
if (args_has(args, 'w'))
return (cmd_wait_for_wake(item, name, args, wc));
if (args_has(args, 'S')) if (args_has(args, 'S'))
return (cmd_wait_for_signal(item, name, wc)); return (cmd_wait_for_signal(item, name, wc));
if (args_has(args, 'L')) if (args_has(args, 'L'))
@@ -136,6 +203,179 @@ cmd_wait_for_exec(struct cmd *self, struct cmdq_item *item)
return (cmd_wait_for_wait(item, name, wc)); return (cmd_wait_for_wait(item, name, wc));
} }
static void
cmd_wait_for_event_print(struct wait_event_item *wei, struct event_payload *ep)
{
struct event_payload_item *epi;
const char *key;
char *value;
epi = event_payload_first(ep);
while (epi != NULL) {
key = event_payload_item_name(epi);
if (*key != '_') {
value = event_payload_item_print(epi);
cmdq_print(wei->item, "%s=%s", key, value);
free(value);
}
epi = event_payload_next(epi);
}
}
static void
cmd_wait_for_event_cb(__unused const char *name, struct event_payload *ep,
void *item_data)
{
struct wait_event_item *wei = item_data;
struct format_tree *ft;
char *expanded;
int flag;
if (wei->verbose)
cmd_wait_for_event_print(wei, ep);
if (wei->filter != NULL) {
ft = format_create(cmdq_get_client(wei->item), wei->item,
FORMAT_NONE, FORMAT_NOJOBS);
event_payload_add_formats(ep, ft, NULL);
expanded = format_expand(ft, wei->filter);
flag = format_true(expanded);
free(expanded);
format_free(ft);
if (!flag)
return;
}
TAILQ_REMOVE(&wait_event_items, wei, entry);
cmdq_continue(wei->item);
cmd_wait_for_event_free(wei);
}
static void
cmd_wait_for_event_free(struct wait_event_item *wei)
{
events_remove_sink(wei->sink);
free(wei->name);
free(wei->filter);
free(wei);
}
static enum cmd_retval
cmd_wait_for_event(struct cmdq_item *item, const char *name, struct args *args)
{
struct wait_event_item *wei;
const char *filter = args_get(args, 'F');
if (!hooks_valid_event_name(name)) {
cmdq_error(item, "invalid event: %s", name);
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'l'))
return (cmd_wait_for_event_list(item, name));
if (args_has(args, 'w'))
return (cmd_wait_for_event_wake(item, name, args));
if (cmdq_get_client(item) == NULL) {
cmdq_error(item, "not able to wait");
return (CMD_RETURN_ERROR);
}
wei = xcalloc(1, sizeof *wei);
wei->item = item;
wei->name = xstrdup(name);
wei->filter = (filter != NULL ? xstrdup(filter) : NULL);
wei->verbose = args_has(args, 'v');
wei->sink = events_add_sink(name, cmd_wait_for_event_cb, wei);
TAILQ_INSERT_TAIL(&wait_event_items, wei, entry);
return (CMD_RETURN_WAIT);
}
static enum cmd_retval
cmd_wait_for_event_list(struct cmdq_item *item, const char *name)
{
struct wait_event_item *wei;
TAILQ_FOREACH(wei, &wait_event_items, entry) {
if (strcmp(wei->name, name) == 0)
cmdq_print(item, "%s", cmd_wait_for_client_name(wei));
}
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_wait_for_event_wake(struct cmdq_item *item, const char *name,
struct args *args)
{
struct wait_event_item *wei, *wei1;
const char *client_name = args_get(args, 'w');
TAILQ_FOREACH_SAFE(wei, &wait_event_items, entry, wei1) {
if (strcmp(wei->name, name) != 0)
continue;
if (strcmp(cmd_wait_for_client_name(wei), client_name) != 0)
continue;
TAILQ_REMOVE(&wait_event_items, wei, entry);
cmdq_continue(wei->item);
cmd_wait_for_event_free(wei);
return (CMD_RETURN_NORMAL);
}
cmdq_error(item, "waiter %s not found", client_name);
return (CMD_RETURN_ERROR);
}
static enum cmd_retval
cmd_wait_for_list(struct cmdq_item *item, struct wait_channel *wc)
{
struct wait_item *wi;
if (wc == NULL)
return (CMD_RETURN_NORMAL);
TAILQ_FOREACH(wi, &wc->waiters, entry)
cmdq_print(item, "%s", cmd_wait_for_item_client_name(wi->item));
TAILQ_FOREACH(wi, &wc->lockers, entry)
cmdq_print(item, "%s", cmd_wait_for_item_client_name(wi->item));
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_wait_for_wake(__unused struct cmdq_item *item, const char *name,
struct args *args, struct wait_channel *wc)
{
struct wait_item *wi, *wi1;
const char *client_name = args_get(args, 'w');
if (wc != NULL) {
TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
name = cmd_wait_for_item_client_name(wi->item);
if (strcmp(name, client_name) != 0)
continue;
cmdq_continue(wi->item);
TAILQ_REMOVE(&wc->waiters, wi, entry);
free(wi);
cmd_wait_for_remove_empty(wc);
return (CMD_RETURN_NORMAL);
}
TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
name = cmd_wait_for_item_client_name(wi->item);
if (strcmp(name, client_name) != 0)
continue;
cmdq_continue(wi->item);
TAILQ_REMOVE(&wc->lockers, wi, entry);
free(wi);
cmd_wait_for_remove_empty(wc);
return (CMD_RETURN_NORMAL);
}
}
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval static enum cmd_retval
cmd_wait_for_signal(__unused struct cmdq_item *item, const char *name, cmd_wait_for_signal(__unused struct cmdq_item *item, const char *name,
struct wait_channel *wc) struct wait_channel *wc)
@@ -245,6 +485,13 @@ cmd_wait_for_flush(void)
{ {
struct wait_channel *wc, *wc1; struct wait_channel *wc, *wc1;
struct wait_item *wi, *wi1; struct wait_item *wi, *wi1;
struct wait_event_item *wei, *wei1;
TAILQ_FOREACH_SAFE(wei, &wait_event_items, entry, wei1) {
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) { RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) { TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {

265
cmd.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: cmd.c,v 1.189 2026/07/17 08:29:34 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -71,6 +71,7 @@ extern const struct cmd_entry cmd_lock_server_entry;
extern const struct cmd_entry cmd_lock_session_entry; extern const struct cmd_entry cmd_lock_session_entry;
extern const struct cmd_entry cmd_move_pane_entry; extern const struct cmd_entry cmd_move_pane_entry;
extern const struct cmd_entry cmd_move_window_entry; extern const struct cmd_entry cmd_move_window_entry;
extern const struct cmd_entry cmd_new_pane_entry;
extern const struct cmd_entry cmd_new_session_entry; extern const struct cmd_entry cmd_new_session_entry;
extern const struct cmd_entry cmd_new_window_entry; extern const struct cmd_entry cmd_new_window_entry;
extern const struct cmd_entry cmd_next_layout_entry; extern const struct cmd_entry cmd_next_layout_entry;
@@ -114,6 +115,7 @@ extern const struct cmd_entry cmd_suspend_client_entry;
extern const struct cmd_entry cmd_swap_pane_entry; extern const struct cmd_entry cmd_swap_pane_entry;
extern const struct cmd_entry cmd_swap_window_entry; extern const struct cmd_entry cmd_swap_window_entry;
extern const struct cmd_entry cmd_switch_client_entry; extern const struct cmd_entry cmd_switch_client_entry;
extern const struct cmd_entry cmd_switch_mode_entry;
extern const struct cmd_entry cmd_unbind_key_entry; extern const struct cmd_entry cmd_unbind_key_entry;
extern const struct cmd_entry cmd_unlink_window_entry; extern const struct cmd_entry cmd_unlink_window_entry;
extern const struct cmd_entry cmd_wait_for_entry; extern const struct cmd_entry cmd_wait_for_entry;
@@ -163,6 +165,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_lock_session_entry, &cmd_lock_session_entry,
&cmd_move_pane_entry, &cmd_move_pane_entry,
&cmd_move_window_entry, &cmd_move_window_entry,
&cmd_new_pane_entry,
&cmd_new_session_entry, &cmd_new_session_entry,
&cmd_new_window_entry, &cmd_new_window_entry,
&cmd_next_layout_entry, &cmd_next_layout_entry,
@@ -206,6 +209,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_swap_pane_entry, &cmd_swap_pane_entry,
&cmd_swap_window_entry, &cmd_swap_window_entry,
&cmd_switch_client_entry, &cmd_switch_client_entry,
&cmd_switch_mode_entry,
&cmd_unbind_key_entry, &cmd_unbind_key_entry,
&cmd_unlink_window_entry, &cmd_unlink_window_entry,
&cmd_wait_for_entry, &cmd_wait_for_entry,
@@ -216,18 +220,11 @@ const struct cmd_entry *cmd_table[] = {
struct cmd { struct cmd {
const struct cmd_entry *entry; const struct cmd_entry *entry;
struct args *args; struct args *args;
u_int group;
char *file; char *file;
u_int line; u_int line;
int parse_flags; int parse_flags;
TAILQ_ENTRY(cmd) qentry;
}; };
TAILQ_HEAD(cmds, cmd);
/* Next group number for new command list. */
static u_int cmd_list_next_group = 1;
/* Log an argument vector. */ /* Log an argument vector. */
void printflike(3, 4) void printflike(3, 4)
@@ -303,6 +300,8 @@ cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv)
if (argc == 0) if (argc == 0)
return (0); return (0);
if (argc < 0 || argc > 1000)
return (-1);
*argv = xcalloc(argc, sizeof **argv); *argv = xcalloc(argc, sizeof **argv);
buf[len - 1] = '\0'; buf[len - 1] = '\0';
@@ -396,13 +395,6 @@ cmd_get_args(struct cmd *cmd)
return (cmd->args); return (cmd->args);
} }
/* Get group for command. */
u_int
cmd_get_group(struct cmd *cmd)
{
return (cmd->group);
}
/* Get file and line for command. */ /* Get file and line for command. */
void void
cmd_get_source(struct cmd *cmd, const char **file, u_int *line) cmd_get_source(struct cmd *cmd, const char **file, u_int *line)
@@ -552,23 +544,6 @@ cmd_free(struct cmd *cmd)
free(cmd); free(cmd);
} }
/* Copy a command. */
struct cmd *
cmd_copy(struct cmd *cmd, int argc, char **argv)
{
struct cmd *new_cmd;
new_cmd = xcalloc(1, sizeof *new_cmd);
new_cmd->entry = cmd->entry;
new_cmd->args = args_copy(cmd->args, argc, argv);
if (cmd->file != NULL)
new_cmd->file = xstrdup(cmd->file);
new_cmd->line = cmd->line;
return (new_cmd);
}
/* Get a command as a string. */ /* Get a command as a string. */
char * char *
cmd_print(struct cmd *cmd) cmd_print(struct cmd *cmd)
@@ -585,175 +560,6 @@ cmd_print(struct cmd *cmd)
return (out); return (out);
} }
/* Create a new command list. */
struct cmd_list *
cmd_list_new(void)
{
struct cmd_list *cmdlist;
cmdlist = xcalloc(1, sizeof *cmdlist);
cmdlist->references = 1;
cmdlist->group = cmd_list_next_group++;
cmdlist->list = xcalloc(1, sizeof *cmdlist->list);
TAILQ_INIT(cmdlist->list);
return (cmdlist);
}
/* Append a command to a command list. */
void
cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd)
{
cmd->group = cmdlist->group;
TAILQ_INSERT_TAIL(cmdlist->list, cmd, qentry);
}
/* Append all commands from one list to another. */
void
cmd_list_append_all(struct cmd_list *cmdlist, struct cmd_list *from)
{
struct cmd *cmd;
TAILQ_FOREACH(cmd, from->list, qentry)
cmd->group = cmdlist->group;
TAILQ_CONCAT(cmdlist->list, from->list, qentry);
}
/* Move all commands from one command list to another. */
void
cmd_list_move(struct cmd_list *cmdlist, struct cmd_list *from)
{
TAILQ_CONCAT(cmdlist->list, from->list, qentry);
cmdlist->group = cmd_list_next_group++;
}
/* Free a command list. */
void
cmd_list_free(struct cmd_list *cmdlist)
{
struct cmd *cmd, *cmd1;
if (--cmdlist->references != 0)
return;
TAILQ_FOREACH_SAFE(cmd, cmdlist->list, qentry, cmd1) {
TAILQ_REMOVE(cmdlist->list, cmd, qentry);
cmd_free(cmd);
}
free(cmdlist->list);
free(cmdlist);
}
/* Copy a command list, expanding %s in arguments. */
struct cmd_list *
cmd_list_copy(const struct cmd_list *cmdlist, int argc, char **argv)
{
struct cmd *cmd;
struct cmd_list *new_cmdlist;
struct cmd *new_cmd;
u_int group = cmdlist->group;
char *s;
s = cmd_list_print(cmdlist, 0);
log_debug("%s: %s", __func__, s);
free(s);
new_cmdlist = cmd_list_new();
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
if (cmd->group != group) {
new_cmdlist->group = cmd_list_next_group++;
group = cmd->group;
}
new_cmd = cmd_copy(cmd, argc, argv);
cmd_list_append(new_cmdlist, new_cmd);
}
s = cmd_list_print(new_cmdlist, 0);
log_debug("%s: %s", __func__, s);
free(s);
return (new_cmdlist);
}
/* Get a command list as a string. */
char *
cmd_list_print(const struct cmd_list *cmdlist, int escaped)
{
struct cmd *cmd, *next;
char *buf, *this;
size_t len;
len = 1;
buf = xcalloc(1, len);
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
this = cmd_print(cmd);
len += strlen(this) + 6;
buf = xrealloc(buf, len);
strlcat(buf, this, len);
next = TAILQ_NEXT(cmd, qentry);
if (next != NULL) {
if (cmd->group != next->group) {
if (escaped)
strlcat(buf, " \\;\\; ", len);
else
strlcat(buf, " ;; ", len);
} else {
if (escaped)
strlcat(buf, " \\; ", len);
else
strlcat(buf, " ; ", len);
}
}
free(this);
}
return (buf);
}
/* Get first command in list. */
struct cmd *
cmd_list_first(struct cmd_list *cmdlist)
{
return (TAILQ_FIRST(cmdlist->list));
}
/* Get next command in list. */
struct cmd *
cmd_list_next(struct cmd *cmd)
{
return (TAILQ_NEXT(cmd, qentry));
}
/* Do all of the commands in this command list have this flag? */
int
cmd_list_all_have(struct cmd_list *cmdlist, int flag)
{
struct cmd *cmd;
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
if (~cmd->entry->flags & flag)
return (0);
}
return (1);
}
/* Do any of the commands in this command list have this flag? */
int
cmd_list_any_have(struct cmd_list *cmdlist, int flag)
{
struct cmd *cmd;
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
if (cmd->entry->flags & flag)
return (1);
}
return (0);
}
/* Adjust current mouse position for a pane. */ /* Adjust current mouse position for a pane. */
int int
cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp, cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
@@ -773,9 +579,9 @@ cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
if (m->statusat == 0 && y >= m->statuslines) if (m->statusat == 0 && y >= m->statuslines)
y -= m->statuslines; y -= m->statuslines;
if (x < wp->xoff || x >= wp->xoff + wp->sx) if ((int)x < wp->xoff || (int)x >= wp->xoff + (int)wp->sx)
return (-1); return (-1);
if (y < wp->yoff || y >= wp->yoff + wp->sy) if ((int)y < wp->yoff || (int)y >= wp->yoff + (int)wp->sy)
return (-1); return (-1);
if (xp != NULL) if (xp != NULL)
@@ -827,20 +633,27 @@ cmd_mouse_pane(struct mouse_event *m, struct session **sp,
if (!window_has_pane(wl->window, wp)) if (!window_has_pane(wl->window, wp))
return (NULL); return (NULL);
} }
if (wl->window->modal != NULL && wp != wl->window->modal)
return (NULL);
if (wlp != NULL) if (wlp != NULL)
*wlp = wl; *wlp = wl;
return (wp); return (wp);
} }
/* Replace the first %% or %idx in template by s. */ /*
* Replace the first %% or any %idx in template by s. %% is intended for use in
* single quotes, so ' is escaped. %%% and %idx% are for double quotes, so a
* list of special characters is escaped. %idx is left unescaped.
*/
char * char *
cmd_template_replace(const char *template, const char *s, int idx) cmd_template_replace(const char *template, const char *s, int idx)
{ {
char ch, *buf; char ch, *buf;
const char *ptr, *cp, quote[] = "\"\\$;~"; const char *ptr, *cp, dquote[] = "\"\\$;~";
int replaced, quoted; int replaced;
size_t len; size_t len, slen;
enum { NQ, SQ, DQ } quote;
if (strchr(template, '%') == NULL) if (strchr(template, '%') == NULL)
return (xstrdup(template)); return (xstrdup(template));
@@ -854,30 +667,52 @@ cmd_template_replace(const char *template, const char *s, int idx)
while (*ptr != '\0') { while (*ptr != '\0') {
switch (ch = *ptr++) { switch (ch = *ptr++) {
case '%': case '%':
if (*ptr < '1' || *ptr > '9' || *ptr - '0' != idx) { if (*ptr >= '1' && *ptr <= '9' && *ptr - '0' == idx) {
ptr++;
quote = NQ;
if (*ptr == '%') {
quote = DQ;
ptr++;
}
} else {
if (*ptr != '%' || replaced) if (*ptr != '%' || replaced)
break; break;
replaced = 1; replaced = 1;
ptr++;
quote = SQ;
if (*ptr == '%') {
quote = DQ;
ptr++;
}
} }
ptr++;
quoted = (*ptr == '%'); slen = strlen(s);
if (quoted) if (slen >= SIZE_MAX / 4 ||
ptr++; len > SIZE_MAX - (slen * 4) - 1)
fatalx("argument too long");
buf = xrealloc(buf, len + (strlen(s) * 3) + 1); buf = xrealloc(buf, len + (slen * 4) + 1);
for (cp = s; *cp != '\0'; cp++) { for (cp = s; *cp != '\0'; cp++) {
if (quoted && strchr(quote, *cp) != NULL) if (quote == SQ && *cp == '\'') {
buf[len++] = '\'';
buf[len++] = '\\';
buf[len++] = '\'';
buf[len++] = '\'';
continue;
}
if (quote == DQ && strchr(dquote, *cp) != NULL)
buf[len++] = '\\'; buf[len++] = '\\';
buf[len++] = *cp; buf[len++] = *cp;
} }
buf[len] = '\0'; buf[len] = '\0';
continue; continue;
} }
if (len > SIZE_MAX - 2)
fatalx("argument too long");
buf = xrealloc(buf, len + 2); buf = xrealloc(buf, len + 2);
buf[len++] = ch; buf[len++] = ch;
buf[len] = '\0'; buf[len] = '\0';
} }
log_debug("%s: %s -> %s", __func__, template, buf);
return (buf); return (buf);
} }

209
colour.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: colour.c,v 1.35 2026/07/06 14:29:10 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -26,6 +26,85 @@
#include "tmux.h" #include "tmux.h"
/* Theme colour slots and their server options. */
static const struct {
const char *name;
const char *dark_option;
const char *light_option;
int terminal_colour;
} colour_theme_table[] = {
{ "themeblack",
"dark-theme-black",
"light-theme-black",
0
},
{ "themewhite",
"dark-theme-white",
"light-theme-white",
7
},
{ "themelightgrey",
"dark-theme-light-grey",
"light-theme-light-grey",
7
},
{ "themedarkgrey",
"dark-theme-dark-grey",
"light-theme-dark-grey",
0
},
{ "themegreen",
"dark-theme-green",
"light-theme-green",
2
},
{ "themeyellow",
"dark-theme-yellow",
"light-theme-yellow",
3
},
{ "themered",
"dark-theme-red",
"light-theme-red",
1
},
{ "themeblue",
"dark-theme-blue",
"light-theme-blue",
4
},
{ "themecyan",
"dark-theme-cyan",
"light-theme-cyan",
6
},
{ "thememagenta",
"dark-theme-magenta",
"light-theme-magenta",
5
}
};
/* Get theme colour option. */
const char *
colour_theme_option(u_int n, enum client_theme theme)
{
if (n >= nitems(colour_theme_table))
return (NULL);
if (theme == THEME_LIGHT)
return (colour_theme_table[n].light_option);
return (colour_theme_table[n].dark_option);
}
/* Get theme terminal colour. */
int
colour_theme_terminal_colour(u_int n)
{
if (n >= nitems(colour_theme_table))
return (8);
return (colour_theme_table[n].terminal_colour);
}
static int static int
colour_dist_sq(int R, int G, int B, int r, int g, int b) colour_dist_sq(int R, int G, int B, int r, int g, int b)
{ {
@@ -120,6 +199,28 @@ colour_force_rgb(int c)
return (-1); return (-1);
} }
/* Dim colour by a percentage. */
int
colour_dim(int c, u_int dim)
{
u_char r, g, b;
if (dim == 0 || COLOUR_DEFAULT(c) || (c & COLOUR_FLAG_THEME))
return (c);
if (dim >= 100)
return (colour_join_rgb(0, 0, 0));
c = colour_force_rgb(c);
if (c == -1)
return (-1);
colour_split_rgb(c, &r, &g, &b);
r = (r * (100 - dim)) / 100;
g = (g * (100 - dim)) / 100;
b = (b * (100 - dim)) / 100;
return (colour_join_rgb(r, g, b));
}
/* Convert colour to a string. */ /* Convert colour to a string. */
const char * const char *
colour_tostring(int c) colour_tostring(int c)
@@ -130,6 +231,13 @@ colour_tostring(int c)
if (c == -1) if (c == -1)
return ("none"); return ("none");
if (c & COLOUR_FLAG_THEME) {
c &= 0xff;
if (c >= 0 && (u_int)c < nitems(colour_theme_table))
return (colour_theme_table[c].name);
return ("invalid");
}
if (c & COLOUR_FLAG_RGB) { if (c & COLOUR_FLAG_RGB) {
colour_split_rgb(c, &r, &g, &b); colour_split_rgb(c, &r, &g, &b);
xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b); xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b);
@@ -182,6 +290,58 @@ colour_tostring(int c)
return ("invalid"); return ("invalid");
} }
/* Convert colour to an SGR escape sequence. */
const char *
colour_toescape(struct client *c, int colour, int bg)
{
static char s[32];
u_char r, g, b;
int n, flags = (TERM_256COLOURS|TERM_RGBCOLOURS);
u_int o = (bg ? 40 : 30);
if (c != NULL && (c->tty.flags & TTY_OPENED) && c->tty.term != NULL)
flags = c->tty.term->flags;
if (colour & COLOUR_FLAG_THEME) {
n = colour & 0xff;
if (c != NULL && (u_int)n < COLOUR_THEME_COUNT)
colour = c->theme_colours[n];
else
colour = colour_theme_terminal_colour(n);
}
if (colour == 8 || colour == 9) {
xsnprintf(s, sizeof s, "\033[%dm", o + 9);
return (s);
}
if ((~flags & TERM_RGBCOLOURS) & (colour & COLOUR_FLAG_RGB)) {
colour_split_rgb(colour, &r, &g, &b);
colour = colour_find_rgb(r, g, b);
}
if ((~flags & TERM_256COLOURS) & (colour & COLOUR_FLAG_256))
colour = colour_256to16(colour);
if (colour & COLOUR_FLAG_RGB) {
colour_split_rgb(colour, &r, &g, &b);
xsnprintf(s, sizeof s, "\033[%d;2;%u;%u;%um", o + 8, r, g, b);
return (s);
}
if (colour & COLOUR_FLAG_256) {
xsnprintf(s, sizeof s, "\033[%d;5;%um", o + 8, colour & 0xff);
return (s);
}
if (colour >= 0 && colour <= 7) {
xsnprintf(s, sizeof s, "\033[%dm", colour + o);
return (s);
}
if (colour >= 90 && colour <= 97) {
xsnprintf(s, sizeof s, "\033[%dm", colour + o - 30);
return (s);
}
return (NULL);
}
/* Convert background colour to theme. */ /* Convert background colour to theme. */
enum client_theme enum client_theme
colour_totheme(int c) colour_totheme(int c)
@@ -230,6 +390,7 @@ colour_fromstring(const char *s)
const char *cp; const char *cp;
int n; int n;
u_char r, g, b; u_char r, g, b;
u_int i;
if (*s == '#' && strlen(s) == 7) { if (*s == '#' && strlen(s) == 7) {
for (cp = s + 1; isxdigit((u_char) *cp); cp++) for (cp = s + 1; isxdigit((u_char) *cp); cp++)
@@ -260,6 +421,11 @@ colour_fromstring(const char *s)
if (strcasecmp(s, "terminal") == 0) if (strcasecmp(s, "terminal") == 0)
return (9); return (9);
for (i = 0; i < nitems(colour_theme_table); i++) {
if (strcasecmp(s, colour_theme_table[i].name) == 0)
return (i|COLOUR_FLAG_THEME);
}
if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0) if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0)
return (0); return (0);
if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0) if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0)
@@ -1082,22 +1248,22 @@ colour_palette_free(struct colour_palette *p)
/* Get a colour from a palette. */ /* Get a colour from a palette. */
int int
colour_palette_get(struct colour_palette *p, int c) colour_palette_get(struct colour_palette *p, int n)
{ {
if (p == NULL) if (p == NULL)
return (-1); return (-1);
if (c >= 90 && c <= 97) if (n >= 90 && n <= 97)
c = 8 + c - 90; n = 8 + n - 90;
else if (c & COLOUR_FLAG_256) else if (n & COLOUR_FLAG_256)
c &= ~COLOUR_FLAG_256; n &= ~COLOUR_FLAG_256;
else if (c >= 8) else if (n >= 8)
return (-1); return (-1);
if (p->palette != NULL && p->palette[c] != -1) if (p->palette != NULL && p->palette[n] != -1)
return (p->palette[c]); return (p->palette[n]);
if (p->default_palette != NULL && p->default_palette[c] != -1) if (p->default_palette != NULL && p->default_palette[n] != -1)
return (p->default_palette[c]); return (p->default_palette[n]);
return (-1); return (-1);
} }
@@ -1107,14 +1273,13 @@ colour_palette_set(struct colour_palette *p, int n, int c)
{ {
u_int i; u_int i;
if (p == NULL || n > 255) if (p == NULL || n < 0 || n > 255)
return (0); return (0);
if (c == -1 && p->palette == NULL) if (c == -1 && p->palette == NULL)
return (0); return (0);
if (c != -1 && p->palette == NULL) { if (p->palette == NULL) {
if (p->palette == NULL)
p->palette = xcalloc(256, sizeof *p->palette); p->palette = xcalloc(256, sizeof *p->palette);
for (i = 0; i < 256; i++) for (i = 0; i < 256; i++)
p->palette[i] = -1; p->palette[i] = -1;
@@ -1129,7 +1294,8 @@ colour_palette_from_option(struct colour_palette *p, struct options *oo)
{ {
struct options_entry *o; struct options_entry *o;
struct options_array_item *a; struct options_array_item *a;
u_int i, n; union options_value *ov;
u_int i;
int c; int c;
if (p == NULL) if (p == NULL)
@@ -1147,12 +1313,11 @@ colour_palette_from_option(struct colour_palette *p, struct options *oo)
p->default_palette = xcalloc(256, sizeof *p->default_palette); p->default_palette = xcalloc(256, sizeof *p->default_palette);
for (i = 0; i < 256; i++) for (i = 0; i < 256; i++)
p->default_palette[i] = -1; p->default_palette[i] = -1;
while (a != NULL) { for (i = 0; i < 256; i++) {
n = options_array_item_index(a); ov = options_array_getv(o, "%u", i);
if (n < 256) { if (ov != NULL) {
c = options_array_item_value(a)->number; c = ov->number;
p->default_palette[n] = c; p->default_palette[i] = c;
} }
a = options_array_next(a);
} }
} }

View File

@@ -388,7 +388,7 @@ int clock_gettime(int, struct timespec *);
/* base64.c */ /* base64.c */
#undef b64_ntop #undef b64_ntop
#undef b64_pton #undef b64_pton
int b64_ntop(const char *, size_t, char *, size_t); int b64_ntop(const u_char *, size_t, char *, size_t);
int b64_pton(const char *, u_char *, size_t); int b64_pton(const char *, u_char *, size_t);
#endif #endif
@@ -410,11 +410,6 @@ int asprintf(char **, const char *, ...);
int vasprintf(char **, const char *, va_list); int vasprintf(char **, const char *, va_list);
#endif #endif
#ifndef HAVE_FGETLN
/* fgetln.c */
char *fgetln(FILE *, size_t *);
#endif
#ifndef HAVE_GETLINE #ifndef HAVE_GETLINE
/* getline.c */ /* getline.c */
ssize_t getline(char **, size_t *, FILE *); ssize_t getline(char **, size_t *, FILE *);
@@ -463,6 +458,9 @@ int utf8proc_wctomb(char *, wchar_t);
#ifdef NEED_FUZZING #ifdef NEED_FUZZING
/* tmux.c */ /* tmux.c */
#define main __weak main #define main __weak main
#define regcomp(preg, pattern, cflags) (0)
#define regexec(preg, string, nmatch, pmatch, eflags) (REG_NOMATCH)
#define regfree(preg) ((void)0)
#endif #endif
/* getopt.c */ /* getopt.c */

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: base64.c,v 1.8 2015/01/16 16:48:51 deraadt Exp $ */ /* $OpenBSD: base64.c,v 1.15 2021/10/25 14:41:09 jca Exp $ */
/* /*
* Copyright (c) 1996 by Internet Software Consortium. * Copyright (c) 1996 by Internet Software Consortium.
@@ -46,15 +46,15 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <arpa/nameser.h>
#include <ctype.h> #include <ctype.h>
#include <resolv.h> #include <resolv.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "compat.h"
static const char Base64[] = static const char Base64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char Pad64 = '='; static const char Pad64 = '=';
@@ -123,15 +123,12 @@ static const char Pad64 = '=';
*/ */
int int
b64_ntop(src, srclength, target, targsize) b64_ntop(unsigned char const *src, size_t srclength, char *target,
u_char const *src; size_t targsize)
size_t srclength;
char *target;
size_t targsize;
{ {
size_t datalength = 0; size_t datalength = 0;
u_char input[3]; unsigned char input[3];
u_char output[4]; unsigned char output[4];
int i; int i;
while (2 < srclength) { while (2 < srclength) {
@@ -187,13 +184,10 @@ b64_ntop(src, srclength, target, targsize)
*/ */
int int
b64_pton(src, target, targsize) b64_pton(char const *src, unsigned char *target, size_t targsize)
char const *src;
u_char *target;
size_t targsize;
{ {
int tarindex, state, ch; int tarindex, state, ch;
u_char nextbyte; unsigned char nextbyte;
char *pos; char *pos;
state = 0; state = 0;

View File

@@ -1,61 +0,0 @@
/*
* Copyright (c) 2015 Joerg Jung <jung@openbsd.org>
*
* 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 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.
*/
/*
* portable fgetln() version, NOT reentrant
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "compat.h"
char *
fgetln(FILE *fp, size_t *len)
{
static char *buf = NULL;
static size_t bufsz = 0;
size_t r = 0;
char *p;
int c, e;
if (!fp || !len) {
errno = EINVAL;
return NULL;
}
if (!buf) {
if (!(buf = calloc(1, BUFSIZ)))
return NULL;
bufsz = BUFSIZ;
}
while ((c = getc(fp)) != EOF) {
buf[r++] = c;
if (r == bufsz) {
if (!(p = reallocarray(buf, 2, bufsz))) {
e = errno;
free(buf);
errno = e;
buf = NULL, bufsz = 0;
return NULL;
}
buf = p, bufsz = 2 * bufsz;
}
if (c == '\n')
break;
}
return (*len = r) ? buf : NULL;
}

View File

@@ -74,8 +74,14 @@ getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
} }
if (ptr + 2 >= eptr) { if (ptr + 2 >= eptr) {
char *nbuf; char *nbuf;
size_t nbufsiz = *bufsiz * 2; size_t nbufsiz;
ssize_t d = ptr - *buf; ssize_t d = ptr - *buf;
if (*bufsiz > SIZE_MAX / 2) {
errno = EOVERFLOW;
return -1;
}
nbufsiz = *bufsiz * 2;
if ((nbuf = realloc(*buf, nbufsiz)) == NULL) if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
return -1; return -1;
*buf = nbuf; *buf = nbuf;

View File

@@ -339,7 +339,7 @@ static int
getopt_internal(int nargc, char * const *nargv, const char *options, getopt_internal(int nargc, char * const *nargv, const char *options,
const struct option *long_options, int *idx, int flags) const struct option *long_options, int *idx, int flags)
{ {
char *oli; /* option letter list index */ const char *oli; /* option letter list index */
int optchar, short_too; int optchar, short_too;
static int posixly_correct = -1; static int posixly_correct = -1;

View File

@@ -31,7 +31,7 @@ getpeereid(int s, uid_t *uid, gid_t *gid)
{ {
#ifdef HAVE_SO_PEERCRED #ifdef HAVE_SO_PEERCRED
struct ucred uc; struct ucred uc;
int len = sizeof uc; socklen_t len = sizeof uc;
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1) if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1)
return (-1); return (-1);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: imsg-buffer.c,v 1.35 2025/06/04 09:06:56 claudio Exp $ */ /* $OpenBSD: imsg-buffer.c,v 1.36 2025/08/25 08:29:49 claudio Exp $ */
/* /*
* Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org> * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org>
@@ -699,8 +699,6 @@ msgbuf_queuelen(struct msgbuf *msgbuf)
void void
msgbuf_clear(struct msgbuf *msgbuf) msgbuf_clear(struct msgbuf *msgbuf)
{ {
struct ibuf *buf;
/* write side */ /* write side */
ibufq_flush(&msgbuf->bufs); ibufq_flush(&msgbuf->bufs);

View File

@@ -81,7 +81,8 @@ struct systemd_job_watch {
}; };
static int static int
job_removed_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) job_removed_handler(sd_bus_message *m, void *userdata,
__unused sd_bus_error *ret_error)
{ {
struct systemd_job_watch *watch = userdata; struct systemd_job_watch *watch = userdata;
const char *path = NULL; const char *path = NULL;
@@ -109,7 +110,7 @@ systemd_move_to_new_cgroup(char **cause)
sd_bus_message *m = NULL, *reply = NULL; sd_bus_message *m = NULL, *reply = NULL;
sd_bus *bus = NULL; sd_bus *bus = NULL;
sd_bus_slot *slot = NULL; sd_bus_slot *slot = NULL;
char *name, *desc, *slice; char *name, *desc, *slice, *unit;
sd_id128_t uuid; sd_id128_t uuid;
int r; int r;
uint64_t elapsed_usec; uint64_t elapsed_usec;
@@ -241,6 +242,26 @@ systemd_move_to_new_cgroup(char **cause)
goto finish; goto finish;
} }
/*
* Try locating systemd unit that started the server, and mark pane units
* as dependent on it. Use "Before" to make sure systemd will not try to
* kill them first.
*/
if (sd_pid_get_user_unit(parent_pid, &unit) == 0 ||
sd_pid_get_unit(parent_pid, &unit) == 0) {
r = sd_bus_message_append(m, "(sv)", "Before", "as", 1, unit);
if (r >= 0) {
r = sd_bus_message_append(m, "(sv)", "PartOf", "as", 1,
unit);
}
free(unit);
if (r < 0) {
xasprintf(cause, "failed to append to properties: %s",
strerror(-r));
goto finish;
}
}
/* End properties array. */ /* End properties array. */
r = sd_bus_message_close_container(m); r = sd_bus_message_close_container(m);
if (r < 0) { if (r < 0) {

View File

@@ -1,6 +1,6 @@
# configure.ac # configure.ac
AC_INIT([tmux], 3.6) AC_INIT([tmux], next-3.8)
AC_PREREQ([2.60]) AC_PREREQ([2.60])
AC_CONFIG_AUX_DIR(etc) AC_CONFIG_AUX_DIR(etc)
@@ -64,6 +64,22 @@ AC_ARG_ENABLE(
) )
AM_CONDITIONAL(IS_DEBUG, test "x$enable_debug" = xyes) AM_CONDITIONAL(IS_DEBUG, test "x$enable_debug" = xyes)
# Is this --enable-optimizations?
AC_ARG_ENABLE(
optimizations,
AS_HELP_STRING(--enable-optimizations, enable optimization build flags),
,
enable_optimizations=yes
)
AM_CONDITIONAL(IS_OPTIMIZED, test "x$enable_optimizations" = xyes)
# Is this --enable-asan?
AC_ARG_ENABLE(
asan,
AS_HELP_STRING(--enable-asan, enable ASAN build flags)
)
AM_CONDITIONAL(IS_ASAN, test "x$enable_asan" = xyes)
# Is this a static build? # Is this a static build?
AC_ARG_ENABLE( AC_ARG_ENABLE(
static, static,
@@ -159,7 +175,6 @@ AC_REPLACE_FUNCS([ \
clock_gettime \ clock_gettime \
closefrom \ closefrom \
explicit_bzero \ explicit_bzero \
fgetln \
freezero \ freezero \
getdtablecount \ getdtablecount \
getdtablesize \ getdtablesize \
@@ -223,6 +238,7 @@ AC_LIBOBJ(getopt_long)
# Look for libevent. Try libevent_core or libevent with pkg-config first then # Look for libevent. Try libevent_core or libevent with pkg-config first then
# look for the library. # look for the library.
libevent_version=off
PKG_CHECK_MODULES( PKG_CHECK_MODULES(
LIBEVENT_CORE, LIBEVENT_CORE,
[libevent_core >= 2], [libevent_core >= 2],
@@ -231,6 +247,8 @@ PKG_CHECK_MODULES(
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBEVENT_CORE_LIBS $LIBS" LIBS="$LIBEVENT_CORE_LIBS $LIBS"
found_libevent=yes found_libevent=yes
libevent_version=`$PKG_CONFIG --modversion libevent_core 2>/dev/null`
test "x$libevent_version" = x && libevent_version=on
], ],
found_libevent=no found_libevent=no
) )
@@ -243,6 +261,8 @@ if test x$found_libevent = xno; then
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBEVENT_LIBS $LIBS" LIBS="$LIBEVENT_LIBS $LIBS"
found_libevent=yes found_libevent=yes
libevent_version=`$PKG_CONFIG --modversion libevent 2>/dev/null`
test "x$libevent_version" = x && libevent_version=on
], ],
found_libevent=no found_libevent=no
) )
@@ -251,7 +271,8 @@ if test x$found_libevent = xno; then
AC_SEARCH_LIBS( AC_SEARCH_LIBS(
event_init, event_init,
[event_core event event-1.4], [event_core event event-1.4],
found_libevent=yes, [found_libevent=yes
libevent_version=on],
found_libevent=no found_libevent=no
) )
fi fi
@@ -278,6 +299,21 @@ fi
# Look for ncurses or curses. Try pkg-config first then directly for the # Look for ncurses or curses. Try pkg-config first then directly for the
# library. # library.
ncurses_version=off
PKG_CHECK_MODULES(
LIBTINFOW,
tinfow,
[
AM_CPPFLAGS="$LIBTINFOW_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$LIBTINFOW_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBTINFOW_LIBS $LIBS"
found_ncurses=yes
ncurses_version=`$PKG_CONFIG --modversion tinfow 2>/dev/null`
test "x$ncurses_version" = x && ncurses_version=on
],
found_ncurses=no
)
if test "x$found_ncurses" = xno; then
PKG_CHECK_MODULES( PKG_CHECK_MODULES(
LIBTINFO, LIBTINFO,
tinfo, tinfo,
@@ -286,18 +322,8 @@ PKG_CHECK_MODULES(
CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS" CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBTINFO_LIBS $LIBS" LIBS="$LIBTINFO_LIBS $LIBS"
found_ncurses=yes found_ncurses=yes
], ncurses_version=`$PKG_CONFIG --modversion tinfo 2>/dev/null`
found_ncurses=no test "x$ncurses_version" = x && ncurses_version=on
)
if test "x$found_ncurses" = xno; then
PKG_CHECK_MODULES(
LIBNCURSES,
ncurses,
[
AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBNCURSES_LIBS $LIBS"
found_ncurses=yes
], ],
found_ncurses=no found_ncurses=no
) )
@@ -311,6 +337,23 @@ if test "x$found_ncurses" = xno; then
CPPFLAGS="$LIBNCURSESW_CFLAGS $SAVED_CPPFLAGS" CPPFLAGS="$LIBNCURSESW_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBNCURSESW_LIBS $LIBS" LIBS="$LIBNCURSESW_LIBS $LIBS"
found_ncurses=yes found_ncurses=yes
ncurses_version=`$PKG_CONFIG --modversion ncursesw 2>/dev/null`
test "x$ncurses_version" = x && ncurses_version=on
],
found_ncurses=no
)
fi
if test "x$found_ncurses" = xno; then
PKG_CHECK_MODULES(
LIBNCURSES,
ncurses,
[
AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBNCURSES_LIBS $LIBS"
found_ncurses=yes
ncurses_version=`$PKG_CONFIG --modversion ncurses 2>/dev/null`
test "x$ncurses_version" = x && ncurses_version=on
], ],
found_ncurses=no found_ncurses=no
) )
@@ -318,8 +361,9 @@ fi
if test "x$found_ncurses" = xno; then if test "x$found_ncurses" = xno; then
AC_SEARCH_LIBS( AC_SEARCH_LIBS(
setupterm, setupterm,
[tinfo terminfo ncurses ncursesw], [tinfow tinfo terminfo ncursesw ncurses],
found_ncurses=yes, [found_ncurses=yes
ncurses_version=on],
found_ncurses=no found_ncurses=no
) )
if test "x$found_ncurses" = xyes; then if test "x$found_ncurses" = xyes; then
@@ -349,6 +393,7 @@ else
LIBS="$LIBS -lcurses" LIBS="$LIBS -lcurses"
CPPFLAGS="$CPPFLAGS -DHAVE_CURSES_H" CPPFLAGS="$CPPFLAGS -DHAVE_CURSES_H"
AC_DEFINE(HAVE_CURSES_H) AC_DEFINE(HAVE_CURSES_H)
ncurses_version=on
else else
AC_MSG_ERROR("curses not found") AC_MSG_ERROR("curses not found")
fi fi
@@ -359,6 +404,7 @@ AC_CHECK_FUNCS([ \
]) ])
# Look for utempter. # Look for utempter.
utempter_version=off
AC_ARG_ENABLE( AC_ARG_ENABLE(
utempter, utempter,
AS_HELP_STRING(--enable-utempter, use utempter if it is installed) AS_HELP_STRING(--enable-utempter, use utempter if it is installed)
@@ -375,17 +421,34 @@ if test "x$enable_utempter" = xyes; then
fi fi
if test "x$enable_utempter" = xyes; then if test "x$enable_utempter" = xyes; then
AC_DEFINE(HAVE_UTEMPTER) AC_DEFINE(HAVE_UTEMPTER)
utempter_version=on
else else
AC_MSG_ERROR("utempter not found") AC_MSG_ERROR("utempter not found")
fi fi
fi fi
# Look for utf8proc. # Look for utf8proc.
utf8proc_version=off
AC_ARG_ENABLE( AC_ARG_ENABLE(
utf8proc, utf8proc,
AS_HELP_STRING(--enable-utf8proc, use utf8proc if it is installed) AS_HELP_STRING(--enable-utf8proc, use utf8proc if it is installed)
) )
try_utf8proc=no
require_utf8proc=no
if test "x$enable_utf8proc" = xyes; then if test "x$enable_utf8proc" = xyes; then
try_utf8proc=yes
elif test "x$enable_utf8proc" = x; then
case "$host_os" in
*darwin*)
try_utf8proc=yes
require_utf8proc=yes
;;
esac
fi
if test "x$try_utf8proc" = xyes; then
SAVED_AM_CPPFLAGS="$AM_CPPFLAGS"
SAVED_OLD_CPPFLAGS="$CPPFLAGS"
SAVED_LIBS="$LIBS"
PKG_CHECK_MODULES( PKG_CHECK_MODULES(
LIBUTF8PROC, LIBUTF8PROC,
libutf8proc, libutf8proc,
@@ -393,7 +456,10 @@ if test "x$enable_utf8proc" = xyes; then
AM_CPPFLAGS="$LIBUTF8PROC_CFLAGS $AM_CPPFLAGS" AM_CPPFLAGS="$LIBUTF8PROC_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$LIBUTF8PROC_CFLAGS $SAVED_CPPFLAGS" CPPFLAGS="$LIBUTF8PROC_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBUTF8PROC_LIBS $LIBS" LIBS="$LIBUTF8PROC_LIBS $LIBS"
] utf8proc_version=`$PKG_CONFIG --modversion libutf8proc 2>/dev/null`
test "x$utf8proc_version" = x && utf8proc_version=on
],
[:]
) )
AC_CHECK_HEADER(utf8proc.h, enable_utf8proc=yes, enable_utf8proc=no) AC_CHECK_HEADER(utf8proc.h, enable_utf8proc=yes, enable_utf8proc=no)
if test "x$enable_utf8proc" = xyes; then if test "x$enable_utf8proc" = xyes; then
@@ -406,13 +472,23 @@ if test "x$enable_utf8proc" = xyes; then
fi fi
if test "x$enable_utf8proc" = xyes; then if test "x$enable_utf8proc" = xyes; then
AC_DEFINE(HAVE_UTF8PROC) AC_DEFINE(HAVE_UTF8PROC)
test "x$utf8proc_version" = xoff && utf8proc_version=on
else
AM_CPPFLAGS="$SAVED_AM_CPPFLAGS"
CPPFLAGS="$SAVED_OLD_CPPFLAGS"
LIBS="$SAVED_LIBS"
utf8proc_version=off
if test "x$require_utf8proc" = xyes; then
enable_utf8proc=
else else
AC_MSG_ERROR("utf8proc not found") AC_MSG_ERROR("utf8proc not found")
fi fi
fi fi
fi
AM_CONDITIONAL(HAVE_UTF8PROC, [test "x$enable_utf8proc" = xyes]) AM_CONDITIONAL(HAVE_UTF8PROC, [test "x$enable_utf8proc" = xyes])
# Check for systemd support. # Check for systemd support.
systemd_version=off
AC_ARG_ENABLE( AC_ARG_ENABLE(
systemd, systemd,
AS_HELP_STRING(--enable-systemd, enable systemd integration) AS_HELP_STRING(--enable-systemd, enable systemd integration)
@@ -426,6 +502,8 @@ if test x"$enable_systemd" = xyes; then
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
LIBS="$SYSTEMD_LIBS $LIBS" LIBS="$SYSTEMD_LIBS $LIBS"
found_systemd=yes found_systemd=yes
systemd_version=`$PKG_CONFIG --modversion libsystemd 2>/dev/null`
test "x$systemd_version" = x && systemd_version=on
], ],
found_systemd=no found_systemd=no
) )
@@ -550,11 +628,34 @@ if test "x$found_malloc_trim" = xyes; then
fi fi
# Build against jemalloc if requested. # Build against jemalloc if requested.
jemalloc_version=off
AC_ARG_ENABLE( AC_ARG_ENABLE(
jemalloc, jemalloc,
AS_HELP_STRING(--enable-jemalloc, use jemalloc if it is installed) AS_HELP_STRING(--enable-jemalloc, use jemalloc if it is installed)
) )
try_jemalloc=no
require_jemalloc=no
if test "x$enable_asan" = xyes && test "x$enable_jemalloc" = xyes; then
AC_MSG_ERROR([--enable-asan and --enable-jemalloc cannot be used together])
fi
if test "x$enable_jemalloc" = xyes; then if test "x$enable_jemalloc" = xyes; then
try_jemalloc=yes
elif test "x$enable_jemalloc" = x; then
case "$host_os" in
*darwin*)
if test "x$enable_asan" = xyes; then
enable_jemalloc=no
else
try_jemalloc=yes
require_jemalloc=yes
fi
;;
esac
fi
if test "x$try_jemalloc" = xyes; then
SAVED_AM_CPPFLAGS="$AM_CPPFLAGS"
SAVED_OLD_CPPFLAGS="$CPPFLAGS"
SAVED_LIBS="$LIBS"
PKG_CHECK_MODULES( PKG_CHECK_MODULES(
JEMALLOC, JEMALLOC,
jemalloc, jemalloc,
@@ -562,9 +663,26 @@ if test "x$enable_jemalloc" = xyes; then
AM_CPPFLAGS="$JEMALLOC_CFLAGS $AM_CPPFLAGS" AM_CPPFLAGS="$JEMALLOC_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBS $JEMALLOC_LIBS" LIBS="$LIBS $JEMALLOC_LIBS"
jemalloc_version=`$PKG_CONFIG --modversion jemalloc 2>/dev/null`
test "x$jemalloc_version" = x && jemalloc_version=on
], ],
AC_MSG_ERROR("jemalloc not found") enable_jemalloc=no
) )
if test "x$enable_jemalloc" = xno; then
AM_CPPFLAGS="$SAVED_AM_CPPFLAGS"
CPPFLAGS="$SAVED_OLD_CPPFLAGS"
LIBS="$SAVED_LIBS"
jemalloc_version=off
if test "x$require_jemalloc" = xyes; then
enable_jemalloc=
else
AC_MSG_ERROR("jemalloc not found")
fi
else
enable_jemalloc=yes
AC_DEFINE(HAVE_JEMALLOC)
test "x$jemalloc_version" = xoff && jemalloc_version=on
fi
fi fi
# Check for CMSG_DATA. On some platforms like HP-UX this requires UNIX 95 # Check for CMSG_DATA. On some platforms like HP-UX this requires UNIX 95
@@ -911,6 +1029,20 @@ case "$host_os" in
AC_MSG_NOTICE([]) AC_MSG_NOTICE([])
AC_MSG_ERROR([must give --enable-utf8proc or --disable-utf8proc]) AC_MSG_ERROR([must give --enable-utf8proc or --disable-utf8proc])
fi fi
#
# macOS calloc(3) does not appear to always zero memory correctly,
# so complain and suggest using jemalloc instead.
#
if test "x$enable_jemalloc" = x; then
AC_MSG_NOTICE([])
AC_MSG_NOTICE([ macOS calloc(3) appears not to correctly])
AC_MSG_NOTICE([ zero allocations in some circumstances;])
AC_MSG_NOTICE([ to avoid this, configuring with])
AC_MSG_NOTICE([ --enable-jemalloc is recommended. To build])
AC_MSG_NOTICE([ without anyway, use --disable-jemalloc])
AC_MSG_NOTICE([])
AC_MSG_ERROR([must give --enable-jemalloc or --disable-jemalloc])
fi
;; ;;
*dragonfly*) *dragonfly*)
AC_MSG_RESULT(dragonfly) AC_MSG_RESULT(dragonfly)
@@ -998,6 +1130,31 @@ AC_MSG_CHECKING(lock-command)
AC_MSG_RESULT($DEFAULT_LOCK_CMD) AC_MSG_RESULT($DEFAULT_LOCK_CMD)
AC_SUBST(DEFAULT_LOCK_CMD) AC_SUBST(DEFAULT_LOCK_CMD)
# Print a summary.
AC_MSG_NOTICE([])
if test "x$enable_asan" = xyes; then
AC_MSG_NOTICE([ASAN: on])
else
AC_MSG_NOTICE([ASAN: off])
fi
if test "x$enable_sixel" = xyes; then
AC_MSG_NOTICE([SIXEL: on])
else
AC_MSG_NOTICE([SIXEL: off])
fi
if test "x$enable_debug" = xyes; then
AC_MSG_NOTICE([debug: on])
else
AC_MSG_NOTICE([debug: off])
fi
AC_MSG_NOTICE([jemalloc: $jemalloc_version])
AC_MSG_NOTICE([libevent: $libevent_version])
AC_MSG_NOTICE([ncurses: $ncurses_version])
AC_MSG_NOTICE([systemd: $systemd_version])
AC_MSG_NOTICE([utempter: $utempter_version])
AC_MSG_NOTICE([utf8proc: $utf8proc_version])
AC_MSG_NOTICE([])
# Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user # Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user
# variables. # variables.
AC_SUBST(AM_CPPFLAGS) AC_SUBST(AM_CPPFLAGS)

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: control-notify.c,v 1.38 2026/08/03 13:38:42 nicm Exp $ */
/* /*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -23,137 +23,187 @@
#include "tmux.h" #include "tmux.h"
/* Should this client be sent events? */
#define CONTROL_SHOULD_NOTIFY_CLIENT(c) \ #define CONTROL_SHOULD_NOTIFY_CLIENT(c) \
((c) != NULL && ((c)->flags & CLIENT_CONTROL)) ((c) != NULL && \
((c)->flags & CLIENT_CONTROL) && \
(~(c)->flags & CLIENT_EXIT) && \
(c)->control_state != NULL)
void /* Notify control clients that pane mode changed. */
control_notify_pane_mode_changed(int pane) static void
control_pane_mode_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{ {
struct window_pane *wp;
struct client *c; struct client *c;
char *value;
wp = event_payload_get_pane(ep, "pane");
if (wp != NULL) {
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%pane-mode-changed %%%u",
control_write(c, "%%pane-mode-changed %%%u", pane); wp->id);
} }
return;
} }
void value = event_payload_print(ep, "pane");
control_notify_window_layout_changed(struct window *w) if (value == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_notify_write(c, "%%pane-mode-changed %s", value);
}
free(value);
}
/* Notify control clients that window layout changed. */
static void
control_window_layout_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{ {
struct client *c; struct client *c;
struct session *s; struct session *s;
struct winlink *wl; struct winlink *wl;
struct window *w = event_payload_get_window(ep, "window");
const char *template; const char *template;
char *cp; char *cp;
if (w == NULL)
return;
template = "%layout-change #{window_id} #{window_layout} " template = "%layout-change #{window_id} #{window_layout} "
"#{window_visible_layout} #{window_raw_flags}"; "#{window_visible_layout} #{window_raw_flags}";
/*
* When the last pane in a window is closed it won't have a layout root
* 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)
return;
cp = format_single(NULL, template, NULL, NULL, wl, NULL);
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue; continue;
s = c->session; s = c->session;
if (winlink_find_by_window_id(&s->windows, w->id) != NULL)
if (winlink_find_by_window_id(&s->windows, w->id) == NULL) control_notify_write(c, "%s", cp);
continue; }
/*
* When the last pane in a window is closed it won't have a
* layout root and we don't need to inform the client about the
* layout change because the whole window will go away soon.
*/
if (w->layout_root == NULL)
continue;
wl = winlink_find_by_window(&s->windows, w);
if (wl != NULL) {
cp = format_single(NULL, template, c, NULL, wl, NULL);
control_write(c, "%s", cp);
free(cp); free(cp);
} }
}
}
void /* Notify control clients that window pane changed. */
control_notify_window_pane_changed(struct window *w) static void
control_window_pane_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{ {
struct client *c; struct client *c;
struct window *w = event_payload_get_window(ep, "window");
if (w == NULL || w->active == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_write(c, "%%window-pane-changed @%u %%%u", w->id, control_notify_write(c, "%%window-pane-changed @%u %%%u", w->id,
w->active->id); w->active->id);
} }
} }
void /* Notify control clients that a window was unlinked. */
control_notify_window_unlinked(__unused struct session *s, struct window *w) static void
control_window_unlinked_cb(__unused const char *name, struct event_payload *ep,
__unused void *sink_data)
{ {
struct client *c; struct client *c;
struct session *cs; struct session *cs;
struct window *w = event_payload_get_window(ep, "window");
if (w == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue; continue;
cs = c->session; cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
control_write(c, "%%window-close @%u", w->id); control_notify_write(c, "%%window-close @%u", w->id);
else else {
control_write(c, "%%unlinked-window-close @%u", w->id); control_notify_write(c, "%%unlinked-window-close @%u",
w->id);
}
} }
} }
void /* Notify control clients that a window was linked. */
control_notify_window_linked(__unused struct session *s, struct window *w) static void
control_window_linked_cb(__unused const char *name, struct event_payload *ep,
__unused void *sink_data)
{ {
struct client *c; struct client *c;
struct session *cs; struct session *cs;
struct window *w = event_payload_get_window(ep, "window");
if (w == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue; continue;
cs = c->session; cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
control_write(c, "%%window-add @%u", w->id); control_notify_write(c, "%%window-add @%u", w->id);
else else {
control_write(c, "%%unlinked-window-add @%u", w->id); control_notify_write(c, "%%unlinked-window-add @%u",
w->id);
}
} }
} }
void /* Notify control clients that a window was renamed. */
control_notify_window_renamed(struct window *w) static void
control_window_renamed_cb(__unused const char *name, struct event_payload *ep,
__unused void *sink_data)
{ {
struct client *c; struct client *c;
struct session *cs; struct session *cs;
struct window *w = event_payload_get_window(ep, "window");
if (w == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue; continue;
cs = c->session; cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) { if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) {
control_write(c, "%%window-renamed @%u %s", w->id, control_notify_write(c, "%%window-renamed @%u %s",
w->name);
} else {
control_write(c, "%%unlinked-window-renamed @%u %s",
w->id, w->name); w->id, w->name);
} else {
control_notify_write(c,
"%%unlinked-window-renamed @%u %s", w->id, w->name);
} }
} }
} }
void /* Notify control clients that a client changed session. */
control_notify_client_session_changed(struct client *cc) static void
control_client_session_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{ {
struct client *cc = event_payload_get_client(ep, "client");
struct client *c; struct client *c;
struct session *s; struct session *s;
if (cc->session == NULL) if (cc == NULL || cc->session == NULL)
return; return;
s = cc->session; s = cc->session;
@@ -162,101 +212,163 @@ control_notify_client_session_changed(struct client *cc)
continue; continue;
if (cc == c) { if (cc == c) {
control_write(c, "%%session-changed $%u %s", s->id, control_notify_write(c, "%%session-changed $%u %s",
s->name); s->id, s->name);
} else { } else {
control_write(c, "%%client-session-changed %s $%u %s", control_notify_write(c,
cc->name, s->id, s->name); "%%client-session-changed %s $%u %s", cc->name,
s->id, s->name);
} }
} }
} }
void /* Notify control clients that a client detached. */
control_notify_client_detached(struct client *cc) static void
control_client_detached_cb(__unused const char *name, struct event_payload *ep,
__unused void *sink_data)
{ {
struct client *cc = event_payload_get_client(ep, "client");
struct client *c; struct client *c;
if (cc == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
control_write(c, "%%client-detached %s", cc->name); continue;
control_notify_write(c, "%%client-detached %s", cc->name);
} }
} }
void /* Notify control clients that a session was renamed. */
control_notify_session_renamed(struct session *s) static void
control_session_renamed_cb(__unused const char *name, struct event_payload *ep,
__unused void *sink_data)
{
struct session *s = event_payload_get_session(ep, "session");
struct client *c;
if (s == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_notify_write(c, "%%session-renamed $%u %s", s->id,
s->name);
}
}
/* Notify control clients that sessions changed. */
static void
control_session_created_cb(__unused const char *name,
__unused struct event_payload *ep, __unused void *sink_data)
{ {
struct client *c; struct client *c;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%sessions-changed");
control_write(c, "%%session-renamed $%u %s", s->id, s->name);
} }
} }
void /* Notify control clients that sessions changed. */
control_notify_session_created(__unused struct session *s) static void
control_session_closed_cb(__unused const char *name,
__unused struct event_payload *ep, __unused void *sink_data)
{ {
struct client *c; struct client *c;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%sessions-changed");
control_write(c, "%%sessions-changed");
} }
} }
void /* Notify control clients that the current window changed. */
control_notify_session_closed(__unused struct session *s) static void
control_session_window_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{ {
struct session *s = event_payload_get_session(ep, "session");
struct client *c; struct client *c;
/*
* A deferred session-window-changed notification can fire after the
* session has been destroyed (which sets curw to NULL) but is kept
* alive by the notification's reference. Skip the notification.
*/
if (s == NULL || s->curw == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%session-window-changed $%u @%u",
control_write(c, "%%sessions-changed"); s->id, s->curw->window->id);
} }
} }
void /* Notify control clients that a paste buffer changed. */
control_notify_session_window_changed(struct session *s) static void
control_paste_buffer_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{ {
const char *pbname = event_payload_get_string(ep, "paste_buffer");
struct client *c; struct client *c;
if (pbname == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%paste-buffer-changed %s", pbname);
control_write(c, "%%session-window-changed $%u @%u", s->id,
s->curw->window->id);
} }
} }
void /* Notify control clients that a paste buffer was deleted. */
control_notify_paste_buffer_changed(const char *name) static void
control_paste_buffer_deleted_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{ {
const char *pbname = event_payload_get_string(ep, "paste_buffer");
struct client *c; struct client *c;
if (pbname == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%paste-buffer-deleted %s", pbname);
control_write(c, "%%paste-buffer-changed %s", name);
} }
} }
/* Build control event sinks. */
void void
control_notify_paste_buffer_deleted(const char *name) control_build_events(void)
{ {
struct client *c; /* Control event sink callbacks. */
static struct {
const char *name;
events_cb cb;
} events[] = {
{ "pane-mode-changed", control_pane_mode_changed_cb },
{ "window-layout-changed", control_window_layout_changed_cb },
{ "window-pane-changed", control_window_pane_changed_cb },
{ "window-unlinked", control_window_unlinked_cb },
{ "window-linked", control_window_linked_cb },
{ "window-renamed", control_window_renamed_cb },
{ "client-session-changed", control_client_session_changed_cb },
{ "client-detached", control_client_detached_cb },
{ "session-renamed", control_session_renamed_cb },
{ "session-created", control_session_created_cb },
{ "session-closed", control_session_closed_cb },
{ "session-window-changed", control_session_window_changed_cb },
{ "paste-buffer-changed", control_paste_buffer_changed_cb },
{ "paste-buffer-deleted", control_paste_buffer_deleted_cb }
};
u_int i;
TAILQ_FOREACH(c, &clients, entry) { for (i = 0; i < nitems(events); i++)
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) events_add_sink(events[i].name, events[i].cb, NULL);
continue;
control_write(c, "%%paste-buffer-deleted %s", name);
}
} }

747
control.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: control.c,v 1.66 2026/08/18 07:43:44 nicm Exp $ */
/* /*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -19,6 +19,9 @@
#include <sys/types.h> #include <sys/types.h>
#include <errno.h>
#include <event.h>
#include <poll.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
@@ -50,6 +53,17 @@ struct control_block {
TAILQ_ENTRY(control_block) all_entry; TAILQ_ENTRY(control_block) all_entry;
}; };
/*
* A notification line deferred because it was generated while a command's
* %begin/%end guard block was open. Notifications must never appear inside a
* guard block, so they are held here and flushed once the block closes.
*/
struct control_line {
char *line;
TAILQ_ENTRY(control_line) entry;
};
/* Control client pane. */ /* Control client pane. */
struct control_pane { struct control_pane {
u_int pane; u_int pane;
@@ -75,45 +89,20 @@ struct control_pane {
}; };
RB_HEAD(control_panes, control_pane); RB_HEAD(control_panes, control_pane);
/* Subscription pane. */ /* Control client window size. */
struct control_sub_pane { struct control_window {
u_int pane;
u_int idx;
char *last;
RB_ENTRY(control_sub_pane) entry;
};
RB_HEAD(control_sub_panes, control_sub_pane);
/* Subscription window. */
struct control_sub_window {
u_int window; u_int window;
u_int idx; u_int sx;
char *last; u_int sy;
RB_ENTRY(control_sub_window) entry; RB_ENTRY(control_window) entry;
}; };
RB_HEAD(control_sub_windows, control_sub_window); RB_HEAD(control_windows, control_window);
/* Control client subscription. */
struct control_sub {
char *name;
char *format;
enum control_sub_type type;
u_int id;
char *last;
struct control_sub_panes panes;
struct control_sub_windows windows;
RB_ENTRY(control_sub) entry;
};
RB_HEAD(control_subs, control_sub);
/* Control client state. */ /* Control client state. */
struct control_state { struct control_state {
struct control_panes panes; struct control_panes panes;
struct control_windows windows;
TAILQ_HEAD(, control_pane) pending_list; TAILQ_HEAD(, control_pane) pending_list;
u_int pending_count; u_int pending_count;
@@ -123,8 +112,14 @@ struct control_state {
struct bufferevent *read_event; struct bufferevent *read_event;
struct bufferevent *write_event; struct bufferevent *write_event;
struct control_subs subs; struct monitor_set *subs;
struct event subs_timer;
/*
* Depth of open %begin/%end guard blocks and notifications deferred
* until the outermost block closes.
*/
int guard_depth;
TAILQ_HEAD(, control_line) deferred;
}; };
/* Low and high watermarks. */ /* Low and high watermarks. */
@@ -154,72 +149,17 @@ control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
} }
RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp); RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp);
/* Compare client subs. */ /* Compare control windows. */
static int static int
control_sub_cmp(struct control_sub *csub1, struct control_sub *csub2) control_window_cmp(struct control_window *cw1, struct control_window *cw2)
{ {
return (strcmp(csub1->name, csub2->name)); if (cw1->window < cw2->window)
}
RB_GENERATE_STATIC(control_subs, control_sub, entry, control_sub_cmp);
/* Compare client subscription panes. */
static int
control_sub_pane_cmp(struct control_sub_pane *csp1,
struct control_sub_pane *csp2)
{
if (csp1->pane < csp2->pane)
return (-1); return (-1);
if (csp1->pane > csp2->pane) if (cw1->window > cw2->window)
return (1);
if (csp1->idx < csp2->idx)
return (-1);
if (csp1->idx > csp2->idx)
return (1); return (1);
return (0); return (0);
} }
RB_GENERATE_STATIC(control_sub_panes, control_sub_pane, entry, RB_GENERATE_STATIC(control_windows, control_window, entry, control_window_cmp);
control_sub_pane_cmp);
/* Compare client subscription windows. */
static int
control_sub_window_cmp(struct control_sub_window *csw1,
struct control_sub_window *csw2)
{
if (csw1->window < csw2->window)
return (-1);
if (csw1->window > csw2->window)
return (1);
if (csw1->idx < csw2->idx)
return (-1);
if (csw1->idx > csw2->idx)
return (1);
return (0);
}
RB_GENERATE_STATIC(control_sub_windows, control_sub_window, entry,
control_sub_window_cmp);
/* Free a subscription. */
static void
control_free_sub(struct control_state *cs, struct control_sub *csub)
{
struct control_sub_pane *csp, *csp1;
struct control_sub_window *csw, *csw1;
RB_FOREACH_SAFE(csp, control_sub_panes, &csub->panes, csp1) {
RB_REMOVE(control_sub_panes, &csub->panes, csp);
free(csp);
}
RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) {
RB_REMOVE(control_sub_windows, &csub->windows, csw);
free(csw);
}
free(csub->last);
RB_REMOVE(control_subs, &cs->subs, csub);
free(csub->name);
free(csub->format);
free(csub);
}
/* Free a block. */ /* Free a block. */
static void static void
@@ -262,6 +202,66 @@ control_add_pane(struct client *c, struct window_pane *wp)
return (cp); return (cp);
} }
/* Get window for this client. */
static struct control_window *
control_get_window(struct client *c, u_int window)
{
struct control_state *cs = c->control_state;
struct control_window cw = { .window = window };
if (cs == NULL)
return (NULL);
return (RB_FIND(control_windows, &cs->windows, &cw));
}
/* Set window size for this client. */
void
control_set_window_size(struct client *c, u_int window, u_int sx, u_int sy)
{
struct control_state *cs = c->control_state;
struct control_window *cw;
if (cs == NULL)
return;
cw = control_get_window(c, window);
if (cw == NULL) {
cw = xcalloc(1, sizeof *cw);
cw->window = window;
RB_INSERT(control_windows, &cs->windows, cw);
}
cw->sx = sx;
cw->sy = sy;
}
/* Get window size for this client. */
int
control_get_window_size(struct client *c, u_int window, u_int *sx, u_int *sy)
{
struct control_window *cw;
if ((cw = control_get_window(c, window)) == NULL)
return (0);
*sx = cw->sx;
*sy = cw->sy;
return (1);
}
/* Clear window size for this client. */
void
control_clear_window_size(struct client *c, u_int window)
{
struct control_state *cs = c->control_state;
struct control_window *cw;
if (cs == NULL)
return;
cw = control_get_window(c, window);
if (cw != NULL) {
RB_REMOVE(control_windows, &cs->windows, cw);
free(cw);
}
}
/* Discard output for a pane. */ /* Discard output for a pane. */
static void static void
control_discard_pane(struct client *c, struct control_pane *cp) control_discard_pane(struct client *c, struct control_pane *cp)
@@ -298,6 +298,7 @@ control_reset_offsets(struct client *c)
struct control_pane *cp, *cp1; struct control_pane *cp, *cp1;
RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) { RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) {
control_discard_pane(c, cp);
RB_REMOVE(control_panes, &cs->panes, cp); RB_REMOVE(control_panes, &cs->panes, cp);
free(cp); free(cp);
} }
@@ -352,6 +353,9 @@ control_set_pane_off(struct client *c, struct window_pane *wp)
struct control_pane *cp; struct control_pane *cp;
cp = control_add_pane(c, wp); cp = control_add_pane(c, wp);
control_discard_pane(c, cp);
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
cp->flags |= CONTROL_PANE_OFF; cp->flags |= CONTROL_PANE_OFF;
} }
@@ -366,7 +370,7 @@ control_continue_pane(struct client *c, struct window_pane *wp)
cp->flags &= ~CONTROL_PANE_PAUSED; cp->flags &= ~CONTROL_PANE_PAUSED;
memcpy(&cp->offset, &wp->offset, sizeof cp->offset); memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued); memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
control_write(c, "%%continue %%%u", wp->id); control_notify_write(c, "%%continue %%%u", wp->id);
} }
} }
@@ -380,52 +384,139 @@ control_pause_pane(struct client *c, struct window_pane *wp)
if (~cp->flags & CONTROL_PANE_PAUSED) { if (~cp->flags & CONTROL_PANE_PAUSED) {
cp->flags |= CONTROL_PANE_PAUSED; cp->flags |= CONTROL_PANE_PAUSED;
control_discard_pane(c, cp); control_discard_pane(c, cp);
control_write(c, "%%pause %%%u", wp->id); control_notify_write(c, "%%pause %%%u", wp->id);
} }
} }
/* Write a line. */ /*
static void printflike(2, 0) * Reset a pane after its buffer has been replaced: drop any output still
control_vwrite(struct client *c, const char *fmt, va_list ap) * queued from the old buffer and start again from the pane's own offset.
{ */
struct control_state *cs = c->control_state;
char *s;
xvasprintf(&s, fmt, ap);
log_debug("%s: %s: writing line: %s", __func__, c->name, s);
bufferevent_write(cs->write_event, s, strlen(s));
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
free(s);
}
/* Write a line. */
void void
control_write(struct client *c, const char *fmt, ...) control_reset_pane(struct client *c, struct window_pane *wp)
{
struct control_pane *cp;
if (c->control_state == NULL)
return;
cp = control_get_pane(c, wp);
if (cp == NULL)
return;
control_discard_pane(c, cp);
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
}
/* 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_state *cs = c->control_state;
struct control_block *cb; struct control_block *cb;
va_list ap;
va_start(ap, fmt);
if (TAILQ_EMPTY(&cs->all_blocks)) { if (TAILQ_EMPTY(&cs->all_blocks)) {
control_vwrite(c, fmt, ap); log_debug("%s: %s: writing line: %s", __func__, c->name, line);
va_end(ap); bufferevent_write(cs->write_event, line, strlen(line));
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
free(line);
return; return;
} }
cb = xcalloc(1, sizeof *cb); cb = xcalloc(1, sizeof *cb);
xvasprintf(&cb->line, fmt, ap); cb->line = line;
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
cb->t = get_timer(); cb->t = get_timer();
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line); log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
bufferevent_enable(cs->write_event, EV_WRITE); bufferevent_enable(cs->write_event, EV_WRITE);
}
/* Flush notifications that were deferred while a guard block was open. */
static void
control_flush_deferred(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_line *cl, *cl1;
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
TAILQ_REMOVE(&cs->deferred, cl, entry);
control_write_line(c, cl->line);
free(cl);
}
}
/*
* Write a line of command output or error text. This is a sink for arbitrary
* user-controlled text (command output, capture-pane, error messages), so it
* must never try to interpret the content: guard tracking is done only in
* control_write_guard.
*/
void
control_write(struct client *c, const char *fmt, ...)
{
va_list ap;
char *line;
va_start(ap, fmt);
xvasprintf(&line, fmt, ap);
va_end(ap); va_end(ap);
control_write_line(c, line);
}
/*
* Write a %begin, %end or %error guard around a command's output. This is the
* only place guard lines are produced, so the block depth is maintained here;
* when the outermost block closes any deferred notifications are flushed after
* it. "guard" is always one of the fixed strings from cmdq_guard, never user
* text.
*/
void
control_write_guard(struct client *c, const char *guard, long t, u_int number,
int flags)
{
struct control_state *cs = c->control_state;
char *line;
if (strcmp(guard, "begin") == 0)
cs->guard_depth++;
xasprintf(&line, "%%%s %ld %u %d", guard, t, number, flags);
control_write_line(c, line);
if (strcmp(guard, "begin") != 0 && cs->guard_depth > 0 &&
--cs->guard_depth == 0)
control_flush_deferred(c);
}
/*
* Write a notification line. Notifications must never appear inside a command's
* %begin/%end guard block, so if one is open the line is deferred until it
* closes.
*/
void
control_notify_write(struct client *c, const char *fmt, ...)
{
struct control_state *cs = c->control_state;
struct control_line *cl;
va_list ap;
char *line;
va_start(ap, fmt);
xvasprintf(&line, fmt, ap);
va_end(ap);
if (cs->guard_depth == 0) {
control_write_line(c, line);
return;
}
log_debug("%s: %s: deferring notification: %s", __func__, c->name,
line);
cl = xcalloc(1, sizeof *cl);
cl->line = line;
TAILQ_INSERT_TAIL(&cs->deferred, cl, entry);
} }
/* Check age for this pane. */ /* Check age for this pane. */
@@ -452,7 +543,7 @@ control_check_age(struct client *c, struct window_pane *wp,
return (0); return (0);
cp->flags |= CONTROL_PANE_PAUSED; cp->flags |= CONTROL_PANE_PAUSED;
control_discard_pane(c, cp); control_discard_pane(c, cp);
control_write(c, "%%pause %%%u", wp->id); control_notify_write(c, "%%pause %%%u", wp->id);
} else { } else {
if (age < CONTROL_MAXIMUM_AGE) if (age < CONTROL_MAXIMUM_AGE)
return (0); return (0);
@@ -475,7 +566,7 @@ control_write_output(struct client *c, struct window_pane *wp)
if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
return; return;
if (c->flags & CONTROL_IGNORE_FLAGS) { if (c->flags & (CONTROL_IGNORE_FLAGS|CLIENT_EXIT)) {
cp = control_get_pane(c, wp); cp = control_get_pane(c, wp);
if (cp != NULL) if (cp != NULL)
goto ignore; goto ignore;
@@ -550,8 +641,9 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
struct evbuffer *buffer = cs->read_event->input; struct evbuffer *buffer = cs->read_event->input;
char *line, *error; char *line, *error;
struct cmd_parse_tree *tree;
struct cmdq_item *new_item;
struct cmdq_state *state; struct cmdq_state *state;
enum cmd_parse_status status;
for (;;) { for (;;) {
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
@@ -565,9 +657,14 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
} }
state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL); state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
status = cmd_parse_and_append(line, NULL, c, state, &error); tree = cmd_parse_from_string(line, NULL, &error);
if (status == CMD_PARSE_ERROR) if (tree == NULL)
cmdq_append(c, cmdq_get_callback(control_error, error)); cmdq_append(c, cmdq_get_callback(control_error, error));
else {
new_item = cmd_invoke_get(tree, state, NULL);
cmdq_append(c, new_item);
cmd_parse_free(tree);
}
cmdq_free_state(state); cmdq_free_state(state);
free(line); free(line);
@@ -585,6 +682,53 @@ control_all_done(struct client *c)
return (EVBUFFER_LENGTH(cs->write_event->output) == 0); return (EVBUFFER_LENGTH(cs->write_event->output) == 0);
} }
/*
* Wait for the terminal to send an empty line or close, used by a control
* client after printing %exit so a wrapping terminal (such as iTerm2) can
* finish reading.
*/
void
control_wait_exit(int fd)
{
struct pollfd pfd;
struct evbuffer *evb;
char *line;
int n;
evb = evbuffer_new();
if (evb == NULL)
fatalx("out of memory");
for (;;) {
line = evbuffer_readln(evb, NULL, EVBUFFER_EOL_LF);
if (line != NULL) {
if (*line == '\0') { /* empty line, stop */
free(line);
break;
}
free(line);
continue; /* drain buffered lines first */
}
memset(&pfd, 0, sizeof pfd);
pfd.fd = fd;
pfd.events = POLLIN;
if (poll(&pfd, 1, INFTIM) == -1) {
if (errno == EINTR)
continue;
break;
}
n = evbuffer_read(evb, fd, -1);
if (n == 0)
break;
if (n == -1 && errno != EAGAIN && errno != EINTR)
break;
}
evbuffer_free(evb);
}
/* Flush all blocks until output. */ /* Flush all blocks until output. */
static void static void
control_flush_all_blocks(struct client *c) control_flush_all_blocks(struct client *c)
@@ -610,7 +754,7 @@ control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
struct evbuffer *message, struct window_pane *wp, size_t size) struct evbuffer *message, struct window_pane *wp, size_t size)
{ {
u_char *new_data; u_char *new_data;
size_t new_size; size_t new_size, start;
u_int i; u_int i;
if (message == NULL) { if (message == NULL) {
@@ -629,10 +773,16 @@ control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
if (new_size < size) if (new_size < size)
fatalx("not enough data: %zu < %zu", new_size, size); fatalx("not enough data: %zu < %zu", new_size, size);
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
if (new_data[i] < ' ' || new_data[i] == '\\') if (new_data[i] < ' ' || new_data[i] == '\\') {
evbuffer_add_printf(message, "\\%03o", new_data[i]); evbuffer_add_printf(message, "\\%03o", new_data[i]);
else } else {
evbuffer_add_printf(message, "%c", new_data[i]); start = i;
while (i + 1 < size &&
new_data[i + 1] >= ' ' &&
new_data[i + 1] != '\\')
i++;
evbuffer_add(message, new_data + start, i - start + 1);
}
} }
window_pane_update_used_data(wp, &cp->offset, size); window_pane_update_used_data(wp, &cp->offset, size);
return (message); return (message);
@@ -754,6 +904,33 @@ control_write_callback(__unused struct bufferevent *bufev, void *data)
bufferevent_disable(cs->write_event, EV_WRITE); bufferevent_disable(cs->write_event, EV_WRITE);
} }
/* Write a subscription change. */
static void
control_sub_change(struct monitor_change *change, __unused void *data)
{
struct client *c = change->c;
struct session *s = change->s;
struct winlink *wl = change->wl;
struct window_pane *wp = change->wp;
struct window *w;
if (wp != NULL) {
w = wp->window;
control_notify_write(c,
"%%subscription-changed %s $%u @%u %u %%%u : %s",
change->name, s->id, w->id, wl->idx, wp->id, change->value);
} else if (wl != NULL) {
w = wl->window;
control_notify_write(c,
"%%subscription-changed %s $%u @%u %u - : %s",
change->name, s->id, w->id, wl->idx, change->value);
} else {
control_notify_write(c,
"%%subscription-changed %s $%u - - - : %s",
change->name, s->id, change->value);
}
}
/* Initialize for control mode. */ /* Initialize for control mode. */
void void
control_start(struct client *c) control_start(struct client *c)
@@ -769,9 +946,11 @@ control_start(struct client *c)
cs = c->control_state = xcalloc(1, sizeof *cs); cs = c->control_state = xcalloc(1, sizeof *cs);
RB_INIT(&cs->panes); RB_INIT(&cs->panes);
RB_INIT(&cs->windows);
TAILQ_INIT(&cs->pending_list); TAILQ_INIT(&cs->pending_list);
TAILQ_INIT(&cs->all_blocks); TAILQ_INIT(&cs->all_blocks);
RB_INIT(&cs->subs); TAILQ_INIT(&cs->deferred);
cs->subs = monitor_create_client(c, control_sub_change, NULL);
cs->read_event = bufferevent_new(c->fd, control_read_callback, cs->read_event = bufferevent_new(c->fd, control_read_callback,
control_write_callback, control_error_callback, c); control_write_callback, control_error_callback, c);
@@ -814,292 +993,63 @@ control_discard(struct client *c)
bufferevent_disable(cs->read_event, EV_READ); bufferevent_disable(cs->read_event, EV_READ);
} }
/* Discard all tmux-owned queued control blocks and stop writing. */
void
control_discard_all(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_block *cb, *cb1;
control_discard(c);
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
control_free_block(cs, cb);
bufferevent_disable(cs->write_event, EV_WRITE);
}
/* Stop control mode. */ /* Stop control mode. */
void void
control_stop(struct client *c) control_stop(struct client *c)
{ {
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
struct control_block *cb, *cb1; struct control_block *cb, *cb1;
struct control_sub *csub, *csub1; struct control_window *cw, *cw1;
struct control_line *cl, *cl1;
if (cs == NULL)
return;
monitor_destroy(cs->subs);
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
TAILQ_REMOVE(&cs->deferred, cl, entry);
free(cl->line);
free(cl);
}
if (~c->flags & CLIENT_CONTROLCONTROL) if (~c->flags & CLIENT_CONTROLCONTROL)
bufferevent_free(cs->write_event); bufferevent_free(cs->write_event);
bufferevent_free(cs->read_event); bufferevent_free(cs->read_event);
RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) control_reset_offsets(c);
control_free_sub(cs, csub); RB_FOREACH_SAFE(cw, control_windows, &cs->windows, cw1) {
if (evtimer_initialized(&cs->subs_timer)) RB_REMOVE(control_windows, &cs->windows, cw);
evtimer_del(&cs->subs_timer); free(cw);
}
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
control_free_block(cs, cb); control_free_block(cs, cb);
control_reset_offsets(c);
c->control_state = NULL;
free(cs); free(cs);
} }
/* Check session subscription. */
static void
control_check_subs_session(struct client *c, struct control_sub *csub)
{
struct session *s = c->session;
struct format_tree *ft;
char *value;
ft = format_create_defaults(NULL, c, s, NULL, NULL);
value = format_expand(ft, csub->format);
format_free(ft);
if (csub->last != NULL && strcmp(value, csub->last) == 0) {
free(value);
return;
}
control_write(c,
"%%subscription-changed %s $%u - - - : %s",
csub->name, s->id, value);
free(csub->last);
csub->last = value;
}
/* Check pane subscription. */
static void
control_check_subs_pane(struct client *c, struct control_sub *csub)
{
struct session *s = c->session;
struct window_pane *wp;
struct window *w;
struct winlink *wl;
struct format_tree *ft;
char *value;
struct control_sub_pane *csp, find;
wp = window_pane_find_by_id(csub->id);
if (wp == NULL || wp->fd == -1)
return;
w = wp->window;
TAILQ_FOREACH(wl, &w->winlinks, wentry) {
if (wl->session != s)
continue;
ft = format_create_defaults(NULL, c, s, wl, wp);
value = format_expand(ft, csub->format);
format_free(ft);
find.pane = wp->id;
find.idx = wl->idx;
csp = RB_FIND(control_sub_panes, &csub->panes, &find);
if (csp == NULL) {
csp = xcalloc(1, sizeof *csp);
csp->pane = wp->id;
csp->idx = wl->idx;
RB_INSERT(control_sub_panes, &csub->panes, csp);
}
if (csp->last != NULL && strcmp(value, csp->last) == 0) {
free(value);
continue;
}
control_write(c,
"%%subscription-changed %s $%u @%u %u %%%u : %s",
csub->name, s->id, w->id, wl->idx, wp->id, value);
free(csp->last);
csp->last = value;
}
}
/* Check all panes subscription. */
static void
control_check_subs_all_panes(struct client *c, struct control_sub *csub)
{
struct session *s = c->session;
struct window_pane *wp;
struct window *w;
struct winlink *wl;
struct format_tree *ft;
char *value;
struct control_sub_pane *csp, find;
RB_FOREACH(wl, winlinks, &s->windows) {
w = wl->window;
TAILQ_FOREACH(wp, &w->panes, entry) {
ft = format_create_defaults(NULL, c, s, wl, wp);
value = format_expand(ft, csub->format);
format_free(ft);
find.pane = wp->id;
find.idx = wl->idx;
csp = RB_FIND(control_sub_panes, &csub->panes, &find);
if (csp == NULL) {
csp = xcalloc(1, sizeof *csp);
csp->pane = wp->id;
csp->idx = wl->idx;
RB_INSERT(control_sub_panes, &csub->panes, csp);
}
if (csp->last != NULL &&
strcmp(value, csp->last) == 0) {
free(value);
continue;
}
control_write(c,
"%%subscription-changed %s $%u @%u %u %%%u : %s",
csub->name, s->id, w->id, wl->idx, wp->id, value);
free(csp->last);
csp->last = value;
}
}
}
/* Check window subscription. */
static void
control_check_subs_window(struct client *c, struct control_sub *csub)
{
struct session *s = c->session;
struct window *w;
struct winlink *wl;
struct format_tree *ft;
char *value;
struct control_sub_window *csw, find;
w = window_find_by_id(csub->id);
if (w == NULL)
return;
TAILQ_FOREACH(wl, &w->winlinks, wentry) {
if (wl->session != s)
continue;
ft = format_create_defaults(NULL, c, s, wl, NULL);
value = format_expand(ft, csub->format);
format_free(ft);
find.window = w->id;
find.idx = wl->idx;
csw = RB_FIND(control_sub_windows, &csub->windows, &find);
if (csw == NULL) {
csw = xcalloc(1, sizeof *csw);
csw->window = w->id;
csw->idx = wl->idx;
RB_INSERT(control_sub_windows, &csub->windows, csw);
}
if (csw->last != NULL && strcmp(value, csw->last) == 0) {
free(value);
continue;
}
control_write(c,
"%%subscription-changed %s $%u @%u %u - : %s",
csub->name, s->id, w->id, wl->idx, value);
free(csw->last);
csw->last = value;
}
}
/* Check all windows subscription. */
static void
control_check_subs_all_windows(struct client *c, struct control_sub *csub)
{
struct session *s = c->session;
struct window *w;
struct winlink *wl;
struct format_tree *ft;
char *value;
struct control_sub_window *csw, find;
RB_FOREACH(wl, winlinks, &s->windows) {
w = wl->window;
ft = format_create_defaults(NULL, c, s, wl, NULL);
value = format_expand(ft, csub->format);
format_free(ft);
find.window = w->id;
find.idx = wl->idx;
csw = RB_FIND(control_sub_windows, &csub->windows, &find);
if (csw == NULL) {
csw = xcalloc(1, sizeof *csw);
csw->window = w->id;
csw->idx = wl->idx;
RB_INSERT(control_sub_windows, &csub->windows, csw);
}
if (csw->last != NULL && strcmp(value, csw->last) == 0) {
free(value);
continue;
}
control_write(c,
"%%subscription-changed %s $%u @%u %u - : %s",
csub->name, s->id, w->id, wl->idx, value);
free(csw->last);
csw->last = value;
}
}
/* Check subscriptions timer. */
static void
control_check_subs_timer(__unused int fd, __unused short events, void *data)
{
struct client *c = data;
struct control_state *cs = c->control_state;
struct control_sub *csub, *csub1;
struct timeval tv = { .tv_sec = 1 };
log_debug("%s: timer fired", __func__);
evtimer_add(&cs->subs_timer, &tv);
RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
switch (csub->type) {
case CONTROL_SUB_SESSION:
control_check_subs_session(c, csub);
break;
case CONTROL_SUB_PANE:
control_check_subs_pane(c, csub);
break;
case CONTROL_SUB_ALL_PANES:
control_check_subs_all_panes(c, csub);
break;
case CONTROL_SUB_WINDOW:
control_check_subs_window(c, csub);
break;
case CONTROL_SUB_ALL_WINDOWS:
control_check_subs_all_windows(c, csub);
break;
}
}
}
/* Add a subscription. */ /* Add a subscription. */
void void
control_add_sub(struct client *c, const char *name, enum control_sub_type type, control_add_sub(struct client *c, const char *name, enum monitor_type type,
int id, const char *format) int id, const char *format)
{ {
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
struct control_sub *csub, find;
struct timeval tv = { .tv_sec = 1 };
find.name = (char *)name; monitor_add(cs->subs, name, type, id, format, MONITOR_NOTIFY_INITIAL);
if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
control_free_sub(cs, csub);
csub = xcalloc(1, sizeof *csub);
csub->name = xstrdup(name);
csub->type = type;
csub->id = id;
csub->format = xstrdup(format);
RB_INSERT(control_subs, &cs->subs, csub);
RB_INIT(&csub->panes);
RB_INIT(&csub->windows);
if (!evtimer_initialized(&cs->subs_timer))
evtimer_set(&cs->subs_timer, control_check_subs_timer, c);
if (!evtimer_pending(&cs->subs_timer, NULL))
evtimer_add(&cs->subs_timer, &tv);
} }
/* Remove a subscription. */ /* Remove a subscription. */
@@ -1107,11 +1057,6 @@ void
control_remove_sub(struct client *c, const char *name) control_remove_sub(struct client *c, const char *name)
{ {
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
struct control_sub *csub, find;
find.name = (char *)name; monitor_remove(cs->subs, name);
if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
control_free_sub(cs, csub);
if (RB_EMPTY(&cs->subs))
evtimer_del(&cs->subs_timer);
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */ /* $OpenBSD: environ.c,v 1.31 2026/06/13 20:07:30 nicm Exp $ */
/* /*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -57,6 +57,9 @@ environ_free(struct environ *env)
{ {
struct environ_entry *envent, *envent1; struct environ_entry *envent, *envent1;
if (env == NULL)
return;
RB_FOREACH_SAFE(envent, environ, env, envent1) { RB_FOREACH_SAFE(envent, environ, env, envent1) {
RB_REMOVE(environ, env, envent); RB_REMOVE(environ, env, envent);
free(envent->name); free(envent->name);
@@ -149,7 +152,8 @@ environ_clear(struct environ *env, const char *name)
void void
environ_put(struct environ *env, const char *var, int flags) environ_put(struct environ *env, const char *var, int flags)
{ {
char *name, *value; char *name;
const char *value;
value = strchr(var, '='); value = strchr(var, '=');
if (value == NULL) if (value == NULL)
@@ -263,11 +267,6 @@ environ_for_session(struct session *s, int no_TERM)
environ_set(env, "TERM_PROGRAM", 0, "%s", "tmux"); environ_set(env, "TERM_PROGRAM", 0, "%s", "tmux");
environ_set(env, "TERM_PROGRAM_VERSION", 0, "%s", getversion()); environ_set(env, "TERM_PROGRAM_VERSION", 0, "%s", getversion());
environ_set(env, "COLORTERM", 0, "truecolor"); environ_set(env, "COLORTERM", 0, "truecolor");
} else {
environ_unset(env, "TERM");
environ_unset(env, "TERM_PROGRAM");
environ_unset(env, "TERM_PROGRAM_VERSION");
environ_unset(env, "COLORTERM");
} }
#ifdef HAVE_SYSTEMD #ifdef HAVE_SYSTEMD

672
events-payload.c Normal file
View File

@@ -0,0 +1,672 @@
/* $OpenBSD: events-payload.c,v 1.1 2026/07/10 13:38:45 nicm Exp $ */
/*
* Copyright (c) 2026 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 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 <event.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "tmux.h"
/* Event payload item. */
struct event_payload_item {
char *name;
enum event_payload_type type;
union {
char *string;
time_t time;
int number;
u_int unsigned_number;
struct client *client;
struct session *session;
struct window *window;
struct window_pane *pane;
struct {
void *ptr;
event_payload_free_cb free_cb;
event_payload_print_cb print_cb;
} pointer;
};
RB_ENTRY(event_payload_item) entry;
};
RB_HEAD(event_payload_tree, event_payload_item);
struct event_payload {
struct event_payload_tree items;
struct cmd_find_state target;
};
static int
event_payload_cmp(struct event_payload_item *epi1,
struct event_payload_item *epi2)
{
return (strcmp(epi1->name, epi2->name));
}
RB_GENERATE_STATIC(event_payload_tree, event_payload_item, entry,
event_payload_cmp);
/* Find an item. */
static struct event_payload_item *
event_payload_find(struct event_payload *ep, const char *name)
{
struct event_payload_item find = { .name = (char *)name };
return (RB_FIND(event_payload_tree, &ep->items, &find));
}
/* Free the target in a payload. */
static void
event_payload_free_target(struct event_payload *ep)
{
struct cmd_find_state *target = &ep->target;
if (target->s != NULL)
session_remove_ref(target->s, __func__);
if (target->w != NULL)
window_remove_ref(target->w, __func__);
if (target->wp != NULL)
window_pane_remove_ref(target->wp, __func__);
cmd_find_clear_state(target, 0);
}
/* Free the value in an item. */
static void
event_payload_free_value(struct event_payload_item *epi)
{
switch (epi->type) {
case EVENT_PAYLOAD_STRING:
free(epi->string);
break;
case EVENT_PAYLOAD_CLIENT:
server_client_unref(epi->client);
break;
case EVENT_PAYLOAD_SESSION:
session_remove_ref(epi->session, __func__);
break;
case EVENT_PAYLOAD_WINDOW:
window_remove_ref(epi->window, __func__);
break;
case EVENT_PAYLOAD_PANE:
window_pane_remove_ref(epi->pane, __func__);
break;
case EVENT_PAYLOAD_POINTER:
if (epi->pointer.free_cb != NULL)
epi->pointer.free_cb(epi->pointer.ptr);
break;
case EVENT_PAYLOAD_INT:
case EVENT_PAYLOAD_UINT:
case EVENT_PAYLOAD_TIME:
break;
}
}
/* Set an item. */
static void
event_payload_set_item(struct event_payload *ep, const char *name,
struct event_payload_item *new)
{
struct event_payload_item *old;
new->name = xstrdup(name);
old = RB_INSERT(event_payload_tree, &ep->items, new);
if (old != NULL) {
RB_REMOVE(event_payload_tree, &ep->items, old);
event_payload_free_value(old);
free(old->name);
free(old);
RB_INSERT(event_payload_tree, &ep->items, new);
}
}
/* Create an event payload. */
struct event_payload *
event_payload_create(void)
{
struct event_payload *ep;
ep = xcalloc(1, sizeof *ep);
RB_INIT(&ep->items);
cmd_find_clear_state(&ep->target, 0);
return (ep);
}
/* Free an event payload. */
void
event_payload_free(struct event_payload *ep)
{
struct event_payload_item *epi, *epi1;
if (ep != NULL) {
RB_FOREACH_SAFE(epi, event_payload_tree, &ep->items, epi1) {
RB_REMOVE(event_payload_tree, &ep->items, epi);
event_payload_free_value(epi);
free(epi->name);
free(epi);
}
event_payload_free_target(ep);
free(ep);
}
}
/* Set the target. */
void
event_payload_set_target(struct event_payload *ep, struct cmd_find_state *fs)
{
struct cmd_find_state *target = &ep->target;
event_payload_free_target(ep);
if (fs->s != NULL) {
session_add_ref(fs->s, __func__);
target->s = fs->s;
}
if (fs->wl != NULL) {
target->idx = fs->wl->idx;
if (target->s == NULL) {
session_add_ref(fs->wl->session, __func__);
target->s = fs->wl->session;
}
} else
target->idx = -1;
if (fs->w != NULL) {
window_add_ref(fs->w, __func__);
target->w = fs->w;
} else if (fs->wl != NULL) {
window_add_ref(fs->wl->window, __func__);
target->w = fs->wl->window;
}
if (fs->wp != NULL) {
window_pane_add_ref(fs->wp, __func__);
target->wp = fs->wp;
}
}
/* Get the target. */
int
event_payload_get_target(struct event_payload *ep, struct cmd_find_state *fs)
{
struct cmd_find_state *t = &ep->target;
struct winlink *wl = NULL;
int flags = fs->flags;
if (t->idx != -1 &&
t->s != NULL &&
t->w != NULL &&
session_alive(t->s)) {
wl = winlink_find_by_index(&t->s->windows, t->idx);
if (wl != NULL && wl->window != t->w)
wl = NULL;
}
cmd_find_clear_state(fs, flags);
fs->s = t->s;
fs->w = t->w;
fs->wp = t->wp;
fs->wl = wl;
fs->idx = (wl != NULL ? wl->idx : -1);
if (cmd_find_valid_state(fs))
return (1);
if (wl != NULL &&
t->wp != NULL &&
window_has_pane(wl->window, t->wp)) {
cmd_find_from_winlink_pane(fs, wl, t->wp, flags);
if (cmd_find_valid_state(fs))
return (1);
}
if (t->wp != NULL &&
cmd_find_from_pane(fs, t->wp, flags) == 0 &&
cmd_find_valid_state(fs))
return (1);
if (wl != NULL) {
cmd_find_from_winlink(fs, wl, flags);
if (cmd_find_valid_state(fs))
return (1);
}
if (t->s != NULL &&
t->w != NULL &&
session_alive(t->s) &&
cmd_find_from_session_window(fs, t->s, t->w, flags) == 0 &&
cmd_find_valid_state(fs))
return (1);
if (t->s != NULL && session_alive(t->s)) {
cmd_find_from_session(fs, t->s, flags);
if (cmd_find_valid_state(fs))
return (1);
}
if (cmd_find_from_nothing(fs, flags) == 0)
return (1);
cmd_find_clear_state(fs, flags);
return (0);
}
/* Set a string item. */
void
event_payload_set_string(struct event_payload *ep, const char *name,
const char *fmt, ...)
{
struct event_payload_item *epi;
va_list ap;
va_start(ap, fmt);
epi = xcalloc(1, sizeof *epi);
epi->type = EVENT_PAYLOAD_STRING;
xvasprintf(&epi->string, fmt, ap);
event_payload_set_item(ep, name, epi);
va_end(ap);
}
/* Set a time item. */
void
event_payload_set_time(struct event_payload *ep, const char *name,
time_t value)
{
struct event_payload_item *epi;
epi = xcalloc(1, sizeof *epi);
epi->type = EVENT_PAYLOAD_TIME;
epi->time = value;
event_payload_set_item(ep, name, epi);
}
/* Set a number item. */
void
event_payload_set_int(struct event_payload *ep, const char *name, int value)
{
struct event_payload_item *epi;
epi = xcalloc(1, sizeof *epi);
epi->type = EVENT_PAYLOAD_INT;
epi->number = value;
event_payload_set_item(ep, name, epi);
}
/* Set an unsigned number item. */
void
event_payload_set_uint(struct event_payload *ep, const char *name, u_int value)
{
struct event_payload_item *epi;
epi = xcalloc(1, sizeof *epi);
epi->type = EVENT_PAYLOAD_UINT;
epi->unsigned_number = value;
event_payload_set_item(ep, name, epi);
}
/* Set a client item. */
void
event_payload_set_client(struct event_payload *ep, const char *name,
struct client *c)
{
struct event_payload_item *epi;
c->references++;
epi = xcalloc(1, sizeof *epi);
epi->type = EVENT_PAYLOAD_CLIENT;
epi->client = c;
event_payload_set_item(ep, name, epi);
}
/* Set a session item. */
void
event_payload_set_session(struct event_payload *ep, const char *name,
struct session *s)
{
struct event_payload_item *epi;
session_add_ref(s, __func__);
epi = xcalloc(1, sizeof *epi);
epi->type = EVENT_PAYLOAD_SESSION;
epi->session = s;
event_payload_set_item(ep, name, epi);
}
/* Set a window item. */
void
event_payload_set_window(struct event_payload *ep, const char *name,
struct window *w)
{
struct event_payload_item *epi;
window_add_ref(w, __func__);
epi = xcalloc(1, sizeof *epi);
epi->type = EVENT_PAYLOAD_WINDOW;
epi->window = w;
event_payload_set_item(ep, name, epi);
}
/* Set a pane item. */
void
event_payload_set_pane(struct event_payload *ep, const char *name,
struct window_pane *wp)
{
struct event_payload_item *epi;
window_pane_add_ref(wp, __func__);
epi = xcalloc(1, sizeof *epi);
epi->type = EVENT_PAYLOAD_PANE;
epi->pane = wp;
event_payload_set_item(ep, name, epi);
}
/* Set a pointer item. */
void
event_payload_set_pointer(struct event_payload *ep, const char *name,
void *ptr, event_payload_free_cb free_cb, event_payload_print_cb print_cb)
{
struct event_payload_item *epi;
epi = xcalloc(1, sizeof *epi);
epi->type = EVENT_PAYLOAD_POINTER;
epi->pointer.ptr = ptr;
epi->pointer.free_cb = free_cb;
epi->pointer.print_cb = print_cb;
event_payload_set_item(ep, name, epi);
}
/* Get a string item. */
const char *
event_payload_get_string(struct event_payload *ep, const char *name)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL || epi->type != EVENT_PAYLOAD_STRING)
return (NULL);
return (epi->string);
}
/* Print a payload item. */
static void
event_payload_add_item(struct event_payload_item *epi, struct evbuffer *evb)
{
switch (epi->type) {
case EVENT_PAYLOAD_STRING:
evbuffer_add_printf(evb, "%s", epi->string);
break;
case EVENT_PAYLOAD_TIME:
evbuffer_add_printf(evb, "%lld", (long long)epi->time);
break;
case EVENT_PAYLOAD_INT:
evbuffer_add_printf(evb, "%d", epi->number);
break;
case EVENT_PAYLOAD_UINT:
evbuffer_add_printf(evb, "%u", epi->unsigned_number);
break;
case EVENT_PAYLOAD_CLIENT:
evbuffer_add_printf(evb, "%s", epi->client->name);
break;
case EVENT_PAYLOAD_SESSION:
evbuffer_add_printf(evb, "$%u", epi->session->id);
break;
case EVENT_PAYLOAD_WINDOW:
evbuffer_add_printf(evb, "@%u", epi->window->id);
break;
case EVENT_PAYLOAD_PANE:
evbuffer_add_printf(evb, "%%%u", epi->pane->id);
break;
case EVENT_PAYLOAD_POINTER:
if (epi->pointer.print_cb != NULL)
epi->pointer.print_cb(epi->pointer.ptr, evb);
else
evbuffer_add_printf(evb, "%p", epi->pointer.ptr);
break;
}
}
/* Print a payload item. */
char *
event_payload_item_print(struct event_payload_item *epi)
{
struct evbuffer *evb;
char *value = NULL;
size_t size;
evb = evbuffer_new();
if (evb == NULL)
fatalx("out of memory");
event_payload_add_item(epi, evb);
if ((size = EVBUFFER_LENGTH(evb)) != 0)
value = xmemdup(EVBUFFER_DATA(evb), size);
else
value = xstrdup("");
evbuffer_free(evb);
return (value);
}
/* Print an item value. */
char *
event_payload_print(struct event_payload *ep, const char *name)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL)
return (NULL);
return (event_payload_item_print(epi));
}
/* Add payload items as formats. */
void
event_payload_add_formats(struct event_payload *ep, struct format_tree *ft,
const char *prefix)
{
struct event_payload_item *epi;
char *name, *value;
const char *key;
if (prefix == NULL)
prefix = "";
RB_FOREACH(epi, event_payload_tree, &ep->items) {
key = epi->name;
if (*key == '_')
continue;
value = event_payload_item_print(epi);
xasprintf(&name, "%s%s", prefix, key);
format_add(ft, name, "%s", value);
free(name);
free(value);
if (epi->type == EVENT_PAYLOAD_SESSION) {
xasprintf(&name, "%s%s_name", prefix, key);
format_add(ft, name, "%s", epi->session->name);
free(name);
} else if (epi->type == EVENT_PAYLOAD_WINDOW) {
xasprintf(&name, "%s%s_name", prefix, key);
format_add(ft, name, "%s", epi->window->name);
free(name);
}
}
}
/* Get the first payload item. */
struct event_payload_item *
event_payload_first(struct event_payload *ep)
{
return (RB_MIN(event_payload_tree, &ep->items));
}
/* Get the next payload item. */
struct event_payload_item *
event_payload_next(struct event_payload_item *epi)
{
return (RB_NEXT(event_payload_tree, , epi));
}
/* Get a payload item name. */
const char *
event_payload_item_name(struct event_payload_item *epi)
{
return (epi->name);
}
/* Get a payload item type. */
enum event_payload_type
event_payload_item_type(struct event_payload_item *epi)
{
return (epi->type);
}
/* Log a payload. */
void
event_payload_log(struct event_payload *ep, const char *fmt, ...)
{
struct event_payload_item *epi;
struct evbuffer *evb;
va_list ap;
char *prefix;
va_start(ap, fmt);
xvasprintf(&prefix, fmt, ap);
va_end(ap);
evb = evbuffer_new();
if (evb == NULL)
fatalx("out of memory");
if (ep != NULL) {
RB_FOREACH(epi, event_payload_tree, &ep->items) {
if (EVBUFFER_LENGTH(evb) != 0)
evbuffer_add_printf(evb, ", ");
evbuffer_add_printf(evb, "%s=", epi->name);
event_payload_add_item(epi, evb);
}
}
log_debug("%s%.*s", prefix, (int)EVBUFFER_LENGTH(evb),
(char *)EVBUFFER_DATA(evb));
evbuffer_free(evb);
free(prefix);
}
/* Get a time item. */
time_t
event_payload_get_time(struct event_payload *ep, const char *name)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL || epi->type != EVENT_PAYLOAD_TIME)
return (0);
return (epi->time);
}
/* Get a number item. */
int
event_payload_get_int(struct event_payload *ep, const char *name, int *value)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL || epi->type != EVENT_PAYLOAD_INT)
return (-1);
*value = epi->number;
return (0);
}
/* Get an unsigned number item. */
int
event_payload_get_uint(struct event_payload *ep, const char *name, u_int *value)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL || epi->type != EVENT_PAYLOAD_UINT)
return (-1);
*value = epi->unsigned_number;
return (0);
}
/* Get a client item. */
struct client *
event_payload_get_client(struct event_payload *ep, const char *name)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL || epi->type != EVENT_PAYLOAD_CLIENT)
return (NULL);
return (epi->client);
}
/* Get a session item. */
struct session *
event_payload_get_session(struct event_payload *ep, const char *name)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL || epi->type != EVENT_PAYLOAD_SESSION)
return (NULL);
return (epi->session);
}
/* Get a window item. */
struct window *
event_payload_get_window(struct event_payload *ep, const char *name)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL || epi->type != EVENT_PAYLOAD_WINDOW)
return (NULL);
return (epi->window);
}
/* Get a pane item. */
struct window_pane *
event_payload_get_pane(struct event_payload *ep, const char *name)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL || epi->type != EVENT_PAYLOAD_PANE)
return (NULL);
return (epi->pane);
}
/* Get a pointer item. */
void *
event_payload_get_pointer(struct event_payload *ep, const char *name)
{
struct event_payload_item *epi;
epi = event_payload_find(ep, name);
if (epi == NULL || epi->type != EVENT_PAYLOAD_POINTER)
return (NULL);
return (epi->pointer.ptr);
}

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