Compare commits

...

886 Commits
3.7c ... master

Author SHA1 Message Date
tmux update bot
e880cf63e0 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add rounded borders option for panes like popups.
2026-09-11 17:00:20 +00:00
nicm
325b40e8c4 Add rounded borders option for panes like popups. 2026-09-11 17:00:18 +00:00
tmux update bot
f95363aa83 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Rather than allowing floating panes to remain outside the window and invisible after resize, move them and resize them so they are fully inside the window. GitHub issue 5582 from Noam Stolero.
2026-09-11 12:58:31 +00:00
nicm
f8fc53b6f8 Rather than allowing floating panes to remain outside the window and
invisible after resize, move them and resize them so they are fully
inside the window. GitHub issue 5582 from Noam Stolero.
2026-09-11 12:58:29 +00:00
Nicholas Marriott
d215280b00 Add rounded border tests. 2026-09-11 11:17:35 +01:00
Nicholas Marriott
d859a0f410 Test for resizing floating panes, from Noam Stolero. 2026-09-11 09:16:55 +01:00
Nicholas Marriott
10d00cc7cb Modal pane test changes. 2026-09-11 09:10:14 +01:00
Nicholas Marriott
14a01dd337 Note changes from Heon Jeong. 2026-09-11 09:05:06 +01:00
tmux update bot
a958883625 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add remain-on-exit failed-key and a -D flag to new-pane to have a modal pane wait for Escape/C-c. Both to allow better compatibility with popups.
2026-09-10 13:04:13 +00:00
nicm
d202aa7ac1 Add remain-on-exit failed-key and a -D flag to new-pane to have a modal
pane wait for Escape/C-c. Both to allow better compatibility with
popups.
2026-09-10 13:04:11 +00:00
Nicholas Marriott
d9a5e82678 Fix tests. 2026-09-10 12:00:01 +01:00
tmux update bot
13c10f672c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a nesting limit for v1 layouts, reported by M Khalilov, fix based on issue 5572 from Afonso Januário. Also tweak some language while here.
2026-09-09 19:23:09 +00:00
nicm
b7c5030004 Add a nesting limit for v1 layouts, reported by M Khalilov, fix based on
issue 5572 from Afonso Januário. Also tweak some language while here.
2026-09-09 19:23:07 +00:00
Nicholas Marriott
4839123510 Tweak changes. 2026-09-09 13:50:42 +01:00
Nicholas Marriott
9b3268a2a0 Bump version. 2026-09-09 13:28:07 +01:00
Nicholas Marriott
5aeacf1cae Update CHANGES. 2026-09-09 13:06:56 +01:00
tmux update bot
7774731b95 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Draw bottom border with pane status line at the top.
  Bound the memory used by buffered control mode command replies (to 64 MB), GitHub issue 5565 from kagari dot shusei at proton dot me.
  Do not adjust prompt position on invalid Unicode, from Kaixuan Li.
2026-09-09 10:12:52 +00:00
nicm
0544f0b210 Draw bottom border with pane status line at the top. 2026-09-09 10:12:50 +00:00
nicm
a3129249d1 Bound the memory used by buffered control mode command replies (to 64
MB), GitHub issue 5565 from kagari dot shusei at proton dot me.
2026-09-09 10:12:50 +00:00
nicm
930c81b819 Do not adjust prompt position on invalid Unicode, from Kaixuan Li. 2026-09-09 10:12:50 +00:00
Nicholas Marriott
fc695b4e86 Test for v1 nesting limit, based on a changed from Afonso Januário. 2026-09-09 10:01:32 +01:00
Nicholas Marriott
36fb7ab084 Tests of border. 2026-09-09 09:32:14 +01:00
tmux update bot
90a7dd4654 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add new layout format which includes floating panes. The new format is now a JSON subset which is less fragile and easier to handle than the old custom format. The old (version 1) format is still supported for control mode clients for now - they must set the new-layouts flag to receive the new format. From Dane Jensen.
2026-09-09 08:27:51 +00:00
nicm
bf43fdc0c7 Add new layout format which includes floating panes. The new format is
now a JSON subset which is less fragile and easier to handle than the
old custom format. The old (version 1) format is still supported for
control mode clients for now - they must set the new-layouts flag to
receive the new format. From Dane Jensen.
2026-09-09 08:27:49 +00:00
Nicholas Marriott
077d6e53cd Test for invalid prompt. 2026-09-09 08:53:51 +01:00
Nicholas Marriott
4e19f7a8dc New layout format regress test, from Dane Jensen. 2026-09-09 08:04:23 +01:00
tmux update bot
7941f7b863 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  The client for the layout format is the client we are sending it to, not the target client.
2026-09-08 21:40:59 +00:00
nicm
c7102ebbe5 The client for the layout format is the client we are sending it to, not
the target client.
2026-09-08 21:40:57 +00:00
tmux update bot
1b87cb752e Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add new-layouts flag for control clients so they can default to old layouts for backwards compatibility.
2026-09-08 11:38:06 +00:00
nicm
d9692f7ecf Add new-layouts flag for control clients so they can default to old
layouts for backwards compatibility.
2026-09-08 11:38:04 +00:00
Nicholas Marriott
98637576bd Test for new-layouts flag. 2026-09-08 11:27:25 +01:00
Nicholas Marriott
d8bdb23108 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a function to find last pane index, from Dane Jensen.
  Add a parser for a subset of JSON, will be used for new layout strings (and maybe some other stuff), from Dane Jensen.
  Add capture-pane -I to show times.
2026-09-08 11:25:17 +01:00
nicm
2191c9b172 Add a function to find last pane index, from Dane Jensen. 2026-09-08 10:22:40 +00:00
nicm
01d1d6580a Add a parser for a subset of JSON, will be used for new layout strings
(and maybe some other stuff), from Dane Jensen.
2026-09-08 10:22:40 +00:00
nicm
96f4c7b9fa Add capture-pane -I to show times. 2026-09-08 10:22:40 +00:00
Nicholas Marriott
6a24d5437b JSON test. 2026-09-08 09:36:26 +01:00
Nicholas Marriott
8396a63128 capture-pane -I test. 2026-09-08 08:32:05 +01:00
tmux update bot
d44bfda26d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Error on invalid relative targets, such as +foo or -0. GitHub issue 5576 from imcusg at gmail dot com.
  Do not silently make a session monitor if the target is unknown, GitHub issue 5575 from zzchun12826 at gmail dot com.
2026-09-07 17:56:25 +00:00
nicm
00f3899aea Error on invalid relative targets, such as +foo or -0. GitHub issue 5576
from imcusg at gmail dot com.
2026-09-07 17:56:23 +00:00
nicm
557967c36c Do not silently make a session monitor if the target is unknown, GitHub
issue 5575 from zzchun12826 at gmail dot com.
2026-09-07 17:56:23 +00:00
Nicholas Marriott
73db0a54e5 Tests from GitHub issue 5576. 2026-09-07 13:05:38 +01:00
tmux update bot
578e07fcbc Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not attempt to write to control clients which have been destroyed, reported by Artur Penttinen in GitHub issue 5570.
2026-09-04 06:00:37 +00:00
nicm
93169e2e62 Do not attempt to write to control clients which have been destroyed,
reported by Artur Penttinen in GitHub issue 5570.
2026-09-04 06:00:36 +00:00
tmux update bot
db24c029be Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Extend neww -S to work with -t as well as -t, GitHub issue 5571 from Mazunki Hoksaas.
2026-09-03 23:28:11 +00:00
nicm
a6f9a206b7 Extend neww -S to work with -t as well as -t, GitHub issue 5571 from
Mazunki Hoksaas.
2026-09-03 23:28:09 +00:00
Nicholas Marriott
ce253864f8 Test for late run-shell on a client that has exited. 2026-09-03 22:39:09 +01:00
tmux update bot
79f1f43830 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Wait for pane exit status in all cases not just remain-on-exit since events need it.
2026-09-03 21:24:20 +00:00
nicm
7b666e62be Wait for pane exit status in all cases not just remain-on-exit since
events need it.
2026-09-03 21:24:19 +00:00
Nicholas Marriott
5d5f66239a Test for -S from Mazunki Hoksaas. 2026-09-03 22:04:34 +01:00
Nicholas Marriott
4b1586fa81 Test for exit status. 2026-09-03 20:12:44 +01:00
Nicholas Marriott
9fa390aee6 Fix error in test. 2026-09-02 12:32:29 +01:00
Nicholas Marriott
ad632726bd Some more regress tests. 2026-09-02 11:16:47 +01:00
Nicholas Marriott
1dd1717462 Fix tests for macOS. 2026-09-02 08:43:21 +01:00
tmux update bot
73db721dab Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Create screen before zoom may need to use it.
2026-09-02 06:15:13 +00:00
nicm
6bb6c286a9 Create screen before zoom may need to use it. 2026-09-02 06:15:11 +00:00
tmux update bot
ca34d190b5 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not allow cursor on/off to escape synchronized updates.
2026-09-01 23:20:22 +00:00
nicm
57a13664cc Do not allow cursor on/off to escape synchronized updates. 2026-09-01 23:20:20 +00:00
Nicholas Marriott
59dc0e75c4 Add a couple more tests. 2026-09-01 22:07:08 +01:00
Nicholas Marriott
2d6aed1d92 Add some more tests for recent stuff. 2026-09-01 21:55:50 +01:00
Nicholas Marriott
3dd575ce55 Update CHANGES. 2026-09-01 21:48:30 +01:00
Nicholas Marriott
60e871658f Remove +x. 2026-09-01 20:52:44 +01:00
Nicholas Marriott
c49dfdf496 Regress test for cursor updates. 2026-09-01 20:51:20 +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
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
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
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
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
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
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
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
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
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
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
nicm
824ef37f5f Change layout sets to build the layout using the new mechanics, from
Dane Jensen.
2026-07-01 16:43:14 +00: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
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
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
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
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
Thomas Adam
8d470017bc Merge branch 'obsd-master' 2026-06-30 22:00:06 +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
Thomas Adam
90fc51542f Merge branch 'obsd-master' 2026-06-30 11:30:06 +01:00
nicm
f8674cc993 Add default features for Ghostty, GitHub issue 5305 from Glib Shpychka. 2026-06-30 10:07:05 +00: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
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
nicm
a97f643bd3 Remove C-r from help since it does not in fact exist. 2026-06-30 06:44:46 +00: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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Thomas Adam
97156f3382 Merge branch 'obsd-master' 2026-06-09 09:30:06 +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
440 changed files with 50486 additions and 9241 deletions

View File

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

View File

@@ -16,8 +16,10 @@ concurrency:
jobs:
action:
runs-on: ubuntu-latest
if: github.repository == 'tmux/tmux'
timeout-minutes: 10
steps:
- uses: dessant/lock-threads@v6
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6
with:
github-token: ${{ github.token }}
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/
.dirstamp
.serena/
AGENTS.md
Makefile
Makefile.in
aclocal.m4
@@ -19,6 +21,7 @@ configure
core
etc/
fuzz/*-fuzzer
regress/logs/
tags
tmux
tmux.1.*

223
CHANGES
View File

@@ -1,3 +1,226 @@
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.
-C closes the pane when the mouse is clicked outside it and -K sends all
keys, including the prefix key, to the pane. A modal pane is now used for
the editor in buffer mode;
- new-pane -A creates a floating pane which remains above a zoomed pane;
modal panes do this by default;
- 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;
- command-prompt -P opens a prompt inside a pane rather than on the status
line;
- pane-border-status has top-floating and bottom-floating, and there are new
default bindings under C-b g for common move and resize operations.
* Layout strings now use a JSON subset format which includes floating panes.
The old format is still accepted; control mode clients receive old layouts
unless they set the new-layouts flag.
* Add support for themes and improve default colours:
- 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, swap 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).
* Add a clear-on-attach server option. When disabled, tmux does not enter the
alternate screen on attach and instead scrolls the existing terminal content
into the scrollback buffer so it remains accessible (issue 5508).
* Copy mode can now automatically refresh as pane content changes with the
refresh-on, refresh-off and refresh-toggle commands. Automatic refresh is off
by default; refresh-now refreshes once and is bound to r. These replace
refresh-from-pane (issue 5165).
* Extend the fill-character option so both inside and outside the window can be
changed, with a new default for unused areas inside the window.
* Change show-options and show-hooks to use formats and add a -F flag to each.
* Allow individual terminal features to be disabled by adding @ to their names
in terminal-features, and add a utf8 feature for terminals which support
UTF-8 output.
* 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).
* Add capture-pane -I to include the time each line entered history.
* Various memory leak fixes from Heon Jeong.
* Add a default C-b T binding to change the current pane title.
* Menus now belong to the window, so appear on all clients.
* 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 copy-mode-current-line-style to set the style of the line containing the
cursor in copy mode (issue 5391).
* Copy mode word commands now recognise all Unicode whitespace characters; a
space in word-separators matches any Unicode whitespace character (issue
5562).
* 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, window_modal_pane, pane_private_modes,
pane_output_generation, history_added, history_collected, history_generation,
hook_fire_count and hook_fire_time.
* 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.
* The active-pane client flag has been removed.
* Fix send-keys -K so keys are inserted in the correct place in the input
queue, like keys from key bindings (issue 3476).
* 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). Queue notifications so they are not
sent inside %begin/%end (issue 5458), do not let a stuck client prevent the
server from exiting (issue 5444), and reset control mode offsets when a pane
is respawned (issue 5498). Bound buffered command replies so a control mode
client that stops reading cannot consume unlimited memory (issue 5565).
* Fix grouped sessions sometimes being left as unusable command targets while
they are being killed (Bryce Miller, issue 5180).
* 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.

View File

@@ -4,7 +4,7 @@ CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c
# Distribution tarball options.
EXTRA_DIST = \
CHANGES README README.ja COPYING example_tmux.conf \
CHANGES README COPYING example_tmux.conf \
osdep-*.c mdoc2man.awk tmux.1
dist_EXTRA_tmux_SOURCES = compat/*.[ch]
@@ -13,7 +13,8 @@ AM_CPPFLAGS += @XOPEN_DEFINES@ \
-DTMUX_VERSION='"@VERSION@"' \
-DTMUX_CONF='"$(sysconfdir)/tmux.conf:~/.tmux.conf:$$XDG_CONFIG_HOME/tmux/tmux.conf:~/.config/tmux/tmux.conf"' \
-DTMUX_LOCK_CMD='"@DEFAULT_LOCK_CMD@"' \
-DTMUX_TERM='"@DEFAULT_TERM@"'
-DTMUX_TERM='"@DEFAULT_TERM@"' \
-DTMUX_MOUSE=1
# Additional object files.
LDADD = $(LIBOBJS)
@@ -42,7 +43,8 @@ AM_CPPFLAGS += -DDEBUG
endif
AM_CPPFLAGS += -iquote.
if IS_ASAN
AM_CFLAGS += -fsanitize=address
AM_CPPFLAGS += -DASAN
AM_CFLAGS += -fsanitize=address -fno-omit-frame-pointer
AM_LDFLAGS += -fsanitize=address
endif
endif
@@ -99,7 +101,6 @@ dist_tmux_SOURCES = \
cmd-detach-client.c \
cmd-display-menu.c \
cmd-display-message.c \
cmd-display-panes.c \
cmd-find-window.c \
cmd-find.c \
cmd-if-shell.c \
@@ -159,16 +160,21 @@ dist_tmux_SOURCES = \
control-notify.c \
control.c \
environ.c \
events-payload.c \
events.c \
file.c \
format.c \
format-draw.c \
format.c \
fuzzy.c \
grid-reader.c \
grid-view.c \
grid.c \
hooks.c \
hyperlinks.c \
input-keys.c \
input.c \
job.c \
json.c \
key-bindings.c \
key-string.c \
layout-custom.c \
@@ -177,13 +183,15 @@ dist_tmux_SOURCES = \
log.c \
menu.c \
mode-tree.c \
monitor.c \
names.c \
notify.c \
options-table.c \
options.c \
paste.c \
popup.c \
proc.c \
prompt-history.c \
prompt.c \
regsub.c \
resize.c \
screen-redraw.c \
@@ -198,9 +206,9 @@ dist_tmux_SOURCES = \
spawn.c \
status.c \
style.c \
tmux-protocol.h \
tmux.c \
tmux.h \
tmux-protocol.h \
tty-acs.c \
tty-draw.c \
tty-features.c \
@@ -209,12 +217,16 @@ dist_tmux_SOURCES = \
tty.c \
utf8-combined.c \
utf8.c \
window-border.c \
window-buffer.c \
window-client.c \
window-clock.c \
window-copy.c \
window-customize.c \
window-panes.c \
window-switch.c \
window-tree.c \
window-visible.c \
window.c \
xmalloc.c \
xmalloc.h

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>
@@ -205,7 +205,7 @@ alerts_check_bell(struct window *w)
}
if (!alerts_action_applies(wl, "bell-action"))
continue;
notify_winlink("alert-bell", wl);
events_fire_winlink("alert-bell", wl);
if (s->flags & SESSION_ALERTED)
continue;
@@ -241,7 +241,7 @@ alerts_check_activity(struct window *w)
}
if (!alerts_action_applies(wl, "activity-action"))
continue;
notify_winlink("alert-activity", wl);
events_fire_winlink("alert-activity", wl);
if (s->flags & SESSION_ALERTED)
continue;
@@ -277,7 +277,7 @@ alerts_check_silence(struct window *w)
}
if (!alerts_action_applies(wl, "silence-action"))
continue;
notify_winlink("alert-silence", wl);
events_fire_winlink("alert-silence", wl);
if (s->flags & SESSION_ALERTED)
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>
@@ -149,7 +149,7 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause,
int optional_argument)
{
struct args_value *argument, *new;
const char *s;
const char *s, *as;
new = xcalloc(1, sizeof *new);
if (*string != '\0') {
@@ -180,12 +180,24 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause,
xasprintf(cause, "-%c expects an argument", flag);
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);
(*i)++;
out:
s = args_value_as_string(new);
log_debug("%s: -%c = %s", __func__, flag, s);
args_set(args, flag, new, 0);
return (0);
}
@@ -750,8 +762,6 @@ args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
cmdq_error(item, "%s", error);
free(error);
}
else
cmdlist->references++;
args_make_commands_free(state);
return (cmdlist);
}
@@ -815,8 +825,10 @@ args_make_commands(struct args_command_state *state, int argc, char **argv,
int i;
if (state->cmdlist != NULL) {
if (argc == 0)
if (argc == 0) {
state->cmdlist->references++;
return (state->cmdlist);
}
return (cmd_list_copy(state->cmdlist, argc, argv));
}
@@ -998,7 +1010,7 @@ args_string_percentage(const char *value, long long minval, long long maxval,
copy = xstrdup(value);
copy[valuelen - 1] = '\0';
ll = strtonum(copy, 0, 100, &errstr);
ll = strtonum(copy, 0, 1000, &errstr);
free(copy);
if (errstr != NULL) {
*cause = xstrdup(errstr);
@@ -1066,7 +1078,7 @@ args_string_percentage_and_expand(const char *value, long long minval,
copy[valuelen - 1] = '\0';
f = format_single_from_target(item, copy);
ll = strtonum(f, 0, 100, &errstr);
ll = strtonum(f, 0, 1000, &errstr);
free(f);
free(copy);
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>
@@ -45,7 +45,7 @@ attributes_tostring(int attr)
(attr & GRID_ATTR_UNDERSCORE_3) ? "curly-underscore," : "",
(attr & GRID_ATTR_UNDERSCORE_4) ? "dotted-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)
buf[len - 1] = '\0';

21
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>
@@ -56,7 +56,7 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data)
if (cfg_item != NULL)
cmdq_continue(cfg_item);
status_prompt_load_history();
prompt_load_history();
return (CMD_RETURN_NORMAL);
}
@@ -223,13 +223,15 @@ cfg_print_causes(struct cmdq_item *item)
{
struct client *c = cmdq_get_client(item);
u_int i;
char *cause;
for (i = 0; i < cfg_ncauses; i++) {
cause = cfg_causes[i];
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
cmdq_print(item, "%s", cfg_causes[i]);
free(cfg_causes[i]);
cmdq_print(item, "%s", cause);
free(cause);
}
free(cfg_causes);
@@ -244,14 +246,16 @@ cfg_show_causes(struct session *s)
struct window_pane *wp;
struct window_mode_entry *wme;
u_int i;
char *cause;
if (cfg_ncauses == 0)
return;
if (c != NULL && (c->flags & CLIENT_CONTROL)) {
for (i = 0; i < cfg_ncauses; i++) {
control_write(c, "%%config-error %s", cfg_causes[i]);
free(cfg_causes[i]);
cause = cfg_causes[i];
control_notify_write(c, "%%config-error %s", cause);
free(cause);
}
goto out;
}
@@ -268,7 +272,8 @@ cfg_show_causes(struct session *s)
wme = TAILQ_FIRST(&wp->modes);
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++) {
window_copy_add(wp, 0, "%s", 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>
@@ -239,9 +239,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
pid_t ppid;
enum msgtype msg;
struct termios tio, saved_tio;
size_t size, linesize = 0;
ssize_t linelen;
char *line = NULL, **caps = NULL, *cause;
size_t size;
char **caps = NULL, *cause;
u_int ncaps = 0;
struct args_value *values;
@@ -277,7 +276,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
proc_set_signals(client_proc, client_signal);
/* Save the flags. */
client_flags = flags;
client_flags = flags|CLIENT_WRITE_ACK;
log_debug("flags are %#llx", (unsigned long long)client_flags);
/* Initialize the client socket and start the server. */
@@ -407,11 +406,6 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
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. */
if (client_attached) {
if (client_exitreason != CLIENT_EXIT_NONE)
@@ -426,15 +420,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
else
printf("%%exit\n");
fflush(stdout);
if (client_flags & CLIENT_CONTROL_WAITEXIT) {
setvbuf(stdin, NULL, _IOLBF, 0);
for (;;) {
linelen = getline(&line, &linesize, stdin);
if (linelen <= 1)
break;
}
free(line);
}
if (client_flags & CLIENT_CONTROL_WAITEXIT)
control_wait_exit(STDIN_FILENO);
if (client_flags & CLIENT_CONTROLCONTROL) {
printf("\033\\");
fflush(stdout);
@@ -442,6 +429,12 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
}
} else if (client_exitreason != CLIENT_EXIT_NONE)
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);
}
@@ -719,6 +712,9 @@ client_dispatch_wait(struct imsg *imsg)
fprintf(stderr, "server version is too old for client\n");
proc_exit(client_proc);
break;
default:
log_debug("unknown message type %u", imsg->hdr.type);
break;
}
}
@@ -805,5 +801,8 @@ client_dispatch_attached(struct imsg *imsg)
system(data);
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
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>
@@ -163,7 +163,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
if (~c->flags & CLIENT_CONTROL)
proc_send(c->peer, MSG_READY, -1, NULL, 0);
notify_client("client-attached", c);
events_fire_client("client-attached", c);
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>

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>
@@ -34,9 +34,10 @@ const struct cmd_entry cmd_break_pane_entry = {
.name = "break-pane",
.alias = "breakp",
.args = { "abdPF:n:s:t:", 0, 0, NULL },
.usage = "[-abdP] [-F format] [-n window-name] [-s src-pane] "
"[-t dst-window]",
.args = { "abdPF:n:s:t:Wx:X:y:Y:", 0, 0, NULL },
.usage = "[-abdPW] [-F format] [-n window-name] [-s src-pane] "
"[-t dst-window] [-x width] [-y height] [-X x-position] "
"[-Y y-position]",
.source = { 's', CMD_FIND_PANE, 0 },
.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
};
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
cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
{
@@ -57,11 +98,19 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
struct session *src_s = source->s;
struct session *dst_s = target->s;
struct window_pane *wp = source->wp;
struct window *w = wl->window;
char *newname, *cause, *cp;
int idx = target->idx, before;
struct window *w = wl->window, *old_w = w;
char *cause, *cp;
int idx = target->idx, before, old_idx = wl->idx;
const char *template, *name = args_get(args, 'n');
if (wp == w->modal) {
cmdq_error(item, "pane is modal");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'W'))
return (cmd_break_pane_float(item, args, w, wp));
if (name != NULL && !check_name(name)) {
cmdq_error(item, "invalid window name: %s", name);
return (CMD_RETURN_ERROR);
@@ -93,6 +142,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
wl = winlink_find_by_window(&dst_s->windows, w);
if (wl == NULL)
return (CMD_RETURN_ERROR);
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
goto out;
}
if (idx != -1 && winlink_find_by_index(&dst_s->windows, idx) != NULL) {
@@ -107,6 +157,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
layout_close_pane(wp);
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);
wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED);
TAILQ_INSERT_HEAD(&w->panes, wp, entry);
@@ -114,22 +165,26 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
w->active = wp;
w->latest = tc;
if (name == NULL) {
newname = default_window_name(w);
window_set_name(w, newname, 0);
free(newname);
} else {
window_set_name(w, name, 0);
free(w->name);
if (name == NULL)
w->name = default_window_name(w);
else {
w->name = clean_name(name, 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);
wp->flags |= PANE_CHANGED;
colour_palette_from_option(&wp->palette, wp->options);
if (idx == -1)
idx = -1 - options_get_number(dst_s->options, "base-index");
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
window_remove_ref(w, __func__);
events_fire_window("window-created", w);
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
if (!args_has(args, 'd')) {
session_select(dst_s, wl->idx);
cmd_find_from_session(current, dst_s, 0);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-capture-pane.c,v 1.69 2026/09/08 07:31:59 nicm Exp $ */
/*
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
.name = "capture-pane",
.alias = "capturep",
.args = { "ab:CeE:FHJLMNpPqS:Tt:", 0, 0, NULL },
.usage = "[-aCeFHJLMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] "
.args = { "ab:CeE:FHIJLMNpPqRS:Tt:", 0, 0, NULL },
.usage = "[-aCeFHIJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
"[-S start-line] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -75,6 +75,108 @@ cmd_capture_pane_append(char *buf, size_t *len, const char *line,
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 *
cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
size_t *len)
@@ -98,7 +200,7 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
tmp[0] = line[i];
tmp[1] = '\0';
} else
xsnprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
snprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
buf = cmd_capture_pane_append(buf, len, tmp,
strlen(tmp));
}
@@ -157,7 +259,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
struct grid_cell *gc = NULL;
struct window_mode_entry *wme;
int n, join_lines, number_lines, flags = 0;
int show_flags, hyperlinks;
int show_flags, show_time, hyperlinks;
u_int *links = NULL, nlinks = 0;
u_int i, sx, top, bottom, tmp;
char *cause, *buf = NULL, *line, b[64], *cp;
@@ -240,6 +342,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
flags |= GRID_STRING_TRIM_SPACES;
number_lines = args_has(args, 'L');
show_flags = args_has(args, 'F');
show_time = args_has(args, 'I');
hyperlinks = args_has(args, 'H');
if (hyperlinks)
links = xreallocarray(NULL, gd->sx, sizeof *links);
@@ -256,6 +359,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
free(line);
continue;
}
gl = grid_peek_line(gd, i);
if (number_lines) {
if (i >= gd->hsize)
@@ -266,11 +370,16 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
if (n >= 0)
buf = cmd_capture_pane_append(buf, len, b, n);
}
if (show_time) {
n = snprintf(b, sizeof b, "%llu ",
(unsigned long long)grid_line_time(gl));
if (n >= 0)
buf = cmd_capture_pane_append(buf, len, b, n);
}
if (show_flags) {
cp = b;
*cp = '\0';
gl = grid_peek_line(gd, i);
if (gl->flags & GRID_LINE_DEAD)
*cp++ = 'D';
if (gl->flags & GRID_LINE_HYPERLINK)
@@ -291,7 +400,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
}
buf = cmd_capture_pane_append(buf, len, line, linelen);
gl = grid_peek_line(gd, i);
if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
buf[(*len)++] = '\n';
@@ -318,11 +426,14 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item)
grid_clear_history(wp->base.grid);
if (args_has(args, 'H'))
screen_reset_hyperlinks(wp->screen);
server_redraw_window(wp->window);
return (CMD_RETURN_NORMAL);
}
len = 0;
if (args_has(args, 'P') && !args_has(args, 'H'))
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);
else
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>
@@ -33,8 +33,8 @@ const struct cmd_entry cmd_choose_tree_entry = {
.name = "choose-tree",
.alias = NULL,
.args = { "F:f:GK:NO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-GNrswZ] [-F format] [-f filter] [-K key-format] "
.args = { "F:f:GhK:kNO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-GhkNrswZ] [-F format] [-f filter] [-K key-format] "
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
.target = { 't', CMD_FIND_PANE, 0 },
@@ -47,8 +47,8 @@ const struct cmd_entry cmd_choose_client_entry = {
.name = "choose-client",
.alias = NULL,
.args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
.args = { "F:f:hiK:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-hikNrZ] [-F format] [-f filter] [-K key-format] "
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
.target = { 't', CMD_FIND_PANE, 0 },
@@ -61,8 +61,8 @@ const struct cmd_entry cmd_choose_buffer_entry = {
.name = "choose-buffer",
.alias = NULL,
.args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
.args = { "F:f:K:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
.usage = "[-kNrZ] [-F format] [-f filter] [-K key-format] "
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
.target = { 't', CMD_FIND_PANE, 0 },
@@ -75,8 +75,8 @@ const struct cmd_entry cmd_customize_mode_entry = {
.name = "customize-mode",
.alias = NULL,
.args = { "F:f:Nt:yZ", 0, 0, NULL },
.usage = "[-NZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE,
.args = { "F:f:kNt:yZ", 0, 0, NULL },
.usage = "[-kNZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -84,6 +84,34 @@ const struct cmd_entry cmd_customize_mode_entry = {
.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
cmd_choose_tree_args_parse(__unused struct args *args, __unused u_int idx,
__unused char **cause)
@@ -116,9 +144,13 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item)
mode = &window_client_mode;
} else if (cmd_get_entry(self) == &cmd_customize_mode_entry)
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
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);
}

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>
@@ -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 *,
struct cmdq_item *);
static int cmd_command_prompt_callback(struct client *, void *,
const char *, int);
static enum prompt_result cmd_command_prompt_callback(struct client *, void *,
const char *, enum prompt_key_result);
static void cmd_command_prompt_free(void *);
const struct cmd_entry cmd_command_prompt_entry = {
.name = "command-prompt",
.alias = NULL,
.args = { "1CbeFiklI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse },
.usage = "[-1CbeFiklN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE
.args = { "1CbeFiklI:NPp:t:T:", 0, 1, cmd_command_prompt_args_parse },
.usage = "[-1CbeFiklNP] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE
" [-T prompt-type] [template]",
.flags = CMD_CLIENT_TFLAG,
@@ -62,6 +62,8 @@ struct cmd_command_prompt_cdata {
int flags;
enum prompt_type prompt_type;
struct window_pane *wp;
struct cmd_command_prompt_prompt *prompts;
u_int count;
u_int current;
@@ -87,10 +89,15 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_command_prompt_cdata *cdata;
char *tmp, *prompts, *prompt, *next_prompt;
char *inputs = NULL, *next_input;
struct window_pane *wp = target->wp;
u_int count = args_count(args);
int wait = !args_has(args, 'b'), space = 1;
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);
if (args_has(args, 'i'))
wait = 0;
@@ -98,6 +105,8 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
cdata = xcalloc(1, sizeof *cdata);
if (wait)
cdata->item = item;
if (pane)
cdata->wp = wp;
cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait,
args_has(args, 'F'));
@@ -146,7 +155,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
}
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) {
cmdq_error(item, "unknown type: %s", type);
cmd_command_prompt_free(cdata);
@@ -167,18 +176,27 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
cdata->flags |= PROMPT_BSPACE_EXIT;
if (args_has(args, 'C'))
cdata->flags |= PROMPT_NOFREEZE;
status_prompt_set(tc, target, cdata->prompts[0].prompt,
cdata->prompts[0].input, cmd_command_prompt_callback,
cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type);
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,
cdata->prompts[0].input, cmd_command_prompt_callback,
cmd_command_prompt_free, cdata, cdata->flags,
cdata->prompt_type);
}
if (!wait)
return (CMD_RETURN_NORMAL);
return (CMD_RETURN_WAIT);
}
static int
static enum prompt_result
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;
char *error;
@@ -188,26 +206,30 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
int argc = 0;
char **argv = NULL;
if (s == NULL)
if (s == NULL || key == PROMPT_KEY_MOVE)
goto out;
if (done) {
if (key == PROMPT_KEY_CLOSE) {
if (cdata->flags & PROMPT_INCREMENTAL)
goto out;
cmd_append_argv(&cdata->argc, &cdata->argv, s);
if (++cdata->current != cdata->count) {
prompt = &cdata->prompts[cdata->current];
status_prompt_update(c, prompt->prompt, prompt->input);
return (1);
if (cdata->wp != NULL) {
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;
argv = cmd_copy_argv(cdata->argc, cdata->argv);
if (!done)
if (key != PROMPT_KEY_CLOSE)
cmd_append_argv(&argc, &argv, s);
if (done) {
else {
cmd_free_argv(cdata->argc, cdata->argv);
cdata->argc = argc;
cdata->argv = cmd_copy_argv(argc, argv);
@@ -220,19 +242,28 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
} else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item);
cmd_list_free(cmdlist);
} else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
}
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:
if (item != NULL)
if (item != NULL) {
cdata->item = NULL;
cmdq_continue(item);
return (0);
}
return (PROMPT_CLOSE);
}
static void
@@ -241,6 +272,11 @@ cmd_command_prompt_free(void *data)
struct cmd_command_prompt_cdata *cdata = data;
u_int i;
if (cdata->item != NULL) {
cmdq_continue(cdata->item);
cdata->item = NULL;
}
for (i = 0; i < cdata->count; i++) {
free(cdata->prompts[i].prompt);
free(cdata->prompts[i].input);

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>
@@ -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 *,
struct cmdq_item *);
static int cmd_confirm_before_callback(struct client *, void *,
const char *, int);
static enum prompt_result cmd_confirm_before_callback(struct client *, void *,
const char *, enum prompt_key_result);
static void cmd_confirm_before_free(void *);
const struct cmd_entry cmd_confirm_before_entry = {
@@ -118,9 +118,9 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_WAIT);
}
static int
static enum prompt_result
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 cmdq_item *item = cdata->item, *new_item;
@@ -151,7 +151,7 @@ out:
cmdq_get_client(item)->retval = retcode;
cmdq_continue(item);
}
return (0);
return (PROMPT_CLOSE);
}
static void

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>
@@ -30,8 +30,8 @@ const struct cmd_entry cmd_copy_mode_entry = {
.name = "copy-mode",
.alias = NULL,
.args = { "deHMqSs:t:u", 0, 0, NULL },
.usage = "[-deHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
.args = { "dekHMqSs:t:u", 0, 0, NULL },
.usage = "[-dekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
.source = { 's', CMD_FIND_PANE, 0 },
.target = { 't', CMD_FIND_PANE, 0 },
@@ -79,7 +79,8 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item)
}
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);
}
@@ -90,7 +91,8 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item)
line_numbers = 1;
if (event != NULL && KEYC_IS_MOUSE(event->key))
line_numbers = 0;
if (!window_pane_set_mode(wp, swp, &window_copy_mode, NULL, args)) {
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'))
window_copy_start_drag(c, &event->m);

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>

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>
@@ -92,7 +92,7 @@ cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause)
}
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 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);
}
/* Position of the previous menu, for -x/-y L. */
format_add(ft, "popup_last_x", "%u", target->w->menu_last_px);
format_add(ft, "popup_last_y", "%u", target->w->menu_last_py + h);
/*
* If there are any status lines, add this window position and the
* status line position.
@@ -238,6 +242,8 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item,
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);
@@ -258,6 +264,8 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item,
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)
@@ -280,6 +288,184 @@ cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item,
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
cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
{
@@ -300,10 +486,6 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
struct options *o = target->s->curw->window->options;
struct options_entry *oe;
if (tc->overlay_draw != NULL)
return (CMD_RETURN_NORMAL);
if (args_has(args, 'C')) {
if (strcmp(args_get(args, 'C'), "-") == 0)
starting_choice = -1;
@@ -349,8 +531,8 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
}
if (menu->count == 0)
goto out;
if (!cmd_display_menu_get_pos(tc, item, args, &px, &py, menu->width + 4,
menu->count + 2))
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');
@@ -371,7 +553,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
if (menu_display(menu, flags, starting_choice, item, px, py, tc, lines,
style, selected_style, border_style, target, NULL, NULL) != 0)
goto out;
return (CMD_RETURN_WAIT);
return (CMD_RETURN_NORMAL);
out:
menu_free(menu);
@@ -409,6 +591,8 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
server_client_clear_overlay(tc);
return (CMD_RETURN_NORMAL);
}
if (tc->flags & CLIENT_CONTROL)
return (CMD_RETURN_NORMAL);
if (!modify && tc->overlay_draw != NULL)
return (CMD_RETURN_NORMAL);
@@ -437,7 +621,8 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
w = tty->sx;
if (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,
w, h))
goto out;
value = args_get(args, 'd');

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-display-message.c,v 1.66 2026/09/08 08:33:10 nicm Exp $ */
/*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = {
.name = "display-message",
.alias = "display",
.args = { "aCc:d:lINpt:F:v", 0, 1, NULL },
.usage = "[-aCIlNpv] [-c target-client] [-d delay] [-F format] "
.args = { "aCc:d:jlINpt:F:v", 0, 1, NULL },
.usage = "[-aCIjlNpv] [-c target-client] [-d delay] [-F format] "
CMD_TARGET_PANE_USAGE " [message]",
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
@@ -67,14 +67,15 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
struct winlink *wl = target->wl;
struct window_pane *wp = target->wp;
const char *template;
char *msg, *cause;
char *msg, *cause = NULL;
int delay = -1, flags, Nflag = args_has(args, 'N');
int Cflag = args_has(args, 'C');
struct format_tree *ft;
u_int count = args_count(args);
struct evbuffer *evb;
struct json_node *jn;
if (args_has(args, 'I')) {
if (args_has(args, 'I') && !args_has(args, 'j')) {
if (wp == NULL)
return (CMD_RETURN_NORMAL);
switch (window_pane_start_input(wp, item, &cause)) {
@@ -107,7 +108,9 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
template = args_string(args, 0);
else
template = args_get(args, 'F');
if (template == NULL)
if (args_has(args, 'j') && template == NULL)
template = "";
else if (template == NULL)
template = DISPLAY_MESSAGE_TEMPLATE;
/*
@@ -129,7 +132,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
format_defaults(ft, c, s, wl, wp);
if (args_has(args, 'a')) {
if (args_has(args, 'a') && !args_has(args, 'j')) {
format_each(ft, cmd_display_message_each, item);
format_free(ft);
return (CMD_RETURN_NORMAL);
@@ -139,6 +142,19 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
msg = xstrdup(template);
else
msg = format_expand_time(ft, template);
if (args_has(args, 'j')) {
jn = json_parse(msg, &cause);
if (jn == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
free(msg);
format_free(ft);
return (CMD_RETURN_ERROR);
}
free(msg);
msg = json_to_string(jn);
json_destroy_node(jn);
}
if (cmdq_get_client(item) == NULL)
cmdq_error(item, "%s", msg);

View File

@@ -1,341 +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_put(struct screen_redraw_ctx *ctx,
struct window_pane *wp, u_int cx, u_int cy, const char *buf, size_t len)
{
struct client *c = ctx->c;
struct tty *tty = &c->tty;
struct visible_ranges *r;
struct visible_range *ri;
u_int i, j;
r = screen_redraw_get_visible_ranges(wp, ctx->ox + cx, ctx->oy + cy,
len, NULL);
for (i = 0; i < r->used; i++) {
ri = &r->ranges[i];
for (j = ri->px; j < ri->px + ri->nx; j++) {
tty_cursor(tty, j - ctx->ox, cy);
tty_putn(tty, &buf[j - ri->px], 1, 1);
}
}
}
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;
u_int cx, cy;
int colour, active_colour;
char buf[16], lbuf[16], rbuf[16], *ptr;
size_t len, llen, rlen;
if (wp->xoff + (int)wp->sx <= ctx->ox ||
wp->xoff >= ctx->ox + (int)ctx->sx ||
wp->yoff + (int)wp->sy <= ctx->oy ||
wp->yoff >= ctx->oy + (int)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;
cx = xoff + px - len / 2;
cy = yoff + py;
cmd_display_panes_put(ctx, wp, cx, cy, buf, len);
cx += len;
cmd_display_panes_put(ctx, wp, cx, cy, " ", 1);
cx++;
cmd_display_panes_put(ctx, wp, cx, cy, lbuf, llen);
} else {
cx = xoff + px - len / 2;
cy = yoff + py;
cmd_display_panes_put(ctx, wp, cx, cy, buf, 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++) {
if (!window_clock_table[idx][j][i - px])
continue;
cx = xoff + i;
cy = yoff + py + j;
cmd_display_panes_put(ctx, wp, cx, cy, " ", 1);
}
}
px += 6;
}
if (sy <= 6)
goto out;
tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL);
if (rlen != 0 && sx >= rlen) {
cx = xoff + sx - rlen;
cy = yoff;
cmd_display_panes_put(ctx, wp, cx, cy, rbuf, rlen);
}
if (llen != 0) {
cx = xoff + sx / 2 + len * 3 - llen - 1;
cy = yoff + py + 5;
cmd_display_panes_put(ctx, wp, cx, cy, lbuf, 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>
@@ -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, '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);
return (CMD_RETURN_NORMAL);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-find.c,v 1.88 2026/09/07 12:05:12 nicm Exp $ */
/*
* 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, >));
}
/* 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. */
static struct session *
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;
if (slist != NULL) {
for (i = 0; i < ssize; i++) {
if (!cmd_find_session_valid(slist[i]))
continue;
if (cmd_find_session_better(slist[i], s, flags))
s = slist[i];
}
} else {
RB_FOREACH(s_loop, sessions, &sessions) {
if (!cmd_find_session_valid(s_loop))
continue;
if (cmd_find_session_better(s_loop, s, flags))
s = s_loop;
}
@@ -372,9 +388,11 @@ cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window)
/* Try as an offset. */
if (!exact && (window[0] == '+' || window[0] == '-')) {
if (window[1] != '\0')
n = strtonum(window + 1, 1, INT_MAX, NULL);
else
if (window[1] != '\0') {
n = strtonum(window + 1, 1, INT_MAX, &errstr);
if (errstr != NULL)
return (-1);
} else
n = 1;
s = fs->s;
if (fs->flags & CMD_FIND_WINDOW_INDEX) {
@@ -610,9 +628,11 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane)
/* Try as an offset. */
if (pane[0] == '+' || pane[0] == '-') {
if (pane[1] != '\0')
n = strtonum(pane + 1, 1, INT_MAX, NULL);
else
if (pane[1] != '\0') {
n = strtonum(pane + 1, 1, INT_MAX, &errstr);
if (errstr != NULL)
return (-1);
} else
n = 1;
wp = fs->w->active;
if (pane[0] == '+')
@@ -868,7 +888,7 @@ cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags)
if (c->session != NULL) {
cmd_find_clear_state(fs, flags);
fs->wp = server_client_get_pane(c);
fs->wp = c->session->curw->window->active;
if (fs->wp == NULL) {
cmd_find_from_session(fs, c->session, flags);
return (0);
@@ -995,7 +1015,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
strcmp(target, "{active}") == 0 ||
strcmp(target, "{current}") == 0) {
c = cmdq_get_client(item);
if (c == NULL) {
if (c == NULL || c->session == NULL) {
cmdq_error(item, "no current client");
goto error;
}

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>
@@ -98,6 +98,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_ERROR);
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
return (CMD_RETURN_NORMAL);
}
@@ -164,9 +165,11 @@ cmd_if_shell_callback(struct job *job)
} else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item);
cmd_list_free(cmdlist);
} else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
}
out:

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>
@@ -30,6 +30,9 @@
*/
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 = {
.name = "join-pane",
@@ -49,8 +52,10 @@ const struct cmd_entry cmd_move_pane_entry = {
.name = "move-pane",
.alias = "movep",
.args = { "bdfhvp:l:s:t:", 0, 0, NULL },
.usage = "[-bdfhv] [-l size] " CMD_SRCDST_PANE_USAGE,
.args = { "bdD::fhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL },
.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 },
.target = { 't', CMD_FIND_PANE, 0 },
@@ -59,6 +64,348 @@ const struct cmd_entry cmd_move_pane_entry = {
.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
cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
{
@@ -70,6 +417,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
struct winlink *src_wl, *dst_wl;
struct window *src_w, *dst_w;
struct window_pane *src_wp, *dst_wp;
const char *s;
char *cause = NULL;
int flags = 0, dst_idx;
struct layout_cell *lc;
@@ -79,18 +427,57 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
dst_wp = target->wp;
dst_w = dst_wl->window;
dst_idx = dst_wl->idx;
server_unzoom_window(dst_w);
if (cmd_get_entry(self) == &cmd_move_pane_entry) {
if (args_has(args, 'M'))
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);
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_wp = source->wp;
src_w = src_wl->window;
if (src_wp == src_w->modal || dst_wp == dst_w->modal) {
cmdq_error(item, "pane is modal");
return (CMD_RETURN_ERROR);
}
server_unzoom_window(dst_w);
server_unzoom_window(src_w);
if (src_wp == dst_wp) {
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");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'h'))
flags |= SPAWN_HORIZONTAL;
if (args_has(args, 'b'))
flags |= SPAWN_BEFORE;
if (args_has(args, 'f'))
flags |= SPAWN_FULLSIZE;
lc = layout_get_tiled_cell(item, args, dst_w, dst_wp, flags, &cause);
if (cause != NULL) {
cmdq_error(item, "size or position %s", cause);
@@ -131,11 +518,12 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
} else
server_status_session(dst_s);
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);
else
notify_window("window-layout-changed", src_w);
notify_window("window-layout-changed", dst_w);
events_fire_window("window-layout-changed", src_w);
events_fire_window("window-layout-changed", dst_w);
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>
@@ -27,13 +27,17 @@
*/
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 = {
.name = "kill-pane",
.alias = "killp",
.args = { "at:", 0, 0, NULL },
.usage = "[-a] " CMD_TARGET_PANE_USAGE,
.args = { "af:t:", 0, 0, NULL },
.usage = "[-a] [-f filter] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -46,22 +50,17 @@ cmd_kill_pane_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 winlink *wl = target->wl;
struct window_pane *loopwp, *tmpwp, *wp = target->wp;
struct window_pane *wp = target->wp;
const char *filter = args_get(args, 'f');
if (args_has(args, 'a')) {
server_unzoom_window(wl->window);
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
if (loopwp == wp)
continue;
server_client_remove_pane(loopwp);
layout_close_pane(loopwp);
window_remove_pane(wl->window, loopwp);
}
server_redraw_window(wl->window);
return (CMD_RETURN_NORMAL);
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);
@@ -69,3 +68,48 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item)
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;
server_unzoom_window(wl->window);
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
if (loopwp == wp)
continue;
if (!cmd_kill_pane_filter(item, s, wl, loopwp, filter))
continue;
server_client_remove_pane(loopwp);
layout_close_pane(loopwp);
window_remove_pane(wl->window, loopwp);
}
server_redraw_window(wl->window);
return (CMD_RETURN_NORMAL);
}
static int
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>

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>
@@ -18,6 +18,8 @@
#include <sys/types.h>
#include <stdlib.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_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 = {
.name = "kill-session",
.alias = NULL,
.args = { "aCgt:", 0, 0, NULL },
.usage = "[-aCg] " CMD_TARGET_SESSION_USAGE,
.args = { "aCgf:t:", 0, 0, NULL },
.usage = "[-aCg] [-f filter] " CMD_TARGET_SESSION_USAGE,
.target = { 't', CMD_FIND_SESSION, 0 },
@@ -50,6 +55,12 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
struct session *s = target->s, *sloop, *stmp;
struct session_group *sg;
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')) {
RB_FOREACH(wl, winlinks, &s->windows) {
@@ -57,14 +68,9 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
wl->flags &= ~WINLINK_ALERTFLAGS;
}
server_redraw_session(s);
} else if (args_has(args, 'a')) {
RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) {
if (sloop != s) {
server_destroy_session(sloop);
session_destroy(sloop, 1, __func__);
}
}
} else if (args_has(args, 'g') &&
} else if (args_has(args, 'a'))
return (cmd_kill_session_all(item, filter));
else if (args_has(args, 'g') &&
(sg = session_group_contains(s)) != NULL) {
TAILQ_FOREACH_SAFE(sloop, &sg->sessions, gentry, stmp) {
server_destroy_session(sloop);
@@ -76,3 +82,42 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
}
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>
@@ -18,6 +18,8 @@
#include <sys/types.h>
#include <stdlib.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_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 = {
.name = "kill-window",
.alias = "killw",
.args = { "at:", 0, 0, NULL },
.usage = "[-a] " CMD_TARGET_WINDOW_USAGE,
.args = { "af:t:", 0, 0, NULL },
.usage = "[-a] [-f filter] " CMD_TARGET_WINDOW_USAGE,
.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 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 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 (!args_has(args, 'k') && !session_is_linked(s, w)) {
@@ -72,39 +82,76 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'a')) {
if (RB_PREV(winlinks, &s->windows, wl) == NULL &&
RB_NEXT(winlinks, &s->windows, wl) == NULL)
return (CMD_RETURN_NORMAL);
/* Kill all windows except the current one. */
do {
found = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window != wl->window) {
server_kill_window(loop->window, 0);
found++;
break;
}
}
} while (found != 0);
/*
* If the current window appears in the session more than once,
* kill it as well.
*/
found = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window == wl->window)
found++;
}
if (found > 1)
server_kill_window(wl->window, 0);
server_renumber_all();
return (CMD_RETURN_NORMAL);
}
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 &&
RB_NEXT(winlinks, &s->windows, wl) == NULL)
return (CMD_RETURN_NORMAL);
/* Kill all windows except the current one. */
do {
found = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window != wl->window &&
cmd_kill_window_filter(item, s, loop, filter)) {
server_kill_window(loop->window, 0);
found++;
break;
}
}
} while (found != 0);
/*
* If the current window appears in the session more than once, kill it
* as well if it matches the filter.
*/
found = kill_current = 0;
RB_FOREACH(loop, winlinks, &s->windows) {
if (loop->window == wl->window) {
found++;
if (cmd_kill_window_filter(item, s, loop, filter))
kill_current = 1;
}
}
if (kill_current && found > 1)
server_kill_window(wl->window, 0);
server_renumber_all();
return (CMD_RETURN_NORMAL);
}
static int
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>

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>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $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>

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>
@@ -223,11 +223,16 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
cmd_list_keys_filter_key_list(filter_notes, filter_key, only, l,
&n);
}
if (single)
if (filter_key && n == 0) {
cmdq_error(item, "unknown key: %s", keystr);
free(prefix);
return (CMD_RETURN_ERROR);
}
if (single && n > 1)
n = 1;
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_defaults(ft, NULL, NULL, NULL, NULL);
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));
@@ -237,7 +242,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
cmd_list_keys_format_add_key_binding(ft, l[i], prefix);
line = format_expand(ft, template);
if ((single && tc != NULL) || n == 1)
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);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-list-panes.c,v 1.41 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -97,6 +97,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
struct cmdq_item *item, int type)
{
struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_client(item);
struct window_pane *wp, **l;
u_int i, n;
struct format_tree *ft;
@@ -147,7 +148,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
wp = l[i];
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", n);
format_defaults(ft, NULL, s, wl, wp);
format_defaults(ft, c, s, wl, wp);
if (filter != NULL) {
expanded = format_expand(ft, filter);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-list-sessions.c,v 1.37 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -53,6 +53,7 @@ static enum cmd_retval
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_client(item);
struct session **l;
u_int n, i;
struct format_tree *ft;
@@ -76,7 +77,7 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
for (i = 0; i < n; i++) {
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", i);
format_defaults(ft, NULL, l[i], NULL, NULL);
format_defaults(ft, c, l[i], NULL, NULL);
if (filter != NULL) {
expanded = format_expand(ft, filter);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-list-windows.c,v 1.51 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -60,6 +60,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct client *c = cmdq_get_client(item);
struct winlink *wl, **l;
struct session *s;
u_int i, n;
@@ -94,7 +95,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
s = wl->session;
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
format_add(ft, "line", "%u", n);
format_defaults(ft, NULL, s, wl, NULL);
format_defaults(ft, c, s, wl, NULL);
if (filter != NULL) {
expanded = format_expand(ft, filter);

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>

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>

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>

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>
@@ -325,7 +325,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
session_group_synchronize_to(s);
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

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-new-window.c,v 1.104 2026/09/03 21:04:11 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -38,8 +38,8 @@ const struct cmd_entry cmd_new_window_entry = {
.name = "new-window",
.alias = "neww",
.args = { "abc:de:F:kn:PSt:", 0, -1, NULL },
.usage = "[-abdkPS] [-c start-directory] [-e environment] [-F format] "
.args = { "abc:de:EF:kn:PSt:", 0, -1, NULL },
.usage = "[-abdEkPS] [-c start-directory] [-e environment] [-F format] "
"[-n window-name] " CMD_TARGET_WINDOW_USAGE
" [shell-command [argument ...]]",
@@ -61,14 +61,24 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
struct session *s = target->s;
struct winlink *wl = target->wl, *new_wl = NULL;
int idx = target->idx, before;
int count = args_count(args);
char *cause = NULL, *cp, *expanded, *wname = NULL;
const char *template, *name;
struct cmd_find_state fs;
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
* name already exists, select it.
* If -S is given, select an existing window instead of creating a
* new one: preferring -t if it already points at a window, or
* otherwise -n if one exists with that name. If neither matches,
* fall through and create a window as normal.
*/
name = args_get(args, 'n');
if (name != NULL) {
@@ -81,32 +91,39 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
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) {
if (strcmp(wl->window->name, expanded) != 0)
continue;
if (new_wl == NULL) {
new_wl = wl;
continue;
if (args_has(args, 'S')) {
if (idx != -1)
new_wl = winlink_find_by_index(&s->windows, idx);
else if (wname != NULL) {
expanded = format_single(item, wname, c, s, NULL, NULL);
RB_FOREACH(wl, winlinks, &s->windows) {
if (strcmp(wl->window->name, expanded) != 0)
continue;
if (new_wl == NULL) {
new_wl = wl;
continue;
}
cmdq_error(item, "multiple windows named %s",
wname);
free(wname);
free(expanded);
return (CMD_RETURN_ERROR);
}
cmdq_error(item, "multiple windows named %s", wname);
free(wname);
free(expanded);
return (CMD_RETURN_ERROR);
}
free(expanded);
if (new_wl != NULL) {
free(wname);
if (args_has(args, 'd'))
return (CMD_RETURN_NORMAL);
if (session_set_current(s, new_wl) == 0)
server_redraw_session(s);
if (c != NULL && c->session != NULL)
s->curw->window->latest = c;
recalculate_sizes();
}
/* Found an existing window to select instead of creating a new one. */
if (new_wl != NULL) {
free(wname);
if (args_has(args, 'd'))
return (CMD_RETURN_NORMAL);
}
if (session_set_current(s, new_wl) == 0)
server_redraw_session(s);
if (c != NULL && c->session != NULL)
s->curw->window->latest = c;
recalculate_sizes();
return (CMD_RETURN_NORMAL);
}
before = args_has(args, 'b');
@@ -134,6 +151,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
sc.cwd = args_get(args, 'c');
sc.flags = 0;
if (args_has(args, 'E') || (count == 1 && *args_string(args, 0) == '\0'))
sc.flags |= SPAWN_EMPTY;
if (args_has(args, 'd'))
sc.flags |= SPAWN_DETACHED;
if (args_has(args, 'k'))

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-parse.y,v 1.60 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -210,7 +210,7 @@ expanded : format
cmd_find_from_client(&fs, c, 0);
fsp = &fs;
}
ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
ft = format_create(c, pi->item, FORMAT_NONE, flags);
format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
$$ = format_expand(ft, $1);
@@ -802,6 +802,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
if (last == NULL) {
pr->status = CMD_PARSE_SUCCESS;
pr->cmdlist = cmd_list_new();
cmd_parse_free_commands(cmds);
return (1);
}
@@ -814,6 +815,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
pi->flags |= CMD_PARSE_NOALIAS;
cmd_parse_build_commands(cmds, pi, pr);
pi->flags &= ~CMD_PARSE_NOALIAS;
cmd_parse_free_commands(cmds);
return (1);
}

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>

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>
@@ -194,9 +194,13 @@ static void
cmd_pipe_pane_read_callback(__unused struct bufferevent *bufev, void *data)
{
struct window_pane *wp = data;
struct evbuffer *evb = wp->pipe_event->input;
struct evbuffer *evb;
size_t available;
if (wp->pipe_event == NULL)
return;
evb = wp->pipe_event->input;
available = EVBUFFER_LENGTH(evb);
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>
@@ -147,6 +147,13 @@ cmdq_get_name(struct cmdq_item *item)
return (item->name);
}
/* Get item command. */
struct cmd *
cmdq_get_cmd(struct cmdq_item *item)
{
return (item->cmd);
}
/* Get item client. */
struct client *
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. */
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 cmdq_state *state = item->state;
struct cmd *cmd = item->cmd;
struct args *args = cmd_get_args(cmd);
struct args_entry *ae;
struct args_value *av;
struct options *oo;
struct event_payload *ep;
va_list ap;
char *name, tmp[32], flag, *arguments;
u_int i;
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)
return;
if (s == NULL)
oo = global_s_options;
else
oo = s->options;
va_start(ap, fmt);
xvasprintf(&name, fmt, ap);
va_end(ap);
o = options_get(oo, name);
if (o == NULL) {
free(name);
return;
}
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);
ep = event_payload_create();
if (current != NULL)
event_payload_set_target(ep, current);
event_payload_set_pointer(ep, "_cmdq_item", item, NULL, NULL);
arguments = args_print(args);
cmdq_add_format(new_state, "hook_arguments", "%s", arguments);
event_payload_set_string(ep, "arguments", "%s", arguments);
free(arguments);
for (i = 0; i < args_count(args); i++) {
xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i);
cmdq_add_format(new_state, tmp, "%s", args_string(args, i));
xsnprintf(tmp, sizeof tmp, "argument_%u", i);
event_payload_set_string(ep, tmp, "%s", args_string(args, i));
}
flag = args_first(args, &ae);
while (flag != 0) {
value = args_get(args, flag);
if (value == NULL) {
xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
cmdq_add_format(new_state, tmp, "1");
} else {
xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
cmdq_add_format(new_state, tmp, "%s", value);
}
xsnprintf(tmp, sizeof tmp, "flag_%c", flag);
if (value == NULL)
event_payload_set_string(ep, tmp, "1");
else
event_payload_set_string(ep, tmp, "%s", value);
i = 0;
av = args_first_value(args, flag);
while (av != NULL) {
xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i);
cmdq_add_format(new_state, tmp, "%s", av->string);
xsnprintf(tmp, sizeof tmp, "flag_%c_%u", flag, i);
event_payload_set_string(ep, tmp, "%s", av->string);
i++;
av = args_next_value(av);
}
@@ -425,20 +411,7 @@ cmdq_insert_hook(struct session *s, struct cmdq_item *item,
flag = args_next(&ae);
}
a = options_array_first(o);
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);
events_fire(name, ep);
free(name);
}
@@ -829,7 +802,7 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
u_int number = item->number;
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. */
@@ -877,7 +850,18 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...)
if (c == NULL) {
cmd_get_source(cmd, &file, &line);
cfg_add_cause("%s:%u: %s", file, line, msg);
if (!cfg_finished) {
if (file != NULL)
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)) {
server_add_message("%s message: %s", c->name, msg);
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>
@@ -46,36 +46,17 @@ const struct cmd_entry cmd_refresh_client_entry = {
static void
cmd_refresh_client_update_subscription(struct client *tc, const char *value)
{
char *copy, *split, *name, *what;
enum control_sub_type subtype;
int subid = -1;
char *name, *format;
enum monitor_type type;
int id;
copy = name = xstrdup(value);
if ((split = strchr(copy, ':')) == NULL) {
control_remove_sub(tc, copy);
goto out;
if (monitor_parse(value, &name, &type, &id, &format) != 0) {
control_remove_sub(tc, value);
return;
}
*split++ = '\0';
what = split;
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);
control_add_sub(tc, name, type, id, format);
free(name);
free(format);
}
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);
const char *size = args_get(args, 'C');
u_int w, x, y;
struct client_window *cw;
if (sscanf(size, "@%u:%ux%u", &w, &x, &y) == 3) {
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__,
tc->name, w, x, y);
cw = server_client_add_client_window(tc, w);
cw->sx = x;
cw->sy = y;
control_set_window_size(tc, w, x, y);
tc->flags |= CLIENT_WINDOWSIZECHANGED;
recalculate_sizes_now(1);
return (CMD_RETURN_NORMAL);
}
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__,
tc->name, w);
cw->sx = 0;
cw->sy = 0;
recalculate_sizes_now(1);
}
log_debug("%s: client %s window @%u: no size", __func__,
tc->name, w);
control_clear_window_size(tc, w);
recalculate_sizes_now(1);
return (CMD_RETURN_NORMAL);
}
@@ -168,6 +142,7 @@ cmd_refresh_report(struct tty *tty, const char *value)
{
struct window_pane *wp;
u_int pane;
int fg, bg;
size_t size = 0;
char *copy, *split;
@@ -184,8 +159,14 @@ cmd_refresh_report(struct tty *tty, const char *value)
if (wp == NULL)
goto out;
tty_keys_colours(tty, split, strlen(split), &size, &wp->control_fg,
&wp->control_bg);
fg = wp->control_fg;
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:
free(copy);

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>
@@ -49,6 +49,8 @@ cmd_rename_session_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 session *s = target->s;
struct event_payload *ep;
struct cmd_find_state fs;
char *newname, *tmp;
tmp = format_single_from_target(item, args_string(args, 0));
@@ -69,13 +71,20 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item)
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);
free(s->name);
s->name = newname;
RB_INSERT(sessions, &sessions, s);
server_status_session(s);
notify_session("session-renamed", s);
events_fire("session-renamed", ep);
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>

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>
@@ -18,6 +18,7 @@
#include <sys/types.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
@@ -31,18 +32,18 @@ static enum cmd_retval cmd_resize_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_resize_pane_mouse_update(struct cmd *,
struct cmdq_item *);
static void cmd_resize_pane_mouse_update_floating(struct client *,
struct mouse_event *);
static void cmd_resize_pane_mouse_update_tiled(struct client *,
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 *);
const struct cmd_entry cmd_resize_pane_entry = {
.name = "resize-pane",
.alias = "resizep",
.args = { "DLMRTt:Ux:y:Z", 0, 1, NULL },
.usage = "[-DLMRTUZ] [-x width] [-y height] " CMD_TARGET_PANE_USAGE " "
"[adjustment]",
.args = { "D::L::MR::Tt:U::x:y:Z", 0, 1, NULL },
.usage = "[-MTZ] [-D lines] [-L columns] [-R columns] [-U lines] "
"[-x width] [-y height] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -58,17 +59,20 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
struct window_pane *wp = target->wp;
struct winlink *wl = target->wl;
struct window *w = wl->window;
const char *errstr;
char *cause;
u_int adjust;
int x, y, status;
struct layout_cell *lc = wp->layout_cell;
enum layout_type type;
const char *errstr, *argval;
const char flags[4] = { 'U', 'D', 'L', 'R' };
char *cause = NULL, flag;
int adjust, x, y, status, opposite = 0;
long unsigned i;
struct grid *gd = wp->base.grid;
if (args_has(args, 'T')) {
if (!TAILQ_EMPTY(&wp->modes))
return (CMD_RETURN_NORMAL);
adjust = screen_size_y(&wp->base) - 1 - wp->base.cy;
if (adjust > gd->hsize)
if (adjust > (int)gd->hsize)
adjust = gd->hsize;
grid_remove_history(gd, adjust);
wp->base.cy += adjust;
@@ -88,34 +92,33 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
}
server_unzoom_window(w);
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);
}
}
lc = wp->layout_cell; /* may have been replaced by unzoom */
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) {
cmdq_error(item, "width %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x);
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);
}
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) {
cmdq_error(item, "height %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
status = options_get_number(w->options, "pane-border-status");
status = window_get_pane_status(w);
switch (status) {
case PANE_STATUS_TOP:
if (y != INT_MAX && wp->yoff == 1)
@@ -126,18 +129,62 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
y++;
break;
}
layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y);
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);
}
if (args_has(args, 'L'))
layout_resize_pane(wp, LAYOUT_LEFTRIGHT, -adjust, 1);
else if (args_has(args, 'R'))
layout_resize_pane(wp, LAYOUT_LEFTRIGHT, adjust, 1);
else if (args_has(args, 'U'))
layout_resize_pane(wp, LAYOUT_TOPBOTTOM, -adjust, 1);
else if (args_has(args, 'D'))
layout_resize_pane(wp, LAYOUT_TOPBOTTOM, adjust, 1);
server_redraw_window(wl->window);
for (i = 0; i < nitems(flags); i++) {
flag = flags[i];
if (!args_has(args, flag))
continue;
argval = args_get(args, flag);
if (argval == NULL) {
if (args_count(args) == 0)
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);
}
@@ -160,21 +207,29 @@ cmd_resize_pane_mouse_update(__unused struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
if (!window_pane_is_floating(wp)) {
c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_tiled;
cmd_resize_pane_mouse_update_tiled(c, &event->m);
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_update_floating;
cmd_resize_pane_mouse_update_floating(c, &event->m);
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
cmd_resize_pane_mouse_update_floating(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;
@@ -195,10 +250,10 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
sy = wp->sy;
left = wp->xoff - 1;
right = wp->xoff + sx;
if (window_pane_show_scrollbar(wp) &&
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_show_scrollbar(wp) &&
} else if (window_pane_scrollbar_reserve(wp) &&
w->sb_pos == PANE_SCROLLBARS_RIGHT) {
right += wp->scrollbar_style.width + wp->scrollbar_style.pad;
}
@@ -216,10 +271,10 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
if ((lx == left || lx == left + 1) && ly == wp->yoff - 1) {
/* Top left corner. */
new_sx = lc->sx + (lx - x);
new_sx = lc->g.sx + (lx - x);
if (new_sx < PANE_MINIMUM)
new_sx = PANE_MINIMUM;
new_sy = lc->sy + (ly - y);
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 */
@@ -229,65 +284,65 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
} else if ((lx == right + 1 || lx == right) &&
ly == wp->yoff - 1) {
/* Top right corner. */
new_sx = x - lc->xoff;
new_sx = x - lc->g.xoff;
if (new_sx < PANE_MINIMUM)
new_sx = PANE_MINIMUM;
new_sy = lc->sy + (ly - y);
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->xoff, new_yoff);
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->sx + (lx - x);
new_sx = lc->g.sx + (lx - x);
if (new_sx < PANE_MINIMUM)
new_sx = PANE_MINIMUM;
new_sy = y - lc->yoff;
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->yoff);
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->xoff;
new_sx = x - lc->g.xoff;
if (new_sx < PANE_MINIMUM)
new_sx = PANE_MINIMUM;
new_sy = y - lc->yoff;
new_sy = y - lc->g.yoff;
if (new_sy < PANE_MINIMUM)
new_sy = PANE_MINIMUM;
layout_set_size(lc, new_sx, new_sy, lc->xoff, lc->yoff);
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->xoff;
new_sx = x - lc->g.xoff;
if (new_sx < PANE_MINIMUM)
return;
layout_set_size(lc, new_sx, lc->sy, lc->xoff, lc->yoff);
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->sx + (lx - x);
new_sx = lc->g.sx + (lx - x);
if (new_sx < PANE_MINIMUM)
return;
new_xoff = x + 1;
layout_set_size(lc, new_sx, lc->sy, new_xoff, lc->yoff);
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->yoff;
new_sy = y - lc->g.yoff;
if (new_sy < PANE_MINIMUM)
return;
layout_set_size(lc, lc->sx, new_sy, lc->xoff, lc->yoff);
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->xoff + (x - lx);
new_xoff = lc->g.xoff + (x - lx);
new_yoff = y + 1;
layout_set_size(lc, lc->sx, lc->sy, new_xoff, new_yoff);
layout_set_size(lc, lc->g.sx, lc->g.sy, new_xoff, new_yoff);
resizes++;
}
if (resizes != 0) {
@@ -298,7 +353,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
}
static void
cmd_resize_pane_mouse_update_tiled(struct client *c, struct mouse_event *m)
cmd_resize_pane_mouse_resize_tiled(struct client *c, struct mouse_event *m)
{
struct winlink *wl;
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>

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>
@@ -34,8 +34,8 @@ const struct cmd_entry cmd_respawn_pane_entry = {
.name = "respawn-pane",
.alias = "respawnp",
.args = { "c:e:kt:", 0, -1, NULL },
.usage = "[-k] [-c start-directory] [-e environment] "
.args = { "c:e:Ekt:", 0, -1, NULL },
.usage = "[-Ek] [-c start-directory] [-e environment] "
CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]",
.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.flags = SPAWN_RESPAWN;
if (args_has(args, 'E'))
sc.flags |= SPAWN_EMPTY;
if (args_has(args, 'k'))
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>
@@ -34,8 +34,8 @@ const struct cmd_entry cmd_respawn_window_entry = {
.name = "respawn-window",
.alias = "respawnw",
.args = { "c:e:kt:", 0, -1, NULL },
.usage = "[-k] [-c start-directory] [-e environment] "
.args = { "c:e:Ekt:", 0, -1, NULL },
.usage = "[-Ek] [-c start-directory] [-e environment] "
CMD_TARGET_WINDOW_USAGE " [shell-command [argument ...]]",
.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.flags = SPAWN_RESPAWN;
if (args_has(args, 'E'))
sc.flags |= SPAWN_EMPTY;
if (args_has(args, 'k'))
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>
@@ -109,6 +109,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item)
window_set_active_pane(w, wp, 1);
cmd_find_from_winlink_pane(current, wl, wp, 0);
window_pop_zoom(w);
redraw_invalidate_scene(w);
server_redraw_window(w);
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>
@@ -90,8 +90,8 @@ cmd_run_shell_print(struct job *job, const char *msg)
cmdq_print(cdata->item, "%s", msg);
return;
}
if (cdata->item != NULL && cdata->client != NULL)
wp = server_client_get_pane(cdata->client);
if (cdata->client != NULL && cdata->client->session != NULL)
wp = cdata->client->session->curw->window->active;
if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0)
wp = fs.wp;
if (wp == NULL)
@@ -100,7 +100,8 @@ cmd_run_shell_print(struct job *job, const char *msg)
wme = TAILQ_FIRST(&wp->modes);
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);
}
@@ -232,9 +233,11 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
} else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item);
cmd_list_free(cmdlist);
} else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
}
if (cdata->item != NULL)

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>

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-select-layout.c,v 1.45 2026/09/09 07:03:39 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -73,12 +73,13 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct client *c = cmdq_get_target_client(item);
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct window_pane *wp = target->wp;
const char *layoutname;
char *oldlayout, *cause;
int next, previous, layout;
char *oldlayout, *cause = NULL;
int next, previous, layout, flags = 0;
server_unzoom_window(w);
@@ -89,8 +90,12 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'p'))
previous = 1;
if (c != NULL &&
(c->flags & CLIENT_CONTROL) &&
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
oldlayout = w->old_layout;
w->old_layout = layout_dump(w, w->layout_root);
w->old_layout = layout_dump(w, w->layout_root, flags);
if (next || previous) {
if (next)
@@ -139,7 +144,7 @@ changed:
free(oldlayout);
recalculate_sizes();
server_redraw_window(w);
notify_window("window-layout-changed", w);
events_fire_window("window-layout-changed", w);
return (CMD_RETURN_NORMAL);
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>
@@ -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
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);
struct cmd_find_state *current = cmdq_get_current(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 window *w = wl->window;
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;
char *title;
const char *style;
struct options_entry *o;
int visible, Zflag = args_has(args, 'Z');
if (entry == &cmd_last_pane_entry || args_has(args, 'l')) {
/*
@@ -121,51 +186,25 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
server_redraw_window_borders(lastwp->window);
server_status_window(lastwp->window);
} 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);
window_redraw_active_switch(w, lastwp);
if (window_set_active_pane(w, lastwp, 1)) {
cmd_find_from_winlink(current, wl, 0);
cmd_select_pane_redraw(w);
}
if (window_pop_zoom(w))
if (!visible && window_pop_zoom(w))
server_redraw_window(w);
}
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'm') || args_has(args, 'M')) {
if (args_has(args, 'm') && !window_pane_visible(wp))
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);
}
if (window_pane_is_floating(wp)) {
window_redraw_active_switch(w, wp);
window_set_active_pane(w, wp, 1);
}
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'm') || args_has(args, 'M'))
return (cmd_select_pane_marked_pane(self, item));
style = args_get(args, 'P');
if (style != NULL) {
@@ -218,7 +257,13 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'T')) {
title = format_single_from_target(item, args_get(args, 'T'));
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_status_window(wp->window);
}
@@ -226,22 +271,20 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
}
if (c != NULL && c->session != NULL && (c->flags & CLIENT_ACTIVEPANE))
activewp = server_client_get_pane(c);
else
activewp = w->active;
if (wp == activewp)
if (wp == w->active)
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);
window_redraw_active_switch(w, wp);
if (c != NULL && c->session != NULL && (c->flags & CLIENT_ACTIVEPANE))
server_client_set_pane(c, wp);
else if (window_set_active_pane(w, wp, 1))
if (window_set_active_pane(w, wp, 1))
cmd_find_from_winlink_pane(current, wl, wp, 0);
cmdq_insert_hook(s, item, current, "after-select-pane");
cmd_select_pane_redraw(w);
if (window_pop_zoom(w))
if (!visible && window_pop_zoom(w))
server_redraw_window(w);
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>

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>
@@ -70,6 +70,7 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after,
struct key_table *table = NULL;
struct key_binding *bd;
struct key_event *event;
struct cmdq_item *new_after = after;
if (args_has(args, 'K')) {
if (tc == NULL)
@@ -77,10 +78,16 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after,
event = xcalloc(1, sizeof *event);
event->key = key|KEYC_SENT;
memset(&event->m, 0, sizeof event->m);
if (server_client_handle_key(tc, event) == 0) {
free(event->buf);
free(event);
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);
return (item);
}
@@ -229,8 +236,10 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item)
if (count == 0) {
if (args_has(args, 'N') || args_has(args, 'R'))
return (CMD_RETURN_NORMAL);
after = args_has(args, 'K') ? item : NULL;
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);
}

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>
@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
@@ -37,67 +38,71 @@ const struct cmd_entry cmd_server_access_entry = {
.name = "server-access",
.alias = NULL,
.args = { "adlrw", 0, 1, NULL },
.usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]",
.args = { "adglrw", 0, 1, NULL },
.usage = "[-adglrw] " CMD_TARGET_PANE_USAGE " [user|group]",
.flags = CMD_CLIENT_CANFAIL,
.exec = cmd_server_access_exec
};
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;
struct server_acl_user *user;
uid_t uid;
if ((user = server_acl_user_find(pw->pw_uid)) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
if (!server_acl_find(id, flags)) {
cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR);
}
TAILQ_FOREACH(loop, &clients, entry) {
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);
server_acl_deny(id, flags);
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_target_client(item);
char *name;
struct passwd *pw = NULL;
char *arg;
const char *name = NULL, *type;
struct passwd *pw;
struct group *gr;
id_t id;
int flags = 0;
if (args_has(args, 'l')) {
server_acl_display(item);
return (CMD_RETURN_NORMAL);
}
if (args_count(args) == 0) {
cmdq_error(item, "missing user argument");
cmdq_error(item, "missing user or group argument");
return (CMD_RETURN_ERROR);
}
name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
if (*name != '\0')
pw = getpwnam(name);
if (pw == NULL) {
cmdq_error(item, "unknown user: %s", name);
free(name);
arg = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
if (args_has(args, 'g')) {
type = "group";
if ((gr = getgrnam(arg)) != NULL) {
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);
}
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",
pw->pw_name);
name);
return (CMD_RETURN_ERROR);
}
@@ -111,36 +116,35 @@ cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
}
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 (server_acl_user_find(pw->pw_uid) != NULL) {
cmdq_error(item, "user %s is already added",
pw->pw_name);
if (server_acl_find(id, flags)) {
cmdq_error(item, "%s %s is already added", type, name);
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. */
} else if (args_has(args, 'r') || args_has(args, 'w')) {
/* -r or -w implies -a if user does not exist. */
if (server_acl_user_find(pw->pw_uid) == NULL)
server_acl_user_allow(pw->pw_uid);
/* -r or -w implies -a if the entry does not exist. */
if (!server_acl_find(id, flags))
server_acl_allow(id, flags);
}
if (args_has(args, 'w')) {
if (server_acl_user_find(pw->pw_uid) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
if (!server_acl_find(id, flags)) {
cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR);
}
server_acl_user_allow_write(pw->pw_uid);
server_acl_allow_write(id, flags);
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'r')) {
if (server_acl_user_find(pw->pw_uid) == NULL) {
cmdq_error(item, "user %s not found", pw->pw_name);
if (!server_acl_find(id, flags)) {
cmdq_error(item, "%s %s not found", type, name);
return (CMD_RETURN_ERROR);
}
server_acl_user_deny_write(pw->pw_uid);
server_acl_deny_write(id, flags);
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>
@@ -100,6 +100,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
cmdq_error(item, "%s", cause);
goto fail;
}
free(bufname);
return (CMD_RETURN_NORMAL);
}
@@ -107,8 +108,10 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
cmdq_error(item, "no data specified");
goto fail;
}
if ((newsize = strlen(args_string(args, 0))) == 0)
if ((newsize = strlen(args_string(args, 0))) == 0) {
free(bufname);
return (CMD_RETURN_NORMAL);
}
if (args_has(args, 'a') && pb != NULL) {
olddata = paste_buffer_data(pb, &bufsize);
@@ -127,6 +130,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'w') && tc != NULL)
tty_set_selection(&tc->tty, "", bufdata, bufsize);
free(bufname);
return (CMD_RETURN_NORMAL);
fail:

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>

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>
@@ -31,6 +31,10 @@ static enum args_parse_type cmd_set_option_args_parse(struct args *,
u_int, char **);
static enum cmd_retval cmd_set_option_exec(struct cmd *,
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 = {
.name = "set-option",
@@ -62,8 +66,9 @@ const struct cmd_entry cmd_set_hook_entry = {
.name = "set-hook",
.alias = NULL,
.args = { "agpRt:uw", 1, 2, cmd_set_option_args_parse },
.usage = "[-agpRuw] " CMD_TARGET_PANE_USAGE " hook [command]",
.args = { "agpERTt:uB:w", 0, 2, cmd_set_option_args_parse },
.usage = "[-agpERTuw] [-B name:what:format] " CMD_TARGET_PANE_USAGE " "
"[hook] [command]",
.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
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)
{
if (args_has(args, 'B'))
return (ARGS_PARSE_COMMANDS_OR_STRING);
if (idx == 1)
return (ARGS_PARSE_COMMANDS_OR_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
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 options *oo;
struct options_entry *parent, *o, *po;
char *name, *argument, *expanded = NULL;
char *cause;
char *name, *argument, *cause;
char *expanded = NULL, *array_key = NULL;
const char *value;
int window, idx, already, error, ambiguous;
int window, already, error, ambiguous;
int scope;
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. */
argument = format_single_from_target(item, args_string(args, 0));
/* If set-hook -R, fire the hook straight away. */
if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'R')) {
notify_hook(item, argument);
hooks_run(item, argument);
free(argument);
return (CMD_RETURN_NORMAL);
}
/* Parse option name and index. */
name = options_match(argument, &idx, &ambiguous);
/* Parse option name and array key. */
name = options_match(argument, &array_key, &ambiguous);
if (name == NULL) {
if (args_has(args, 'q'))
goto out;
@@ -140,21 +291,23 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
o = options_get_only(oo, name);
parent = options_get(oo, name);
/* Check that array options and indexes match up. */
if (idx != -1 && (*name == '@' || !options_is_array(parent))) {
/* Check that array options and keys match up. */
if (array_key != NULL && (*name == '@' || !options_is_array(parent))) {
cmdq_error(item, "not an array: %s", argument);
goto fail;
}
/* With -o, check this option is not already set. */
if (!args_has(args, 'u') && args_has(args, 'o')) {
if (idx == -1)
if (array_key == NULL)
already = (o != NULL);
else {
if (o == NULL)
already = 0;
else if (options_array_get(o, array_key) != NULL)
already = 1;
else
already = (options_array_get(o, idx) != NULL);
already = 0;
}
if (already) {
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);
if (po == NULL)
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);
free(cause);
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 (o == NULL)
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);
free(cause);
goto fail;
@@ -191,7 +345,9 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
goto fail;
}
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),
options_table_entry(parent)->name, value,
args_has(args, 'a'), &cause);
@@ -207,7 +363,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
}
if (o == NULL)
o = options_empty(oo, options_table_entry(parent));
if (idx == -1) {
if (array_key == NULL) {
if (!append)
options_array_clear(o);
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);
goto fail;
}
} else if (options_array_set(o, idx, value, append,
} else if (options_array_set(o, array_key, value, append,
&cause) != 0) {
cmdq_error(item, "%s", cause);
free(cause);
@@ -229,11 +385,13 @@ out:
free(argument);
free(expanded);
free(name);
free(array_key);
return (CMD_RETURN_NORMAL);
fail:
free(argument);
free(expanded);
free(name);
free(array_key);
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>

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>

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>
@@ -27,10 +27,23 @@
* 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 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 *,
int, struct options *);
@@ -38,8 +51,8 @@ const struct cmd_entry cmd_show_options_entry = {
.name = "show-options",
.alias = "show",
.args = { "AgHpqst:vw", 0, 1, NULL },
.usage = "[-AgHpqsvw] " CMD_TARGET_PANE_USAGE " [option]",
.args = { "AgF:Hpqst:vw", 0, 1, NULL },
.usage = "[-AgHpqsvw] [-F format] " CMD_TARGET_PANE_USAGE " [option]",
.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",
.alias = "showw",
.args = { "gvt:", 0, 1, NULL },
.usage = "[-gv] " CMD_TARGET_WINDOW_USAGE " [option]",
.args = { "F:gvt:", 0, 1, NULL },
.usage = "[-gv] [-F format] " CMD_TARGET_WINDOW_USAGE " [option]",
.target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL },
@@ -64,8 +77,8 @@ const struct cmd_entry cmd_show_hooks_entry = {
.name = "show-hooks",
.alias = NULL,
.args = { "gpt:w", 0, 1, NULL },
.usage = "[-gpw] " CMD_TARGET_PANE_USAGE " [hook]",
.args = { "BF:gpt:w", 0, 1, NULL },
.usage = "[-Bgpw] [-F format] " CMD_TARGET_PANE_USAGE " [hook]",
.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 options *oo;
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;
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);
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));
}
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 (args_has(args, 'q'))
goto out;
@@ -124,9 +147,20 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
parent = 1;
} else
parent = 0;
if (o != NULL)
cmd_show_options_print(self, item, o, idx, parent);
else if (*name == '@') {
if (o != NULL) {
if (cmd_get_entry(self) == &cmd_show_hooks_entry &&
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'))
goto out;
cmdq_error(item, "invalid option: %s", argument);
@@ -135,64 +169,171 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item)
out:
free(name);
free(array_key);
free(argument);
return (CMD_RETURN_NORMAL);
fail:
free(name);
free(array_key);
free(argument);
return (CMD_RETURN_ERROR);
}
static void
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 options_array_item *a;
const char *name = options_name(o);
char *value, *tmp = NULL, *escaped;
struct args *args = cmd_get_args(self);
struct options_array_item *a;
struct format_tree *ft;
const char *name = options_name(o);
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) {
xasprintf(&tmp, "%s[%d]", name, idx);
name = tmp;
} else {
if (options_is_array(o)) {
a = options_array_first(o);
if (a == NULL) {
if (!args_has(args, 'v'))
cmdq_print(item, "%s", name);
return;
}
if (array_key != NULL)
value = options_to_string(o, array_key, 0);
else if (options_is_array(o)) {
a = options_array_first(o);
if (a != NULL) {
while (a != NULL) {
idx = options_array_item_index(a);
cmd_show_options_print(self, item, o, idx,
array_key = options_array_item_key(a);
cmd_show_options_print(self, item, o, array_key,
parent);
a = options_array_next(a);
}
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 (args_has(args, 'v'))
cmdq_print(item, "%s", value);
else if (options_is_string(o)) {
escaped = args_escape(value);
if (parent)
cmdq_print(item, "%s* %s", name, escaped);
else
cmdq_print(item, "%s %s", name, escaped);
free(escaped);
if (template == NULL)
template = SHOW_OPTIONS_TEMPLATE;
if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK))
is_hook = 1;
else if (oe == NULL)
is_user = 1;
ft = format_create_from_target(item);
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 {
if (parent)
cmdq_print(item, "%s* %s", name, value);
else
cmdq_print(item, "%s %s", name, value);
format_add(ft, "option_array_key", "%s", "");
format_add(ft, "option_has_array_key", "0");
}
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
@@ -202,18 +343,29 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope,
struct args *args = cmd_get_args(self);
const struct options_table_entry *oe;
struct options_entry *o;
struct options_array_item *a;
const char *name;
u_int idx;
int parent;
int parent, is_user_hook;
if (cmd_get_entry(self) != &cmd_show_hooks_entry) {
o = options_first(oo);
while (o != NULL) {
if (options_table_entry(o) == NULL)
cmd_show_options_print(self, item, o, -1, 0);
o = options_next(o);
o = options_first(oo);
while (o != NULL) {
if (options_table_entry(o) == NULL) {
name = options_name(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++) {
if (~oe->scope & scope)
@@ -237,24 +389,7 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope,
} else
parent = 0;
if (!options_is_array(o))
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);
}
}
cmd_show_options_print(self, item, o, NULL, parent);
}
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>
@@ -55,56 +55,44 @@ cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self);
const char *typestr = args_get(args, 'T');
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 (typestr == NULL) {
for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) {
for (hidx = 0; hidx < status_prompt_hsize[tidx];
hidx++)
free(status_prompt_hlist[tidx][hidx]);
free(status_prompt_hlist[tidx]);
status_prompt_hlist[tidx] = NULL;
status_prompt_hsize[tidx] = 0;
}
for (t = 0; t < PROMPT_NTYPES; t++)
prompt_history_clear(t);
} else {
type = status_prompt_type(typestr);
type = prompt_type(typestr);
if (type == PROMPT_TYPE_INVALID) {
cmdq_error(item, "invalid type: %s", typestr);
return (CMD_RETURN_ERROR);
}
for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++)
free(status_prompt_hlist[type][hidx]);
free(status_prompt_hlist[type]);
status_prompt_hlist[type] = NULL;
status_prompt_hsize[type] = 0;
prompt_history_clear(type);
}
return (CMD_RETURN_NORMAL);
}
if (typestr == NULL) {
for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) {
cmdq_print(item, "History for %s:\n",
status_prompt_type_string(tidx));
for (hidx = 0; hidx < status_prompt_hsize[tidx];
hidx++) {
cmdq_print(item, "%d: %s", hidx + 1,
status_prompt_hlist[tidx][hidx]);
for (t = 0; t < PROMPT_NTYPES; t++) {
typestr = prompt_type_string(t);
cmdq_print(item, "History for %s:\n", typestr);
for (h = 0; h < prompt_history_size(t); h++) {
v = prompt_history_get(t, h);
cmdq_print(item, "%d: %s", h + 1, v);
}
cmdq_print(item, "%s", "");
}
} else {
type = status_prompt_type(typestr);
type = prompt_type(typestr);
if (type == PROMPT_TYPE_INVALID) {
cmdq_error(item, "invalid type: %s", typestr);
return (CMD_RETURN_ERROR);
}
cmdq_print(item, "History for %s:\n",
status_prompt_type_string(type));
for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) {
cmdq_print(item, "%d: %s", hidx + 1,
status_prompt_hlist[type][hidx]);
cmdq_print(item, "History for %s:\n", prompt_type_string(type));
for (h = 0; h < prompt_history_size(type); h++) {
v = prompt_history_get(type, h);
cmdq_print(item, "%d: %s", h + 1, v);
}
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>
@@ -50,6 +50,7 @@ const struct cmd_entry cmd_source_file_entry = {
struct cmd_source_file_data {
struct cmdq_item *item;
struct client *client;
int flags;
struct cmdq_item *after;
@@ -60,10 +61,24 @@ struct cmd_source_file_data {
u_int nfiles;
};
static enum cmd_retval
cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
static void
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) {
cmd_source_file_depth--;
@@ -74,36 +89,36 @@ cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
}
cfg_print_causes(item);
cmd_source_file_free_data(cdata);
return (CMD_RETURN_NORMAL);
}
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;
u_int i;
if (cfg_finished) {
if (cdata->retval == CMD_RETURN_ERROR &&
c != NULL &&
c->session == NULL)
c->retval = 1;
new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL);
cmdq_insert_after(cdata->after, new_item);
if (!cfg_finished) {
cmd_source_file_free_data(cdata);
return;
}
for (i = 0; i < cdata->nfiles; i++)
free(cdata->files[i]);
free(cdata->files);
free(cdata);
if (cdata->retval == CMD_RETURN_ERROR &&
c != NULL &&
c->session == NULL)
c->retval = 1;
new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
cmdq_insert_after(cdata->after, new_item);
}
static void
cmd_source_file_done(struct client *c, const char *path, int error,
int closed, struct evbuffer *buffer, void *data)
cmd_source_file_done(__unused struct client *oc, const char *path,
int error, int closed, struct evbuffer *buffer, void *data)
{
struct cmd_source_file_data *cdata = data;
struct cmdq_item *item = cdata->item;
struct client *c = cdata->client;
void *bdata = EVBUFFER_DATA(buffer);
size_t bsize = EVBUFFER_LENGTH(buffer);
u_int n;
@@ -127,7 +142,7 @@ cmd_source_file_done(struct client *c, const char *path, int error,
if (n < cdata->nfiles)
file_read(c, cdata->files[n], cmd_source_file_done, cdata);
else {
cmd_source_file_complete(c, cdata);
cmd_source_file_complete(cdata);
cmdq_continue(item);
}
}
@@ -187,6 +202,9 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
cdata = xcalloc(1, sizeof *cdata);
cdata->item = item;
cdata->client = c;
if (c != NULL)
c->references++;
if (args_has(args, 'q'))
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);
retval = CMD_RETURN_WAIT;
} else
cmd_source_file_complete(c, cdata);
cmd_source_file_complete(cdata);
free(cwd);
return (retval);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -33,16 +33,19 @@
#define SPLIT_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}"
static enum cmd_retval cmd_split_window_exec(struct cmd *, 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 = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL },
.usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] "
.args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
.usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] "
"[-c start-directory] [-e environment] "
"[-F format] [-l size] [-m message] [-p percentage] "
"[-s style] [-S active-border-style] "
"[-R inactive-border-style] [-x width] [-y height] "
"[-R inactive-border-style] [-T title] [-x width] [-y height] "
"[-X x-position] [-Y y-position] " CMD_TARGET_PANE_USAGE " "
"[shell-command [argument ...]]",
@@ -56,12 +59,12 @@ const struct cmd_entry cmd_split_window_entry = {
.name = "split-window",
.alias = "splitw",
.args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:vZ", 0, -1, NULL },
.usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] "
"[-F format] [-l size] [-m message] [-p percentage] "
"[-s style] [-S active-border-style] "
"[-R inactive-border-style] " CMD_TARGET_PANE_USAGE " "
"[shell-command [argument ...]]",
.args = { "bB:c:de:EfF:hIkl:m:p:PR:s:S:t:T:vWZ", 0, -1, NULL },
.usage = "[-bdefhIklPvWZ] [-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] "
CMD_TARGET_PANE_USAGE " [shell-command [argument ...]]",
.target = { 't', CMD_FIND_PANE, 0 },
@@ -80,28 +83,71 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
struct session *s = target->s;
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct window_pane *wp = target->wp, *new_wp;
struct window_pane *wp = target->wp, *new_wp = NULL;
struct layout_cell *lc = NULL;
struct event_payload *ep;
struct cmd_find_state fs;
struct key_event *event = cmdq_get_event(item);
int input, empty, is_floating, flags = 0;
const char *template, *style;
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;
enum pane_lines lines;
u_int count = args_count(args);
if (window_active_pane_is_over_zoom(w))
restore_zoom = 1;
if (cmd_get_entry(self) == &cmd_new_pane_entry)
is_floating = !args_has(args, 'L');
else
is_floating = 0;
else {
if (!window_pane_is_visible(wp))
restore_zoom = 0;
if (!restore_zoom)
window_unzoom(w, 1);
is_floating = window_pane_is_floating(wp);
flags |= SPAWN_SPLIT;
}
flags = is_floating ? SPAWN_FLOATING : 0;
if (args_has(args, 'O')) {
if (!is_floating) {
cmdq_error(item, "modal pane must be floating");
return (CMD_RETURN_ERROR);
}
if (w->modal != NULL) {
cmdq_error(item, "window already has a modal pane");
return (CMD_RETURN_ERROR);
}
}
if (args_has(args, 'M') && is_floating) {
if (event == NULL || !event->m.valid || tc == NULL)
return (CMD_RETURN_NORMAL);
}
if (is_floating)
flags |= SPAWN_FLOATING;
if (args_has(args, 'h'))
flags |= SPAWN_HORIZONTAL;
if (args_has(args, 'b'))
flags |= SPAWN_BEFORE;
if (args_has(args, 'f'))
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)
if (input || (count == 1 && *args_string(args, 0) == '\0'))
empty = 1;
else
empty = args_has(args, 'E');
@@ -114,13 +160,28 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (empty)
flags |= SPAWN_EMPTY;
if (is_floating)
lc = layout_get_floating_cell(item, args, w, wp, &cause);
else
if ((value = args_get(args, 'B')) == NULL)
lines = window_get_pane_lines(w);
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, "size or position %s", cause);
cmdq_error(item, "%s", cause);
free(cause);
if (restore_zoom)
window_pop_zoom(w);
return (CMD_RETURN_ERROR);
}
@@ -142,28 +203,31 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
sc.idx = -1;
sc.cwd = args_get(args, 'c');
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) {
cmdq_error(item, "create pane failed: %s", cause);
free(cause);
if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
return (CMD_RETURN_ERROR);
/*
* spawn_pane has already torn the half-built pane down (its
* fork-failure path removes the pane and destroys the layout
* 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;
if (args_has(args, 'D') && args_has(args, 'O'))
new_wp->flags |= PANE_CLOSEONCANCEL;
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);
return (CMD_RETURN_ERROR);
goto fail;
}
options_set_string(new_wp->options, "window-active-style", 0,
"%s", style);
@@ -175,7 +239,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (options_set_string(new_wp->options,
"pane-active-border-style", 0, "%s", style) == NULL) {
cmdq_error(item, "bad active border style: %s", style);
return (CMD_RETURN_ERROR);
goto fail;
}
}
style = args_get(args, 'R');
@@ -184,9 +248,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
"%s", style) == NULL) {
cmdq_error(item, "bad inactive border style: %s",
style);
return (CMD_RETURN_ERROR);
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')) {
@@ -195,34 +261,48 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
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) {
switch (window_pane_start_input(new_wp, item, &cause)) {
case -1:
server_client_remove_pane(new_wp);
if (!is_floating)
layout_close_pane(new_wp);
window_remove_pane(wp->window, new_wp);
cmdq_error(item, "%s", cause);
free(cause);
if (sc.argv != NULL)
cmd_free_argv(sc.argc, sc.argv);
environ_free(sc.environ);
return (CMD_RETURN_ERROR);
goto fail;
case 1:
input = 0;
break;
}
}
if (!args_has(args, 'd'))
if (~flags & SPAWN_DETACHED)
cmd_find_from_winlink_pane(current, wl, new_wp, 0);
if (!is_floating) {
if (restore_zoom) {
window_pop_zoom(wp->window);
server_redraw_window(wp->window);
} 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 ((template = args_get(args, 'F')) == NULL)
template = SPLIT_WINDOW_TEMPLATE;
@@ -239,5 +319,106 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
environ_free(sc.environ);
if (input)
return (CMD_RETURN_WAIT);
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>
@@ -42,6 +42,22 @@ const struct cmd_entry cmd_swap_pane_entry = {
.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
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 layout_cell *src_lc, *dst_lc;
u_int sx, sy, xoff, yoff;
int src_idx, dst_idx;
dst_w = target->wl->window;
dst_wp = target->wp;
dst_idx = target->wl->idx;
src_w = source->wl->window;
src_wp = source->wp;
src_idx = source->wl->idx;
if (src_wp == src_w->modal || dst_wp == dst_w->modal) {
cmdq_error(item, "pane is modal");
return (CMD_RETURN_ERROR);
}
if (window_push_zoom(dst_w, 0, args_has(args, 'Z')))
server_redraw_window(dst_w);
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_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 = cmd_swap_pane_next_tiled_pane(src_wp);
}
} 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_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 = cmd_swap_pane_prev_tiled_pane(src_wp);
}
}
if (src_w != dst_w && window_push_zoom(src_w, 0, args_has(args, 'Z')))
@@ -79,12 +117,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
if (src_wp == dst_wp)
goto out;
if (window_pane_is_floating(src_wp) ||
window_pane_is_floating(dst_wp)) {
cmdq_error(item, "cannot swap floating panes");
return (CMD_RETURN_ERROR);
}
server_client_remove_pane(src_wp);
server_client_remove_pane(dst_wp);
@@ -114,10 +146,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
dst_wp->layout_cell = src_lc;
dst_lc->wp = src_wp;
src_wp->layout_cell = dst_lc;
if (window_pane_is_floating(src_wp) != window_pane_is_floating(dst_wp)) {
src_wp->layout_cell->flags ^= LAYOUT_CELL_FLOATING;
dst_wp->layout_cell->flags ^= LAYOUT_CELL_FLOATING;
}
src_wp->window = dst_w;
options_set_parent(src_wp->options, dst_w->options);
@@ -153,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(&dst_wp->palette, dst_wp->options);
layout_fix_panes(src_w, NULL);
redraw_invalidate_scene(src_w);
server_redraw_window(src_w);
}
layout_fix_panes(dst_w, NULL);
redraw_invalidate_scene(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)
notify_window("window-layout-changed", dst_w);
events_fire_window("window-layout-changed", dst_w);
out:
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>
@@ -38,7 +38,7 @@ const struct cmd_entry cmd_swap_window_entry = {
.source = { 's', CMD_FIND_WINDOW, CMD_FIND_DEFAULT_MARKED },
.target = { 't', CMD_FIND_WINDOW, 0 },
.flags = 0,
.flags = CMD_AFTERHOOK,
.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>
@@ -53,7 +53,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state target;
const char *tflag = args_get(args, 't');
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 session *s;
@@ -139,11 +139,15 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
if (wl != NULL && wp != NULL && wp != wl->window->active) {
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);
window_redraw_active_switch(w, wp);
window_set_active_pane(w, wp, 1);
if (window_pop_zoom(w))
if (!visible && window_pop_zoom(w))
server_redraw_window(w);
}
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>

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>
@@ -34,8 +34,8 @@ const struct cmd_entry cmd_wait_for_entry = {
.name = "wait-for",
.alias = "wait",
.args = { "LSU", 1, 1, NULL },
.usage = "[-L|-S|-U] channel",
.args = { "EF:LSUlvw:", 1, 1, NULL },
.usage = "[-ELSUlv] [-F format] [-w waiter] name",
.flags = 0,
.exec = cmd_wait_for_exec
@@ -46,6 +46,17 @@ struct wait_item {
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 {
const char *name;
int locked;
@@ -76,9 +87,28 @@ static enum cmd_retval cmd_wait_for_lock(struct cmdq_item *, const char *,
struct wait_channel *);
static enum cmd_retval cmd_wait_for_unlock(struct cmdq_item *, const char *,
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 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 *
cmd_wait_for_add(const char *name)
@@ -117,16 +147,53 @@ cmd_wait_for_remove(struct wait_channel *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
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);
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);
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'))
return (cmd_wait_for_signal(item, name, wc));
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));
}
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
cmd_wait_for_signal(__unused struct cmdq_item *item, const char *name,
struct wait_channel *wc)
@@ -245,6 +485,13 @@ cmd_wait_for_flush(void)
{
struct wait_channel *wc, *wc1;
struct wait_item *wi, *wi1;
struct wait_event_item *wei, *wei1;
TAILQ_FOREACH_SAFE(wei, &wait_event_items, entry, wei1) {
TAILQ_REMOVE(&wait_event_items, wei, entry);
cmdq_continue(wei->item);
cmd_wait_for_event_free(wei);
}
RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {

59
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>
@@ -115,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_window_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_unlink_window_entry;
extern const struct cmd_entry cmd_wait_for_entry;
@@ -208,6 +209,7 @@ const struct cmd_entry *cmd_table[] = {
&cmd_swap_pane_entry,
&cmd_swap_window_entry,
&cmd_switch_client_entry,
&cmd_switch_mode_entry,
&cmd_unbind_key_entry,
&cmd_unlink_window_entry,
&cmd_wait_for_entry,
@@ -830,20 +832,27 @@ cmd_mouse_pane(struct mouse_event *m, struct session **sp,
if (!window_has_pane(wl->window, wp))
return (NULL);
}
if (wl->window->modal != NULL && wp != wl->window->modal)
return (NULL);
if (wlp != NULL)
*wlp = wl;
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 *
cmd_template_replace(const char *template, const char *s, int idx)
{
char ch, *buf;
const char *ptr, *cp, quote[] = "\"\\$;~";
int replaced, quoted;
size_t len;
char ch, *buf;
const char *ptr, *cp, dquote[] = "\"\\$;~";
int replaced;
size_t len, slen;
enum { NQ, SQ, DQ } quote;
if (strchr(template, '%') == NULL)
return (xstrdup(template));
@@ -857,30 +866,52 @@ cmd_template_replace(const char *template, const char *s, int idx)
while (*ptr != '\0') {
switch (ch = *ptr++) {
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)
break;
replaced = 1;
}
ptr++;
quoted = (*ptr == '%');
if (quoted)
ptr++;
quote = SQ;
if (*ptr == '%') {
quote = DQ;
ptr++;
}
}
buf = xrealloc(buf, len + (strlen(s) * 3) + 1);
slen = strlen(s);
if (slen >= SIZE_MAX / 4 ||
len > SIZE_MAX - (slen * 4) - 1)
fatalx("argument too long");
buf = xrealloc(buf, len + (slen * 4) + 1);
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++] = *cp;
}
buf[len] = '\0';
continue;
}
if (len > SIZE_MAX - 2)
fatalx("argument too long");
buf = xrealloc(buf, len + 2);
buf[len++] = ch;
buf[len] = '\0';
}
log_debug("%s: %s -> %s", __func__, template, buf);
return (buf);
}

182
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>
@@ -26,6 +26,85 @@
#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
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);
}
/* 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. */
const char *
colour_tostring(int c)
@@ -130,6 +231,13 @@ colour_tostring(int c)
if (c == -1)
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) {
colour_split_rgb(c, &r, &g, &b);
xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b);
@@ -182,6 +290,58 @@ colour_tostring(int c)
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. */
enum client_theme
colour_totheme(int c)
@@ -230,6 +390,7 @@ colour_fromstring(const char *s)
const char *cp;
int n;
u_char r, g, b;
u_int i;
if (*s == '#' && strlen(s) == 7) {
for (cp = s + 1; isxdigit((u_char) *cp); cp++)
@@ -260,6 +421,11 @@ colour_fromstring(const char *s)
if (strcasecmp(s, "terminal") == 0)
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)
return (0);
if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0)
@@ -1128,7 +1294,8 @@ colour_palette_from_option(struct colour_palette *p, struct options *oo)
{
struct options_entry *o;
struct options_array_item *a;
u_int i, n;
union options_value *ov;
u_int i;
int c;
if (p == NULL)
@@ -1146,12 +1313,11 @@ colour_palette_from_option(struct colour_palette *p, struct options *oo)
p->default_palette = xcalloc(256, sizeof *p->default_palette);
for (i = 0; i < 256; i++)
p->default_palette[i] = -1;
while (a != NULL) {
n = options_array_item_index(a);
if (n < 256) {
c = options_array_item_value(a)->number;
p->default_palette[n] = c;
for (i = 0; i < 256; i++) {
ov = options_array_getv(o, "%u", i);
if (ov != NULL) {
c = ov->number;
p->default_palette[i] = c;
}
a = options_array_next(a);
}
}

View File

@@ -410,11 +410,6 @@ int asprintf(char **, const char *, ...);
int vasprintf(char **, const char *, va_list);
#endif
#ifndef HAVE_FGETLN
/* fgetln.c */
char *fgetln(FILE *, size_t *);
#endif
#ifndef HAVE_GETLINE
/* getline.c */
ssize_t getline(char **, size_t *, FILE *);

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) {
char *nbuf;
size_t nbufsiz = *bufsiz * 2;
size_t nbufsiz;
ssize_t d = ptr - *buf;
if (*bufsiz > SIZE_MAX / 2) {
errno = EOVERFLOW;
return -1;
}
nbufsiz = *bufsiz * 2;
if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
return -1;
*buf = nbuf;

View File

@@ -339,7 +339,7 @@ static int
getopt_internal(int nargc, char * const *nargv, const char *options,
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;
static int posixly_correct = -1;

View File

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

View File

@@ -1,6 +1,6 @@
# configure.ac
AC_INIT([tmux], 3.7b)
AC_INIT([tmux], next-3.9)
AC_PREREQ([2.60])
AC_CONFIG_AUX_DIR(etc)
@@ -76,9 +76,7 @@ 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),
,
enable_asan=no
AS_HELP_STRING(--enable-asan, enable ASAN build flags)
)
AM_CONDITIONAL(IS_ASAN, test "x$enable_asan" = xyes)
@@ -177,7 +175,6 @@ AC_REPLACE_FUNCS([ \
clock_gettime \
closefrom \
explicit_bzero \
fgetln \
freezero \
getdtablecount \
getdtablesize \
@@ -241,6 +238,7 @@ AC_LIBOBJ(getopt_long)
# Look for libevent. Try libevent_core or libevent with pkg-config first then
# look for the library.
libevent_version=off
PKG_CHECK_MODULES(
LIBEVENT_CORE,
[libevent_core >= 2],
@@ -249,6 +247,8 @@ PKG_CHECK_MODULES(
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBEVENT_CORE_LIBS $LIBS"
found_libevent=yes
libevent_version=`$PKG_CONFIG --modversion libevent_core 2>/dev/null`
test "x$libevent_version" = x && libevent_version=on
],
found_libevent=no
)
@@ -261,6 +261,8 @@ if test x$found_libevent = xno; then
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBEVENT_LIBS $LIBS"
found_libevent=yes
libevent_version=`$PKG_CONFIG --modversion libevent 2>/dev/null`
test "x$libevent_version" = x && libevent_version=on
],
found_libevent=no
)
@@ -269,7 +271,8 @@ if test x$found_libevent = xno; then
AC_SEARCH_LIBS(
event_init,
[event_core event event-1.4],
found_libevent=yes,
[found_libevent=yes
libevent_version=on],
found_libevent=no
)
fi
@@ -296,6 +299,7 @@ fi
# Look for ncurses or curses. Try pkg-config first then directly for the
# library.
ncurses_version=off
PKG_CHECK_MODULES(
LIBTINFOW,
tinfow,
@@ -304,6 +308,8 @@ PKG_CHECK_MODULES(
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
)
@@ -316,6 +322,8 @@ if test "x$found_ncurses" = xno; then
CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBTINFO_LIBS $LIBS"
found_ncurses=yes
ncurses_version=`$PKG_CONFIG --modversion tinfo 2>/dev/null`
test "x$ncurses_version" = x && ncurses_version=on
],
found_ncurses=no
)
@@ -329,6 +337,8 @@ if test "x$found_ncurses" = xno; then
CPPFLAGS="$LIBNCURSESW_CFLAGS $SAVED_CPPFLAGS"
LIBS="$LIBNCURSESW_LIBS $LIBS"
found_ncurses=yes
ncurses_version=`$PKG_CONFIG --modversion ncursesw 2>/dev/null`
test "x$ncurses_version" = x && ncurses_version=on
],
found_ncurses=no
)
@@ -342,6 +352,8 @@ if test "x$found_ncurses" = xno; then
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
)
@@ -350,7 +362,8 @@ if test "x$found_ncurses" = xno; then
AC_SEARCH_LIBS(
setupterm,
[tinfow tinfo terminfo ncursesw ncurses],
found_ncurses=yes,
[found_ncurses=yes
ncurses_version=on],
found_ncurses=no
)
if test "x$found_ncurses" = xyes; then
@@ -380,6 +393,7 @@ else
LIBS="$LIBS -lcurses"
CPPFLAGS="$CPPFLAGS -DHAVE_CURSES_H"
AC_DEFINE(HAVE_CURSES_H)
ncurses_version=on
else
AC_MSG_ERROR("curses not found")
fi
@@ -390,6 +404,7 @@ AC_CHECK_FUNCS([ \
])
# Look for utempter.
utempter_version=off
AC_ARG_ENABLE(
utempter,
AS_HELP_STRING(--enable-utempter, use utempter if it is installed)
@@ -406,17 +421,34 @@ if test "x$enable_utempter" = xyes; then
fi
if test "x$enable_utempter" = xyes; then
AC_DEFINE(HAVE_UTEMPTER)
utempter_version=on
else
AC_MSG_ERROR("utempter not found")
fi
fi
# Look for utf8proc.
utf8proc_version=off
AC_ARG_ENABLE(
utf8proc,
AS_HELP_STRING(--enable-utf8proc, use utf8proc if it is installed)
)
try_utf8proc=no
require_utf8proc=no
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(
LIBUTF8PROC,
libutf8proc,
@@ -424,7 +456,10 @@ if test "x$enable_utf8proc" = xyes; then
AM_CPPFLAGS="$LIBUTF8PROC_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$LIBUTF8PROC_CFLAGS $SAVED_CPPFLAGS"
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)
if test "x$enable_utf8proc" = xyes; then
@@ -437,13 +472,23 @@ if test "x$enable_utf8proc" = xyes; then
fi
if test "x$enable_utf8proc" = xyes; then
AC_DEFINE(HAVE_UTF8PROC)
test "x$utf8proc_version" = xoff && utf8proc_version=on
else
AC_MSG_ERROR("utf8proc not found")
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
AC_MSG_ERROR("utf8proc not found")
fi
fi
fi
AM_CONDITIONAL(HAVE_UTF8PROC, [test "x$enable_utf8proc" = xyes])
# Check for systemd support.
systemd_version=off
AC_ARG_ENABLE(
systemd,
AS_HELP_STRING(--enable-systemd, enable systemd integration)
@@ -457,6 +502,8 @@ if test x"$enable_systemd" = xyes; then
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
LIBS="$SYSTEMD_LIBS $LIBS"
found_systemd=yes
systemd_version=`$PKG_CONFIG --modversion libsystemd 2>/dev/null`
test "x$systemd_version" = x && systemd_version=on
],
found_systemd=no
)
@@ -581,11 +628,34 @@ if test "x$found_malloc_trim" = xyes; then
fi
# Build against jemalloc if requested.
jemalloc_version=off
AC_ARG_ENABLE(
jemalloc,
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
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(
JEMALLOC,
jemalloc,
@@ -593,9 +663,26 @@ if test "x$enable_jemalloc" = xyes; then
AM_CPPFLAGS="$JEMALLOC_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
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
# Check for CMSG_DATA. On some platforms like HP-UX this requires UNIX 95
@@ -942,6 +1029,20 @@ case "$host_os" in
AC_MSG_NOTICE([])
AC_MSG_ERROR([must give --enable-utf8proc or --disable-utf8proc])
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*)
AC_MSG_RESULT(dragonfly)
@@ -1029,6 +1130,31 @@ AC_MSG_CHECKING(lock-command)
AC_MSG_RESULT($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
# variables.
AC_SUBST(AM_CPPFLAGS)

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: control-notify.c,v 1.39 2026/09/08 10:20:08 nicm Exp $ */
/*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -23,31 +23,59 @@
#include "tmux.h"
/* Should this client be sent events? */
#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
control_notify_pane_mode_changed(int pane)
/* Notify control clients that pane mode changed. */
static void
control_pane_mode_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{
struct client *c;
struct window_pane *wp;
struct client *c;
char *value;
wp = event_payload_get_pane(ep, "pane");
if (wp != NULL) {
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_notify_write(c, "%%pane-mode-changed %%%u",
wp->id);
}
return;
}
value = event_payload_print(ep, "pane");
if (value == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_write(c, "%%pane-mode-changed %%%u", pane);
control_notify_write(c, "%%pane-mode-changed %s", value);
}
free(value);
}
void
control_notify_window_layout_changed(struct window *w)
/* 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 session *s;
struct winlink *wl;
const char *template;
char *cp;
struct client *c;
struct session *s;
struct winlink *wl;
struct window *w = event_payload_get_window(ep, "window");
struct format_tree *ft;
const char *template;
char *cp;
if (w == NULL)
return;
template = "%layout-change #{window_id} #{window_layout} "
"#{window_visible_layout} #{window_raw_flags}";
@@ -57,101 +85,132 @@ control_notify_window_layout_changed(struct window *w)
* and we don't need to inform the client about the layout change
* because the whole window will go away soon.
*/
wl = TAILQ_FIRST(&w->winlinks);
if (wl == NULL || w->layout_root == NULL)
if (TAILQ_FIRST(&w->winlinks) == NULL || w->layout_root == NULL)
return;
cp = format_single(NULL, template, NULL, NULL, wl, NULL);
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue;
s = c->session;
if (winlink_find_by_window_id(&s->windows, w->id) != NULL)
control_write(c, "%s", cp);
wl = winlink_find_by_window_id(&s->windows, w->id);
if (wl == NULL)
continue;
ft = format_create(c, NULL, FORMAT_NONE, 0);
format_defaults(ft, c, s, wl, NULL);
cp = format_expand(ft, template);
format_free(ft);
control_notify_write(c, "%s", cp);
free(cp);
}
free(cp);
}
void
control_notify_window_pane_changed(struct window *w)
/* Notify control clients that window pane changed. */
static void
control_window_pane_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{
struct client *c;
struct window *w = event_payload_get_window(ep, "window");
if (w->active == NULL)
if (w == NULL || w->active == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
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);
}
}
void
control_notify_window_unlinked(__unused struct session *s, struct window *w)
/* Notify control clients that a window was unlinked. */
static void
control_window_unlinked_cb(__unused const char *name, struct event_payload *ep,
__unused void *sink_data)
{
struct client *c;
struct session *cs;
struct client *c;
struct session *cs;
struct window *w = event_payload_get_window(ep, "window");
if (w == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue;
cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
control_write(c, "%%window-close @%u", w->id);
else
control_write(c, "%%unlinked-window-close @%u", w->id);
control_notify_write(c, "%%window-close @%u", w->id);
else {
control_notify_write(c, "%%unlinked-window-close @%u",
w->id);
}
}
}
void
control_notify_window_linked(__unused struct session *s, struct window *w)
/* Notify control clients that a window was linked. */
static void
control_window_linked_cb(__unused const char *name, struct event_payload *ep,
__unused void *sink_data)
{
struct client *c;
struct session *cs;
struct client *c;
struct session *cs;
struct window *w = event_payload_get_window(ep, "window");
if (w == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue;
cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
control_write(c, "%%window-add @%u", w->id);
else
control_write(c, "%%unlinked-window-add @%u", w->id);
control_notify_write(c, "%%window-add @%u", w->id);
else {
control_notify_write(c, "%%unlinked-window-add @%u",
w->id);
}
}
}
void
control_notify_window_renamed(struct window *w)
/* Notify control clients that a window was renamed. */
static void
control_window_renamed_cb(__unused const char *name, struct event_payload *ep,
__unused void *sink_data)
{
struct client *c;
struct session *cs;
struct client *c;
struct session *cs;
struct window *w = event_payload_get_window(ep, "window");
if (w == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
continue;
cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) {
control_write(c, "%%window-renamed @%u %s", w->id,
w->name);
} else {
control_write(c, "%%unlinked-window-renamed @%u %s",
control_notify_write(c, "%%window-renamed @%u %s",
w->id, w->name);
} else {
control_notify_write(c,
"%%unlinked-window-renamed @%u %s", w->id, w->name);
}
}
}
void
control_notify_client_session_changed(struct client *cc)
/* Notify control clients that a client changed session. */
static void
control_client_session_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{
struct client *c;
struct session *s;
struct client *cc = event_payload_get_client(ep, "client");
struct client *c;
struct session *s;
if (cc->session == NULL)
if (cc == NULL || cc->session == NULL)
return;
s = cc->session;
@@ -160,101 +219,163 @@ control_notify_client_session_changed(struct client *cc)
continue;
if (cc == c) {
control_write(c, "%%session-changed $%u %s", s->id,
s->name);
control_notify_write(c, "%%session-changed $%u %s",
s->id, s->name);
} else {
control_write(c, "%%client-session-changed %s $%u %s",
cc->name, s->id, s->name);
control_notify_write(c,
"%%client-session-changed %s $%u %s", cc->name,
s->id, s->name);
}
}
}
void
control_notify_client_detached(struct client *cc)
/* Notify control clients that a client detached. */
static void
control_client_detached_cb(__unused const char *name, struct event_payload *ep,
__unused void *sink_data)
{
struct client *c;
struct client *cc = event_payload_get_client(ep, "client");
struct client *c;
if (cc == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (CONTROL_SHOULD_NOTIFY_CLIENT(c))
control_write(c, "%%client-detached %s", cc->name);
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_notify_write(c, "%%client-detached %s", cc->name);
}
}
void
control_notify_session_renamed(struct session *s)
/* Notify control clients that a session was renamed. */
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;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_write(c, "%%session-renamed $%u %s", s->id, s->name);
control_notify_write(c, "%%sessions-changed");
}
}
void
control_notify_session_created(__unused struct session *s)
/* Notify control clients that sessions changed. */
static void
control_session_closed_cb(__unused const char *name,
__unused struct event_payload *ep, __unused void *sink_data)
{
struct client *c;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_write(c, "%%sessions-changed");
control_notify_write(c, "%%sessions-changed");
}
}
void
control_notify_session_closed(__unused struct session *s)
/* Notify control clients that the current window changed. */
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;
/*
* 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) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_write(c, "%%sessions-changed");
control_notify_write(c, "%%session-window-changed $%u @%u",
s->id, s->curw->window->id);
}
}
void
control_notify_session_window_changed(struct session *s)
/* Notify control clients that a paste buffer changed. */
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;
if (pbname == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_write(c, "%%session-window-changed $%u @%u", s->id,
s->curw->window->id);
control_notify_write(c, "%%paste-buffer-changed %s", pbname);
}
}
void
control_notify_paste_buffer_changed(const char *name)
/* Notify control clients that a paste buffer was deleted. */
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;
if (pbname == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_write(c, "%%paste-buffer-changed %s", name);
control_notify_write(c, "%%paste-buffer-deleted %s", pbname);
}
}
/* Build control event sinks. */
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) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_write(c, "%%paste-buffer-deleted %s", name);
}
for (i = 0; i < nitems(events); i++)
events_add_sink(events[i].name, events[i].cb, NULL);
}

811
control.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: control.c,v 1.68 2026/09/09 08:30:05 nicm Exp $ */
/*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -19,6 +19,9 @@
#include <sys/types.h>
#include <errno.h>
#include <event.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -50,6 +53,17 @@ struct control_block {
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. */
struct control_pane {
u_int pane;
@@ -75,56 +89,38 @@ struct control_pane {
};
RB_HEAD(control_panes, control_pane);
/* Subscription pane. */
struct control_sub_pane {
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 {
/* Control client window size. */
struct control_window {
u_int window;
u_int idx;
char *last;
u_int sx;
u_int sy;
RB_ENTRY(control_sub_window) entry;
RB_ENTRY(control_window) entry;
};
RB_HEAD(control_sub_windows, control_sub_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);
RB_HEAD(control_windows, control_window);
/* Control client state. */
struct control_state {
struct control_panes panes;
struct control_windows windows;
TAILQ_HEAD(, control_pane) pending_list;
u_int pending_count;
TAILQ_HEAD(, control_block) all_blocks;
size_t queued_reply_bytes;
struct bufferevent *read_event;
struct bufferevent *write_event;
struct control_subs subs;
struct event subs_timer;
struct monitor_set *subs;
/*
* 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. */
@@ -137,6 +133,9 @@ struct control_state {
/* Maximum age for clients that are not using pause mode. */
#define CONTROL_MAXIMUM_AGE 300000
/* Maximum buffered command replies for a client that is not reading. */
#define CONTROL_MAXIMUM_REPLY_BUFFER (64 * 1024 * 1024)
/* Flags to ignore client. */
#define CONTROL_IGNORE_FLAGS \
(CLIENT_CONTROL_NOOUTPUT| \
@@ -154,79 +153,31 @@ control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
}
RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp);
/* Compare client subs. */
/* Compare control windows. */
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));
}
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)
if (cw1->window < cw2->window)
return (-1);
if (csp1->pane > csp2->pane)
return (1);
if (csp1->idx < csp2->idx)
return (-1);
if (csp1->idx > csp2->idx)
if (cw1->window > cw2->window)
return (1);
return (0);
}
RB_GENERATE_STATIC(control_sub_panes, control_sub_pane, entry,
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->last);
free(csp);
}
RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) {
RB_REMOVE(control_sub_windows, &csub->windows, csw);
free(csw->last);
free(csw);
}
free(csub->last);
RB_REMOVE(control_subs, &cs->subs, csub);
free(csub->name);
free(csub->format);
free(csub);
}
RB_GENERATE_STATIC(control_windows, control_window, entry, control_window_cmp);
/* Free a block. */
static void
control_free_block(struct control_state *cs, struct control_block *cb)
{
size_t size;
if (cb->size == 0 && cb->line != NULL) {
size = strlen(cb->line) + 1;
if (cs->queued_reply_bytes > size)
cs->queued_reply_bytes -= size;
else
cs->queued_reply_bytes = 0;
}
free(cb->line);
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
free(cb);
@@ -264,6 +215,66 @@ control_add_pane(struct client *c, struct window_pane *wp)
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. */
static void
control_discard_pane(struct client *c, struct control_pane *cp)
@@ -372,7 +383,7 @@ control_continue_pane(struct client *c, struct window_pane *wp)
cp->flags &= ~CONTROL_PANE_PAUSED;
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
control_write(c, "%%continue %%%u", wp->id);
control_notify_write(c, "%%continue %%%u", wp->id);
}
}
@@ -386,52 +397,186 @@ control_pause_pane(struct client *c, struct window_pane *wp)
if (~cp->flags & CONTROL_PANE_PAUSED) {
cp->flags |= CONTROL_PANE_PAUSED;
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)
control_vwrite(struct client *c, const char *fmt, va_list ap)
/*
* Reset a pane after its buffer has been replaced: drop any output still
* queued from the old buffer and start again from the pane's own offset.
*/
void
control_reset_pane(struct client *c, struct window_pane *wp)
{
struct control_state *cs = c->control_state;
char *s;
struct control_pane *cp;
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);
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 a line. */
void
control_write(struct client *c, const char *fmt, ...)
/*
* Check if the replies buffered for a client, including one about to be
* added, have grown too large and kill it if so. Returns 1 if further output
* for the client should be dropped.
*/
static int
control_check_reply_buffer(struct client *c, size_t added)
{
struct control_state *cs = c->control_state;
size_t size;
if (c->flags & CLIENT_CONTROL_DISCARD)
return (1);
size = EVBUFFER_LENGTH(cs->write_event->output);
size += cs->queued_reply_bytes;
size += added;
if (size < CONTROL_MAXIMUM_REPLY_BUFFER)
return (0);
log_debug("%s: %s: %zu bytes of replies buffered", __func__, c->name,
size);
if (~c->flags & CLIENT_EXIT) {
c->exit_message = xstrdup("too far behind");
c->flags |= CLIENT_EXIT;
control_discard(c);
}
c->flags |= CLIENT_CONTROL_DISCARD;
return (1);
}
/* Write an already-formatted line, queueing it behind %output if needed. */
static void
control_write_line(struct client *c, char *line)
{
struct control_state *cs = c->control_state;
struct control_block *cb;
va_list ap;
size_t size = strlen(line) + 1;
va_start(ap, fmt);
if (control_check_reply_buffer(c, size)) {
free(line);
return;
}
if (TAILQ_EMPTY(&cs->all_blocks)) {
control_vwrite(c, fmt, ap);
va_end(ap);
log_debug("%s: %s: writing line: %s", __func__, c->name, line);
bufferevent_write(cs->write_event, line, size - 1);
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
free(line);
return;
}
cb = xcalloc(1, sizeof *cb);
xvasprintf(&cb->line, fmt, ap);
cb->line = line;
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
cs->queued_reply_bytes += size;
cb->t = get_timer();
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
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, ...)
{
struct control_state *cs = c->control_state;
char *line;
va_list ap;
if (cs == NULL)
return;
va_start(ap, fmt);
xvasprintf(&line, fmt, 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 (cs == NULL)
return;
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;
if (cs == NULL)
return;
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. */
@@ -458,7 +603,7 @@ control_check_age(struct client *c, struct window_pane *wp,
return (0);
cp->flags |= CONTROL_PANE_PAUSED;
control_discard_pane(c, cp);
control_write(c, "%%pause %%%u", wp->id);
control_notify_write(c, "%%pause %%%u", wp->id);
} else {
if (age < CONTROL_MAXIMUM_AGE)
return (0);
@@ -481,7 +626,7 @@ control_write_output(struct client *c, struct window_pane *wp)
if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
return;
if (c->flags & CONTROL_IGNORE_FLAGS) {
if (c->flags & (CONTROL_IGNORE_FLAGS|CLIENT_EXIT)) {
cp = control_get_pane(c, wp);
if (cp != NULL)
goto ignore;
@@ -591,6 +736,53 @@ control_all_done(struct client *c)
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. */
static void
control_flush_all_blocks(struct client *c)
@@ -766,6 +958,33 @@ control_write_callback(__unused struct bufferevent *bufev, void *data)
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. */
void
control_start(struct client *c)
@@ -781,9 +1000,11 @@ control_start(struct client *c)
cs = c->control_state = xcalloc(1, sizeof *cs);
RB_INIT(&cs->panes);
RB_INIT(&cs->windows);
TAILQ_INIT(&cs->pending_list);
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,
control_write_callback, control_error_callback, c);
@@ -826,27 +1047,49 @@ control_discard(struct client *c)
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);
cs->queued_reply_bytes = 0;
bufferevent_disable(cs->write_event, EV_WRITE);
}
/* Stop control mode. */
void
control_stop(struct client *c)
{
struct control_state *cs = c->control_state;
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)
bufferevent_free(cs->write_event);
bufferevent_free(cs->read_event);
RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1)
control_free_sub(cs, csub);
if (evtimer_initialized(&cs->subs_timer))
evtimer_del(&cs->subs_timer);
control_reset_offsets(c);
RB_FOREACH_SAFE(cw, control_windows, &cs->windows, cw1) {
RB_REMOVE(control_windows, &cs->windows, cw);
free(cw);
}
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
control_free_block(cs, cb);
@@ -854,313 +1097,14 @@ control_stop(struct client *c)
free(cs);
}
/* Check session subscription. */
static void
control_check_subs_session(struct client *c, struct control_sub *csub,
struct format_tree *ft)
{
struct session *s = c->session;
char *value;
value = format_expand(ft, csub->format);
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 for a pane. */
static void
control_check_subs_all_panes_one(struct client *c, struct control_sub *csub,
struct format_tree *ft, struct winlink *wl, struct window_pane *wp)
{
struct session *s = c->session;
struct window *w = wl->window;
char *value;
struct control_sub_pane *csp, find;
value = format_expand(ft, csub->format);
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);
return;
}
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 for a window. */
static void
control_check_subs_all_windows_one(struct client *c, struct control_sub *csub,
struct format_tree *ft, struct winlink *wl)
{
struct session *s = c->session;
struct window *w = wl->window;
char *value;
struct control_sub_window *csw, find;
value = format_expand(ft, csub->format);
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);
return;
}
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 session *s = c->session;
struct format_tree *ft;
struct winlink *wl;
struct window_pane *wp;
struct timeval tv = { .tv_sec = 1 };
int have_session = 0, have_all_panes = 0;
int have_all_windows = 0;
log_debug("%s: timer fired", __func__);
evtimer_add(&cs->subs_timer, &tv);
if (s == NULL)
return;
/* Find which subscription types are present. */
RB_FOREACH(csub, control_subs, &cs->subs) {
switch (csub->type) {
case CONTROL_SUB_SESSION:
have_session = 1;
break;
case CONTROL_SUB_ALL_PANES:
have_all_panes = 1;
break;
case CONTROL_SUB_ALL_WINDOWS:
have_all_windows = 1;
break;
default:
break;
}
}
/* Check session subscriptions. */
if (have_session) {
ft = format_create_defaults(NULL, c, s, NULL, NULL);
RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
if (csub->type == CONTROL_SUB_SESSION)
control_check_subs_session(c, csub, ft);
}
format_free(ft);
}
/* Check pane and window subscriptions. */
RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
switch (csub->type) {
case CONTROL_SUB_PANE:
control_check_subs_pane(c, csub);
break;
case CONTROL_SUB_WINDOW:
control_check_subs_window(c, csub);
break;
case CONTROL_SUB_SESSION:
case CONTROL_SUB_ALL_PANES:
case CONTROL_SUB_ALL_WINDOWS:
break;
}
}
/* Check all-panes subscriptions. */
if (have_all_panes) {
RB_FOREACH(wl, winlinks, &s->windows) {
TAILQ_FOREACH(wp, &wl->window->panes, entry) {
ft = format_create_defaults(NULL, c, s, wl, wp);
RB_FOREACH_SAFE(csub, control_subs, &cs->subs,
csub1) {
if (csub->type != CONTROL_SUB_ALL_PANES)
continue;
control_check_subs_all_panes_one(c,
csub, ft, wl, wp);
}
format_free(ft);
}
}
}
/* Check all-windows subscriptions. */
if (have_all_windows) {
RB_FOREACH(wl, winlinks, &s->windows) {
ft = format_create_defaults(NULL, c, s, wl, NULL);
RB_FOREACH_SAFE(csub, control_subs, &cs->subs,
csub1) {
if (csub->type != CONTROL_SUB_ALL_WINDOWS)
continue;
control_check_subs_all_windows_one(c, csub, ft,
wl);
}
format_free(ft);
}
}
}
/* Add a subscription. */
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)
{
struct control_state *cs = c->control_state;
struct control_sub *csub, find;
struct timeval tv = { .tv_sec = 1 };
find.name = (char *)name;
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);
monitor_add(cs->subs, name, type, id, format, MONITOR_NOTIFY_INITIAL);
}
/* Remove a subscription. */
@@ -1168,11 +1112,6 @@ void
control_remove_sub(struct client *c, const char *name)
{
struct control_state *cs = c->control_state;
struct control_sub *csub, find;
find.name = (char *)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);
monitor_remove(cs->subs, name);
}

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>
@@ -152,7 +152,8 @@ environ_clear(struct environ *env, const char *name)
void
environ_put(struct environ *env, const char *var, int flags)
{
char *name, *value;
char *name;
const char *value;
value = strchr(var, '=');
if (value == NULL)

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);
}

199
events.c Normal file
View File

@@ -0,0 +1,199 @@
/* $OpenBSD: events.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 <stdlib.h>
#include <string.h>
#include "tmux.h"
/* Event sink. */
struct events_sink {
char *name;
events_cb cb;
void *data;
int dead;
u_int generation;
TAILQ_ENTRY(events_sink) entry;
};
TAILQ_HEAD(events_sinks, events_sink);
static struct events_sinks events_sinks = TAILQ_HEAD_INITIALIZER(events_sinks);
static u_int events_dispatching;
static u_int events_generation;
/* Free an event sink. */
static void
events_free_sink(struct events_sink *es)
{
TAILQ_REMOVE(&events_sinks, es, entry);
free(es->name);
free(es);
}
/* Free dead event sinks. */
static void
events_free_dead(void)
{
struct events_sink *es, *es1;
TAILQ_FOREACH_SAFE(es, &events_sinks, entry, es1) {
if (es->dead)
events_free_sink(es);
}
}
/* Add an event sink. */
struct events_sink *
events_add_sink(const char *name, events_cb cb, void *data)
{
struct events_sink *es;
es = xcalloc(1, sizeof *es);
es->name = xstrdup(name);
es->cb = cb;
es->data = data;
es->generation = ++events_generation;
TAILQ_INSERT_TAIL(&events_sinks, es, entry);
return (es);
}
/* Remove an event sink. */
void
events_remove_sink(struct events_sink *es)
{
if (es != NULL && !es->dead) {
if (events_dispatching != 0)
es->dead = 1;
else
events_free_sink(es);
}
}
/* Fire an event. */
void
events_fire(const char *name, struct event_payload *ep)
{
struct events_sink *es;
u_int generation = events_generation;
event_payload_set_string(ep, "event", "%s", name);
if (log_get_level() != 0)
event_payload_log(ep, "%s: %s: ", __func__, name);
events_dispatching++;
TAILQ_FOREACH(es, &events_sinks, entry) {
if (es->dead || es->generation > generation)
continue;
if (strcmp(es->name, name) == 0)
es->cb(name, ep, es->data);
}
if (--events_dispatching == 0)
events_free_dead();
event_payload_free(ep);
}
/* Fire a client event. */
void
events_fire_client(const char *name, struct client *c)
{
struct event_payload *ep;
struct cmd_find_state fs;
ep = event_payload_create();
cmd_find_from_client(&fs, c, 0);
event_payload_set_target(ep, &fs);
event_payload_set_client(ep, "client", c);
if (fs.s != NULL)
event_payload_set_session(ep, "session", fs.s);
if (fs.w != NULL)
event_payload_set_window(ep, "window", fs.w);
if (fs.wl != NULL)
event_payload_set_int(ep, "window_index", fs.wl->idx);
else if (fs.idx != -1)
event_payload_set_int(ep, "window_index", fs.idx);
if (fs.wp != NULL)
event_payload_set_pane(ep, "pane", fs.wp);
events_fire(name, ep);
}
/* Fire a session event. */
void
events_fire_session(const char *name, struct session *s)
{
struct event_payload *ep;
struct cmd_find_state fs;
ep = event_payload_create();
if (session_alive(s)) {
cmd_find_from_session(&fs, s, 0);
event_payload_set_target(ep, &fs);
}
event_payload_set_session(ep, "session", s);
events_fire(name, ep);
}
/* Fire a window event. */
void
events_fire_window(const char *name, struct window *w)
{
struct event_payload *ep;
struct cmd_find_state fs;
ep = event_payload_create();
cmd_find_from_window(&fs, w, 0);
event_payload_set_target(ep, &fs);
event_payload_set_window(ep, "window", w);
events_fire(name, ep);
}
/* Fire a pane event. */
void
events_fire_pane(const char *name, struct window_pane *wp)
{
struct event_payload *ep;
struct cmd_find_state fs;
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);
events_fire(name, ep);
}
/* Fire a winlink event. */
void
events_fire_winlink(const char *name, struct winlink *wl)
{
struct event_payload *ep;
struct cmd_find_state fs;
ep = event_payload_create();
cmd_find_from_winlink(&fs, wl, 0);
event_payload_set_target(ep, &fs);
event_payload_set_session(ep, "session", wl->session);
event_payload_set_window(ep, "window", wl->window);
event_payload_set_int(ep, "window_index", wl->idx);
events_fire(name, ep);
}

87
file.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: file.c,v 1.22 2026/08/17 07:56:56 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -185,6 +185,7 @@ file_can_print(struct client *c)
{
if (c == NULL ||
(c->flags & CLIENT_ATTACHED) ||
(c->flags & CLIENT_DEAD) ||
(c->flags & CLIENT_CONTROL))
return (0);
return (1);
@@ -501,7 +502,8 @@ file_push(struct client_file *cf)
} else if (cf->stream > 2) {
close.stream = cf->stream;
proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
file_fire_done(cf);
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
file_fire_done(cf);
}
free(msg);
}
@@ -526,14 +528,48 @@ file_write_left(struct client_files *files)
return (waiting != 0);
}
/* Finish writing a client file. */
static void
file_write_finished(struct client_file *cf)
{
struct msg_write_done msg;
if (cf->event != NULL) {
bufferevent_free(cf->event);
cf->event = NULL;
}
if (cf->fd != -1) {
if (close(cf->fd) != 0 && cf->error == 0)
cf->error = errno;
cf->fd = -1;
}
msg.stream = cf->stream;
msg.error = cf->error;
proc_send(cf->peer, MSG_WRITE_DONE, -1, &msg, sizeof msg);
if (cf->cb != NULL)
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
file_free(cf);
}
/* Client file write error callback. */
static void
file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
file_write_error_callback(__unused struct bufferevent *bev, short what,
void *arg)
{
struct client_file *cf = arg;
int error;
if (what & EVBUFFER_ERROR)
error = errno;
else
error = EIO;
if (error == 0)
error = EIO;
log_debug("write error file %d", cf->stream);
cf->error = error;
bufferevent_free(cf->event);
cf->event = NULL;
@@ -541,7 +577,9 @@ file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
close(cf->fd);
cf->fd = -1;
if (cf->cb != NULL)
if (cf->closed)
file_write_finished(cf);
else if (cf->cb != NULL)
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
}
@@ -553,15 +591,10 @@ file_write_callback(__unused struct bufferevent *bev, void *arg)
log_debug("write check file %d", cf->stream);
if (cf->cb != NULL)
if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0)
file_write_finished(cf);
else if (cf->cb != NULL)
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) {
bufferevent_free(cf->event);
close(cf->fd);
RB_REMOVE(client_files, cf->tree, cf);
file_free(cf);
}
}
/* Handle a file write open message (client). */
@@ -662,14 +695,10 @@ file_write_close(struct client_files *files, struct imsg *imsg)
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
fatalx("unknown stream number");
log_debug("close file %d", cf->stream);
cf->closed = 1;
if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) {
if (cf->event != NULL)
bufferevent_free(cf->event);
if (cf->fd != -1)
close(cf->fd);
RB_REMOVE(client_files, files, cf);
file_free(cf);
file_write_finished(cf);
}
}
@@ -828,6 +857,28 @@ file_write_ready(struct client_files *files, struct imsg *imsg)
return (0);
}
/* Handle a write done message (server). */
int
file_write_done(struct client_files *files, struct imsg *imsg)
{
struct msg_write_done *msg = imsg->data;
size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
struct client_file find, *cf;
if (msglen != sizeof *msg)
return (-1);
find.stream = msg->stream;
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
return (0);
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
return (0);
log_debug("file %d write done", cf->stream);
cf->error = msg->error;
file_fire_done(cf);
return (0);
}
/* Handle read data message (server). */
int
file_read_data(struct client_files *files, struct imsg *imsg)

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: format-draw.c,v 1.34 2026/07/21 07:20:18 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -710,6 +710,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
"AFTER" };
size_t size = strlen(expanded);
struct screen *os = octx->s, s[TOTAL];
struct hyperlinks *hl = os->hyperlinks;
struct screen_write_ctx ctx[TOTAL];
u_int ocx = os->cx, ocy = os->cy, n, i, width[TOTAL];
u_int map[] = { LEFT,
@@ -723,7 +724,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
struct grid_cell gc, current_default, base_default;
struct style sy, saved_sy;
struct utf8_data *ud = &sy.gc.data;
const char *cp, *end;
const char *cp, *end, *link_uri;
enum utf8_state more;
char *tmp;
struct format_range *fr = NULL, *fr1;
@@ -838,6 +839,17 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
sy.gc.fg = base->fg;
}
/*
* Resolve any hyperlink and store it in the cell. The URI
* doubles as the internal ID so repeated links share one entry
* and the ID stays stable across redraws.
*/
link_uri = style_link(&sy);
if (link_uri != NULL && hl != NULL)
sy.gc.link = hyperlinks_put(hl, link_uri, link_uri);
else
sy.gc.link = 0;
/* If this style has a fill colour, store it for later. */
if (sy.fill != 8)
fill = sy.fill;
@@ -946,7 +958,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
if (srs != NULL) {
if (fr != NULL && !format_is_type(fr, &sy)) {
if (s[current].cx != fr->start) {
fr->end = s[current].cx + 1;
fr->end = s[current].cx;
TAILQ_INSERT_TAIL(&frs, fr, entry);
} else
free(fr);

1372
format.c

File diff suppressed because it is too large Load Diff

655
fuzzy.c Normal file
View File

@@ -0,0 +1,655 @@
/* $OpenBSD: fuzzy.c,v 1.1 2026/06/26 14:40:30 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 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 <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
/*
* Fuzzy matching in the style of fzf. The pattern is split into groups by |
* and each group is split on spaces into terms. A row matches if any group
* matches; within a group all positive terms must match and all inverse terms
* must not match.
*
* Plain positive terms are fuzzy subsequences. A leading ' makes a term an
* exact substring match, ^ anchors a term at the start and $ anchors it at
* the end. A leading ! inverts the term. Plain inverse terms are exact
* substring matches rather than inverse fuzzy matches, like fzf.
*
* Both the pattern and the text are UTF-8. The text may contain tmux style
* directives (#[...]); these and their contents are invisible to matching and
* occupy no columns, but align= styles do move the surrounding text and are
* accounted for exactly as format_draw lays it out (the no-list layout, see
* format_draw_none). Matching is smart-case: case is ignored unless the pattern
* contains an uppercase character (ASCII case folding only; other characters
* are compared exactly by their UTF-8 data).
*
* On a match a bitstr_t of the requested display width is returned with a bit
* set for every column occupied by a matched character, so the caller can
* highlight them; NULL is returned if there is no match. A cheap fzf-style
* score (matches at the start, after word boundaries and in contiguous runs
* score higher) is also produced so callers can rank best-match-first.
*/
#define FUZZY_BONUS_EXACT 1000
#define FUZZY_BONUS_PREFIX 200
#define FUZZY_BONUS_SUFFIX 100
#define FUZZY_BONUS_START 12
#define FUZZY_BONUS_BOUNDARY 8
#define FUZZY_BONUS_CONSECUTIVE 6
#define FUZZY_PENALTY_LEADING 1
#define FUZZY_PENALTY_LEADING_MAX 10
#define FUZZY_PENALTY_GAP 1
/* A single visible character of the text. */
struct fuzzy_char {
enum style_align align;
struct utf8_data ud; /* original UTF-8 data */
u_int width; /* display width */
u_int offset; /* within its alignment */
};
/* One parsed query term. */
struct fuzzy_term {
int inverse;
int exact;
int prefix;
int suffix;
const char *text;
size_t len;
};
/* Is this character a word boundary, so a match after it scores higher? */
static int
fuzzy_is_boundary(const struct utf8_data *ud)
{
static const char *boundary = " -_/.:";
if (ud->size != 1)
return (0);
return (strchr(boundary, ud->data[0]) != NULL);
}
/*
* Compare two characters, folding ASCII case if wanted. UTF-8 is compared
* directly without case folding.
*/
static int
fuzzy_char_equal(const struct utf8_data *a, const struct utf8_data *b, int fold)
{
if (fold &&
a->size == 1 &&
b->size == 1 &&
a->data[0] < 0x80 &&
b->data[0] < 0x80)
return (tolower(a->data[0]) == tolower(b->data[0]));
return (a->size == b->size && memcmp(a->data, b->data, a->size) == 0);
}
/* Map a style alignment onto one of the four layout columns. */
static enum style_align
fuzzy_align(enum style_align align)
{
if (align == STYLE_ALIGN_DEFAULT)
return (STYLE_ALIGN_LEFT);
return (align);
}
/* Add a visible character to the array, updating the alignment width. */
static void
fuzzy_add(struct fuzzy_char **cs, u_int *ncs, u_int *alloc, enum style_align a,
const struct utf8_data *ud, u_int *widths)
{
struct fuzzy_char *fc;
if (*ncs == *alloc) {
*alloc = (*alloc == 0) ? 64 : *alloc * 2;
*cs = xreallocarray(*cs, *alloc, sizeof **cs);
}
fc = &(*cs)[(*ncs)++];
fc->align = a;
memcpy(&fc->ud, ud, sizeof fc->ud);
fc->width = ud->width;
fc->offset = widths[a];
widths[a] += ud->width;
}
/* Decode a character as UTF-8. */
static const char *
fuzzy_decode_one(const char *cp, const char *end, struct utf8_data *ud)
{
enum utf8_state more;
const char *start = cp;
if ((more = utf8_open(ud, (u_char)*cp)) == UTF8_MORE) {
while (++cp != end && more == UTF8_MORE)
more = utf8_append(ud, (u_char)*cp);
if (more == UTF8_DONE)
return (cp);
cp = start;
}
utf8_set(ud, (u_char)*cp);
return (cp + 1);
}
/*
* Scan the text into an array of visible characters, skipping styles and
* recording the alignment and intra-alignment offset of each. Returns the
* array and its length and fills in the per-alignment widths.
*/
static struct fuzzy_char *
fuzzy_scan(const char *text, u_int *ncs, u_int *widths)
{
struct fuzzy_char *cs = NULL;
u_int alloc = 0, n, leading, i;
enum style_align current = STYLE_ALIGN_LEFT;
struct style sy;
const char *cp = text, *textend = text + strlen(text);
const char *end;
struct utf8_data ud, hash, bracket;
char *tmp;
*ncs = 0;
memset(widths, 0, sizeof *widths * (STYLE_ALIGN_ABSOLUTE_CENTRE + 1));
style_set(&sy, &grid_default_cell);
utf8_set(&hash, '#');
utf8_set(&bracket, '[');
while (*cp != '\0') {
/* Handle a run of #s, which may introduce a style. */
if (*cp == '#') {
for (n = 0; cp[n] == '#'; n++)
/* nothing */;
if (cp[n] != '[') {
/* Escaped #s: ##->#, so half (rounded up). */
leading = (n % 2 == 0) ? n / 2 : n / 2 + 1;
for (i = 0; i < leading; i++) {
fuzzy_add(&cs, ncs, &alloc, current,
&hash, widths);
}
cp += n;
continue;
}
/* Even count: all #s escaped, the [ is literal. */
for (i = 0; i < n / 2; i++)
fuzzy_add(&cs, ncs, &alloc, current, &hash,
widths);
if (n % 2 == 0) {
fuzzy_add(&cs, ncs, &alloc, current, &bracket,
widths);
cp += n + 1;
continue;
}
/* Odd count: this is a style, find and parse it. */
end = format_skip(cp + n + 1, "]");
if (end == NULL)
break;
tmp = xstrndup(cp + n + 1, end - (cp + n + 1));
if (style_parse(&sy, &grid_default_cell, tmp) == 0)
current = fuzzy_align(sy.align);
free(tmp);
cp = end + 1;
continue;
}
/* Decode one character, multibyte or single byte. */
cp = fuzzy_decode_one(cp, textend, &ud);
/*
* Skip non-printable single bytes (control characters and raw
* bytes left over from a failed decode); keep printable ASCII
* and any decoded UTF-8.
*/
if (ud.size == 1 && (ud.data[0] <= 0x1f || ud.data[0] >= 0x7f))
continue;
fuzzy_add(&cs, ncs, &alloc, current, &ud, widths);
}
return (cs);
}
/*
* Work out the display column of a visible character given the trimmed widths
* and start columns of each alignment. Returns 0 and sets the column if the
* character is visible, otherwise returns -1.
*/
static int
fuzzy_column(const struct fuzzy_char *fc, const u_int *start, const u_int *src,
const u_int *vis, u_int *column)
{
enum style_align a = fc->align;
if (fc->offset < src[a] || fc->offset >= src[a] + vis[a])
return (-1);
*column = start[a] + (fc->offset - src[a]);
return (0);
}
/* Decode a UTF-8 term into an array of characters. */
static u_int
fuzzy_decode(const char *tok, size_t len, struct utf8_data *out)
{
const char *cp = tok, *end = tok + len;
u_int n = 0;
while (cp != end)
cp = fuzzy_decode_one(cp, end, &out[n++]);
return (n);
}
/* Add the score for a fuzzy token matched at the given positions. */
static int
fuzzy_score_positions(const u_int *pos, u_int npos, const struct fuzzy_char *cs)
{
u_int i, gap, span;
int score = 0;
if (npos == 0)
return (0);
if (pos[0] == 0)
score += FUZZY_BONUS_START;
else {
if (fuzzy_is_boundary(&cs[pos[0] - 1].ud))
score += FUZZY_BONUS_BOUNDARY;
if (pos[0] < FUZZY_PENALTY_LEADING_MAX)
score -= pos[0] * FUZZY_PENALTY_LEADING;
else {
score -= FUZZY_PENALTY_LEADING_MAX *
FUZZY_PENALTY_LEADING;
}
}
for (i = 1; i < npos; i++) {
if (pos[i] == pos[i - 1] + 1)
score += FUZZY_BONUS_CONSECUTIVE;
else if (fuzzy_is_boundary(&cs[pos[i] - 1].ud))
score += FUZZY_BONUS_BOUNDARY;
}
span = pos[npos - 1] - pos[0] + 1;
gap = span - npos;
score -= gap * FUZZY_PENALTY_GAP;
return (score);
}
/*
* Match a token as a subsequence of the visible characters. Returns if the
* token matches.
*/
static int
fuzzy_match_fuzzy(const struct utf8_data *tok, u_int toklen,
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
{
u_int pi, ci, *pos;
int found, value;
if (toklen == 0 || ncs == 0)
return (0);
pos = xcalloc(toklen, sizeof *pos);
/* First find a subsequence from the start. */
ci = 0;
for (pi = 0; pi < toklen; pi++) {
while (ci != ncs &&
!fuzzy_char_equal(&tok[pi], &cs[ci].ud, fold))
ci++;
if (ci == ncs) {
free(pos);
return (0);
}
pos[pi] = ci++;
}
/* Then compact it backwards to prefer a shorter span. */
ci = pos[toklen - 1];
for (pi = toklen; pi > 0; pi--) {
found = 0;
for (;;) {
if (fuzzy_char_equal(&tok[pi - 1], &cs[ci].ud, fold)) {
pos[pi - 1] = ci;
found = 1;
break;
}
if (ci == 0)
break;
ci--;
}
if (!found) {
free(pos);
return (0);
}
if (pi != 1)
ci--;
}
value = fuzzy_score_positions(pos, toklen, cs);
*score += value;
for (pi = 0; pi < toklen; pi++)
matched[pos[pi]] = 1;
free(pos);
return (1);
}
/* Score an exact, prefix or suffix match. */
static int
fuzzy_score_exact(u_int start, u_int toklen, u_int ncs,
const struct fuzzy_char *cs, int prefix, int suffix)
{
int score;
score = FUZZY_BONUS_EXACT + toklen * FUZZY_BONUS_CONSECUTIVE;
if (prefix)
score += FUZZY_BONUS_PREFIX;
if (suffix)
score += FUZZY_BONUS_SUFFIX;
if (start == 0)
score += FUZZY_BONUS_START;
else if (fuzzy_is_boundary(&cs[start - 1].ud))
score += FUZZY_BONUS_BOUNDARY;
if (start < FUZZY_PENALTY_LEADING_MAX)
score -= start * FUZZY_PENALTY_LEADING;
else
score -= FUZZY_PENALTY_LEADING_MAX * FUZZY_PENALTY_LEADING;
if (!prefix && !suffix)
score -= ncs - (start + toklen);
return (score);
}
/* Match an exact, prefix or suffix term against the visible characters. */
static int
fuzzy_match_exact(const struct utf8_data *tok, u_int toklen,
struct fuzzy_char *cs, u_int ncs, int fold, int prefix, int suffix,
int *score, char *matched)
{
u_int start, end, i, j, best = 0;
int ok, found = 0, value, bestscore = 0;
if (toklen == 0 || toklen > ncs)
return (0);
if (prefix && suffix) {
if (toklen != ncs)
return (0);
start = 0;
end = 1;
} else if (prefix) {
start = 0;
end = 1;
} else if (suffix) {
start = ncs - toklen;
end = start + 1;
} else {
start = 0;
end = ncs - toklen + 1;
}
for (i = start; i < end; i++) {
ok = 1;
for (j = 0; j < toklen; j++) {
if (!fuzzy_char_equal(&tok[j], &cs[i + j].ud, fold)) {
ok = 0;
break;
}
}
if (!ok)
continue;
value = fuzzy_score_exact(i, toklen, ncs, cs, prefix, suffix);
if (!found || value > bestscore) {
found = 1;
best = i;
bestscore = value;
}
}
if (!found)
return (0);
*score += bestscore;
if (matched != NULL) {
for (i = 0; i < toklen; i++)
matched[best + i] = 1;
}
return (1);
}
/* Parse one term. */
static int
fuzzy_parse_term(const char *start, const char *end, struct fuzzy_term *term)
{
memset(term, 0, sizeof *term);
if (start == end)
return (0);
if (*start == '!') {
term->inverse = 1;
start++;
}
if (start == end)
return (0);
if (*start == '\'') {
term->exact = 1;
start++;
} else if (*start == '^') {
term->exact = 1;
term->prefix = 1;
start++;
}
if (start == end)
return (0);
if (end[-1] == '$') {
term->exact = 1;
term->suffix = 1;
end--;
}
if (start == end)
return (0);
if (term->inverse)
term->exact = 1;
term->text = start;
term->len = end - start;
return (1);
}
/* Match one parsed term. */
static int
fuzzy_match_term(const struct fuzzy_term *term, struct utf8_data *tok,
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
{
u_int toklen;
int value = 0, matched_term;
toklen = fuzzy_decode(term->text, term->len, tok);
if (term->exact) {
matched_term = fuzzy_match_exact(tok, toklen, cs, ncs, fold,
term->prefix, term->suffix, &value,
term->inverse ? NULL : matched);
} else {
matched_term = fuzzy_match_fuzzy(tok, toklen, cs, ncs, fold,
&value, term->inverse ? NULL : matched);
}
if (term->inverse)
return (!matched_term);
if (!matched_term)
return (0);
*score += value;
return (1);
}
/* Match one AND group of terms. */
static int
fuzzy_match_group(const char *start, const char *end, struct utf8_data *tok,
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
{
const char *cp = start, *sp;
struct fuzzy_term term;
int any = 0;
*score = 0;
while (cp != end) {
while (cp != end && *cp == ' ')
cp++;
if (cp == end)
break;
sp = cp;
while (cp != end && *cp != ' ')
cp++;
if (!fuzzy_parse_term(sp, cp, &term))
return (0);
any = 1;
if (!fuzzy_match_term(&term, tok, cs, ncs, fold, score,
matched))
return (0);
}
return (any);
}
/*
* Fuzzy match pattern against text, which is drawn into a region of the given
* display width. Returns a bitstr_t of width bits with a bit set for each
* column occupied by a matched character, or NULL if there is no match. A
* higher returned score is better.
*/
bitstr_t *
fuzzy_match(const char *pattern, const char *text, u_int width, u_int *score)
{
struct fuzzy_char *cs;
char *matched = NULL, *best = NULL, *groupmatched;
struct utf8_data *tok;
bitstr_t *mask;
u_int ncs, i, j, column;
u_int widths[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
u_int start[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
u_int src[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
u_int vis[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
u_int wl, wc, wr, wa;
const char *cp, *sp;
int bestscore = 0, groupscore, found = 0, fold;
if (width == 0)
return (NULL);
/* An empty query matches everything, with nothing highlighted. */
for (cp = pattern; *cp == ' ' || *cp == '|'; cp++)
/* nothing */;
if (*cp == '\0') {
if (score != NULL)
*score = 0;
return (bit_alloc(width));
}
/* Smart-case: fold unless the pattern has an uppercase character. */
fold = 1;
for (cp = pattern; *cp != '\0'; cp++) {
if (*cp >= 'A' && *cp <= 'Z') {
fold = 0;
break;
}
}
/* Scan the text into visible characters. */
cs = fuzzy_scan(text, &ncs, widths);
matched = xcalloc(ncs == 0 ? 1 : ncs, sizeof *matched);
best = xcalloc(ncs == 0 ? 1 : ncs, sizeof *best);
tok = xreallocarray(NULL, strlen(pattern) + 1, sizeof *tok);
/* Match each |-separated group and keep the best-scoring one. */
cp = pattern;
while (*cp != '\0') {
while (*cp == ' ' || *cp == '|')
cp++;
if (*cp == '\0')
break;
sp = cp;
while (*cp != '\0' && *cp != '|')
cp++;
memset(matched, 0, ncs == 0 ? 1 : ncs);
groupmatched = matched;
if (fuzzy_match_group(sp, cp, tok, cs, ncs, fold,
&groupscore, groupmatched)) {
if (!found || groupscore > bestscore) {
found = 1;
bestscore = groupscore;
memcpy(best, matched, ncs == 0 ? 1 : ncs);
}
}
}
free(tok);
if (!found) {
free(best);
free(matched);
free(cs);
return (NULL);
}
/*
* Work out the trimmed widths and start columns of each alignment,
* mirroring format_draw_none.
*/
wl = widths[STYLE_ALIGN_LEFT];
wc = widths[STYLE_ALIGN_CENTRE];
wr = widths[STYLE_ALIGN_RIGHT];
wa = widths[STYLE_ALIGN_ABSOLUTE_CENTRE];
while (wl + wc + wr > width) {
if (wc > 0)
wc--;
else if (wr > 0)
wr--;
else
wl--;
}
if (wa > width)
wa = width;
start[STYLE_ALIGN_LEFT] = 0;
src[STYLE_ALIGN_LEFT] = 0;
vis[STYLE_ALIGN_LEFT] = wl;
start[STYLE_ALIGN_RIGHT] = width - wr;
src[STYLE_ALIGN_RIGHT] = widths[STYLE_ALIGN_RIGHT] - wr;
vis[STYLE_ALIGN_RIGHT] = wr;
start[STYLE_ALIGN_CENTRE] =
wl + ((width - wr) - wl) / 2 - wc / 2;
src[STYLE_ALIGN_CENTRE] = widths[STYLE_ALIGN_CENTRE] / 2 - wc / 2;
vis[STYLE_ALIGN_CENTRE] = wc;
start[STYLE_ALIGN_ABSOLUTE_CENTRE] = (width - wa) / 2;
src[STYLE_ALIGN_ABSOLUTE_CENTRE] = 0;
vis[STYLE_ALIGN_ABSOLUTE_CENTRE] = wa;
/* Set a bit for each column of each matched character. */
mask = bit_alloc(width);
for (i = 0; i < ncs; i++) {
if (!best[i])
continue;
if (fuzzy_column(&cs[i], start, src, vis, &column) != 0)
continue;
for (j = 0; j < cs[i].width && column + j < width; j++)
bit_set(mask, column + j);
}
free(best);
free(matched);
free(cs);
if (score != NULL)
*score = (bestscore < 0) ? 0 : (u_int)bestscore;
return (mask);
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: grid-reader.c,v 1.11 2026/07/29 17:42:56 nicm Exp $ */
/*
* Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.com>
@@ -54,11 +54,8 @@ grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all, int onemore)
px = gr->gd->sx;
else if (onemore)
px = grid_reader_line_length(gr);
else {
px = grid_reader_line_length(gr);
if (px != 0)
px--;
}
else
px = grid_line_limit(gr->gd, gr->cy);
if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) {
grid_reader_cursor_start_of_line(gr, 0);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: grid-view.c,v 1.39 2026/08/03 12:58:53 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -47,9 +47,9 @@ grid_view_set_cell(struct grid *gd, u_int px, u_int py,
/* Set padding. */
void
grid_view_set_padding(struct grid *gd, u_int px, u_int py)
grid_view_set_padding(struct grid *gd, u_int px, u_int py, int bg)
{
grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py));
grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py), bg);
}
/* Set cells. */

298
grid.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD$ */
/* $OpenBSD: grid.c,v 1.158 2026/09/01 12:49:49 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -18,6 +18,9 @@
#include <sys/types.h>
#ifdef __APPLE__
#include <assert.h>
#endif
#include <stdlib.h>
#include <string.h>
@@ -56,6 +59,42 @@ static const struct grid_cell_entry grid_cleared_entry = {
{ .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED
};
#ifdef __APPLE__
void
grid_check_is_clear(struct grid *gd)
{
struct grid_line *gl;
u_int yy, ny;
assert(gd != NULL);
if (gd->sy == 0) {
assert(gd->linedata == NULL);
return;
}
assert(gd->linedata != NULL);
ny = gd->hsize + gd->sy;
for (yy = 0; yy < ny; yy++) {
gl = &gd->linedata[yy];
assert(gl->celldata == NULL);
assert(gl->cellused == 0);
assert(gl->cellsize == 0);
assert(gl->extddata == NULL);
assert(gl->extdsize == 0);
assert(gl->flags == 0);
assert(gl->time == 0);
}
}
#else
void
grid_check_is_clear(__unused struct grid *gd)
{
}
#endif
/* Store cell in entry. */
static void
grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc,
@@ -86,7 +125,8 @@ grid_need_extended_cell(const struct grid_cell_entry *gce,
return (1);
if (gc->data.size > 1 || gc->data.width > 1)
return (1);
if ((gc->fg & COLOUR_FLAG_RGB) || (gc->bg & COLOUR_FLAG_RGB))
if ((gc->fg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) ||
(gc->bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)))
return (1);
if (gc->us != 8) /* only supports 256 or RGB */
return (1);
@@ -193,6 +233,25 @@ grid_get_line(struct grid *gd, u_int line)
return (&gd->linedata[line]);
}
/* Get line time. */
time_t
grid_line_time(const struct grid_line *gl)
{
if (gl->time == 0)
return (0);
return (start_time.tv_sec + gl->time - 1);
}
/* Set line time. */
static void
grid_line_set_time(struct grid_line *gl)
{
if (current_time == 0)
gl->time = 0;
else
gl->time = current_time - start_time.tv_sec + 1;
}
/* Adjust number of lines. */
void
grid_adjust_lines(struct grid *gd, u_int lines)
@@ -218,7 +277,7 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved)
if (bg != 8)
gee->bg = bg;
} else if (bg != 8) {
if (bg & COLOUR_FLAG_RGB) {
if (bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) {
grid_get_extended_cell(gl, gce, gce->flags);
gee = grid_extended_cell(gl, gce, &grid_cleared_cell);
gee->bg = bg;
@@ -286,13 +345,21 @@ grid_set_tab(struct grid_cell *gc, u_int width)
static void
grid_free_line(struct grid *gd, u_int py)
{
free(gd->linedata[py].celldata);
free(gd->linedata[py].extddata);
memset(&gd->linedata[py], 0, sizeof gd->linedata[py]);
struct grid_line *gl = &gd->linedata[py];
#ifdef __APPLE__
assert(gl->cellused <= gl->cellsize);
assert(gl->extdsize == 0 || gl->extddata != NULL);
assert(gl->cellsize == 0 || gl->celldata != NULL);
#endif
free(gl->celldata);
free(gl->extddata);
memset(gl, 0, sizeof *gl);
}
/* Free several lines. */
static void
void
grid_free_lines(struct grid *gd, u_int py, u_int ny)
{
u_int yy;
@@ -307,24 +374,21 @@ grid_create(u_int sx, u_int sy, u_int hlimit)
{
struct grid *gd;
gd = xmalloc(sizeof *gd);
gd = xcalloc(1, sizeof *gd);
gd->sx = sx;
gd->sy = sy;
if (hlimit != 0)
gd->flags = GRID_HISTORY;
else
gd->flags = 0;
gd->hscrolled = 0;
gd->hsize = 0;
gd->hlimit = hlimit;
if (gd->sy != 0)
gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata);
else
gd->linedata = NULL;
#ifdef __APPLE__
assert(gd->hsize == 0);
#endif
grid_check_is_clear(gd);
return (gd);
}
@@ -405,6 +469,7 @@ grid_collect_history(struct grid *gd, int all)
grid_trim_history(gd, ny);
gd->hsize -= ny;
gd->scroll_collected += ny;
if (gd->hscrolled > gd->hsize)
gd->hscrolled = gd->hsize;
}
@@ -440,8 +505,9 @@ grid_scroll_history(struct grid *gd, u_int bg)
gd->hscrolled++;
grid_compact_line(&gd->linedata[gd->hsize]);
gd->linedata[gd->hsize].time = current_time;
grid_line_set_time(&gd->linedata[gd->hsize]);
gd->hsize++;
gd->scroll_added++;
}
/* Clear the history. */
@@ -452,6 +518,7 @@ grid_clear_history(struct grid *gd)
gd->hscrolled = 0;
gd->hsize = 0;
gd->scroll_generation++;
gd->linedata = xreallocarray(gd->linedata, gd->sy,
sizeof *gd->linedata);
@@ -480,7 +547,7 @@ grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg)
/* Move the line into the history. */
memcpy(gl_history, gl_upper, sizeof *gl_history);
gl_history->time = current_time;
grid_line_set_time(gl_history);
/* Then move the region up and clear the bottom line. */
memmove(gl_upper, gl_upper + 1, (lower - upper) * sizeof *gl_upper);
@@ -489,6 +556,7 @@ grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg)
/* Move the history offset down over the line. */
gd->hscrolled++;
gd->hsize++;
gd->scroll_added++;
}
/* Expand line to fit to cell. */
@@ -614,9 +682,13 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
/* Set padding at position. */
void
grid_set_padding(struct grid *gd, u_int px, u_int py)
grid_set_padding(struct grid *gd, u_int px, u_int py, int bg)
{
grid_set_cell(gd, px, py, &grid_padding_cell);
struct grid_cell gc;
memcpy(&gc, &grid_padding_cell, sizeof gc);
gc.bg = bg;
grid_set_cell(gd, px, py, &gc);
}
/* Set cells at position. */
@@ -788,9 +860,16 @@ grid_string_cells_fg(const struct grid_cell *gc, int *values)
{
size_t n;
u_char r, g, b;
int c;
n = 0;
if (gc->fg & COLOUR_FLAG_256) {
if (gc->fg & COLOUR_FLAG_THEME) {
c = colour_theme_terminal_colour(gc->fg & 0xff);
if (c == 8)
values[n++] = 39;
else
values[n++] = c + 30;
} else if (gc->fg & COLOUR_FLAG_256) {
values[n++] = 38;
values[n++] = 5;
values[n++] = gc->fg & 0xff;
@@ -837,9 +916,16 @@ grid_string_cells_bg(const struct grid_cell *gc, int *values)
{
size_t n;
u_char r, g, b;
int c;
n = 0;
if (gc->bg & COLOUR_FLAG_256) {
if (gc->bg & COLOUR_FLAG_THEME) {
c = colour_theme_terminal_colour(gc->bg & 0xff);
if (c == 8)
values[n++] = 49;
else
values[n++] = c + 40;
} else if (gc->bg & COLOUR_FLAG_256) {
values[n++] = 48;
values[n++] = 5;
values[n++] = gc->bg & 0xff;
@@ -886,9 +972,19 @@ grid_string_cells_us(const struct grid_cell *gc, int *values)
{
size_t n;
u_char r, g, b;
int c;
n = 0;
if (gc->us & COLOUR_FLAG_256) {
if (gc->us & COLOUR_FLAG_THEME) {
c = colour_theme_terminal_colour(gc->us & 0xff);
if (c == 8)
values[n++] = 59;
else {
values[n++] = 58;
values[n++] = 5;
values[n++] = c;
}
} else if (gc->us & COLOUR_FLAG_256) {
values[n++] = 58;
values[n++] = 5;
values[n++] = gc->us & 0xff;
@@ -1510,6 +1606,7 @@ grid_reflow(struct grid *gd, u_int sx)
free(gd->linedata);
gd->linedata = target->linedata;
free(target);
gd->scroll_generation++;
}
/* Convert to position based on wrapped lines. */
@@ -1538,7 +1635,7 @@ grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy)
void
grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
{
u_int yy, ay = 0;
u_int yy, ay = 0, ey = gd->hsize + gd->sy - 1;
for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) {
if (ay == wy)
@@ -1552,7 +1649,7 @@ grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
* until we find the end or the line now containing wx.
*/
if (wx == UINT_MAX) {
while (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
while (yy < ey && gd->linedata[yy].flags & GRID_LINE_WRAPPED)
yy++;
wx = gd->linedata[yy].cellused;
} else {
@@ -1588,26 +1685,155 @@ grid_line_length(struct grid *gd, u_int py)
return (px);
}
/* Get last position on line, not including padding. */
u_int
grid_line_limit(struct grid *gd, u_int py)
{
struct grid_cell gc;
u_int px;
px = grid_line_length(gd, py);
if (px == 0)
return (0);
px--;
while (px > 0) {
grid_get_cell(gd, px, py, &gc);
if (~gc.flags & GRID_FLAG_PADDING)
break;
px--;
}
return (px);
}
/* Check if character is in set. */
int
grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
{
struct grid_cell gc, tmp_gc;
u_int pxx;
int has_tab, has_space;
has_tab = (strchr(set, '\t') != NULL);
has_space = (strchr(set, ' ') != NULL);
grid_get_cell(gd, px, py, &gc);
if (strchr(set, '\t')) {
if (gc.flags & GRID_FLAG_PADDING) {
pxx = px;
do
grid_get_cell(gd, --pxx, py, &tmp_gc);
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
if (tmp_gc.flags & GRID_FLAG_TAB)
return (tmp_gc.data.width - (px - pxx));
} else if (gc.flags & GRID_FLAG_TAB)
return (gc.data.width);
}
if (gc.flags & GRID_FLAG_PADDING)
if (gc.flags & GRID_FLAG_PADDING) {
if (!has_tab && !has_space)
return (0);
pxx = px;
do
grid_get_cell(gd, --pxx, py, &tmp_gc);
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
if (((has_tab || has_space) &&
(tmp_gc.flags & GRID_FLAG_TAB)) ||
(has_space && utf8_has_whitespace(&tmp_gc.data)))
return (tmp_gc.data.width - (px - pxx));
return (0);
}
if ((has_tab || has_space) && (gc.flags & GRID_FLAG_TAB))
return (gc.data.width);
if (has_space && utf8_has_whitespace(&gc.data))
return (gc.data.width == 0 ? 1 : gc.data.width);
return (utf8_cstrhas(set, &gc.data));
}
/* Line flags to string. */
const char *
grid_line_flags_string(int flags)
{
static char s[128];
*s = '\0';
if (flags & GRID_LINE_WRAPPED)
strlcat(s, "WRAPPED,", sizeof s);
if (flags & GRID_LINE_EXTENDED)
strlcat(s, "EXTENDED,", sizeof s);
if (flags & GRID_LINE_DEAD)
strlcat(s, "DEAD,", sizeof s);
if (flags & GRID_LINE_START_PROMPT)
strlcat(s, "START_PROMPT,", sizeof s);
if (flags & GRID_LINE_SECOND_PROMPT)
strlcat(s, "SECOND_PROMPT,", sizeof s);
if (flags & GRID_LINE_START_COMMAND)
strlcat(s, "START_COMMAND,", sizeof s);
if (flags & GRID_LINE_START_OUTPUT)
strlcat(s, "START_OUTPUT,", sizeof s);
if (flags & GRID_LINE_END_OUTPUT)
strlcat(s, "END_OUTPUT,", sizeof s);
if (flags & GRID_LINE_HYPERLINK)
strlcat(s, "HYPERLINK,", sizeof s);
if (*s == '\0')
return ("NONE");
s[strlen(s) - 1] = '\0';
return (s);
}
/* Cell flags to string. */
const char *
grid_cell_flags_string(int flags)
{
static char s[128];
*s = '\0';
if (flags & GRID_FLAG_FG256)
strlcat(s, "FG256,", sizeof s);
if (flags & GRID_FLAG_BG256)
strlcat(s, "BG256,", sizeof s);
if (flags & GRID_FLAG_PADDING)
strlcat(s, "PADDING,", sizeof s);
if (flags & GRID_FLAG_EXTENDED)
strlcat(s, "EXTENDED,", sizeof s);
if (flags & GRID_FLAG_SELECTED)
strlcat(s, "SELECTED,", sizeof s);
if (flags & GRID_FLAG_CLEARED)
strlcat(s, "CLEARED,", sizeof s);
if (flags & GRID_FLAG_TAB)
strlcat(s, "TAB,", sizeof s);
if (flags & GRID_FLAG_NOPALETTE)
strlcat(s, "NOPALETTE,", sizeof s);
if (*s == '\0')
return ("NONE");
s[strlen(s) - 1] = '\0';
return (s);
}
/* Cell attributes to string. */
const char *
grid_cell_attr_string(int attr)
{
static char s[256];
*s = '\0';
if (attr & GRID_ATTR_CHARSET)
strlcat(s, "CHARSET,", sizeof s);
if (attr & GRID_ATTR_BRIGHT)
strlcat(s, "BRIGHT,", sizeof s);
if (attr & GRID_ATTR_DIM)
strlcat(s, "DIM,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE)
strlcat(s, "UNDERSCORE,", sizeof s);
if (attr & GRID_ATTR_BLINK)
strlcat(s, "BLINK,", sizeof s);
if (attr & GRID_ATTR_REVERSE)
strlcat(s, "REVERSE,", sizeof s);
if (attr & GRID_ATTR_HIDDEN)
strlcat(s, "HIDDEN,", sizeof s);
if (attr & GRID_ATTR_ITALICS)
strlcat(s, "ITALICS,", sizeof s);
if (attr & GRID_ATTR_STRIKETHROUGH)
strlcat(s, "STRIKETHROUGH,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE_2)
strlcat(s, "UNDERSCORE_2,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE_3)
strlcat(s, "UNDERSCORE_3,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE_4)
strlcat(s, "UNDERSCORE_4,", sizeof s);
if (attr & GRID_ATTR_UNDERSCORE_5)
strlcat(s, "UNDERSCORE_5,", sizeof s);
if (attr & GRID_ATTR_OVERLINE)
strlcat(s, "OVERLINE,", sizeof s);
if (*s == '\0')
return ("NONE");
s[strlen(s) - 1] = '\0';
return (s);
}

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