Compare commits

..

83 Commits

Author SHA1 Message Date
Nicholas Marriott
b3f8598903 Merge branch 'master' into local_windows_panes
# Conflicts:
#	cmd-switch-client.c
2026-09-01 20:23:17 +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
1b86039df3 Merge branch 'master' into local_windows_panes 2026-08-17 13:45:26 +01:00
Nicholas Marriott
ff9c89da0c Merge branch 'master' into local_windows_panes 2026-07-21 08:36:11 +01:00
Nicholas Marriott
fde879c347 Add local panes as well. 2026-07-17 16:30:02 +01:00
Nicholas Marriott
38808509d5 Add support for local windows for a client. 2026-07-17 15:48:41 +01:00
86 changed files with 3984 additions and 6482 deletions

View File

@@ -17,8 +17,9 @@ 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'

View File

@@ -17,7 +17,7 @@ jobs:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
if: github.repository == 'tmux/tmux'
timeout-minutes: 45
timeout-minutes: 300
strategy:
fail-fast: false
@@ -38,7 +38,9 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: dependencies
if: runner.os == 'Linux'
@@ -81,7 +83,7 @@ jobs:
- name: logs
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: regress-logs-${{ matrix.name }}
path: regress/logs/*.log

View File

@@ -1,715 +0,0 @@
# Image Redraw and Damage Plan
## Purpose
Make image redraws correct and efficient for tiled panes, floating panes,
scrolling, copy-mode selection, pane borders, pane status lines, scrollbars,
and clients using different output protocols.
The intended model is retained: the visible screen is a scene of horizontal
cell spans. A change damages cell-aligned regions, and the compositor redraws
only scene spans intersecting those regions.
Synchronized output may hide unavoidable intermediate terminal updates, but
it must not be required for correctness.
## Current state and recovery point
The last clean, pushed commit on `4902-image-support` is:
```
ec9859b1 sixel: preserve image aspect across client geometries
```
That commit already contains the important persistent model:
- sparse image placements represented by spans attached to grid lines;
- input-protocol semantics and placement ordering;
- source coordinates for cropping placement spans;
- movement, copying, duplication, and removal of image spans with grid data;
- per-client output backends for Kitty, SIXEL, and fallback rendering; and
- cached image/rendering data.
The current uncommitted changes touch eight files and mix several attempted
optimizations. They are known to damage SIXEL in Mintty and cause unnecessary
redraws with floating panes. Do not commit them as the solution.
Before discarding them, save one patch outside the branch for reference:
```
git diff -- image.c regress/image-support.sh screen-redraw.c screen-write.c \
server-client.c tmux.h tty.c window-copy.c \
> /tmp/4902-image-redraw-experiment.patch
```
Then restore those tracked files to `ec9859b1` before beginning the new work.
Do this only after the user explicitly approves the rollback.
### Ideas worth retaining, but reimplementing separately
- Redraw damage should be restricted to scene-visible spans.
- Image-related selection tests are useful.
- Floating-pane and pane-status regression scenarios are useful.
- Window chrome must be emitted after pane image/text composition whenever
the same damaged region contains both.
- A placement's complete canvas may need to be represented even where its
pixels are transparent; visibility and alpha must then be tested while
compositing rather than by losing the placement geometry.
- Native scrolling should be selected according to the affected region and
backend behaviour.
### Changes not worth retaining in their present form
- Promoting an obscured-pane update to `PANE_REDRAW` and then sometimes to
`CLIENT_REDRAWWINDOW`.
- Repairing images directly from individual `tty_cmd_*` text functions.
- Separate ad-hoc redraw paths for images, selection, scrollbars, borders,
and floating panes.
- The current `TTY_CTX_IMAGE_SCROLL`, `TTY_CTX_PANE_FULL_WIDTH`, and
`CLIENT_REDRAWSCROLLBARS` plumbing as a combined patch.
- Assuming an unchanged pane-status string does not need physical redraw
after some other drawing operation has covered its cells.
## Required invariants
These rules should be written down in code comments and enforced throughout
the implementation.
1. **Input determines semantics.** Whether text removes an image layer, lies
above it, or lies below it is determined by the protocol received from the
application. It must not depend on whether the outer client uses Kitty,
SIXEL, or fallback output.
2. **Output backends encode a scene.** An output backend may choose different
escape sequences, caches, or cropping, but it must render the same logical
result.
3. **One compositor owns repair.** No output path may independently erase and
reconstruct image cells without going through the same scene ordering.
4. **Damage is regional.** Ordinary content changes do not become pane-wide
or window-wide redraws. Full redraw remains appropriate for attachment,
resize, geometry changes, terminal reset, or invalid scene geometry.
5. **Damage uses cell coordinates.** Pixel offsets are handled when an image
backend maps the damaged cells to a source-pixel crop.
6. **Logical state changes before physical output.** Grid text and image spans
are moved, removed, or inserted first. Rendering then reads the completed
logical state.
7. **Chrome is part of composition.** Pane borders, pane status lines,
scrollbars, menus, and floating panes must not be repaired independently in
an order that allows image output to cover them afterward.
8. **No-image behaviour remains unchanged.** When a damaged region does not
intersect image data or graphical overlays, existing fast text output and
native scrolling should remain available.
## Established image semantics
The image semantics present at the `ec9859b1` recovery point are a
compatibility requirement. They resulted from the review discussion in PR
#5445, particularly the temporal-versus-positional model described in
[Theory and Practice of Sprixels](https://nick-black.com/dankwiki/index.php?title=Theory_and_Practice_of_Sprixels#Transparency_and_z-ordering),
and were accepted after commit `a4abd616`, which added the corresponding
manual visual checks.
- SIXEL input is temporal. Transparent pixels preserve what existed when the
SIXEL was emitted. A later text write annihilates the affected SIXEL cells;
the SIXEL does not remain logically behind that text.
- Kitty input is positional. Negative-z placements remain below the text plane;
nonnegative placements remain above it. Text writes do not convert or remove
Kitty placements merely because the output client uses SIXEL.
- Overlapping placements remain separate ordered layers. Transparent pixels in
an upper placement reveal retained lower placements or text.
- Input protocol, z-index, and placement order determine the logical scene. The
attached client's output protocol only determines how that scene is encoded.
- Redraw, damage, scrolling, copy mode, and backend conversion must preserve
these rules exactly.
The manual checks introduced by `a4abd616` and the corresponding regression
cases are semantic compatibility tests, not merely visual diagnostics.
## Coordinate spaces
Keep conversions explicit. Do not pass ambiguous `x` and `y` values between
layers.
- **Grid coordinates:** include history and belong to a pane's `struct grid`.
- **Pane coordinates:** visible cells inside a pane.
- **Window coordinates:** include pane offsets and floating-pane positions.
- **Client scene coordinates:** window coordinates clipped by the client's
viewport (`ox`, `oy`, `sx`, and `sy`).
- **Terminal coordinates:** include the client status-line offset and any
terminal viewport offset.
- **Image source coordinates:** identify cell rows/columns within a placement;
the backend maps these to source pixels using terminal cell geometry and
placement pixel offsets.
Store damage in window coordinates. Translate and clip it separately for each
client when drawing, because clients may have different viewports, status-line
positions, cell geometries, and output backends.
## Compartmentalization
Keep image implementation details in the existing image files. The rest of
tmux should know only that some scene cells require image-aware composition.
### `image.c`
Own all protocol-independent image state and composition policy:
- placements, spans, input protocol, z-index, serial ordering, and alpha;
- applying input semantics when text overwrites image cells;
- testing whether a grid rectangle intersects image placements;
- mapping a damaged cell rectangle to ordered image fragments;
- deciding which logical image layers are below or above the text plane;
- generic backend dispatch; and
- public image helpers used by the redraw subsystem.
### `image-sixel.c`
Own only SIXEL-specific output details:
- cached quantized/indexed representations;
- mapping cell crops to pixel and six-pixel-band crops;
- emitting SIXEL fragments;
- alpha and palette encoding; and
- backend-specific reset, erase, and scrolling capabilities.
### `image-kitty.c`
Own only Kitty-specific output details:
- client-side transmission and placement caches;
- placement creation, clipping, movement, and deletion;
- Kitty IDs and geometry; and
- backend-specific reset and scrolling behaviour.
### Other tmux files
- `screen-redraw.c` owns scene construction, damage clipping, and the ordering
of pane content versus window chrome. It calls generic `image.c` helpers and
contains no Kitty or SIXEL protocol logic.
- `screen-write.c` updates logical grid state and reports pane/window damage.
It must not render image fragments or select an output backend.
- `tty.c` owns terminal text and scrolling primitives. It receives an already
selected native-scroll operation; it must not reconstruct image layers or
call image repair after individual characters.
- `server-client.c` schedules and drains per-client damage. It must not inspect
image placements or output protocols.
- `window-copy.c` reports copy-mode damage and copies logical image state using
generic image helpers; it must not render images itself.
- `tmux.h` exposes only the minimum generic structures and function prototypes
required across those boundaries.
Prefer one generic call from `screen-redraw.c` into `image.c` over `ENABLE_IMAGES`
branches scattered through tty commands. A build without image support should
retain the existing redraw path with minimal conditional code.
## Damage representation
Add a small regional damage abstraction owned by the redraw subsystem. Do not
add per-cell queues or enlarge `struct grid_cell`.
A damage entry needs:
```
x, y, sx, sy window-cell rectangle
reason content, scroll exposure, pane move, chrome, and so on
```
The exact names should follow tmux style. A short list or dynamic array per
client is sufficient. When a new rectangle is added:
- clip it to the window;
- discard empty rectangles;
- merge overlapping or directly adjacent rectangles where that does not make
the result substantially larger;
- cap the list at a modest number; above the cap, replace it with their union;
- promote to a full redraw only if geometry is invalid or the union covers
most of the client scene.
A shared window damage list must not be cleared after the first client draws.
Either append translated window rectangles to every affected client, or give
damage events monotonically increasing sequence numbers so each client can
consume them independently. The simpler initial implementation is to append
window-coordinate rectangles to each client currently displaying the window.
Suggested public operations are conceptually:
```
redraw_damage_window(window, x, y, sx, sy, reason)
redraw_damage_pane(window_pane, x, y, sx, sy, reason)
redraw_damage_client(client, x, y, sx, sy, reason)
```
Do not expose image-backend details through these calls.
## Regional scene composition
Extend `screen-redraw.c` so an existing `redraw_scene` can be drawn through a
set of clipping rectangles.
For every damaged line:
1. Intersect the damage rectangle with that line's scene spans.
2. Split only at damage boundaries; do not rebuild the complete scene.
3. Draw intersecting pane content in logical layer order.
4. Draw window chrome intersecting the same rectangle last.
For a pane span, the scene has already selected the topmost visible pane at
that position. Composition inside that pane should then be explicit:
1. image placements below the text plane, such as negative Kitty z-indexes;
2. the text plane, including the requested copy-mode style;
3. surviving temporal SIXEL placements and images above the text plane;
4. pane borders, pane status, scrollbars, menus, and other chrome.
The exact placement order is already represented by `struct image_placement`
and its input protocol, z-index, and serial. Do not derive it from the client's
output backend.
Only the topmost pane content is normally needed because panes are opaque.
Underlying pane content is retained in its own grid and becomes visible when a
floating pane moves, at which time the old floating-pane rectangle is damaged
and recomposed.
### Image fragments
An `image_span` is placement metadata, not a separately stored bitmap. For an
intersection, compute the corresponding source-cell rectangle and ask the
backend to emit that crop from its cached rendering.
- Do not rerun quantization or dithering for every small damage rectangle.
- SIXEL output operates in six-pixel vertical bands. Expand or clip the
backend operation as required, then ensure any additionally touched cells
are included in the same composition pass.
- A transparent top image requires lower image layers to be drawn first.
- Kitty placements may be deleted, clipped, or replaced only within the
damaged placement region.
## Fast text writes
Do not route every ordinary character through a deferred full compositor.
For an application text write:
1. Apply input-protocol damage rules to the logical image spans. For example,
text written after a SIXEL image removes the affected SIXEL coverage.
2. Test whether the affected cells still intersect any image layer or scene
overlay requiring composition.
3. If not, retain the existing direct `tty_cmd_cell`/`tty_cmd_cells` path.
4. If they do, suppress conflicting direct terminal repair and enqueue only
those cells for regional composition.
This decision belongs above the backend-specific tty output functions. Avoid
callbacks such as `image_draw_after_text()` from several `tty_cmd_*` functions;
they reproduce ordering logic in multiple places.
## Selection and copy mode
Copy mode must not mutate the application's logical image placements.
- Treat selection as a temporary tmux UI style on the text plane.
- Follow the chosen policy that selection highlighting does not overwrite
image-covered cells.
- Mouse motion should damage only cells whose selection state actually
changed.
- If changed selection cells do not intersect visible images, draw them using
the normal text fast path.
- If they intersect an image canvas, compose those cells once; do not erase a
whole image or pane.
- Entering or leaving copy mode should damage the visible pane rectangle once,
not repeatedly for every unchanged mouse event.
Copy-mode image spans should be copied before its text view is composed, but
this should be implemented through the same logical state and damage API, not
through special tty repair calls.
## Floating panes
### Focus changes
Changing the active pane should damage only cells whose appearance changes:
- affected pane borders;
- pane status cells whose style or contents change; and
- any active-pane indicators.
It must not redraw pane interiors or retransmit unrelated SIXEL images.
### Moving or resizing a floating pane
For an old rectangle `A` and new rectangle `B`:
- invalidate the scene geometry;
- damage `A union B` (represented as a few rectangles rather than necessarily
one large bounding box);
- rebuild the scene because pane ownership changed;
- recompose only spans intersecting those rectangles.
The old rectangle restores tiled panes and any image fragments that were
previously hidden. The new rectangle draws the floating pane and its chrome.
Multiple overlapping floating panes follow their window z-order.
## Scrolling
Implement scrolling in three stages. Do not begin with the most complicated
native-scroll optimization.
### Stage 1: correct regional redraw
1. Move grid text and image spans using the existing grid operations.
2. Record the pane scroll rectangle and newly exposed rows.
3. Recompose that rectangle from the completed logical scene.
This may still retransmit more image data than desired, but it establishes a
correct regional baseline without pane- or window-wide redraws.
### Stage 2: native scrolling without obstructions
Use the terminal's native scroll operation only when:
- the backend/terminal is known to scroll its image pixels consistently;
- the source and destination rectangle belong to the same visible pane;
- no floating pane, menu, overlay, pane border/status, or reserved scrollbar
intersects it;
- required vertical and horizontal margins are available and reliable; and
- existing BCE and terminal-size checks permit the operation.
After native scrolling, compose only newly exposed rows and any protocol-sized
boundary expansion.
### Stage 3: mixed native scrolling around obstructions
Derive safe rectangles from visible scene spans:
- a destination cell is native-scrollable only when its source cell and
destination cell are both visible parts of the same pane;
- coalesce equal horizontal ranges across adjacent rows into rectangles;
- native-scroll those maximal rectangles;
- recompose cells crossing an obstruction boundary and newly exposed cells.
With one floating pane, left and right strips can often scroll over the full
height. Above and below it, cells whose source crosses behind the floating pane
must be recomposed rather than recovered from terminal pixels.
As a simpler optional strategy, native-scroll the full tiled-pane rectangle
and recompose the floating pane's old/current rectangles afterward. This is
correct if all disturbed cells are marked, but may visibly flash without
synchronized output. Prefer safe-rectangle scrolling once correctness is
established.
If rectangle decomposition becomes too fragmented, fall back to regional
composition. It is an optimization, never a separate correctness path.
## Redraw scheduling
Do not turn queued regional damage into `CLIENT_REDRAWWINDOW` merely because
the tty output buffer is nonempty. Preserve the damage list and process it
after output drains.
Use full redraw only for:
- initial attachment;
- client or terminal geometry changes;
- a terminal reset or invalid backend cache;
- layout/viewport changes where rebuilding and clipping regions is not yet
safe; or
- a damage union large enough that full redraw is cheaper.
Focus changes, selection changes, ordinary scrolling, and moving one floating
pane should remain regional.
## Debugging instrumentation
Add temporary debug logging before optimizing:
- damage reason and window rectangle;
- client clipping and coordinate translation;
- scene generation used;
- native-scroll rectangles selected;
- spans recomposed in each layer phase; and
- image fragments emitted by each backend.
The log should make it possible to answer, for every SIXEL retransmission,
which damage rectangle required it. Remove excessively noisy instrumentation
before the final commit or keep it behind normal verbose logging.
## Tests
Build the tests before each optimization and keep each test focused on one
invariant.
### Automated regressions
1. **No-image baseline:** ordinary output, scrolling, copy mode, floating pane
focus, borders, status lines, and scrollbars behave unchanged.
2. **Focus only:** changing focus with a SIXEL elsewhere emits no SIXEL DCS and
updates only the affected border/status cells.
3. **Selection outside image:** emits no image output.
4. **Selection crossing image:** the image remains intact during and after the
drag; only changed non-image cells are highlighted.
5. **Unobstructed scroll:** logical image spans move with text; where native
scrolling is supported, no complete SIXEL retransmission occurs.
6. **Scroll with floating pane:** both pane-status lines, borders, scrollbar,
floating-pane contents, and visible image fragments remain correct.
7. **Move floating pane:** old and new rectangles are restored; unrelated image
regions are not emitted.
8. **Multiple floating panes:** overlapping z-order remains correct while the
top pane moves.
9. **Transparent placement:** lower images/text show through correctly, and
moving/selection does not treat transparency as absent placement geometry.
10. **Cross-backend clients:** the same input scene produces equivalent results
on Kitty, SIXEL, and fallback clients attached simultaneously.
11. **Pane status top/bottom:** unchanged status contents are physically
restored if their cells were damaged.
12. **Scrollbar reserved/overlay:** scroll thumb and track remain present and
images do not overwrite them.
Where possible, capture raw terminal output and assert both presence and
absence of DCS/APC sequences. `capture-pane` alone cannot prove physical output
ordering because it observes logical grid contents rather than terminal image
pixels.
### Manual matrix
Test at least:
- Microsoft Terminal with SIXEL;
- Mintty with SIXEL;
- WezTerm with SIXEL;
- Kitty with Kitty output;
- a Kitty client attached to a session created from a SIXEL client and the
reverse; and
- a terminal without graphical image support.
For each, test scrolling by application output and mouse wheel, selecting text,
entering/leaving copy mode, changing focus, opening/closing/moving a floating
pane, pane status at top and bottom, and scrollbars.
## Implementation steps and commit boundaries
This must not be implemented as one monolithic change. Each numbered step is a
separate commit, review, and testing gate. Do not begin the next step until the
current step passes its focused tests. If a step exposes a pre-existing bug,
record it before expanding the patch.
### Step 0: archive the experiment and recover the baseline
**Goal:** Begin development from known source rather than repairing the mixed
experimental diff.
**Work:**
- Preserve the current diff and this plan on a clearly named WIP branch.
- Return `4902-image-support` to clean commit `ec9859b1`.
- Build with and without image support.
- Run the existing image regression and ordinary tmux regression suite.
**Gate:** The branch is clean and existing behaviour is reproduced. No product
code is changed in this step.
### Step 1: add focused regressions and tracing
**Goal:** Establish observable failures before changing redraw behaviour.
**Primary files:** `regress/image-support.sh`, temporary debug logging in
`screen-redraw.c` if necessary.
**Work:** Add independent tests for focus-only changes, selection crossing an
image, unobstructed scrolling, scrolling beneath a floating pane, moving a
floating pane, both pane-status lines, and scrollbars. Raw terminal output must
be used where a test needs to prove that SIXEL or Kitty data was or was not
emitted.
**Gate:** Each test either passes on the baseline or has a documented expected
failure. Tests must not depend on timing races or visual screenshots. The
existing edge-case checks introduced by `a4abd616` must pass unchanged on the
recovery baseline and establish the semantic compatibility result expected by
later gates.
**Not included:** No redraw implementation changes.
### Step 2: introduce regional damage scheduling
**Goal:** Represent and preserve small window-coordinate damage rectangles
without changing the rendered result.
**Primary files:** `screen-redraw.c`, `server-client.c`, minimal declarations in
`tmux.h`.
**Work:** Add per-client damage storage, clipping, merging, a complexity cap,
and verbose logging. Existing callers may initially convert the damage to the
same redraw they used before. Pending tty output must retain the region rather
than promote it automatically to a full-window redraw.
**Gate:** No-image and image output remain visually and byte-for-byte equivalent
where practical. Unit-style tests verify rectangle clipping and merging.
**Not included:** No image-layer traversal, selection changes, floating-pane
optimization, or native scrolling.
### Step 3: add a generic image-region composition API
**Goal:** Give the scene compositor one protocol-independent way to render the
image layers intersecting a cell rectangle.
**Primary files:** `image.c`, `image-sixel.c`, `image-kitty.c`, with only generic
prototypes in `tmux.h`.
**Work:**
- In `image.c`, intersect damage with placement spans and preserve logical
input/z/serial order.
- In `image-sixel.c`, emit a crop from cached quantized data and handle SIXEL
band alignment.
- In `image-kitty.c`, update/delete/place only the intersecting cached
placements.
- Define clearly separate below-text and above-text passes.
**Gate:** Rendering a whole image as adjacent damaged rectangles produces the
same final scene as rendering it in one operation. Test transparent and
overlapping placements on both output backends. The `a4abd616` semantic
compatibility checks must continue to pass unchanged.
**Not included:** No changes to individual tty text commands and no native
scroll optimization.
### Step 4: clip scene composition to damage rectangles
**Goal:** Make `screen-redraw.c` repaint only scene spans intersecting queued
damage.
**Primary files:** `screen-redraw.c`; generic calls into `image.c`.
**Work:** For each damaged line, intersect existing redraw spans, compose the
selected pane's below-text images, text, and above-text images, then draw
intersecting chrome last. Full redraw continues to use the same compositor with
a full-client damage rectangle.
**Gate:** Full and tiled panes redraw correctly; pane borders, pane status,
scrollbars, menus, and images have deterministic output order. No floating-pane
movement or scroll optimization is added yet.
### Step 5: route text and selection damage through the compositor
**Goal:** Repair only cells where text or copy-mode selection interacts with
image layers.
**Primary files:** `screen-write.c`, `window-copy.c`, `screen-redraw.c`, and
logical helpers in `image.c`.
**Work:** Apply input semantics to logical spans first. Preserve direct tty text
output when no image placement intersects the cells. Otherwise enqueue those
cells for composition. Selection changes damage only cells whose selection
state changed and follow the policy of not overwriting image-covered cells.
**Gate:** Dragging selection across an image never damages it, entering/leaving
copy mode restores the same scene, and selection outside images emits no image
data. The `a4abd616` semantic compatibility checks must continue to pass
unchanged.
**Not included:** Do not add image callbacks to `tty_cmd_cell`,
`tty_cmd_cells`, or `tty_cmd_clearcharacter`.
### Step 6: make floating-pane changes regional
**Goal:** Focus, movement, resize, open, and close operations damage only the
cells whose scene ownership or chrome changed.
**Primary files:** floating-pane/window code that reports geometry changes,
plus `screen-redraw.c`. Image implementation remains in the image files.
**Work:** Focus changes enqueue border/status cells only. Movement and resize
enqueue the old and new pane rectangles and invalidate scene geometry. Regional
composition restores underlying panes/images in the old rectangle and draws
the floating pane in the new rectangle.
**Gate:** Changing focus emits no unrelated SIXEL/APC. Moving a floating pane
does not damage images, either pane-status line, borders, or scrollbars. Test
one and multiple overlapping floating panes.
### Step 7: implement correct regional scrolling without native optimization
**Goal:** Establish correct scrolling using logical state plus regional
composition.
**Primary files:** grid/image state helpers in `image.c`, damage reporting in
`screen-write.c`, composition in `screen-redraw.c`.
**Work:** Move text and image spans first, then damage the scroll rectangle and
compose it. Do not use special image repair from `tty.c`.
**Gate:** Application scrolling, mouse-wheel copy-mode scrolling, partial
scroll regions, scrollbars, and scrolling beneath floating panes are correct on
Kitty, Microsoft Terminal, Mintty, and WezTerm. This step may redraw more than
the final optimized version, but must not redraw outside the scroll rectangle.
The `a4abd616` semantic compatibility checks must continue to pass unchanged.
### Step 8: enable native scrolling for unobstructed rectangles
**Goal:** Avoid retransmitting image pixels when the terminal can safely move
them.
**Primary files:** scroll planning in `screen-redraw.c`, generic backend
capability in `image.c`, backend facts in `image-sixel.c` and `image-kitty.c`,
terminal primitive execution in `tty.c`.
**Work:** Select native scrolling only when source/destination cells belong to
the same unobstructed pane and terminal margins/backend behaviour permit it.
Compose newly exposed rows afterward. Retain Step 7 as the fallback.
**Gate:** Raw output proves the native scroll is used and a complete image is
not retransmitted. Final output must match Step 7 on every tested backend.
### Step 9: decompose scrolling around floating panes
**Goal:** Combine native scrolling of safe rectangles with composition of
obstructed and boundary rectangles.
**Primary files:** scroll planning and scene-span traversal in
`screen-redraw.c`; no new protocol logic outside the image files.
**Work:** Derive maximal rectangles where both source and destination are
visible cells of the same pane, coalesce adjacent equal ranges, natively scroll
them, and compose everything else. Enforce a fragmentation threshold that
falls back to Step 7.
**Gate:** Scrolling beneath one or several floating panes produces the same
final scene as Step 7, reduces emitted image data, and never loses pane status,
borders, scrollbars, or images.
### Step 10: cleanup, performance checks, and full regression
**Goal:** Remove superseded paths only after all replacements are proven.
**Work:** Remove obsolete redraw helpers and flags introduced or made unused by
the new path. Reduce temporary logging. Check memory growth, rectangle-list
bounds, output byte counts, and behaviour without image support. Run the entire
regression suite and the manual terminal matrix.
**Gate:** All completion criteria below are satisfied. Synchronized output may
improve appearance, but disabling it does not alter final contents. The
`a4abd616` semantic compatibility checks must continue to pass unchanged.
Do not squash these implementation steps during development. Keeping their
boundaries visible makes it possible to bisect correctness and performance
regressions. They can be reorganized later only after review.
## Completion criteria
The work is complete when:
- Mintty, Microsoft Terminal, WezTerm, and Kitty show no persistent damage;
- focus and selection do not retransmit unrelated images;
- floating-pane movement redraws only old/new affected regions;
- scrolling preserves images, pane borders, both tiled and floating pane
status lines, and scrollbars;
- input semantics produce the same composition on every output backend;
- no-image performance and behaviour remain unchanged;
- full regression tests pass; and
- synchronized output improves appearance but disabling it never changes the
final screen contents.

View File

@@ -5,8 +5,7 @@ CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c
# Distribution tarball options.
EXTRA_DIST = \
CHANGES README COPYING example_tmux.conf \
osdep-*.c mdoc2man.awk tmux.1 \
tools/generate-image-diacritics.sh
osdep-*.c mdoc2man.awk tmux.1
dist_EXTRA_tmux_SOURCES = compat/*.[ch]
# Preprocessor flags.
@@ -86,6 +85,7 @@ endif
# List of sources.
dist_tmux_SOURCES = \
active.c \
alerts.c \
arguments.c \
attributes.c \
@@ -247,9 +247,9 @@ if HAVE_UTF8PROC
nodist_tmux_SOURCES += compat/utf8proc.c
endif
# Enable image support.
if ENABLE_IMAGES
dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c
# Enable sixel support.
if ENABLE_SIXEL
dist_tmux_SOURCES += image.c image-sixel.c
endif
# Add bits for fuzzing if enabled.

737
active.c Normal file
View File

@@ -0,0 +1,737 @@
/* $OpenBSD$ */
/*
* 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 "tmux.h"
/* Local last-window stack entry. */
struct active_window_entry {
u_int window;
TAILQ_ENTRY(active_window_entry) entry;
};
/* Local last-window stack. */
TAILQ_HEAD(active_window_stack, active_window_entry);
/* Per-client and per-session local active window state. */
struct active_window {
struct client *client;
struct session *session;
u_int window;
struct active_window_stack lastw;
RB_ENTRY(active_window) entry;
};
RB_HEAD(active_windows, active_window);
/* Pane entry in a client-local last-pane stack. */
struct active_pane_entry {
struct window_pane *wp;
TAILQ_ENTRY(active_pane_entry) entry;
};
TAILQ_HEAD(active_pane_stack, active_pane_entry);
/* Per-client and per-window local active pane state. */
struct active_pane {
struct client *client;
struct window *window;
enum active_mode mode;
struct window_pane *pane;
struct active_pane_stack last_panes;
RB_ENTRY(active_pane) entry;
};
RB_HEAD(active_panes, active_pane);
/* Compare local active window states by client and session. */
static int
active_window_cmp(struct active_window *aw1, struct active_window *aw2)
{
uintptr_t c1 = (uintptr_t)aw1->client;
uintptr_t c2 = (uintptr_t)aw2->client;
uintptr_t s1 = (uintptr_t)aw1->session;
uintptr_t s2 = (uintptr_t)aw2->session;
if (c1 < c2)
return (-1);
if (c1 > c2)
return (1);
if (s1 < s2)
return (-1);
if (s1 > s2)
return (1);
return (0);
}
RB_GENERATE_STATIC(active_windows, active_window, entry, active_window_cmp);
/* Compare local active pane states by client and window. */
static int
active_pane_cmp(struct active_pane *ap1, struct active_pane *ap2)
{
uintptr_t c1 = (uintptr_t)ap1->client;
uintptr_t c2 = (uintptr_t)ap2->client;
uintptr_t w1 = (uintptr_t)ap1->window;
uintptr_t w2 = (uintptr_t)ap2->window;
if (c1 < c2)
return (-1);
if (c1 > c2)
return (1);
if (w1 < w2)
return (-1);
if (w1 > w2)
return (1);
return (0);
}
RB_GENERATE_STATIC(active_panes, active_pane, entry, active_pane_cmp);
/* All local active window states. */
static struct active_windows active_windows = RB_INITIALIZER(&active_windows);
/* All local active pane states. */
static struct active_panes active_panes = RB_INITIALIZER(&active_panes);
/* Find local active window state for a client and session. */
static struct active_window *
active_window_get(struct client *c, struct session *s)
{
struct active_window aw;
if (c == NULL || s == NULL)
return (NULL);
aw.client = c;
aw.session = s;
return (RB_FIND(active_windows, &active_windows, &aw));
}
/* Add local active window state for a client and session. */
static struct active_window *
active_window_add(struct client *c, struct session *s, struct winlink *wl)
{
struct active_window *aw;
aw = active_window_get(c, s);
if (aw == NULL) {
aw = xcalloc(1, sizeof *aw);
aw->client = c;
aw->session = s;
TAILQ_INIT(&aw->lastw);
RB_INSERT(active_windows, &active_windows, aw);
}
if (wl != NULL)
aw->window = wl->window->id;
return (aw);
}
/* Remove a window from a local last-window stack. */
static void
active_window_stack_remove(struct active_window_stack *stack, u_int window)
{
struct active_window_entry *awe, *awe1;
TAILQ_FOREACH_SAFE(awe, stack, entry, awe1) {
if (awe->window == window) {
TAILQ_REMOVE(stack, awe, entry);
free(awe);
}
}
}
/* Push a window onto a local last-window stack. */
static void
active_window_stack_push(struct active_window_stack *stack, u_int window)
{
struct active_window_entry *awe;
active_window_stack_remove(stack, window);
awe = xcalloc(1, sizeof *awe);
awe->window = window;
TAILQ_INSERT_HEAD(stack, awe, entry);
}
/* Free local active window state. */
static void
active_window_free(struct active_window *aw)
{
struct active_window_entry *awe;
while (!TAILQ_EMPTY(&aw->lastw)) {
awe = TAILQ_FIRST(&aw->lastw);
TAILQ_REMOVE(&aw->lastw, awe, entry);
free(awe);
}
RB_REMOVE(active_windows, &active_windows, aw);
free(aw);
}
/* Resolve local active window state to a valid winlink. */
static struct winlink *
active_window_resolve(struct active_window *aw)
{
struct winlink *wl, *next;
u_int window;
if (aw == NULL || aw->session->curw == NULL)
return (NULL);
wl = winlink_find_by_window_id(&aw->session->windows, aw->window);
if (wl != NULL)
return (wl);
while (!TAILQ_EMPTY(&aw->lastw)) {
window = TAILQ_FIRST(&aw->lastw)->window;
next = winlink_find_by_window_id(&aw->session->windows, window);
active_window_stack_remove(&aw->lastw, window);
if (next != NULL) {
aw->window = next->window->id;
return (next);
}
}
aw->window = aw->session->curw->window->id;
return (aw->session->curw);
}
/* Notify the server that a client's effective window changed. */
static void
active_window_changed(struct client *c, struct session *s, struct winlink *wl,
struct winlink *old)
{
struct event_payload *ep;
struct cmd_find_state fs;
if (wl == NULL || wl == old)
return;
if (options_get_number(global_options, "focus-events")) {
if (old != NULL)
window_update_focus(old->window);
window_update_focus(wl->window);
}
winlink_clear_flags(wl);
window_update_activity(wl->window);
tty_update_window_offset(wl->window);
if (c != NULL && c->session == s)
wl->window->latest = c;
ep = event_payload_create();
cmd_find_from_winlink(&fs, wl, 0);
event_payload_set_target(ep, &fs);
event_payload_set_client(ep, "client", c);
event_payload_set_session(ep, "session", s);
event_payload_set_window(ep, "window", wl->window);
event_payload_set_window(ep, "new_window", wl->window);
event_payload_set_int(ep, "window_index", wl->idx);
event_payload_set_int(ep, "new_window_index", wl->idx);
if (old != NULL) {
event_payload_set_window(ep, "old_window", old->window);
event_payload_set_int(ep, "old_window_index", old->idx);
}
events_fire("client-window-changed", ep);
if (c != NULL) {
c->flags |= CLIENT_REDRAWWINDOW|CLIENT_REDRAWSTATUS|
CLIENT_REDRAWBORDERS;
tty_update_client_offset(c);
status_update_cache(s);
}
recalculate_sizes();
}
/* Find local active pane state for a client and window. */
static struct active_pane *
active_pane_get(struct client *c, struct window *w)
{
struct active_pane ap;
if (c == NULL || w == NULL)
return (NULL);
ap.client = c;
ap.window = w;
return (RB_FIND(active_panes, &active_panes, &ap));
}
/* Add local active pane state for a client and window. */
static struct active_pane *
active_pane_add(struct client *c, struct window *w)
{
struct active_pane *ap;
ap = active_pane_get(c, w);
if (ap == NULL) {
ap = xcalloc(1, sizeof *ap);
ap->client = c;
ap->window = w;
TAILQ_INIT(&ap->last_panes);
RB_INSERT(active_panes, &active_panes, ap);
}
return (ap);
}
/* Remove a pane from a client-local last-pane stack. */
static void
active_pane_stack_remove(struct active_pane *ap,
struct window_pane *wp)
{
struct active_pane_entry *ape, *ape1;
TAILQ_FOREACH_SAFE(ape, &ap->last_panes, entry, ape1) {
if (ape->wp == wp) {
TAILQ_REMOVE(&ap->last_panes, ape, entry);
free(ape);
}
}
}
/* Push a pane onto a client-local last-pane stack. */
static void
active_pane_stack_push(struct active_pane *ap, struct window_pane *wp)
{
struct active_pane_entry *ape;
if (wp == NULL)
return;
active_pane_stack_remove(ap, wp);
ape = xcalloc(1, sizeof *ape);
ape->wp = wp;
TAILQ_INSERT_HEAD(&ap->last_panes, ape, entry);
}
/* Free local active pane state. */
static void
active_pane_free(struct active_pane *ap)
{
struct active_pane_entry *ape, *ape1;
RB_REMOVE(active_panes, &active_panes, ap);
TAILQ_FOREACH_SAFE(ape, &ap->last_panes, entry, ape1) {
TAILQ_REMOVE(&ap->last_panes, ape, entry);
free(ape);
}
free(ap);
}
/* Check whether a pane can be the effective pane for a window. */
static int
active_pane_valid(struct window *w, struct window_pane *wp)
{
if (w == NULL || wp == NULL || wp->window != w)
return (0);
if (!window_has_pane(w, wp))
return (0);
if (w->modal != NULL && wp != w->modal)
return (0);
return (1);
}
/* Get the shared pane for a window, accounting for modal panes. */
static struct window_pane *
active_pane_default(struct window *w)
{
if (w == NULL)
return (NULL);
if (w->modal != NULL)
return (w->modal);
return (w->active);
}
/* Return if a client has local window selection for a session. */
int
active_is_local_window(struct client *c, struct session *s)
{
return (active_window_get(c, s) != NULL);
}
/* Make a client use local or shared window selection for a session. */
void
active_set_local_window(struct client *c, struct session *s,
enum active_mode mode)
{
struct active_window *aw;
struct winlink *wl, *old;
if (c == NULL || s == NULL || s->curw == NULL)
return;
old = active_get_effective_winlink(c, s);
if (mode == ACTIVE_LOCAL) {
active_window_add(c, s, old);
if (c != NULL && c->session == s)
c->flags |= CLIENT_REDRAWSTATUS;
return;
}
aw = active_window_get(c, s);
if (aw != NULL) {
active_window_free(aw);
wl = active_get_effective_winlink(c, s);
active_window_changed(c, s, wl, old);
if (c != NULL && c->session == s)
c->flags |= CLIENT_REDRAWSTATUS;
}
}
/* Return the effective winlink for a client and session. */
struct winlink *
active_get_effective_winlink(struct client *c, struct session *s)
{
struct active_window *aw;
if (s == NULL)
return (NULL);
aw = active_window_get(c, s);
if (aw != NULL)
return (active_window_resolve(aw));
return (s->curw);
}
/* Return the effective window for a client and session. */
struct window *
active_get_effective_window(struct client *c, struct session *s)
{
struct winlink *wl;
wl = active_get_effective_winlink(c, s);
if (wl == NULL)
return (NULL);
return (wl->window);
}
/* Select a winlink for a client and session. */
int
active_select_window(struct client *c, struct session *s, struct winlink *wl)
{
struct active_window *aw;
struct winlink *old;
int changed;
if (wl == NULL)
return (-1);
aw = active_window_get(c, s);
if (aw == NULL)
return (session_set_current(s, wl));
old = active_window_resolve(aw);
if (old == wl)
return (1);
if (old != NULL)
active_window_stack_push(&aw->lastw, old->window->id);
active_window_stack_remove(&aw->lastw, wl->window->id);
aw->window = wl->window->id;
changed = (old != wl);
active_window_changed(c, s, wl, old);
return (changed ? 0 : 1);
}
/* Select a window by index. */
int
active_select_window_index(struct client *c, struct session *s, int idx)
{
struct winlink *wl;
wl = winlink_find_by_index(&s->windows, idx);
return (active_select_window(c, s, wl));
}
/* Select the next window. */
int
active_next_window(struct client *c, struct session *s, int alert)
{
struct winlink *wl;
wl = active_get_effective_winlink(c, s);
if (wl == NULL)
return (-1);
do {
wl = winlink_next(wl);
if (wl == NULL)
wl = RB_MIN(winlinks, &s->windows);
if (!alert || (wl->flags & WINLINK_ALERTFLAGS))
return (active_select_window(c, s, wl));
} while (wl != active_get_effective_winlink(c, s));
return (-1);
}
/* Select the previous window. */
int
active_previous_window(struct client *c, struct session *s, int alert)
{
struct winlink *wl;
wl = active_get_effective_winlink(c, s);
if (wl == NULL)
return (-1);
do {
wl = winlink_previous(wl);
if (wl == NULL)
wl = RB_MAX(winlinks, &s->windows);
if (!alert || (wl->flags & WINLINK_ALERTFLAGS))
return (active_select_window(c, s, wl));
} while (wl != active_get_effective_winlink(c, s));
return (-1);
}
/* Select the last window. */
int
active_last_window(struct client *c, struct session *s)
{
struct active_window *aw;
struct winlink *wl;
u_int window;
aw = active_window_get(c, s);
if (aw == NULL)
return (session_last(s));
while (!TAILQ_EMPTY(&aw->lastw)) {
window = TAILQ_FIRST(&aw->lastw)->window;
wl = winlink_find_by_window_id(&s->windows, window);
if (wl != NULL)
return (active_select_window(c, s, wl));
active_window_stack_remove(&aw->lastw, window);
}
return (-1);
}
/* Return if this winlink is the effective winlink. */
int
active_is_effective_window(struct client *c, struct session *s,
struct winlink *wl)
{
return (wl != NULL && wl == active_get_effective_winlink(c, s));
}
/* Return if this winlink is the last effective winlink. */
int
active_is_last_window(struct client *c, struct session *s, struct winlink *wl)
{
struct active_window *aw;
struct active_window_entry *awe;
if (wl == NULL)
return (0);
aw = active_window_get(c, s);
if (aw == NULL)
return (wl == TAILQ_FIRST(&s->lastw));
TAILQ_FOREACH(awe, &aw->lastw, entry) {
if (winlink_find_by_window_id(&s->windows, awe->window) == NULL)
continue;
return (wl->window->id == awe->window);
}
return (0);
}
/* Return if a client has local pane selection for a window. */
int
active_has_local_pane(struct client *c, struct window *w)
{
struct active_pane *ap;
ap = active_pane_get(c, w);
return (ap != NULL && ap->mode == ACTIVE_LOCAL);
}
/* Make a client use local or shared pane selection for a window. */
void
active_set_pane_mode(struct client *c, struct window *w, enum active_mode mode)
{
struct active_pane *ap;
struct window_pane *wp;
if (c == NULL || w == NULL)
return;
if (mode == ACTIVE_SHARED) {
ap = active_pane_get(c, w);
if (ap != NULL)
ap->mode = ACTIVE_SHARED;
server_redraw_client(c);
server_status_client(c);
return;
}
if (server_client_how_many() < 2) {
ap = active_pane_get(c, w);
if (ap != NULL)
ap->mode = ACTIVE_SHARED;
server_redraw_client(c);
server_status_client(c);
return;
}
wp = active_get_effective_pane(c, w);
if (wp == NULL)
wp = active_pane_default(w);
ap = active_pane_add(c, w);
ap->mode = ACTIVE_LOCAL;
ap->pane = wp;
server_redraw_client(c);
server_status_client(c);
}
/* Return the effective active pane for a client and window. */
struct window_pane *
active_get_effective_pane(struct client *c, struct window *w)
{
struct active_pane *ap;
struct window_pane *wp;
wp = active_pane_default(w);
ap = active_pane_get(c, w);
if (ap == NULL || ap->mode != ACTIVE_LOCAL)
return (wp);
if (!active_pane_valid(w, ap->pane)) {
ap->pane = wp;
if (wp != NULL)
active_pane_stack_remove(ap, wp);
return (wp);
}
return (ap->pane);
}
/* Return the effective last pane for a client and window. */
struct window_pane *
active_get_last_pane(struct client *c, struct window *w)
{
struct active_pane *ap;
struct active_pane_entry *ape, *ape1;
if (!active_has_local_pane(c, w))
return (w == NULL ? NULL : TAILQ_FIRST(&w->last_panes));
ap = active_pane_get(c, w);
TAILQ_FOREACH_SAFE(ape, &ap->last_panes, entry, ape1) {
if (active_pane_valid(w, ape->wp))
return (ape->wp);
TAILQ_REMOVE(&ap->last_panes, ape, entry);
free(ape);
}
return (NULL);
}
/* Select a pane using either shared or local state. */
int
active_set_pane(struct client *c, struct window *w, struct window_pane *wp,
int notify)
{
struct active_pane *ap;
struct window_pane *lastwp;
if (!active_pane_valid(w, wp))
return (0);
if (!active_has_local_pane(c, w))
return (window_set_active_pane(w, wp, notify));
lastwp = active_get_effective_pane(c, w);
if (wp == lastwp)
return (0);
ap = active_pane_add(c, w);
active_pane_stack_remove(ap, wp);
active_pane_stack_push(ap, lastwp);
ap->pane = wp;
if (c != NULL) {
server_redraw_client(c);
server_status_client(c);
}
return (1);
}
/* Remove all local pane state if fewer than two clients remain. */
void
active_check_clients(void)
{
struct active_pane *ap, *ap1;
if (server_client_how_many() >= 2)
return;
RB_FOREACH_SAFE(ap, active_panes, &active_panes, ap1) {
if (ap->mode == ACTIVE_LOCAL) {
server_redraw_client(ap->client);
server_status_client(ap->client);
}
active_pane_free(ap);
}
}
/* Remove all state for a client. */
void
active_remove_client(struct client *c)
{
struct active_window *aw, *aw1;
struct active_pane *ap, *ap1;
RB_FOREACH_SAFE(aw, active_windows, &active_windows, aw1) {
if (aw->client == c)
active_window_free(aw);
}
RB_FOREACH_SAFE(ap, active_panes, &active_panes, ap1) {
if (ap->client == c)
active_pane_free(ap);
}
}
/* Remove all window state for a session. */
void
active_remove_session(struct session *s)
{
struct active_window *aw, *aw1;
RB_FOREACH_SAFE(aw, active_windows, &active_windows, aw1) {
if (aw->session == s)
active_window_free(aw);
}
}
/* Remove a window from all local state. */
void
active_remove_window(struct window *w)
{
struct active_window *aw;
struct active_pane *ap, *ap1;
RB_FOREACH(aw, active_windows, &active_windows) {
active_window_stack_remove(&aw->lastw, w->id);
if (aw->window == w->id && aw->session->curw != NULL)
aw->window = aw->session->curw->window->id;
}
RB_FOREACH_SAFE(ap, active_panes, &active_panes, ap1) {
if (ap->window == w)
active_pane_free(ap);
}
}
/* Remove a pane from all local pane state. */
void
active_remove_pane(struct window_pane *wp)
{
struct active_pane *ap, *ap1;
RB_FOREACH_SAFE(ap, active_panes, &active_panes, ap1) {
active_pane_stack_remove(ap, wp);
if (ap->pane == wp)
ap->pane = NULL;
}
}

View File

@@ -314,7 +314,7 @@ alerts_set_message(struct winlink *wl, const char *type, const char *option)
tty_putcode(&c->tty, TTYC_BEL);
if (visual == VISUAL_OFF)
continue;
if (c->session->curw == wl) {
if (active_get_effective_winlink(c, c->session) == wl) {
status_message_set(c, -1, 1, 0, 0,
"%s in current window", type);
} else {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: arguments.c,v 1.66 2026/06/26 09:54:56 nicm Exp $ */
/* $OpenBSD: arguments.c,v 1.67 2026/08/25 06:04:33 nicm Exp $ */
/*
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -762,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);
}
@@ -827,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));
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: client.c,v 1.167 2026/08/17 07:56:56 nicm Exp $ */
/* $OpenBSD: client.c,v 1.168 2026/08/17 20:14:31 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -712,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;
}
}
@@ -798,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

@@ -93,7 +93,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
if (wl != NULL) {
if (wp != NULL)
window_set_active_pane(wp->window, wp, 1);
session_set_current(s, wl);
active_select_window(c, s, wl);
if (wp != NULL)
cmd_find_from_winlink_pane(current, wl, wp, 0);
else

View File

@@ -93,14 +93,18 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *current = cmdq_get_current(item);
struct cmd_find_state *target = cmdq_get_target(item);
struct cmd_find_state *source = cmdq_get_source(item);
struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item);
struct client *ac;
struct winlink *wl = source->wl;
struct winlink *current_wl;
struct session *src_s = source->s;
struct session *dst_s = target->s;
struct window_pane *wp = source->wp;
struct window *w = wl->window, *old_w = w;
char *cause, *cp;
int idx = target->idx, before, old_idx = wl->idx;
int detached, local;
const char *template, *name = args_get(args, 'n');
if (wp == w->modal) {
@@ -117,11 +121,18 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
}
before = args_has(args, 'b');
ac = tc;
if (ac == NULL || ac->session != dst_s)
ac = c;
detached = args_has(args, 'd');
local = (!detached && active_is_local_window(ac, dst_s));
if (args_has(args, 'a') || before) {
if (target->wl != NULL)
idx = winlink_shuffle_up(dst_s, target->wl, before);
else
idx = winlink_shuffle_up(dst_s, dst_s->curw, before);
else {
current_wl = active_get_effective_winlink(ac, dst_s);
idx = winlink_shuffle_up(dst_s, current_wl, before);
}
if (idx == -1)
return (CMD_RETURN_ERROR);
}
@@ -129,7 +140,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
if (window_count_panes(w, 1) == 1) {
if (server_link_window(src_s, wl, dst_s, idx, 0,
!args_has(args, 'd'), &cause) != 0) {
!detached && !local, &cause) != 0) {
cmdq_error(item, "%s", cause);
free(cause);
return (CMD_RETURN_ERROR);
@@ -142,6 +153,13 @@ 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);
if (local) {
active_select_window(ac, dst_s, wl);
if (ac != NULL && ac->session == dst_s)
cmd_find_from_client(current, ac, 0);
if (ac != NULL && ac->session == dst_s)
server_redraw_client(ac);
}
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
goto out;
}
@@ -185,9 +203,17 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
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);
if (!detached) {
if (local)
active_select_window(ac, dst_s, wl);
else
session_select(dst_s, wl->idx);
if (local && ac != NULL && ac->session == dst_s)
cmd_find_from_client(current, ac, 0);
else
cmd_find_from_session(current, dst_s, 0);
if (local && ac != NULL && ac->session == dst_s)
server_redraw_client(ac);
}
server_redraw_session(src_s);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-command-prompt.c,v 1.75 2026/06/25 11:39:11 nicm Exp $ */
/* $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>
@@ -242,9 +242,11 @@ 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);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-display-menu.c,v 1.52 2026/07/14 19:07:03 nicm Exp $ */
/* $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>
@@ -483,7 +483,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
char *title, *cause = NULL;
int flags = 0, starting_choice = 0;
u_int px, py, i, count = args_count(args);
struct options *o = target->s->curw->window->options;
struct options *o = target->w->options;
struct options_entry *oe;
if (args_has(args, 'C')) {
@@ -584,13 +584,15 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
u_int px, py, w, h, count = args_count(args);
struct args_value *av;
struct environ *env = NULL;
struct options *o = s->curw->window->options;
struct options *o = target->w->options;
struct options_entry *oe;
if (args_has(args, 'C')) {
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);

View File

@@ -884,13 +884,13 @@ 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 = c->session->curw->window->active;
fs->wl = active_get_effective_winlink(c, c->session);
fs->wp = active_get_effective_pane(c, fs->wl->window);
if (fs->wp == NULL) {
cmd_find_from_session(fs, c->session, flags);
return (0);
}
fs->s = c->session;
fs->wl = fs->s->curw;
fs->w = fs->wl->window;
cmd_find_log_state(__func__, fs);
@@ -931,6 +931,57 @@ unknown_pane:
return (cmd_find_from_nothing(fs, flags));
}
/* Find state from mouse target. */
static int
cmd_find_target_from_mouse(struct cmd_find_state *fs, struct cmdq_item *item,
enum cmd_find_type type)
{
struct mouse_event *m = &cmdq_get_event(item)->m;
struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item);
switch (type) {
case CMD_FIND_PANE:
if (m->s != -1 && m->w == -1 && m->wp == -1 &&
(fs->s = session_find_by_id(m->s)) != NULL) {
if (tc != NULL && tc->session == fs->s)
fs->wl = active_get_effective_winlink(tc, fs->s);
else if (c != NULL && c->session == fs->s)
fs->wl = active_get_effective_winlink(c, fs->s);
else
fs->wl = fs->s->curw;
if (fs->wl != NULL) {
fs->w = fs->wl->window;
fs->wp = fs->w->active;
break;
}
}
fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
if (fs->wp != NULL) {
fs->w = fs->wl->window;
break;
}
/* FALLTHROUGH */
case CMD_FIND_WINDOW:
case CMD_FIND_SESSION:
fs->wl = cmd_mouse_window(m, &fs->s);
if (fs->s != NULL && m->w == -1 &&
tc != NULL && tc->session == fs->s)
fs->wl = active_get_effective_winlink(tc, fs->s);
else if (fs->s != NULL && m->w == -1 &&
c != NULL && c->session == fs->s)
fs->wl = active_get_effective_winlink(c, fs->s);
if (fs->wl == NULL && fs->s != NULL)
fs->wl = fs->s->curw;
if (fs->wl != NULL) {
fs->w = fs->wl->window;
fs->wp = fs->w->active;
}
break;
}
return (fs->wp == NULL ? -1 : 0);
}
/*
* Split target into pieces and resolve for the given type. Fills in the given
* state. Returns 0 on success or -1 on error.
@@ -939,7 +990,6 @@ int
cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
const char *target, enum cmd_find_type type, int flags)
{
struct mouse_event *m;
struct client *c;
struct cmd_find_state current;
char *colon, *period, *copy = NULL, tmp[256];
@@ -1015,35 +1065,15 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
cmdq_error(item, "no current client");
goto error;
}
fs->wl = c->session->curw;
fs->wp = c->session->curw->window->active;
fs->w = c->session->curw->window;
fs->wl = active_get_effective_winlink(c, c->session);
fs->wp = fs->wl->window->active;
fs->w = fs->wl->window;
goto found;
}
/* Mouse target is a plain = or {mouse}. */
if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) {
m = &cmdq_get_event(item)->m;
switch (type) {
case CMD_FIND_PANE:
fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
if (fs->wp != NULL) {
fs->w = fs->wl->window;
break;
}
/* FALLTHROUGH */
case CMD_FIND_WINDOW:
case CMD_FIND_SESSION:
fs->wl = cmd_mouse_window(m, &fs->s);
if (fs->wl == NULL && fs->s != NULL)
fs->wl = fs->s->curw;
if (fs->wl != NULL) {
fs->w = fs->wl->window;
fs->wp = fs->w->active;
}
break;
}
if (fs->wp == NULL) {
if (cmd_find_target_from_mouse(fs, item, type) != 0) {
if (~flags & CMD_FIND_QUIET)
cmdq_error(item, "no mouse target");
goto error;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-if-shell.c,v 1.86 2025/08/01 09:05:51 nicm Exp $ */
/* $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

@@ -413,13 +413,16 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *current = cmdq_get_current(item);
struct cmd_find_state *target = cmdq_get_target(item);
struct cmd_find_state *source = cmdq_get_source(item);
struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item);
struct client *ac;
struct session *dst_s;
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;
int flags = 0, dst_idx, local;
struct layout_cell *lc;
dst_s = target->s;
@@ -427,6 +430,10 @@ 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;
ac = tc;
if (ac == NULL || ac->session != dst_s)
ac = c;
local = active_is_local_window(ac, dst_s);
if (cmd_get_entry(self) == &cmd_move_pane_entry) {
if (args_has(args, 'M'))
@@ -512,9 +519,18 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
if (!args_has(args, 'd')) {
window_set_active_pane(dst_w, src_wp, 1);
session_select(dst_s, dst_idx);
cmd_find_from_session(current, dst_s, 0);
server_redraw_session(dst_s);
if (local)
active_select_window(ac, dst_s, dst_wl);
else
session_select(dst_s, dst_idx);
if (local && ac != NULL && ac->session == dst_s)
cmd_find_from_client(current, ac, 0);
else
cmd_find_from_session(current, dst_s, 0);
if (local && ac != NULL && ac->session == dst_s)
server_redraw_client(ac);
else
server_redraw_session(dst_s);
} else
server_status_session(dst_s);

View File

@@ -62,12 +62,15 @@ cmd_move_window_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self);
struct cmd_find_state *source = cmdq_get_source(item);
struct cmd_find_state target;
struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item);
struct client *ac;
const char *tflag = args_get(args, 't');
struct session *src = source->s;
struct session *dst;
struct winlink *wl = source->wl;
struct winlink *wl = source->wl, *dstwl, *current_wl;
char *cause;
int idx, kflag, dflag, sflag, before;
int idx, kflag, dflag, sflag, before, local;
if (args_has(args, 'r')) {
if (cmd_find_target(&target, item, tflag, CMD_FIND_SESSION,
@@ -90,23 +93,37 @@ cmd_move_window_exec(struct cmd *self, struct cmdq_item *item)
dflag = args_has(args, 'd');
sflag = args_has(args, 's');
ac = tc;
if (ac == NULL || ac->session != dst)
ac = c;
local = (!dflag && active_is_local_window(ac, dst));
before = args_has(args, 'b');
if (args_has(args, 'a') || before) {
if (target.wl != NULL)
idx = winlink_shuffle_up(dst, target.wl, before);
else
idx = winlink_shuffle_up(dst, dst->curw, before);
else {
current_wl = active_get_effective_winlink(ac, dst);
idx = winlink_shuffle_up(dst, current_wl, before);
}
if (idx == -1)
return (CMD_RETURN_ERROR);
}
if (server_link_window(src, wl, dst, idx, kflag, !dflag, &cause) != 0) {
if (server_link_window(src, wl, dst, idx, kflag, !dflag && !local,
&cause) != 0) {
cmdq_error(item, "%s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
dstwl = winlink_find_by_window(&dst->windows, wl->window);
if (cmd_get_entry(self) == &cmd_move_window_entry)
server_unlink_window(src, wl);
if (local && dstwl != NULL) {
active_select_window(ac, dst, dstwl);
if (ac != NULL && ac->session == dst)
server_redraw_client(ac);
}
/*
* Renumber the winlinks in the src session only, the destination

View File

@@ -58,9 +58,11 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item);
struct spawn_context sc = { 0 };
struct client *tc = cmdq_get_target_client(item);
struct client *ac;
struct session *s = target->s;
struct winlink *wl = target->wl, *new_wl = NULL;
int idx = target->idx, before, count = args_count(args);
int detached, local;
char *cause = NULL, *cp, *expanded, *wname = NULL;
const char *template, *name;
struct cmd_find_state fs;
@@ -107,10 +109,15 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
free(wname);
if (args_has(args, 'd'))
return (CMD_RETURN_NORMAL);
if (session_set_current(s, new_wl) == 0)
server_redraw_session(s);
if (active_select_window(c, s, new_wl) == 0) {
if (active_is_local_window(c, s) && c != NULL &&
c->session == s)
server_redraw_client(c);
else
server_redraw_session(s);
}
if (c != NULL && c->session != NULL)
s->curw->window->latest = c;
active_get_effective_window(c, s)->latest = c;
recalculate_sizes();
return (CMD_RETURN_NORMAL);
}
@@ -140,10 +147,16 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
sc.idx = idx;
sc.cwd = args_get(args, 'c');
ac = tc;
if (ac == NULL || ac->session != s)
ac = c;
detached = args_has(args, 'd');
local = (!detached && active_is_local_window(ac, s));
sc.flags = 0;
if (args_has(args, 'E') || (count == 1 && *args_string(args, 0) == '\0'))
sc.flags |= SPAWN_EMPTY;
if (args_has(args, 'd'))
if (detached || local)
sc.flags |= SPAWN_DETACHED;
if (args_has(args, 'k'))
sc.flags |= SPAWN_KILL;
@@ -153,7 +166,13 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
free(cause);
goto fail;
}
if (!args_has(args, 'd') || new_wl == s->curw) {
if (local) {
if (active_select_window(ac, s, new_wl) == 0)
cmd_find_from_winlink(current, new_wl, 0);
if (ac != NULL && ac->session == s)
server_redraw_client(ac);
server_status_session_group(s);
} else if (!detached || new_wl == s->curw) {
cmd_find_from_winlink(current, new_wl, 0);
server_redraw_session_group(s);
} else

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-parse.y,v 1.58 2026/04/27 12:31:11 nicm Exp $ */
/* $OpenBSD: cmd-parse.y,v 1.59 2026/08/31 07:51:56 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -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: cmd-queue.c,v 1.122 2026/08/03 13:38:42 nicm Exp $ */
/* $OpenBSD: cmd-queue.c,v 1.123 2026/08/24 20:34:26 nicm Exp $ */
/*
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -850,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

@@ -203,7 +203,7 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'c'))
tc->pan_window = NULL;
else {
w = tc->session->curw->window;
w = active_get_effective_window(tc, tc->session);
if (tc->pan_window != w) {
tc->pan_window = w;
tc->pan_ox = tty->oox;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-resize-pane.c,v 1.67 2026/07/10 13:38:45 nicm Exp $ */
/* $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>
@@ -92,6 +92,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
}
server_unzoom_window(w);
lc = wp->layout_cell; /* may have been replaced by unzoom */
if (args_has(args, 'x')) {
x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-run-shell.c,v 1.93 2026/07/17 12:42:51 nicm Exp $ */
/* $OpenBSD: cmd-run-shell.c,v 1.94 2026/08/25 06:04:33 nicm Exp $ */
/*
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
@@ -79,7 +79,10 @@ static void
cmd_run_shell_print(struct job *job, const char *msg)
{
struct cmd_run_shell_data *cdata = job_get_data(job);
struct client *c = cdata->client;
struct session *s;
struct window_pane *wp = NULL;
struct window *w;
struct cmd_find_state fs;
struct window_mode_entry *wme;
@@ -90,8 +93,11 @@ cmd_run_shell_print(struct job *job, const char *msg)
cmdq_print(cdata->item, "%s", msg);
return;
}
if (cdata->client != NULL && cdata->client->session != NULL)
wp = cdata->client->session->curw->window->active;
if (c != NULL && c->session != NULL) {
s = c->session;
w = active_get_effective_window(c, s);
wp = active_get_effective_pane(c, w);
}
if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0)
wp = fs.wp;
if (wp == NULL)
@@ -233,9 +239,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: cmd-select-pane.c,v 1.77 2026/07/17 12:42:51 nicm Exp $ */
/* $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>
@@ -33,8 +33,8 @@ const struct cmd_entry cmd_select_pane_entry = {
.name = "select-pane",
.alias = "selectp",
.args = { "DdegLlMmP:RT:t:UZ", 0, 0, NULL }, /* -P and -g deprecated */
.usage = "[-DdeLlMmRUZ] [-T title] " CMD_TARGET_PANE_USAGE,
.args = { "DdegLlMmP:RsST:t:UZ", 0, 0, NULL }, /* -P and -g deprecated */
.usage = "[-DdeLlMmRsSUZ] [-T title] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -68,10 +68,11 @@ cmd_select_pane_redraw(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session == NULL || (c->flags & CLIENT_CONTROL))
continue;
if (c->session->curw->window == w && tty_window_bigger(&c->tty))
if (active_get_effective_window(c, c->session) == w &&
tty_window_bigger(&c->tty))
server_redraw_client(c);
else {
if (c->session->curw->window == w)
if (active_get_effective_window(c, c->session) == w)
c->flags |= CLIENT_REDRAWBORDERS;
if (session_has(c->session, w))
c->flags |= CLIENT_REDRAWSTATUS;
@@ -84,6 +85,7 @@ static enum cmd_retval
cmd_select_pane_marked_pane(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_client(item);
struct cmd_find_state *target = cmdq_get_target(item);
struct event_payload *ep;
struct cmd_find_state fs;
@@ -137,8 +139,9 @@ cmd_select_pane_marked_pane(struct cmd *self, struct cmdq_item *item)
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);
if (!active_has_local_pane(c, wp->window))
window_redraw_active_switch(wp->window, wp);
active_set_pane(c, wp->window, wp, 1);
}
return (CMD_RETURN_NORMAL);
}
@@ -152,25 +155,40 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item);
struct event_payload *ep;
struct cmd_find_state fs;
struct client *c = cmdq_get_client(item);
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct session *s = target->s;
struct window_pane *wp = target->wp, *lastwp;
struct window_pane *wp = target->wp, *lastwp, *activewp;
struct options *oo = wp->options;
char *title;
const char *style;
struct options_entry *o;
int visible, Zflag = args_has(args, 'Z');
if (args_has(args, 's') || args_has(args, 'S')) {
if (c == NULL || c->session == NULL) {
cmdq_error(item, "no current client");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 's'))
active_set_pane_mode(c, w, ACTIVE_LOCAL);
else
active_set_pane_mode(c, w, ACTIVE_SHARED);
return (CMD_RETURN_NORMAL);
}
if (entry == &cmd_last_pane_entry || args_has(args, 'l')) {
/*
* Check for no last pane found in case the other pane was
* spawned without being visited (for example split-window -d).
*/
lastwp = TAILQ_FIRST(&w->last_panes);
lastwp = active_get_last_pane(c, w);
if (lastwp == NULL && window_count_panes(w, 1) == 2) {
lastwp = TAILQ_PREV(w->active, window_panes, entry);
activewp = active_get_effective_pane(c, w);
lastwp = TAILQ_PREV(activewp, window_panes, entry);
if (lastwp == NULL)
lastwp = TAILQ_NEXT(w->active, entry);
lastwp = TAILQ_NEXT(activewp, entry);
}
if (lastwp == NULL) {
cmdq_error(item, "no last pane");
@@ -185,14 +203,19 @@ 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)) {
if (!active_has_local_pane(c, w))
window_redraw_active_switch(w, lastwp);
if (active_set_pane(c, 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);
@@ -266,16 +289,22 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
}
if (wp == w->active)
activewp = active_get_effective_pane(c, w);
if (wp == activewp)
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 (window_set_active_pane(w, wp, 1))
if (!active_has_local_pane(c, w))
window_redraw_active_switch(w, wp);
if (active_set_pane(c, 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

@@ -33,8 +33,8 @@ const struct cmd_entry cmd_select_window_entry = {
.name = "select-window",
.alias = "selectw",
.args = { "lnpTt:", 0, 0, NULL },
.usage = "[-lnpT] " CMD_TARGET_WINDOW_USAGE,
.args = { "LlSnpTt:", 0, 0, NULL },
.usage = "[-LlnpST] " CMD_TARGET_WINDOW_USAGE,
.target = { 't', CMD_FIND_WINDOW, 0 },
@@ -88,8 +88,9 @@ cmd_select_window_exec(struct cmd *self, struct cmdq_item *item)
struct client *c = cmdq_get_client(item);
struct cmd_find_state *current = cmdq_get_current(item);
struct cmd_find_state *target = cmdq_get_target(item);
struct winlink *wl = target->wl;
struct winlink *wl = target->wl, *current_wl;
struct session *s = target->s;
enum active_mode mode;
int next, previous, last, activity;
next = (cmd_get_entry(self) == &cmd_next_window_entry);
@@ -102,48 +103,104 @@ cmd_select_window_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'l'))
last = 1;
if (args_has(args, 'L') || args_has(args, 'S')) {
if (c == NULL || c->session == NULL) {
cmdq_error(item, "no current client");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'S'))
mode = ACTIVE_SHARED;
else
mode = ACTIVE_LOCAL;
active_set_local_window(c, s, mode);
if (!next &&
!previous &&
!last &&
!args_has(args, 't') &&
!args_has(args, 'T')) {
if (current->s == s) {
if (c->session == s)
cmd_find_from_client(current, c, 0);
else
cmd_find_from_session(current, s, 0);
}
if (c->session == s)
server_redraw_client(c);
else
server_redraw_session(s);
cmdq_insert_hook(s, item, current, "after-select-window");
recalculate_sizes();
return (CMD_RETURN_NORMAL);
}
}
if (active_is_local_window(c, s))
mode = ACTIVE_LOCAL;
else
mode = ACTIVE_SHARED;
if (next || previous || last) {
activity = args_has(args, 'a');
if (next) {
if (session_next(s, activity) != 0) {
if (active_next_window(c, s, activity) != 0) {
cmdq_error(item, "no next window");
return (CMD_RETURN_ERROR);
}
} else if (previous) {
if (session_previous(s, activity) != 0) {
if (active_previous_window(c, s, activity) != 0) {
cmdq_error(item, "no previous window");
return (CMD_RETURN_ERROR);
}
} else {
if (session_last(s) != 0) {
if (active_last_window(c, s) != 0) {
cmdq_error(item, "no last window");
return (CMD_RETURN_ERROR);
}
}
cmd_find_from_session(current, s, 0);
server_redraw_session(s);
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
cmd_find_from_client(current, c, 0);
else
cmd_find_from_session(current, s, 0);
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
server_redraw_client(c);
else
server_redraw_session(s);
cmdq_insert_hook(s, item, current, "after-select-window");
} else {
/*
* If -T and select-window is invoked on same window as
* current, switch to previous window.
*/
if (args_has(args, 'T') && wl == s->curw) {
if (session_last(s) != 0) {
current_wl = active_get_effective_winlink(c, s);
if (args_has(args, 'T') && wl == current_wl) {
if (active_last_window(c, s) != 0) {
cmdq_error(item, "no last window");
return (-1);
}
if (current->s == s)
if (current->s == s) {
if (mode == ACTIVE_LOCAL && c != NULL &&
c->session == s)
cmd_find_from_client(current, c, 0);
else
cmd_find_from_session(current, s, 0);
}
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
server_redraw_client(c);
else
server_redraw_session(s);
} else if (active_select_window(c, s, wl) == 0) {
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
cmd_find_from_client(current, c, 0);
else
cmd_find_from_session(current, s, 0);
server_redraw_session(s);
} else if (session_select(s, wl->idx) == 0) {
cmd_find_from_session(current, s, 0);
server_redraw_session(s);
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
server_redraw_client(c);
else
server_redraw_session(s);
}
cmdq_insert_hook(s, item, current, "after-select-window");
}
if (c != NULL && c->session != NULL)
s->curw->window->latest = c;
active_get_effective_window(c, s)->latest = c;
recalculate_sizes();
return (CMD_RETURN_NORMAL);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-set-buffer.c,v 1.37 2026/02/15 17:43:26 nicm Exp $ */
/* $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: cmd-source-file.c,v 1.62 2025/11/18 08:42:09 nicm Exp $ */
/* $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: cmd-split-window.c,v 1.148 2026/08/07 08:10:53 nicm Exp $ */
/* $OpenBSD: cmd-split-window.c,v 1.150 2026/08/20 09:19:24 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
.name = "new-pane",
.alias = "newp",
.args = { "bB:Cc:de:EfF:hIkl:LMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
.usage = "[-bCdefhIklMOPvWZ] [-B border-lines] "
.args = { "AbB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
.usage = "[-AbCdefhIkKLMOPvWZ] [-B border-lines] "
"[-c start-directory] [-e environment] "
"[-F format] [-l size] [-m message] [-p percentage] "
"[-s style] [-S active-border-style] "
@@ -89,6 +89,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state fs;
struct key_event *event = cmdq_get_event(item);
int input, empty, is_floating, flags = 0;
int restore_zoom = 0;
const char *template, *style, *value;
char *cause = NULL, *cp, *title;
const struct options_table_entry *oe;
@@ -96,10 +97,16 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
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 {
window_unzoom(w, 1);
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;
}
@@ -133,7 +140,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'Z'))
flags |= SPAWN_ZOOM;
if (args_has(args, 'O'))
flags |= SPAWN_MODAL;
flags |= SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
if (is_floating && args_has(args, 'A'))
flags |= SPAWN_FLOATOVERZOOM;
if ((w->flags & WINDOW_ZOOMED) && (flags & SPAWN_FLOATOVERZOOM))
restore_zoom = 1;
input = args_has(args, 'I');
if (input || (count == 1 && *args_string(args, 0) == '\0'))
@@ -169,6 +180,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (cause != NULL) {
cmdq_error(item, "%s", cause);
free(cause);
if (restore_zoom)
window_pop_zoom(w);
return (CMD_RETURN_ERROR);
}
@@ -202,6 +215,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
*/
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;
@@ -271,7 +286,10 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
if (~flags & SPAWN_DETACHED)
cmd_find_from_winlink_pane(current, wl, new_wp, 0);
if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) {
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);
}
@@ -322,8 +340,9 @@ fail:
if (!is_floating)
layout_close_pane(new_wp);
window_remove_pane(wp->window, new_wp);
} else if (args_has(args, 'O'))
window_pop_modal_zoom(wp->window);
}
if (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);

View File

@@ -48,10 +48,14 @@ cmd_swap_window_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self);
struct cmd_find_state *source = cmdq_get_source(item);
struct cmd_find_state *target = cmdq_get_target(item);
struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item);
struct client *ac;
struct session *src = source->s, *dst = target->s;
struct session_group *sg_src, *sg_dst;
struct winlink *wl_src = source->wl, *wl_dst = target->wl;
struct window *w_src, *w_dst;
int local_src, local_dst;
sg_src = session_group_contains(src);
sg_dst = session_group_contains(dst);
@@ -79,10 +83,22 @@ cmd_swap_window_exec(struct cmd *self, struct cmdq_item *item)
if (marked_pane.wl == wl_src)
marked_pane.wl = wl_dst;
ac = tc;
if (ac == NULL)
ac = c;
local_dst = active_is_local_window(ac, dst);
local_src = active_is_local_window(ac, src);
if (args_has(args, 'd')) {
session_select(dst, wl_dst->idx);
if (src != dst)
session_select(src, wl_src->idx);
if (local_dst)
active_select_window(ac, dst, wl_dst);
else
session_select(dst, wl_dst->idx);
if (src != dst) {
if (local_src)
active_select_window(ac, src, wl_src);
else
session_select(src, wl_src->idx);
}
}
session_group_synchronize_from(src);
server_redraw_session_group(src);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-switch-client.c,v 1.74 2026/05/22 15:22:43 nicm Exp $ */
/* $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,11 +53,12 @@ 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, window_target;
int Zflag = args_has(args, 'Z');
struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item);
struct session *s;
struct winlink *wl;
struct winlink *wl, *current_wl;
struct window *w;
struct window_pane *wp;
const char *tablename;
@@ -65,8 +66,13 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
struct sort_criteria sort_crit;
uid_t uid;
if (tflag != NULL &&
(tflag[strcspn(tflag, ":.%")] != '\0' || strcmp(tflag, "=") == 0)) {
window_target = 0;
if (tflag != NULL) {
if (tflag[strcspn(tflag, ":.%")] != '\0' ||
strcmp(tflag, "=") == 0)
window_target = 1;
}
if (window_target) {
type = CMD_FIND_PANE;
flags = 0;
} else {
@@ -137,18 +143,27 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
} else {
if (cmdq_get_client(item) == NULL)
return (CMD_RETURN_NORMAL);
if (wl != NULL && wp != NULL && wp != wl->window->active) {
if (window_target && 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) {
session_set_current(s, wl);
cmd_find_from_session(current, s, 0);
if (window_target && wl != NULL) {
active_select_window(tc, s, wl);
if (active_is_local_window(tc, s)) {
current_wl = active_get_effective_winlink(tc, s);
cmd_find_from_winlink(current, current_wl, 0);
} else
cmd_find_from_session(current, s, 0);
}
}

View File

@@ -530,34 +530,15 @@ if test "x$enable_cgroups" = xyes; then
fi
fi
# Enable image support.
AC_ARG_ENABLE(
images,
AS_HELP_STRING(--enable-images, enable Kitty/SIXEL/ASCII images)
)
# Keep the old option as an alias for one compatibility cycle.
# Enable sixel support.
AC_ARG_ENABLE(
sixel,
AS_HELP_STRING(--enable-sixel, deprecated alias for --enable-images)
AS_HELP_STRING(--enable-sixel, enable sixel images)
)
if test "x$enable_sixel" = xyes; then
enable_images=yes
AC_DEFINE(ENABLE_SIXEL)
fi
if test "x$enable_images" = xyes; then
PKG_CHECK_MODULES(
IMAGE,
[libpng >= 1.6 zlib],
[
AM_CPPFLAGS="$IMAGE_CFLAGS $AM_CPPFLAGS"
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
LIBS="$IMAGE_LIBS $LIBS"
],
AC_MSG_ERROR("image support requires libpng 1.6 and zlib")
)
AC_DEFINE(ENABLE_IMAGES)
fi
AM_CONDITIONAL(ENABLE_IMAGES, [test "x$enable_images" = xyes])
AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes])
# Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
AC_MSG_CHECKING(for b64_ntop)
@@ -1156,10 +1137,10 @@ if test "x$enable_asan" = xyes; then
else
AC_MSG_NOTICE([ASAN: off])
fi
if test "x$enable_images" = xyes; then
AC_MSG_NOTICE([images: on])
if test "x$enable_sixel" = xyes; then
AC_MSG_NOTICE([SIXEL: on])
else
AC_MSG_NOTICE([images: off])
AC_MSG_NOTICE([SIXEL: off])
fi
if test "x$enable_debug" = xyes; then
AC_MSG_NOTICE([debug: on])

View File

@@ -222,6 +222,28 @@ control_client_session_changed_cb(__unused const char *name,
}
}
/* Notify control clients that a client changed its effective window. */
static void
control_client_window_changed_cb(__unused const char *name,
struct event_payload *ep, __unused void *sink_data)
{
struct client *cc = event_payload_get_client(ep, "client");
struct session *s = event_payload_get_session(ep, "session");
struct window *w = event_payload_get_window(ep, "window");
struct client *c;
if (cc == NULL || s == NULL || w == NULL)
return;
TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue;
control_write(c, "%%client-window-changed %s $%u @%u",
cc->name, s->id, w->id);
}
}
/* Notify control clients that a client detached. */
static void
control_client_detached_cb(__unused const char *name, struct event_payload *ep,
@@ -359,6 +381,7 @@ control_build_events(void)
{ "window-linked", control_window_linked_cb },
{ "window-renamed", control_window_renamed_cb },
{ "client-session-changed", control_client_session_changed_cb },
{ "client-window-changed", control_client_window_changed_cb },
{ "client-detached", control_client_detached_cb },
{ "session-renamed", control_session_renamed_cb },
{ "session-created", control_session_created_cb },

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: control.c,v 1.65 2026/08/04 11:18:22 nicm Exp $ */
/* $OpenBSD: control.c,v 1.66 2026/08/18 07:43:44 nicm Exp $ */
/*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -388,6 +388,25 @@ control_pause_pane(struct client *c, struct window_pane *wp)
}
}
/*
* 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_pane *cp;
if (c->control_state == NULL)
return;
cp = control_get_pane(c, wp);
if (cp == NULL)
return;
control_discard_pane(c, cp);
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
}
/* Write an already-formatted line, queueing it behind %output if needed. */
static void
control_write_line(struct client *c, char *line)

212
format.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: format.c,v 1.412 2026/08/05 07:31:08 nicm Exp $ */
/* $OpenBSD: format.c,v 1.415 2026/08/31 19:34:09 nicm Exp $ */
/*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -806,7 +806,7 @@ format_cb_window_active_clients(struct format_tree *ft)
if (client_session == NULL)
continue;
if (w == client_session->curw->window)
if (w == active_get_effective_window(loop, client_session))
n++;
}
@@ -838,7 +838,7 @@ format_cb_window_active_clients_list(struct format_tree *ft)
if (client_session == NULL)
continue;
if (w == client_session->curw->window) {
if (w == active_get_effective_window(loop, client_session)) {
if (EVBUFFER_LENGTH(buffer) > 0)
evbuffer_add(buffer, ",", 1);
evbuffer_add_printf(buffer, "%s", loop->name);
@@ -1067,7 +1067,8 @@ format_cb_pane_fg(struct format_tree *ft)
if (wp == NULL)
return (NULL);
tty_default_colours(&gc, wp, NULL);
tty_default_colours(&gc, wp,
active_get_effective_pane(ft->c, wp->window), NULL);
return (xstrdup(colour_tostring(gc.fg)));
}
@@ -1118,7 +1119,8 @@ format_cb_pane_bg(struct format_tree *ft)
if (wp == NULL)
return (NULL);
tty_default_colours(&gc, wp, NULL);
tty_default_colours(&gc, wp,
active_get_effective_pane(ft->c, wp->window), NULL);
return (xstrdup(colour_tostring(gc.bg)));
}
@@ -1887,6 +1889,37 @@ format_cb_cursor_blinking(struct format_tree *ft)
return (NULL);
}
/* Callback for history_added. */
static void *
format_cb_history_added(struct format_tree *ft)
{
if (ft->wp != NULL)
return (format_printf("%u", ft->wp->base.grid->scroll_added));
return (NULL);
}
/* Callback for history_collected. */
static void *
format_cb_history_collected(struct format_tree *ft)
{
struct window_pane *wp = ft->wp;
if (wp != NULL)
return (format_printf("%u", wp->base.grid->scroll_collected));
return (NULL);
}
/* Callback for history_generation. */
static void *
format_cb_history_generation(struct format_tree *ft)
{
struct window_pane *wp = ft->wp;
if (wp != NULL)
return (format_printf("%u", wp->base.grid->scroll_generation));
return (NULL);
}
/* Callback for history_limit. */
static void *
format_cb_history_limit(struct format_tree *ft)
@@ -2159,14 +2192,45 @@ format_cb_pane_private_modes(struct format_tree *ft)
static void *
format_cb_pane_active(struct format_tree *ft)
{
struct window_pane *active;
if (ft->wp != NULL) {
if (ft->wp == ft->wp->window->active)
active = active_get_effective_pane(ft->c, ft->wp->window);
if (ft->wp == active)
return (xstrdup("1"));
return (xstrdup("0"));
}
return (NULL);
}
/* Callback for pane_local_active. */
static void *
format_cb_pane_local_active(struct format_tree *ft)
{
struct window_pane *active;
if (ft->wp != NULL && active_has_local_pane(ft->c, ft->wp->window)) {
active = active_get_effective_pane(ft->c, ft->wp->window);
if (ft->wp == active)
return (xstrdup("1"));
return (xstrdup("0"));
}
return (NULL);
}
/* Callback for pane_local_mode. */
static void *
format_cb_pane_local_mode(struct format_tree *ft)
{
struct window *w = ft->w;
if (ft->wp != NULL)
w = ft->wp->window;
if (active_has_local_pane(ft->c, w))
return (xstrdup("1"));
return (xstrdup("0"));
}
/* Callback for pane_at_left. */
static void *
format_cb_pane_at_left(struct format_tree *ft)
@@ -2276,6 +2340,19 @@ format_cb_pane_last_output_time(struct format_tree *ft)
return (NULL);
}
/* Callback for pane_output_generation. */
static void *
format_cb_pane_output_generation(struct format_tree *ft)
{
unsigned long long value;
if (ft->wp != NULL) {
value = ft->wp->output_generation;
return (format_printf("%llu", value));
}
return (NULL);
}
/* Callback for pane_last_prompt_time. */
static void *
format_cb_pane_last_prompt_time(struct format_tree *ft)
@@ -2445,7 +2522,7 @@ static void *
format_cb_pane_last(struct format_tree *ft)
{
if (ft->wp != NULL) {
if (ft->wp == TAILQ_FIRST(&ft->wp->window->last_panes))
if (ft->wp == active_get_last_pane(ft->c, ft->wp->window))
return (xstrdup("1"));
return (xstrdup("0"));
}
@@ -3018,26 +3095,52 @@ format_cb_sixel_support(__unused struct format_tree *ft)
#endif
}
/* Callback for image_support. */
static void *
format_cb_image_support(__unused struct format_tree *ft)
{
#ifdef ENABLE_IMAGES
return (xstrdup("1"));
#else
return (xstrdup("0"));
#endif
}
/* Callback for active_window_index. */
static void *
format_cb_active_window_index(struct format_tree *ft)
{
if (ft->s != NULL)
return (format_printf("%u", ft->s->curw->idx));
struct winlink *wl;
if (ft->s != NULL) {
wl = active_get_effective_winlink(ft->c, ft->s);
if (wl != NULL)
return (format_printf("%u", wl->idx));
}
return (NULL);
}
/* Callback for active_window_id. */
static void *
format_cb_active_window_id(struct format_tree *ft)
{
struct winlink *wl;
if (ft->s != NULL) {
wl = active_get_effective_winlink(ft->c, ft->s);
if (wl != NULL)
return (format_printf("@%u", wl->window->id));
}
return (NULL);
}
/* Callback for local_active_window. */
static void *
format_cb_local_active_window(struct format_tree *ft)
{
if (ft->c != NULL && ft->s != NULL)
return (format_printf("%d", active_is_local_window(ft->c, ft->s)));
return (xstrdup("0"));
}
/* Callback for active_window_mode. */
static void *
format_cb_active_window_mode(struct format_tree *ft)
{
if (ft->c != NULL && ft->s != NULL && active_is_local_window(ft->c, ft->s))
return (xstrdup("local"));
return (xstrdup("shared"));
}
/* Callback for last_window_index. */
static void *
format_cb_last_window_index(struct format_tree *ft)
@@ -3055,8 +3158,11 @@ format_cb_last_window_index(struct format_tree *ft)
static void *
format_cb_window_active(struct format_tree *ft)
{
struct winlink *wl;
if (ft->wl != NULL) {
if (ft->wl == ft->wl->session->curw)
wl = active_get_effective_winlink(ft->c, ft->wl->session);
if (ft->wl == wl)
return (xstrdup("1"));
return (xstrdup("0"));
}
@@ -3136,7 +3242,7 @@ static void *
format_cb_window_flags(struct format_tree *ft)
{
if (ft->wl != NULL)
return (xstrdup(window_printable_flags(ft->wl, 1)));
return (xstrdup(window_printable_flags(ft->c, ft->wl, 1)));
return (NULL);
}
@@ -3323,7 +3429,7 @@ static void *
format_cb_window_raw_flags(struct format_tree *ft)
{
if (ft->wl != NULL)
return (xstrdup(window_printable_flags(ft->wl, 0)));
return (xstrdup(window_printable_flags(ft->c, ft->wl, 0)));
return (NULL);
}
@@ -3533,9 +3639,15 @@ struct format_table_entry {
* Only variables which are added by the caller go into the tree.
*/
static const struct format_table_entry format_table[] = {
{ "active_window_id", FORMAT_TABLE_STRING,
format_cb_active_window_id
},
{ "active_window_index", FORMAT_TABLE_STRING,
format_cb_active_window_index
},
{ "active_window_mode", FORMAT_TABLE_STRING,
format_cb_active_window_mode
},
{ "alternate_on", FORMAT_TABLE_STRING,
format_cb_alternate_on
},
@@ -3674,12 +3786,21 @@ static const struct format_table_entry format_table[] = {
{ "cursor_y", FORMAT_TABLE_STRING,
format_cb_cursor_y
},
{ "history_added", FORMAT_TABLE_STRING,
format_cb_history_added
},
{ "history_all_bytes", FORMAT_TABLE_STRING,
format_cb_history_all_bytes
},
{ "history_bytes", FORMAT_TABLE_STRING,
format_cb_history_bytes
},
{ "history_collected", FORMAT_TABLE_STRING,
format_cb_history_collected
},
{ "history_generation", FORMAT_TABLE_STRING,
format_cb_history_generation
},
{ "history_limit", FORMAT_TABLE_STRING,
format_cb_history_limit
},
@@ -3692,9 +3813,6 @@ static const struct format_table_entry format_table[] = {
{ "host_short", FORMAT_TABLE_STRING,
format_cb_host_short
},
{ "image_support", FORMAT_TABLE_STRING,
format_cb_image_support
},
{ "insert_flag", FORMAT_TABLE_STRING,
format_cb_insert_flag
},
@@ -3707,6 +3825,9 @@ static const struct format_table_entry format_table[] = {
{ "last_window_index", FORMAT_TABLE_STRING,
format_cb_last_window_index
},
{ "local_active_window", FORMAT_TABLE_STRING,
format_cb_local_active_window
},
{ "mouse_all_flag", FORMAT_TABLE_STRING,
format_cb_mouse_all_flag
},
@@ -3851,6 +3972,12 @@ static const struct format_table_entry format_table[] = {
{ "pane_left", FORMAT_TABLE_STRING,
format_cb_pane_left
},
{ "pane_local_active", FORMAT_TABLE_STRING,
format_cb_pane_local_active
},
{ "pane_local_mode", FORMAT_TABLE_STRING,
format_cb_pane_local_mode
},
{ "pane_marked", FORMAT_TABLE_STRING,
format_cb_pane_marked
},
@@ -3863,6 +3990,9 @@ static const struct format_table_entry format_table[] = {
{ "pane_mode", FORMAT_TABLE_STRING,
format_cb_pane_mode
},
{ "pane_output_generation", FORMAT_TABLE_STRING,
format_cb_pane_output_generation
},
{ "pane_path", FORMAT_TABLE_STRING,
format_cb_pane_path
},
@@ -4401,7 +4531,7 @@ format_quote_shell(const char *s)
at = out = xmalloc(strlen(s) * 2 + 1);
for (cp = s; *cp != '\0'; cp++) {
if (strchr("|&;<>()$`\\\"'*?[# =%\n\t", *cp) != NULL)
if (strchr("|&;<>(){}$`\\\"'*?[# =%\n\t", *cp) != NULL)
*at++ = '\\';
*at++ = *cp;
}
@@ -5253,15 +5383,18 @@ format_add_window_neighbour(struct format_tree *nft, struct winlink *wl,
struct session *s, const char *prefix)
{
struct options_entry *o;
struct winlink *current;
const char *oname;
char *key, *prefixed, *oval;
current = active_get_effective_winlink(nft->c, s);
xasprintf(&key, "%s_window_index", prefix);
format_add(nft, key, "%u", wl->idx);
free(key);
xasprintf(&key, "%s_window_active", prefix);
format_add(nft, key, "%d", wl == s->curw);
format_add(nft, key, "%d", wl == current);
free(key);
o = options_first(wl->window->options);
@@ -5289,10 +5422,10 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
struct cmdq_item *item = ft->item;
struct format_tree *nft;
struct format_expand_state next;
char *all, *active, *use, *expanded, *value;
char *all, *active_fmt, *use, *expanded, *value;
struct evbuffer *buffer;
size_t size;
struct winlink *wl, **l;
struct winlink *wl, **l, *active_wl;
struct window *w;
int i, n;
@@ -5301,9 +5434,9 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
return (NULL);
}
if (format_choose(es, fmt, &all, &active, 0) != 0) {
if (format_choose(es, fmt, &all, &active_fmt, 0) != 0) {
all = xstrdup(fmt);
active = NULL;
active_fmt = NULL;
}
buffer = evbuffer_new();
@@ -5311,12 +5444,13 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
fatalx("out of memory");
l = sort_get_winlinks_session(s, &n, sc);
active_wl = active_get_effective_winlink(ft->c, s);
for (i = 0; i < n; i++) {
wl = l[i];
w = wl->window;
format_log(es, "window loop: %u @%u", wl->idx, w->id);
if (active != NULL && wl == s->curw)
use = active;
if (active_fmt != NULL && wl == active_wl)
use = active_fmt;
else
use = all;
nft = format_create(c, item, FORMAT_WINDOW|w->id, ft->flags);
@@ -5325,11 +5459,11 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
format_add(nft, "loop_last_flag", "%d", i == n - 1);
/* Add neighbour window data to the format tree. */
if (i > 0 && l[i - 1] == s->curw)
if (i > 0 && l[i - 1] == active_wl)
format_add(nft, "window_after_active", "1");
else
format_add(nft, "window_after_active", "0");
if (i + 1 < n && l[i + 1] == s->curw)
if (i + 1 < n && l[i + 1] == active_wl)
format_add(nft, "window_before_active", "1");
else
format_add(nft, "window_before_active", "0");
@@ -5349,7 +5483,7 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
free(expanded);
}
free(active);
free(active_fmt);
free(all);
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
@@ -6937,9 +7071,9 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s,
if (s == NULL && c != NULL)
s = c->session;
if (wl == NULL && s != NULL)
wl = s->curw;
wl = active_get_effective_winlink(c, s);
if (wp == NULL && wl != NULL)
wp = wl->window->active;
wp = active_get_effective_pane(c, wl->window);
if (c != NULL)
format_defaults_client(ft, c);

120
grid.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: grid.c,v 1.156 2026/08/03 12:58:53 nicm Exp $ */
/* $OpenBSD: grid.c,v 1.158 2026/09/01 12:49:49 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -86,9 +86,6 @@ grid_check_is_clear(struct grid *gd)
assert(gl->extdsize == 0);
assert(gl->flags == 0);
assert(gl->time == 0);
#ifdef ENABLE_IMAGES
assert(gl->images == NULL);
#endif
}
}
#else
@@ -356,9 +353,6 @@ grid_free_line(struct grid *gd, u_int py)
assert(gl->cellsize == 0 || gl->celldata != NULL);
#endif
#ifdef ENABLE_IMAGES
image_grid_free_line(gd, gl);
#endif
free(gl->celldata);
free(gl->extddata);
memset(gl, 0, sizeof *gl);
@@ -403,9 +397,6 @@ void
grid_destroy(struct grid *gd)
{
grid_free_lines(gd, 0, gd->hsize + gd->sy);
#ifdef ENABLE_IMAGES
image_grid_free(gd);
#endif
free(gd->linedata);
free(gd);
}
@@ -738,9 +729,6 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
if (nx == 0 || ny == 0)
return;
#ifdef ENABLE_IMAGES
image_grid_damage(gd, px, py, nx, ny);
#endif
if (px == 0 && nx == gd->sx) {
grid_clear_lines(gd, py, ny, bg);
@@ -776,10 +764,6 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
void
grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
{
struct grid_line *gl;
#ifdef ENABLE_IMAGES
struct image_line *images;
#endif
u_int yy;
if (ny == 0)
@@ -791,18 +775,8 @@ grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
return;
for (yy = py; yy < py + ny; yy++) {
gl = &gd->linedata[yy];
#ifdef ENABLE_IMAGES
image_grid_damage(gd, 0, yy, gd->sx, 1);
images = gl->images;
#endif
free(gl->celldata);
free(gl->extddata);
memset(gl, 0, sizeof *gl);
grid_free_line(gd, yy);
grid_empty_line(gd, yy, bg);
#ifdef ENABLE_IMAGES
gl->images = images;
#endif
}
if (py != 0)
gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
@@ -867,9 +841,6 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
grid_expand_line(gd, py, px + nx, 8);
grid_expand_line(gd, py, dx + nx, 8);
#ifdef ENABLE_IMAGES
image_grid_move_cells(gd, dx, px, py, nx);
#endif
memmove(&gl->celldata[dx], &gl->celldata[px],
nx * sizeof *gl->celldata);
if (dx + nx > gl->cellused)
@@ -1242,6 +1213,7 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx,
grid_get_cell(gd, xx, py, &gc);
if (gc.flags & GRID_FLAG_PADDING)
continue;
if (lastgc != NULL && (flags & GRID_STRING_WITH_SEQUENCES)) {
grid_string_cells_code(*lastgc, &gc, code, sizeof code,
flags, s, &has_link);
@@ -1308,18 +1280,11 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
{
struct grid_line *dstl, *srcl;
u_int yy;
#ifdef ENABLE_IMAGES
u_int original_dy, original_sy;
#endif
if (dy + ny > dst->hsize + dst->sy)
ny = dst->hsize + dst->sy - dy;
if (sy + ny > src->hsize + src->sy)
ny = src->hsize + src->sy - sy;
#ifdef ENABLE_IMAGES
original_dy = dy;
original_sy = sy;
#endif
grid_free_lines(dst, dy, ny);
for (yy = 0; yy < ny; yy++) {
@@ -1327,9 +1292,6 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
dstl = &dst->linedata[dy];
memcpy(dstl, srcl, sizeof *dstl);
#ifdef ENABLE_IMAGES
dstl->images = NULL;
#endif
if (srcl->cellsize != 0) {
dstl->celldata = xreallocarray(NULL,
srcl->cellsize, sizeof *dstl->celldata);
@@ -1349,9 +1311,6 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
sy++;
dy++;
}
#ifdef ENABLE_IMAGES
image_grid_duplicate_lines(dst, original_dy, src, original_sy, ny);
#endif
}
/* Mark line as dead. */
@@ -1362,30 +1321,6 @@ grid_reflow_dead(struct grid_line *gl)
gl->flags = GRID_LINE_DEAD;
}
/* Image rows are cell-aligned and must not be reflowed like text. */
static int
grid_reflow_has_image(struct grid_line *gl)
{
struct grid_cell gc;
u_int i;
#ifdef ENABLE_IMAGES
if (image_grid_line_has_images(gl))
return (1);
#endif
if (~gl->flags & GRID_LINE_EXTENDED)
return (0);
for (i = 0; i < gl->cellused; i++) {
grid_get_cell1(gl, i, &gc);
/* Kitty Unicode placeholder base character (U+10EEEE). */
if (gc.data.size >= 4 && gc.data.data[0] == 0xf4 &&
gc.data.data[1] == 0x8e && gc.data.data[2] == 0xbb &&
gc.data.data[3] == 0xae)
return (1);
}
return (0);
}
/* Add lines, return the first new one. */
static struct grid_line *
grid_reflow_add(struct grid *gd, u_int n)
@@ -1448,10 +1383,6 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
break;
line = yy + 1 + lines;
/* Do not join wrapped text to a cell-aligned image row. */
if (grid_reflow_has_image(&gd->linedata[line]))
break;
/* If the next line is empty, skip it. */
if (~gd->linedata[line].flags & GRID_LINE_WRAPPED)
wrapped = 0;
@@ -1512,7 +1443,8 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
/* Remove the lines that were completely consumed. */
for (i = yy + 1; i < yy + 1 + lines; i++) {
grid_free_line(gd, i);
free(gd->linedata[i].celldata);
free(gd->linedata[i].extddata);
grid_reflow_dead(&gd->linedata[i]);
}
@@ -1614,12 +1546,6 @@ grid_reflow(struct grid *gd, u_int sx)
if (gl->flags & GRID_LINE_DEAD)
continue;
/* Keep image layers at their original cell coordinates. */
if (grid_reflow_has_image(gl)) {
grid_reflow_move(target, gl);
continue;
}
/*
* Work out the width of this line. at is the point at which
* the available width is hit, and width is the full line
@@ -1709,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)
@@ -1723,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 {
@@ -1785,21 +1711,29 @@ 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));
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1600
image.c

File diff suppressed because it is too large Load Diff

113
input.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: input.c,v 1.269 2026/07/20 11:16:33 nicm Exp $ */
/* $OpenBSD: input.c,v 1.271 2026/08/31 19:34:09 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -145,9 +145,6 @@ struct input_ctx {
*/
struct evbuffer *since_ground;
struct event ground_timer;
#ifdef ENABLE_IMAGES
void *kitty_state;
#endif
};
/* Helper functions. */
@@ -186,9 +183,6 @@ static void input_enter_osc(struct input_ctx *);
static void input_exit_osc(struct input_ctx *);
static void input_enter_apc(struct input_ctx *);
static void input_exit_apc(struct input_ctx *);
#ifdef ENABLE_IMAGES
static int input_handle_kitty(struct input_ctx *, const u_char *, size_t);
#endif
static void input_enter_rename(struct input_ctx *);
static void input_exit_rename(struct input_ctx *);
@@ -922,9 +916,6 @@ input_free(struct input_ctx *ictx)
event_del(&ictx->request_timer);
free(ictx->input_buf);
#ifdef ENABLE_IMAGES
kitty_free_state(ictx->kitty_state);
#endif
evbuffer_free(ictx->since_ground);
event_del(&ictx->ground_timer);
@@ -947,7 +938,6 @@ input_reset(struct input_ctx *ictx, int clear)
screen_write_start_pane(sctx, wp, &wp->base);
else
screen_write_start(sctx, &wp->base);
sctx->flags |= SCREEN_WRITE_INPUT;
screen_write_reset(sctx);
screen_write_stop(sctx);
}
@@ -1059,6 +1049,7 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
if (len == 0)
return;
wp->output_generation++;
window_update_activity(wp->window);
if (~wp->flags & PANE_ACTIVITY) {
wp->flags |= PANE_ACTIVITY;
@@ -1075,7 +1066,6 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
screen_write_start_pane(sctx, wp, &wp->base);
else
screen_write_start(sctx, &wp->base);
sctx->flags |= SCREEN_WRITE_INPUT;
log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id,
ictx->state->name, len, (int)len, buf);
@@ -1095,7 +1085,6 @@ input_parse_screen(struct input_ctx *ictx, struct screen *s,
return;
screen_write_start_callback(sctx, s, cb, arg);
sctx->flags |= SCREEN_WRITE_INPUT;
input_parse(ictx, buf, len);
screen_write_stop(sctx);
}
@@ -1595,7 +1584,7 @@ input_csi_dispatch(struct input_ctx *ictx)
case -1:
break;
case 0:
#ifdef ENABLE_IMAGES
#ifdef ENABLE_SIXEL
input_reply(ictx, 1, "\033[?1;2;4c");
#else
input_reply(ictx, 1, "\033[?1;2c");
@@ -1990,7 +1979,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx)
screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
break;
case 2026:
screen_write_stop_sync(ictx->wp);
screen_write_end_sync(sctx);
break;
case 2031:
screen_write_mode_clear(sctx, MODE_THEME_UPDATES);
@@ -2113,7 +2102,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
static void
input_csi_dispatch_sm_graphics(__unused struct input_ctx *ictx)
{
#ifdef ENABLE_IMAGES
#ifdef ENABLE_SIXEL
int n, m, o;
if (ictx->param_list_len > 3)
@@ -2638,10 +2627,10 @@ input_dcs_dispatch(struct input_ctx *ictx)
const char prefix[] = "tmux;";
const u_int prefixlen = (sizeof prefix) - 1;
long long allow_passthrough = 0;
#ifdef ENABLE_IMAGES
#ifdef ENABLE_SIXEL
struct window *w;
struct sixel_image *si;
int p1, p2;
int p2;
#endif
if (wp == NULL)
@@ -2654,18 +2643,15 @@ input_dcs_dispatch(struct input_ctx *ictx)
return (0);
}
#ifdef ENABLE_IMAGES
#ifdef ENABLE_SIXEL
if (wp != NULL && buf[0] == 'q' && ictx->interm_len == 0) {
w = wp->window;
if (input_split(ictx) != 0)
return (0);
p1 = input_get(ictx, 0, 0, 0);
if (p1 == -1)
p1 = 0;
p2 = input_get(ictx, 1, 0, 0);
if (p2 == -1)
p2 = 0;
si = sixel_parse(buf, len, p1, p2, w->xpixel, w->ypixel);
si = sixel_parse(buf, len, p2, w->xpixel, w->ypixel);
if (si != NULL)
screen_write_sixelimage(sctx, si, ictx->cell.cell.bg);
}
@@ -2804,57 +2790,6 @@ input_enter_apc(struct input_ctx *ictx)
}
/* APC terminator (ST) received. */
#ifdef ENABLE_IMAGES
static int
input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
{
struct screen_write_ctx *sctx = &ictx->ctx;
struct window_pane *wp = ictx->wp;
struct image *im;
u_int image_id = 0, replace_id = 0, quiet = 0;
u_int placement_id = 0;
int32_t z = 0;
char action = '\0', delete = '\0';
int status;
if (wp == NULL)
return (0);
im = kitty_parse_image(&ictx->kitty_state, buf, len,
wp->window->xpixel,
wp->window->ypixel, &image_id, &replace_id, &quiet, &action,
&delete, &placement_id, &z, &status);
if (status == KITTY_PARSE_MORE)
return (1);
if (status != KITTY_PARSE_OK) {
if (quiet < 2 && action != '\0') {
if (status == KITTY_PARSE_MISSING)
input_reply(ictx, 0, "\033_Gi=%u;ENOENT\033\\",
image_id);
else
input_reply(ictx, 0, "\033_Gi=%u;EINVAL\033\\",
image_id);
}
return (1);
}
if (replace_id != 0)
image_clear(sctx, replace_id);
if (im != NULL) {
if (action == 'd')
image_clear_kitty(sctx, delete, image_id, placement_id, z);
else
image_write_kitty(sctx, im, ictx->cell.cell.bg, image_id,
placement_id, z);
image_free(image_get_id(im));
if (quiet == 0 && image_id != 0)
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
} else if (action == 'd')
image_clear_kitty(sctx, delete, image_id, placement_id, z);
else if ((action == 't' || action == 'q' || action == 'u') && quiet == 0)
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
return (1);
}
#endif
static void
input_exit_apc(struct input_ctx *ictx)
{
@@ -2865,13 +2800,6 @@ input_exit_apc(struct input_ctx *ictx)
return;
log_debug("%s: \"%s\"", __func__, ictx->input_buf);
#ifdef ENABLE_IMAGES
if (ictx->input_len > 1 && ictx->input_buf[0] == 'G' &&
input_handle_kitty(ictx, ictx->input_buf + 1,
ictx->input_len - 1))
return;
#endif
if (wp != NULL &&
options_get_number(wp->options, "allow-set-title") &&
screen_set_title(sctx->s, ictx->input_buf, 1)) {
@@ -2932,12 +2860,6 @@ input_top_bit_set(struct input_ctx *ictx)
{
struct screen_write_ctx *sctx = &ictx->ctx;
struct utf8_data *ud = &ictx->utf8data;
#ifdef ENABLE_IMAGES
struct grid_cell gc;
struct image *im;
u_int x, source_x, source_y, image_id, placement_id;
int32_t z;
#endif
ictx->flags &= ~INPUT_LAST;
@@ -2965,20 +2887,6 @@ input_top_bit_set(struct input_ctx *ictx)
utf8_copy(&ictx->cell.cell.data, ud);
screen_write_collect_add(sctx, &ictx->cell.cell);
#ifdef ENABLE_IMAGES
if (sctx->s->cx != 0) {
x = sctx->s->cx - 1; /* cx-1 is the cell just written. */
grid_view_get_cell(sctx->s->grid, x, sctx->s->cy, &gc);
if (kitty_placeholder_to_image(ictx->kitty_state,
sctx->s->grid, &gc, x, sctx->s->cy, &im, &source_x,
&source_y, &image_id, &placement_id, &z)) {
grid_view_set_cell(sctx->s->grid, x, sctx->s->cy, &gc);
image_place_cell_kitty(sctx, im, x, sctx->s->cy, source_x,
source_y, image_id, placement_id, z);
}
}
#endif
utf8_copy(&ictx->last, &ictx->cell.cell.data);
ictx->flags |= INPUT_LAST;
@@ -3170,7 +3078,8 @@ input_osc_10(struct input_ctx *ictx, const char *p)
return;
c = window_pane_get_fg_control_client(wp);
if (c == -1) {
tty_default_colours(&defaults, wp, NULL);
tty_default_colours(&defaults, wp, wp->window->active,
NULL);
if (COLOUR_DEFAULT(defaults.fg))
c = window_pane_get_fg(wp);
else

View File

@@ -31,6 +31,8 @@
" 'Rename' 'r' {command-prompt -I '#S' {rename-session -- '%%'}}" \
" 'Detach' 'd' {detach-client}" \
" ''" \
" '#{?local_active_window,Shared Window,Local Window}' 'L' {select-window #{?local_active_window,-S,-L}}" \
" ''" \
" 'New Session' 's' {new-session}" \
" 'New Window' 'w' {new-window}"
#define DEFAULT_WINDOW_MENU \
@@ -72,6 +74,8 @@
" '#{?#{!:#{pane_floating_flag}},Horizontal Split,}' 'h' {split-window -h}" \
" '#{?#{!:#{pane_floating_flag}},Vertical Split,}' 'v' {split-window -v}" \
" ''" \
" '#{?#{>=:#{window_active_clients},2},#{?pane_local_mode,Shared Active Pane,Local Active Pane},}' 'a' {if -F '#{pane_local_mode}' {select-pane -S} {select-pane -s}}" \
" ''" \
" '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Up,}' 'u' {swap-pane -U}" \
" '#{?#{&&:#{!:#{pane_floating_flag}},#{>:#{window_panes},1}},Swap Down,}' 'd' {swap-pane -D}" \
" '#{?pane_marked_set,,-}Swap Marked' 's' {swap-pane}" \
@@ -503,6 +507,7 @@ key_bindings_init(void)
/* Mouse button 1 down on pane. */
"bind -n MouseDown1Pane { select-pane -t=; send -M }",
"bind -n M-MouseDown1Pane { move-pane -P front -t= }",
/* Mouse button 1 drag on pane. */
"bind -n MouseDrag1Pane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }",
@@ -524,6 +529,7 @@ key_bindings_init(void)
/* Mouse button 1 on border. */
"bind -n MouseDown1Border { select-pane -M }",
"bind -n M-MouseDown1Border { move-pane -P front -t= }",
/* Mouse button 1 drag on border. */
"bind -n MouseDrag1Border { resize-pane -M }",

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: layout.c,v 1.96 2026/07/15 13:02:33 nicm Exp $ */
/* $OpenBSD: layout.c,v 1.98 2026/08/25 18:38:05 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -539,6 +539,10 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
status = window_get_pane_status(w);
/* Floating cells do not take space from the tiled layout. */
if (!layout_cell_is_tiled(lc) && !layout_cell_has_tiled_child(lc))
return (0);
if (lc->type == LAYOUT_WINDOWPANE) {
/* Space available in this cell only. */
if (type == LAYOUT_LEFTRIGHT) {
@@ -568,6 +572,9 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
/* Different type: minimum of available space in child cells. */
minimum = UINT_MAX;
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue;
available = layout_resize_check(w, lcchild, type);
if (available < minimum)
minimum = available;
@@ -661,7 +668,7 @@ layout_resize_set_size(struct window *w, struct layout_cell *lc,
/* Find and return the nearest neighbour to a cell in a specific direction. */
static struct layout_cell *
layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction)
layout_cell_get_neighbour_dir(struct layout_cell *lc, int direction)
{
struct layout_cell *lcn = lc;
@@ -695,9 +702,9 @@ layout_cell_get_neighbour(struct layout_cell *lc)
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
direction = !direction;
lcother = layout_cell_get_neighbour_direction(lc, direction);
lcother = layout_cell_get_neighbour_dir(lc, direction);
if (lcother == NULL)
lcother = layout_cell_get_neighbour_direction(lc, !direction);
lcother = layout_cell_get_neighbour_dir(lc, !direction);
return (lcother);
}
@@ -860,7 +867,7 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
size = lc->g.sx;
else
size = lc->g.sy;
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
if (layout_cell_is_last_tiled(lc))
change = size - new_size;
else
change = new_size - size;
@@ -985,11 +992,11 @@ layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
if (lcparent == NULL)
return;
/* If this is the last cell, move back one. */
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) {
do
lc = TAILQ_PREV(lc, layout_cells, entry);
while (lc->flags & LAYOUT_CELL_FLOATING);
/* If this is the last tiled cell, move back one. */
if (layout_cell_is_last_tiled(lc)) {
lc = layout_cell_get_neighbour_dir(lc, 0);
if (lc == NULL)
return;
}
layout_resize_layout(wp->window, lc, type, change, opposite);
@@ -1007,22 +1014,22 @@ layout_resize_pane_grow(struct window *w, struct layout_cell *lc,
lcadd = lc;
/* Look towards the tail for a suitable cell for reduction. */
lcremove = TAILQ_NEXT(lc, entry);
lcremove = layout_cell_get_neighbour_dir(lc, 1);
while (lcremove != NULL) {
size = layout_resize_check(w, lcremove, type);
if (size > 0)
break;
lcremove = TAILQ_NEXT(lcremove, entry);
lcremove = layout_cell_get_neighbour_dir(lcremove, 1);
}
/* If none found, look towards the head. */
if (opposite && lcremove == NULL) {
lcremove = TAILQ_PREV(lc, layout_cells, entry);
lcremove = layout_cell_get_neighbour_dir(lc, 0);
while (lcremove != NULL) {
size = layout_resize_check(w, lcremove, type);
if (size > 0)
break;
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
}
}
if (lcremove == NULL)
@@ -1050,13 +1057,13 @@ layout_resize_pane_shrink(struct window *w, struct layout_cell *lc,
size = layout_resize_check(w, lcremove, type);
if (size != 0)
break;
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
} while (lcremove != NULL);
if (lcremove == NULL)
return (0);
/* And add onto the next cell (from the original cell). */
lcadd = TAILQ_NEXT(lc, entry);
lcadd = layout_cell_get_neighbour_dir(lc, 1);
if (lcadd == NULL)
return (0);
@@ -1650,7 +1657,10 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args,
return (NULL);
}
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM));
if (window_active_pane_is_over_zoom(w))
window_push_zoom(w, 0, 1);
else
window_push_zoom(w, 1, (flags & SPAWN_ZOOM));
lc = layout_split_pane(wp, type, size, flags);
if (lc == NULL)
*cause = xstrdup("no space for a new pane");
@@ -1677,8 +1687,10 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args,
return (NULL);
}
if (flags & SPAWN_MODAL)
window_push_modal_zoom(w);
if (flags & SPAWN_FLOATOVERZOOM)
window_push_zoom(wp->window, 0, 1);
else if (window_active_pane_is_over_zoom(w))
window_push_zoom(wp->window, 0, 1);
else
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM));
lcnew = layout_floating_pane(w, wp, &fg);

2
menu.c
View File

@@ -586,7 +586,7 @@ menu_display(struct menu *menu, int flags, int starting_choice,
struct options *o;
if (fs == NULL)
w = c->session->curw->window;
w = active_get_effective_window(c, c->session);
else
w = fs->w;
o = w->options;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: options-table.c,v 1.242 2026/07/27 08:03:01 nicm Exp $ */
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
/*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -305,6 +305,16 @@ const struct options_table_entry options_table[] = {
"When this is reached, the oldest buffer is deleted."
},
{ .name = "clear-on-attach",
.type = OPTIONS_TABLE_FLAG,
.scope = OPTIONS_TABLE_SERVER,
.default_num = 1,
.text = "Whether to use the alternate screen and clear it when "
"a client is attached. When disabled, tmux does not "
"enter the alternate screen on attach so terminal "
"content before tmux remains in scrollback."
},
{ .name = "command-alias",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
@@ -541,7 +551,6 @@ const struct options_table_entry options_table[] = {
.scope = OPTIONS_TABLE_SERVER,
.flags = OPTIONS_TABLE_IS_ARRAY,
.default_str = "xterm*:clipboard:ccolour:cstyle:focus:title,"
"xterm-kitty*:kitty,"
"screen*:title,"
"rxvt*:ignorefkeys",
.separator = ",",
@@ -1116,7 +1125,8 @@ const struct options_table_entry options_table[] = {
{ .name = "status-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION,
.default_str = "bg=themegreen,fg=themeblack",
.default_str = "bg=#{?local_active_window,themeblue,themegreen},"
"fg=themeblack",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Style of the status line."
@@ -1241,7 +1251,8 @@ const struct options_table_entry options_table[] = {
* underscore.
*/
.default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~",
.text = "Characters considered to separate words."
.text = "Characters considered to separate words; a space matches "
"any character with the Unicode White_Space property."
},
/* Window options. */
@@ -1543,8 +1554,9 @@ const struct options_table_entry options_table[] = {
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_str = "fg=#{?pane_modal_flag,themeblue,"
"#{?pane_marked,thememagenta,"
"#{?pane_local_active,themecyan,"
"#{?synchronize-panes,themered,"
"#{?pane_in_mode,themeyellow,themegreen}}}}",
"#{?pane_in_mode,themeyellow,themegreen}}}}}",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Style of the active pane border."
@@ -1972,6 +1984,8 @@ const struct options_table_entry options_table[] = {
"Run when a client is resized."),
OPTIONS_TABLE_HOOK("client-session-changed", "",
"Run when a client's attached session is changed."),
OPTIONS_TABLE_HOOK("client-window-changed", "",
"Run when a client's effective window is changed."),
OPTIONS_TABLE_HOOK("client-light-theme", "",
"Run when a client switches to a light theme."),
OPTIONS_TABLE_HOOK("client-dark-theme", "",

13
popup.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: popup.c,v 1.76 2026/07/14 19:07:03 nicm Exp $ */
/* $OpenBSD: popup.c,v 1.77 2026/08/28 08:02:16 nicm Exp $ */
/*
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -79,7 +79,8 @@ popup_free(struct popup_data *pd)
if (pd->job != NULL)
job_free(pd->job);
input_free(pd->ictx);
if (pd->ictx != NULL)
input_free(pd->ictx);
free(pd->r.ranges);
screen_free(&pd->s);
@@ -102,9 +103,9 @@ popup_reapply_styles(struct popup_data *pd)
if (s == NULL)
return;
o = s->curw->window->options;
o = active_get_effective_window(c, s)->options;
ft = format_create_defaults(NULL, c, s, s->curw, NULL);
ft = format_create_defaults(NULL, c, s, NULL, NULL);
/* Reapply popup style from options. */
memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
@@ -569,9 +570,9 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
struct style sytmp;
if (s != NULL)
o = s->curw->window->options;
o = active_get_effective_window(c, s)->options;
else
o = c->session->curw->window->options;
o = active_get_effective_window(c, c->session)->options;
if (lines == BOX_LINES_DEFAULT)
lines = options_get_number(o, "popup-border-lines");

151
regress/active-window.sh Normal file
View File

@@ -0,0 +1,151 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
LANG=C.UTF-8
export TERM LC_ALL LANG
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMPDIR=$(mktemp -d)
IN1="$TMPDIR/in1"
IN2="$TMPDIR/in2"
OUT1="$TMPDIR/out1"
OUT2="$TMPDIR/out2"
PID1=
PID2=
cleanup()
{
[ -n "$PID1" ] && kill "$PID1" 2>/dev/null
[ -n "$PID2" ] && kill "$PID2" 2>/dev/null
$TMUX kill-server 2>/dev/null
rm -rf "$TMPDIR"
}
trap cleanup EXIT
fail()
{
echo "$1"
echo "client1:"
cat "$OUT1" 2>/dev/null
echo "client2:"
cat "$OUT2" 2>/dev/null
exit 1
}
wait_for()
{
file=$1
pattern=$2
i=0
while [ "$i" -lt 60 ]; do
if grep -F -- "$pattern" "$file" >/dev/null 2>&1; then
return 0
fi
sleep 0.1
i=$((i + 1))
done
fail "missing '$pattern' in $file"
}
send1()
{
printf '%s\n' "$*" >&3
}
send2()
{
printf '%s\n' "$*" >&4
}
check_server()
{
out=$($TMUX display-message -p -t "$1" "$2")
[ "$out" = "$3" ] || fail "expected '$3' for $1 $2, got '$out'"
}
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -s aw -x 80 -y 24 -n w0 || exit 1
$TMUX new-window -d -t aw: -n w1 || exit 1
$TMUX new-window -d -t aw: -n w2 || exit 1
$TMUX new-session -d -s aw2 -x 80 -y 24 -n x0 || exit 1
$TMUX new-window -d -t aw2: -n x1 || exit 1
mkfifo "$IN1" "$IN2" || exit 1
: >"$OUT1"
: >"$OUT2"
$TMUX -C attach-session -t aw <"$IN1" >"$OUT1" 2>&1 &
PID1=$!
$TMUX -C attach-session -t aw <"$IN2" >"$OUT2" 2>&1 &
PID2=$!
exec 3>"$IN1"
exec 4>"$IN2"
send1 'display-message -p C1READY'
send2 'display-message -p C2READY'
wait_for "$OUT1" C1READY
wait_for "$OUT2" C2READY
# Default shared selection: client 1 changes session current, client 2 follows.
send1 'display-message -p "C1A #{active_window_index} #{local_active_window} #{window_index}"'
wait_for "$OUT1" 'C1A 0 0 0'
send1 'select-window -t :1'
wait_for "$OUT2" '%session-window-changed $0 @1'
send2 'display-message -p "C2A #{active_window_index} #{local_active_window} #{window_index}"'
wait_for "$OUT2" 'C2A 1 0 1'
check_server 'aw:' '#{window_index}' '1'
# Local selection changes only client 1 and becomes the default command target.
send1 'select-window -L'
send1 'select-window -t :2'
send1 'display-message -p "C1B #{active_window_index} #{local_active_window} #{window_index}"'
wait_for "$OUT1" 'C1B 2 1 2'
send2 'display-message -p "C2B #{active_window_index} #{local_active_window} #{window_index}"'
wait_for "$OUT2" 'C2B 1 0 1'
check_server 'aw:' '#{window_index}' '1'
# Local last-window history is independent of the session shared stack.
send1 'last-window'
send1 'display-message -p "C1C #{active_window_index} #{window_index}"'
wait_for "$OUT1" 'C1C 1 1'
send1 'next-window'
send1 'display-message -p "C1D #{active_window_index} #{window_index}"'
wait_for "$OUT1" 'C1D 2 2'
check_server 'aw:' '#{window_index}' '1'
# Session switches preserve per-session local selections.
send1 'switch-client -t aw2'
send1 'select-window -L'
send1 'select-window -t :1'
send1 'display-message -p "C1E #{session_name} #{active_window_index} #{local_active_window}"'
wait_for "$OUT1" 'C1E aw2 1 1'
send1 'switch-client -t aw'
send1 'display-message -p "C1F #{session_name} #{active_window_index} #{local_active_window}"'
wait_for "$OUT1" 'C1F aw 2 1'
# Returning to shared mode immediately follows the session current window.
send1 'select-window -S'
send1 'display-message -p "C1G #{active_window_index} #{local_active_window} #{window_index}"'
wait_for "$OUT1" 'C1G 1 0 1'
# Invalid local windows recover to a valid session window without changing curw.
send1 'select-window -L'
send1 'select-window -t :2'
$TMUX kill-window -t aw:2 || fail 'kill-window failed'
send1 'display-message -p "C1H #{active_window_index} #{local_active_window} #{window_index}"'
wait_for "$OUT1" 'C1H 1 1 1'
check_server 'aw:' '#{window_index}' '1'
# Creating a window without -d in local mode selects it locally only.
send1 'new-window -n w3'
send1 'display-message -p "C1I #{local_active_window} #{window_name}"'
wait_for "$OUT1" 'C1I 1 w3'
send2 'display-message -p "C2C #{local_active_window} #{window_name}"'
wait_for "$OUT2" 'C2C 0 w1'
check_server 'aw:' '#{window_name}' 'w1'
exit 0

View File

@@ -28,7 +28,7 @@ $TMUX kill-server 2>/dev/null
TMP=$(mktemp)
TMP2=$(mktemp)
trap 'rm -f "$TMP" "$TMP2"; $TMUX kill-server 2>/dev/null' 0 1 15
trap 'rm -f "$TMP" "$TMP2" "$TMP".*; $TMUX kill-server 2>/dev/null' 0 1 15
# check_ok $cmd...
#
@@ -270,6 +270,27 @@ if [ $? -eq 0 ]; then
exit 1
fi
# A write error after a successful open must be reported to the server.
dd if=/dev/zero of="$TMP.big" bs=100000 count=1 2>/dev/null
check_ok load-buffer -b write-error "$TMP.big"
mkfifo "$TMP.fifo" || exit 1
dd if="$TMP.fifo" of="$TMP.got" bs=1 count=8 2>/dev/null &
reader=$!
exec 3>"$TMP.fifo"
out=$($TMUX save-buffer -b write-error "$TMP.fifo" 3>&- 2>&1)
status=$?
exec 3>&-
wait "$reader"
if [ $status -eq 0 ]; then
echo "save-buffer after write error succeeded."
exit 1
fi
if [ "$(wc -c <"$TMP.got")" -ne 8 ]; then
echo "save-buffer write error test wrote wrong amount."
exit 1
fi
check_ok delete-buffer -b write-error
# save-buffer - writes to stdout and load-buffer - reads from stdin.
out=$($TMUX save-buffer -b sb -)
if [ "$out" != "data" ]; then

View File

@@ -0,0 +1,163 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
fail()
{
echo "$1"
[ -z "$TMUX" ] || $TMUX kill-server 2>/dev/null
exit 1
}
goto_cell()
{
row=$1
column=$2
$TMUX send-keys -X history-top || fail "$mode: history-top failed"
$TMUX send-keys -X start-of-line || fail "$mode: start-of-line failed"
[ "$row" -eq 0 ] ||
$TMUX send-keys -X -N "$row" cursor-down ||
fail "$mode: cursor-down failed"
[ "$column" -eq 0 ] ||
$TMUX send-keys -X -N "$column" cursor-right ||
fail "$mode: cursor-right failed"
}
check_cursor()
{
expected=$1
actual=$($TMUX display-message -p '#{copy_cursor_x},#{copy_cursor_y}')
[ "$actual" = "$expected" ] ||
fail "$mode: expected cursor $expected, got $actual"
}
check_word()
{
row=$1
column=$2
expected=$3
goto_cell "$row" "$column"
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
actual=$($TMUX show-buffer 2>/dev/null)
[ "$actual" = "$expected" ] ||
fail "$mode: expected word '$expected', got '$actual'"
}
check_next_word()
{
row=$1
expected=$2
goto_cell "$row" 0
$TMUX send-keys -X next-word || fail "$mode: next-word failed"
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
actual=$($TMUX show-buffer 2>/dev/null)
[ "$actual" = "$expected" ] ||
fail "$mode: expected next word '$expected', got '$actual'"
}
for mode in emacs vi; do
TMUX="$TEST_TMUX -Lunicode-whitespace-$mode-$$ -f/dev/null"
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -x16 -y20 \
"printf 'aa bb\naa\tbb\naa\302\240bb\naa\343\200\200bb\naa:bb\naaaaaaaaaaaaaaa\302\240b\nabcdefghijklmnopq\nhardone\nhardtwo\naa\341\232\200bb\naa\342\200\200bb\naa\342\200\250bb\naa\342\200\251bb\naa\342\200\257bb\naa\342\201\237bb\nxa\302\205b c\n'; exec cat" ||
fail "$mode: new-session failed"
$TMUX set-option -g window-size manual || fail "$mode: set size failed"
$TMUX set-window-option -g mode-keys "$mode" ||
fail "$mode: set mode-keys failed"
$TMUX set-window-option -g word-separators "" ||
fail "$mode: clear word-separators failed"
$TMUX copy-mode || fail "$mode: copy-mode failed"
# ASCII space and TAB retain their existing behaviour.
goto_cell 0 0
$TMUX send-keys -X next-space || fail "$mode: ASCII next-space failed"
check_cursor 3,0
goto_cell 1 0
$TMUX send-keys -X next-space || fail "$mode: TAB next-space failed"
check_cursor 8,1
$TMUX send-keys -X previous-space ||
fail "$mode: TAB previous-space failed"
check_cursor 0,1
# NBSP is whitespace for selection and word movement.
goto_cell 2 0
$TMUX send-keys -X next-word || fail "$mode: NBSP next-word failed"
check_cursor 3,2
$TMUX send-keys -X previous-word ||
fail "$mode: NBSP previous-word failed"
check_cursor 0,2
check_word 2 0 aa
goto_cell 2 0
$TMUX send-keys -X next-word-end ||
fail "$mode: NBSP next-word-end failed"
if [ "$mode" = emacs ]; then
check_cursor 2,2
else
check_cursor 1,2
fi
# U+3000 is two columns wide; both its leading and padding cells must be
# skipped as one whitespace character in either direction.
goto_cell 3 0
$TMUX send-keys -X next-word || fail "$mode: U+3000 next-word failed"
check_cursor 4,3
$TMUX send-keys -X previous-word ||
fail "$mode: U+3000 previous-word failed"
check_cursor 0,3
goto_cell 3 0
$TMUX send-keys -X next-space-end ||
fail "$mode: U+3000 next-space-end failed"
if [ "$mode" = emacs ]; then
check_cursor 2,3
else
check_cursor 1,3
fi
# Other characters in word-separators remain literal when space enables
# Unicode whitespace matching.
$TMUX set-window-option -g word-separators ': ' ||
fail "$mode: set custom word-separators failed"
goto_cell 4 0
$TMUX send-keys -X next-word || fail "$mode: literal next-word failed"
check_cursor 2,4
$TMUX set-window-option -g word-separators "" ||
fail "$mode: restore word-separators failed"
# Whitespace in the last column remains a boundary across a soft wrap.
goto_cell 5 0
$TMUX send-keys -X next-space ||
fail "$mode: wrapped NBSP next-space failed"
check_cursor 0,6
# A word split only by a soft wrap remains one word, while a hard line
# boundary still terminates selection.
check_word 7 0 abcdefghijklmnopq
check_word 9 0 hardone
# Check the remaining representative White_Space ranges and U+0085
# combined into a cell with another character.
check_next_word 11 bb
check_next_word 12 bb
check_next_word 13 bb
check_next_word 14 bb
check_next_word 15 bb
check_next_word 16 bb
goto_cell 17 0
$TMUX send-keys -X next-word ||
fail "$mode: combined U+0085 next-word failed"
check_cursor 2,17
$TMUX kill-server 2>/dev/null
done
exit 0

View File

@@ -199,5 +199,74 @@ must_fail $TMUX resize-pane -t "$id" -x 0
must_fail $TMUX resize-pane -t "$id" -y 0
$TMUX kill-pane -t "$id" || exit 1
# --- Tiled pane resize with floating cells in the layout ---
$TMUX set-option -w -u pane-border-lines || exit 1
base=$($TMUX display-message -p '#{pane_id}')
# A floating cell after the last tiled cell must not become the recipient of a
# relative or absolute resize. This is the sequence from GitHub issue 5135.
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
-x 50% -y 50% -X 50% -Y 50% 'sleep 100') ||
fail "new-pane for tiled resize test failed"
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
fail "split-window for tiled resize test failed"
$TMUX resize-pane -t "$lower" -U 5 || fail "relative tiled resize failed"
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 13
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
$TMUX resize-pane -t "$lower" -y 11 || fail "absolute tiled reset failed"
$TMUX resize-pane -t "$lower" -y 16 || fail "absolute tiled resize failed"
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
$TMUX kill-pane -t "$floating" || exit 1
$TMUX kill-pane -t "$lower" || exit 1
# A floating cell between tiled siblings must be skipped when finding the cell
# which donates space to a resize.
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
fail "split-window for middle floating cell test failed"
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
-x 20 -y 8 -X 30 -Y 8 'sleep 100') ||
fail "new-pane for middle floating cell test failed"
$TMUX resize-pane -t "$base" -D 3 || fail "resize past floating cell failed"
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 6
$TMUX kill-pane -t "$floating" || exit 1
$TMUX kill-pane -t "$lower" || exit 1
# A small floating child must not limit the available tiled space in a nested
# layout with a different split direction.
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
fail "split-window for nested floating cell test failed"
right=$($TMUX split-window -dhPF '#{pane_id}' -t "$lower" 'sleep 100') ||
fail "horizontal split for nested floating cell test failed"
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$lower" \
-x 20 -y 3 -X 30 -Y 10 'sleep 100') ||
fail "new-pane for nested floating cell test failed"
$TMUX resize-pane -t "$base" -D 3 || fail "nested tiled resize failed"
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
must_equal "$($TMUX display-message -p -t "$right" '#{pane_top}')" 16
must_equal "$($TMUX display-message -p -t "$right" '#{pane_height}')" 8
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 1
$TMUX kill-pane -t "$floating" || exit 1
$TMUX kill-pane -t "$right" || exit 1
$TMUX kill-pane -t "$lower" || exit 1
$TMUX kill-server 2>/dev/null
exit 0

View File

@@ -1,437 +0,0 @@
#!/bin/sh
# Grid-resident image layers: Kitty input, history, copy and overwrite.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Limage$$ -f/dev/null"
TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
TMP=$(mktemp)
FIXTURE=$(pwd)/monkey-2.sixel.txt
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
$TMUX new-session -d -x 20 -y 8 "
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,m=1;/wAA/wD/\033\\'
printf '\033_Gm=0;AP8AAP///////w==\033\\'
i=0
while [ \$i -lt 10 ]; do printf '\nline-%d' \$i; i=\$((i + 1)); done
sleep 10"
sleep 1
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
# Images scroll as grid cells, while capture output contains ordinary spaces.
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
$TMUX capture-pane -pS- >$TMP || exit 1
grep '_G' $TMP >/dev/null && exit 1
# Copy mode duplicates the backing grid, including image layers.
$TMUX copy-mode || exit 1
$TMUX send-keys -X history-top || exit 1
$TMUX capture-pane -p >$TMP || exit 1
$TMUX send-keys -X cancel || exit 1
# Ordinary text updates the image underlay through the normal grid write path.
# Deleting the image reveals the newly written text.
$TMUX new-window -d "
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,i=8;/wAA/wD/AP8AAP///////w==\033\\'
printf '\033[HXY'
printf '\033_Ga=d,d=i,q=2,i=8\033\\'
sleep 10"
sleep 1
[ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1
# The protocol default is transmit-only; a later placement uses the retained
# immutable image and advances by the same canonical one-cell footprint.
$TMUX new-window -d "
printf '\033_Gq=2,f=32,s=1,v=1,c=1,r=1,i=9;/wAA/w==\033\\'
printf '\033_Ga=p,q=2,i=9\033\\'
printf '\033_Ga=d,d=I,q=2,i=9\033\\'
sleep 10"
sleep 1
[ "$($TMUX display-message -pt:2 '#{cursor_y}')" = 1 ] || exit 1
# PNG Kitty input uses the shared image decoder and canonical cell sizing.
$TMUX new-window -d "
printf '\033_Ga=T,q=2,f=100;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURf8AAP///0EdNBEAAAABYktHRAH/Ai3eAAAAB3RJTUUH6ggCDAECH324BwAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\033\\'
sleep 10"
sleep 1
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 1 ] || exit 1
# SIXEL input reaches the same grid layer and copy-mode paths.
$TMUX new-window -d "cat '$FIXTURE'; sleep 10"
sleep 1
[ "$($TMUX display-message -pt:4 '#{cursor_y}')" -gt 0 ] || exit 1
$TMUX copy-mode -t:4 || exit 1
$TMUX send-keys -t:4 -X history-top || exit 1
$TMUX capture-pane -pt:4 >$TMP || exit 1
# A 26-pixel raster occupies two default 16-pixel cells, but the final cell
# remains only partially filled instead of stretching the raster to 32 pixels.
# A client without an image feature exposes this in the sampled text backend.
$TMUX kill-server 2>/dev/null
$TMUX2 new-session -d -x 10 -y 4 "
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
sleep 10" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
$TMUX new-session -d -x 10 -y 4 || exit 1
$TMUX set -g status off || exit 1
$TMUX pipe-pane -O "cat >$TMP" || exit 1
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
$TMUX send-keys Enter || exit 1
sleep 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
grep -q '^#=' $TMP || exit 1
# Re-emitting the image preserves the 26-pixel raster rather than expanding
# it to the two complete 16-pixel grid cells occupied by the image.
$TMUX pipe-pane || exit 1
$TMUX pipe-pane -O "cat >$TMP" || exit 1
$TMUX2 refresh-client -R || exit 1
sleep 1
grep -a '"1;1;26;26' $TMP >/dev/null || exit 1
# A selection redraw uses the single-cell path. It must leave ASCII image
# cells visible (graphical clients skip these cells to avoid erasing pixels).
$TMUX2 copy-mode || exit 1
$TMUX2 send-keys -X history-top || exit 1
$TMUX2 send-keys -X start-of-line || exit 1
$TMUX2 send-keys -N 4 -X cursor-right || exit 1
$TMUX2 send-keys -X begin-selection || exit 1
$TMUX pipe-pane || exit 1
$TMUX pipe-pane -O "cat >$TMP" || exit 1
$TMUX2 send-keys -X cursor-right || exit 1
sleep 1
$TMUX pipe-pane || exit 1
grep -a '"1;1;' $TMP >/dev/null && exit 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
grep -q '^#=' $TMP || exit 1
# Selection must also leave transparent cells inside the image canvas alone.
# This 26-pixel canvas paints only its first pixel column, while the text below
# it keeps the copy-mode cursor movable across both occupied terminal cells.
$TMUX2 send-keys -X cancel || exit 1
$TMUX2 new-window -d "
printf 'ab\r'
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0~-#0~-#0~-#0~-#0B\033\\'
sleep 10" || exit 1
$TMUX2 select-window -t:1 || exit 1
sleep 1
$TMUX2 copy-mode || exit 1
$TMUX2 send-keys -X top-line || exit 1
$TMUX2 send-keys -X start-of-line || exit 1
$TMUX2 send-keys -X cursor-right || exit 1
$TMUX2 send-keys -X begin-selection || exit 1
$TMUX pipe-pane || exit 1
$TMUX pipe-pane -O "cat >$TMP" || exit 1
$TMUX2 send-keys -X cursor-right || exit 1
sleep 1
$TMUX pipe-pane || exit 1
grep -a 'b' $TMP >/dev/null && exit 1
# Scrolling moves the terminal's image rows with its text rows. It must not
# force tmux to erase and retransmit the complete SIXEL image.
$TMUX2 send-keys -X cancel || exit 1
SCROLL_WINDOW=$($TMUX2 new-window -P -F '#{window_id}' \
"cat '$FIXTURE'; exec sh") || exit 1
sleep 1
$TMUX pipe-pane || exit 1
$TMUX pipe-pane -O "cat >$TMP" || exit 1
$TMUX2 send-keys -t"$SCROLL_WINDOW" -l \
"i=0; while [ \$i -lt 12 ]; do printf 'scroll-marker-%d\\n' \$i; i=\$((i + 1)); done" || exit 1
$TMUX2 send-keys -t"$SCROLL_WINDOW" Enter || exit 1
sleep 1
grep -a 'scroll-marker-11' $TMP >/dev/null || exit 1
grep -a '"1;1;' $TMP >/dev/null && exit 1
# A retained Kitty image may be placed repeatedly using source rectangles.
# Crop the green and white right column from a red/green/blue/white image.
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new-session -d -x 10 -y 4 "
printf '\033_Ga=t,q=2,f=32,s=2,v=2,i=10;/wAA/wD/AP8AAP///////w==\033\\'
printf '\033_Ga=p,q=2,i=10,x=1,y=0,w=1,h=2,c=1,r=2\033\\'
sleep 10" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX new-session -d -x 10 -y 4 || exit 1
$TMUX set -g status off || exit 1
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
$TMUX send-keys Enter || exit 1
sleep 1
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "*" ] || exit 1
[ "$(sed -n 2p $TMP)" = "@" ] || exit 1
# C=1 places the image without moving the cursor or scrolling. The four-row
# image is clipped at the bottom of this four-row pane, leaving its white last
# row off screen.
$TMUX2 new-window -d "
printf '\033[2;3H'
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
sleep 10" || exit 1
$TMUX2 select-window -t:1 || exit 1
sleep 1
[ "$($TMUX2 display-message -p '#{cursor_x},#{cursor_y}')" = "2,1" ] || exit 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 3p $TMP)" = " -" ] || exit 1
[ "$(sed -n 4p $TMP)" = " *" ] || exit 1
# With normal cursor movement, scrolling is calculated from the full image
# height. Rows which scrolled above the pane are then cropped from the top, so
# the bottom three source rows remain visible and the cursor is on the last row.
$TMUX2 new-window -d "
printf '\033[3;1H'
printf '\033_Ga=T,q=2,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
sleep 10" || exit 1
$TMUX2 select-window -t:2 || exit 1
sleep 1
[ "$($TMUX2 display-message -p '#{cursor_y}')" = 3 ] || exit 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "-" ] || exit 1
[ "$(sed -n 2p $TMP)" = "*" ] || exit 1
[ "$(sed -n 3p $TMP)" = "@" ] || exit 1
# Image rows remain cell-aligned when a narrower terminal causes text
# reflow. The ten-column rows are clipped to five columns, not split into four
# wrapped rows.
$TMUX2 new-window -d "
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=2,c=10,r=2;/wAA//////8=\033\\'
sleep 10" || exit 1
$TMUX2 select-window -t:3 || exit 1
sleep 1
$TMUX resize-window -x 5 -y 4 || exit 1
sleep 1
[ "$($TMUX2 display-message -p '#{window_width}x#{window_height}')" = "5x4" ] || exit 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "....." ] || exit 1
[ "$(sed -n 2p $TMP)" = "@@@@@" ] || exit 1
[ -z "$(sed -n 3p $TMP)" ] || exit 1
# Kitty virtual placements use U+10EEEE placeholder cells. Keep their rows at
# fixed coordinates when narrowing the terminal, clipping instead of reflowing
# the second half onto the following row.
$TMUX resize-window -x 10 -y 4 || exit 1
sleep 1
$TMUX2 new-window -d "
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
sleep 10" || exit 1
$TMUX2 select-window -t:4 || exit 1
sleep 1
$TMUX resize-window -x 5 -y 4 || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ -n "$(sed -n 1p $TMP)" ] || exit 1
[ -z "$(sed -n 2p $TMP)" ] || exit 1
$TMUX resize-window -x 10 -y 4 || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 1p $TMP | wc -c)" = 61 ] || exit 1
[ -z "$(sed -n 2p $TMP)" ] || exit 1
# A placement ID supplied with transmit-and-place is reused by a later place.
# This is the sequence used by chawan: the newline moves the cursor down before
# the second command, so the folder moves down one row without leaving a copy.
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=T,f=100,m=0;iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAACmUlEQVR4XmOgF2CEWVRfX8/w//9/BkZGRgy7YeKNjY1kuwtsam1tLQMTE5MG0MBsoEWW6KYBxf8DxScD6UVNTU1kWcZYU1MD0qjLzMx8EmgYJy5TgJb8/ffvn+2vX7+Od3Z2kmwZC9AnIE0FIEuAhl0H4ulA9k9kk4Bi4UB1TkA8H2iRYXl5+Xd2dna8loGCG2Q2LLgZQXED1PEIaLjs379/g4Ds9VDLwQYBxRiAPpFmZWW9DFQjCDRgLxDfJeQloJpdQHPWA9X9A1kGs+gZ0BBJoKQtUOIIeqSDgheoKR6IF5ASZkAH3gKaCzLzFQsxGltaWhjq6uoWguIJ6hi82oCG8wIV+AEdpga0LBfIrmUh1oWg1AYM5iVA9UuI1JMGVDcTiCNIsghkOChIQ0NDwfasXr0ap33QeL8GUgD0HTeIZmIgAUAtYQZqYYZZSKx2oi2CWaKpqbkIhEm1jOg40tLSAjleEBgUUSAGkJ8PpN8Q6yOCFoF8AvQBTvOAqZHh+vXrDPjijGAcIcXJUqBPQOXda5iNQP5rkBiQv5SYYMTrIxUVFZC5IkDCCk8QWQHVgdS8xBeMeBNDe3s7A7CwfQl0uSmwKDIBYhdQGQbCIDZIDCQHUgNSS7ZFII2gUgEU6UADzwJz+kUgDa6zoOyzIDmoGgaKLAJpBheKkArxC7BIeQDCQPEvIEuJrQyJzkdQA38ADbcHYaBFP0ipcVGqCWDYBwPxOkYs1TkDCeA/pEkQBDRnLZD9GKhVjgUkCGTsBgomAekWIJYG4p8MlAFQrZgJMgJo/m4QzQJ1fT9QIBLI1gTiSRRaAm/gAM38DjSvH2QeuHECLW1xNk7IsRhoyXGgJVOBem+AExPMEHzNLTItYiAlVTJQCwAAJxkMkgbHo2UAAAAASUVORK5CYII=\033\\'
printf '\n'
sleep 1
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=p;\033\\'
printf '\n'
sleep 10") || exit 1
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ -z "$(sed -n 1p $TMP)" ] || exit 1
[ -n "$(sed -n 2p $TMP)" ] || exit 1
# A nonzero Kitty placement ID identifies one placement of an image. Reusing
# the same image and placement IDs moves it rather than leaving the old cells.
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=11;/////w==\033\\'
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
sleep 10") || exit 1
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ -z "$(sed -n 2p $TMP)" ] || exit 1
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
# Deleting one placement ID leaves other placements of the image intact.
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=12;/////w==\033\\'
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=12,p=7,c=2,r=1\033\\'
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=12,p=8,c=2,r=1\033\\'
printf '\033_Ga=d,d=i,q=2,i=12,p=7\033\\'
sleep 10") || exit 1
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ -z "$(sed -n 2p $TMP)" ] || exit 1
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
# Image layers retain the cells beneath them. Deleting a transparent image
# must reveal the original text rather than replacing it with spaces.
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
printf 'XY'
printf '\033[H'
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=1,c=2,r=1,i=13,p=7;////AA==\033\\'
printf '\033_Ga=d,d=i,q=2,i=13,p=7\033\\'
sleep 10") || exit 1
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
sleep 1
[ "$($TMUX2 capture-pane -pS0 -E0)" = "XY" ] || exit 1
# Text written over a Kitty placement updates its underlay without deleting
# the image. Deleting the placement afterwards reveals the updated text.
TEXT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
printf 'test\r'
printf '\033_Ga=T,q=2,C=1,f=32,s=2,v=2,c=2,r=2,i=14,p=7;/wAA/wD/AP8AAP///////w==\033\\'
printf '\r'
printf 'test\n'
printf 'test\n'
$TEST_TMUX wait-for image-text-delete-$$
printf '\033_Ga=d,d=i,q=2,i=14,p=7\033\\'
sleep 10") || exit 1
$TMUX2 select-window -t"$TEXT_WINDOW" || exit 1
sleep 1
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = ".*st" ] || exit 1
[ "$(sed -n 2p $TMP)" = " @st" ] || exit 1
$TMUX2 wait-for -S image-text-delete-$$ || exit 1
sleep 1
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "test" ] || exit 1
[ "$(sed -n 2p $TMP)" = "test" ] || exit 1
# Kitty z-indexes and SIXEL's temporal plane are properties of the input, not
# of the outer terminal. Exercise the logical scene through the fallback
# backend: higher nonnegative z-indexes cover lower ones and text, ordinary
# negative z-indexes cover backgrounds but not text, and very negative ones
# remain below the background.
Z_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=21;/wAA/w==\033\\'
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=22;/////w==\033\\'
printf 'X\r'
printf '\033_Ga=p,q=2,C=1,i=21,p=1,z=2,c=1,r=1\033\\'
printf '\033_Ga=p,q=2,C=1,i=22,p=2,z=1,c=1,r=1\033\\'
printf '\033[2;1H\033[41m \033[0m\r'
printf '\033_Ga=p,q=2,C=1,i=21,p=3,z=-1,c=1,r=1\033\\'
printf '\033[3;1HY\r'
printf '\033_Ga=p,q=2,C=1,i=21,p=4,z=-1,c=1,r=1\033\\'
printf '\033[4;1H\033[41m \033[0m\r'
printf '\033_Ga=p,q=2,C=1,i=21,p=5,z=-1073741825,c=1,r=1\033\\'
$TEST_TMUX wait-for image-z-top-$$
printf '\033_Ga=d,d=z,q=2,z=2\033\\'
$TEST_TMUX wait-for image-z-lower-$$
printf '\033_Ga=d,d=z,q=2,z=1\033\\'
sleep 10") || exit 1
$TMUX2 select-window -t"$Z_WINDOW" || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "." ] || exit 1
[ "$(sed -n 2p $TMP)" = "." ] || exit 1
[ "$(sed -n 3p $TMP)" = "Y" ] || exit 1
[ -z "$(sed -n 4p $TMP)" ] || exit 1
$TMUX2 wait-for -S image-z-top-$$ || exit 1
sleep 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "@" ] || exit 1
$TMUX2 wait-for -S image-z-lower-$$ || exit 1
sleep 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "X" ] || exit 1
# A weighted median at the maximum channel level must still leave colours on
# both sides of the split. This skewed black, grey and white image used to stop
# palette generation after one colour instead of producing three.
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new-session -d -x 20 -y 4 "
printf '\033_Ga=T,q=2,C=1,f=24,s=100,v=1,c=10,r=1;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\033\\'
sleep 10" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
$TMUX new-session -d -x 20 -y 4 || exit 1
$TMUX set -g status off || exit 1
$TMUX pipe-pane -O "cat >$TMP" || exit 1
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
$TMUX send-keys Enter || exit 1
sleep 1
grep -a '#2;2;' $TMP >/dev/null || exit 1
# A pane redraw on a SIXEL terminal must not clear the status line, which is
# outside the window scene and may not be redrawn after the pane scrolls.
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new-session -d -x 20 -y 8 || exit 1
$TMUX2 set -g 'status-format[0]' 'STATUS' || exit 1
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
$TMUX new-session -d -x 20 -y 8 || exit 1
$TMUX set -g status off || exit 1
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
$TMUX send-keys Enter || exit 1
sleep 1
$TMUX2 send-keys -l 'seq 30' || exit 1
$TMUX2 send-keys Enter || exit 1
sleep 1
$TMUX capture-pane -p >$TMP || exit 1
[ "$(tail -n 1 $TMP)" = "STATUS" ] || exit 1
# A partial redraw of a tiled pane must clear only its visible scene spans.
# Clearing its entire rectangle would erase a floating pane above it, while
# the tiled pane redraw correctly skips the obscured area and cannot restore it.
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new-session -d -x 40 -y 15 \
"printf 'TILED PANE'; sleep 10" || exit 1
TILED_PANE=$($TMUX2 display-message -p '#{pane_id}') || exit 1
FLOATING_PANE=$($TMUX2 new-pane -dP -F '#{pane_id}' -x 20 -y 6 -X 10 -Y 4 \
"printf 'FLOATING PANE'; sleep 10") || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
$TMUX2 select-pane -t"$FLOATING_PANE" || exit 1
$TMUX new-session -d -x 40 -y 15 || exit 1
$TMUX set -g status off || exit 1
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
$TMUX send-keys Enter || exit 1
sleep 1
$TMUX2 copy-mode -t"$TILED_PANE" || exit 1
sleep 1
$TMUX capture-pane -p >$TMP || exit 1
grep 'TILED PANE' $TMP >/dev/null || exit 1
grep 'FLOATING PANE' $TMP >/dev/null || exit 1
# An obscured pane is normally redrawn a line at a time when it scrolls, but
# that path cannot restore graphical image layers. It must defer to the scene
# redraw so the visible part of the image is emitted around the floating pane.
IMAGE_PANE=$($TMUX2 new-window -dP -F '#{pane_id}' \
"cat '$FIXTURE'; exec sh") || exit 1
$TMUX2 set -g pane-border-status top || exit 1
$TMUX2 set -pt"$IMAGE_PANE" pane-border-format 'IMAGE STATUS' || exit 1
FLOATING_PANE=$($TMUX2 new-pane -dP -F '#{pane_id}' -x 20 -y 6 -X 10 -Y 4 \
"printf 'FLOATING PANE'; sleep 10") || exit 1
$TMUX2 set -pt"$FLOATING_PANE" pane-border-format 'FLOAT STATUS' || exit 1
$TMUX2 select-pane -t"$FLOATING_PANE" || exit 1
sleep 1
$TMUX pipe-pane || exit 1
$TMUX pipe-pane -O "cat >$TMP" || exit 1
$TMUX2 send-keys -t"$IMAGE_PANE" -l \
"i=0; while [ \$i -lt 5 ]; do printf 'image-scroll-%d\\n' \$i; i=\$((i + 1)); done" || exit 1
$TMUX2 send-keys -t"$IMAGE_PANE" Enter || exit 1
sleep 1
$TMUX pipe-pane || exit 1
grep -a 'image-scroll-4' $TMP >/dev/null || exit 1
grep -a '"1;1;' $TMP >/dev/null || exit 1
$TMUX capture-pane -p >$TMP || exit 1
grep 'IMAGE STATUS' $TMP >/dev/null || exit 1
grep 'FLOAT STATUS' $TMP >/dev/null || exit 1
exit 0

View File

@@ -0,0 +1,201 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
LANG=C.UTF-8
LC_ALL=C.UTF-8
export TERM LANG LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMPDIR=${TMPDIR:-/tmp}/tmux-local-pane.$$
mkdir -p "$TMPDIR" || exit 1
IN1="$TMPDIR/in1"
IN2="$TMPDIR/in2"
OUT1="$TMPDIR/out1"
OUT2="$TMPDIR/out2"
cleanup()
{
$TMUX kill-server 2>/dev/null
rm -rf "$TMPDIR"
}
trap cleanup 0 1 15
fail()
{
echo "$1"
exit 1
}
wait_for()
{
file=$1
pattern=$2
i=0
while [ "$i" -lt 20 ]; do
if grep -q "$pattern" "$file"; then
return 0
fi
sleep 1
i=$((i + 1))
done
echo "missing '$pattern' in $file"
cat "$file"
return 1
}
send1()
{
printf '%s\n' "$*" >&3
}
send2()
{
printf '%s\n' "$*" >&4
}
check_fmt()
{
out=$($TMUX display-message -p -t "$1" "$2" 2>&1)
if [ "$out" != "$3" ]; then
echo "Format '$2' for '$1' wrong."
echo "Expected: '$3'"
echo "But got: '$out'"
exit 1
fi
}
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -s active -x 80 -y 24 || exit 1
$TMUX split-window -d -h -t active:0 || exit 1
$TMUX split-window -d -v -t active:0.0 || exit 1
style=$($TMUX show-options -wgv pane-active-border-style) || exit 1
case "$style" in
*pane_local_active*themecyan*) ;;
*) fail "pane-active-border-style does not include local active colour" ;;
esac
p0=$($TMUX display-message -p -t active:0.0 '#{pane_id}') || exit 1
p1=$($TMUX display-message -p -t active:0.1 '#{pane_id}') || exit 1
p2=$($TMUX display-message -p -t active:0.2 '#{pane_id}') || exit 1
$TMUX select-pane -t "$p0" || exit 1
check_fmt active:0 '#{pane_id}' "$p0"
mkfifo "$IN1" "$IN2" || exit 1
: >"$OUT1"
: >"$OUT2"
$TMUX -C attach-session -t active <"$IN1" >"$OUT1" 2>&1 &
PID1=$!
exec 3>"$IN1"
send1 'display-message -p C1_READY'
wait_for "$OUT1" 'C1_READY' || exit 1
# One attached client cannot enter local mode.
send1 'select-pane -s'
send1 "select-pane -t $p1"
send1 'display-message -p "C1_SINGLE:#{pane_id}:#{pane_active}:#{pane_local_active}"'
wait_for "$OUT1" "C1_SINGLE:$p1:1:" || exit 1
check_fmt active:0 '#{pane_id}' "$p1"
send1 "select-pane -t $p0"
check_fmt active:0 '#{pane_id}' "$p0"
$TMUX -C attach-session -t active <"$IN2" >"$OUT2" 2>&1 &
PID2=$!
exec 4>"$IN2"
send2 'display-message -p C2_READY'
wait_for "$OUT2" 'C2_READY' || exit 1
# Both clients start in shared mode and see the window active pane.
send1 'display-message -p "C1_START:#{pane_id}:#{pane_active}:#{pane_local_active}"'
send2 'display-message -p "C2_START:#{pane_id}:#{pane_active}:#{pane_local_active}"'
wait_for "$OUT1" "C1_START:$p0:1:" || exit 1
wait_for "$OUT2" "C2_START:$p0:1:" || exit 1
# Client 1 enters local mode and selects a pane without changing shared state.
send1 'select-pane -s'
send1 "select-pane -t $p1"
send1 'display-message -p "C1_LOCAL:#{pane_id}:#{pane_active}:#{pane_local_active}:#{pane_local_mode}"'
send1 "display-message -p -t $p0 \"C1_LOCAL_INACTIVE:#{pane_id}:#{pane_active}:#{pane_local_active}\""
wait_for "$OUT1" "C1_LOCAL:$p1:1:1:1" || exit 1
wait_for "$OUT1" "C1_LOCAL_INACTIVE:$p0:0:0" || exit 1
check_fmt active:0 '#{pane_id}' "$p0"
send2 'display-message -p "C2_SHARED:#{pane_id}:#{pane_active}:#{pane_local_active}"'
wait_for "$OUT2" "C2_SHARED:$p0:1:" || exit 1
# Client 2 can keep an independent local pane in the same window.
send2 'select-pane -s'
send2 "select-pane -t $p2"
send2 'display-message -p "C2_LOCAL:#{pane_id}:#{pane_active}:#{pane_local_active}"'
wait_for "$OUT2" "C2_LOCAL:$p2:1:1" || exit 1
send1 'display-message -p "C1_STILL:#{pane_id}:#{pane_active}:#{pane_local_active}"'
wait_for "$OUT1" "C1_STILL:$p1:1:1" || exit 1
# Local last-pane history is independent of the shared window history.
send1 "select-pane -t $p2"
send1 'last-pane'
send1 'display-message -p "C1_LAST:#{pane_id}:#{pane_last}"'
wait_for "$OUT1" "C1_LAST:$p1:0" || exit 1
check_fmt active:0 '#{pane_id}' "$p0"
# Switching back to shared mode immediately follows window->active.
send1 'select-pane -S'
send1 'display-message -p "C1_SHARED_AGAIN:#{pane_id}:#{pane_active}"'
wait_for "$OUT1" "C1_SHARED_AGAIN:$p0:1" || exit 1
send1 "select-pane -t $p1"
check_fmt active:0 '#{pane_id}' "$p1"
send2 'display-message -p "C2_STILL_LOCAL:#{pane_id}:#{pane_active}"'
wait_for "$OUT2" "C2_STILL_LOCAL:$p2:1" || exit 1
# Local selection survives switching away and back to the window.
send2 'new-window -n other'
send2 'previous-window'
send2 'display-message -p "C2_AFTER_WINDOW_SWITCH:#{pane_id}:#{pane_active}"'
wait_for "$OUT2" "C2_AFTER_WINDOW_SWITCH:$p2:1" || exit 1
# Removing a locally active pane recovers to the shared active pane.
$TMUX kill-pane -t "$p2" || exit 1
send2 'display-message -p "C2_AFTER_KILL:#{pane_id}:#{pane_active}"'
wait_for "$OUT2" "C2_AFTER_KILL:$p1:1" || exit 1
# Local selection of a floating pane does not raise it unless requested.
f1=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 \
-t active:0 'sleep 100') || exit 1
f2=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 12 -Y 5 \
-t active:0 'sleep 100') || exit 1
check_fmt "$f1" '#{pane_z}' 1
check_fmt "$f2" '#{pane_z}' 0
send1 'select-pane -s'
send1 "select-pane -t $f1"
send1 "display-message -p -t $f1 \"C1_FLOAT_LOCAL:#{pane_id}:#{pane_active}:#{pane_z}\""
wait_for "$OUT1" "C1_FLOAT_LOCAL:$f1:1:1" || exit 1
check_fmt "$f1" '#{pane_z}' 1
send1 "select-pane -M -t $f1"
send1 "display-message -p -t $f1 \"C1_FLOAT_BORDER:#{pane_id}:#{pane_active}:#{pane_z}\""
wait_for "$OUT1" "C1_FLOAT_BORDER:$f1:1:1" || exit 1
check_fmt "$f1" '#{pane_z}' 1
send1 "move-pane -P front -t $f1"
send1 "display-message -p -t $f1 \"C1_FLOAT_RAISE:#{pane_z}\""
wait_for "$OUT1" "C1_FLOAT_RAISE:0" || exit 1
check_fmt "$f1" '#{pane_z}' 0
$TMUX kill-pane -t "$f1" || exit 1
$TMUX kill-pane -t "$f2" || exit 1
# When only one client remains, local mode is cleared.
send1 'select-pane -s'
send1 "select-pane -t $p0"
send1 'display-message -p "C1_LOCAL_BEFORE_DETACH:#{pane_id}:#{pane_local_active}"'
wait_for "$OUT1" "C1_LOCAL_BEFORE_DETACH:$p0:1" || exit 1
kill "$PID2" 2>/dev/null
wait "$PID2" 2>/dev/null
send1 'display-message -p "C1_AFTER_DETACH:#{pane_id}:#{pane_local_active}"'
wait_for "$OUT1" "C1_AFTER_DETACH:$p1:" || exit 1
kill "$PID1" 2>/dev/null
wait "$PID1" 2>/dev/null
$TMUX display-message -p alive >/dev/null 2>&1 || fail "server died"
exit 0

View File

@@ -105,6 +105,24 @@ drag()
sleep 1
}
meta_drag()
{
scol="$1"
srow="$2"
ecol="$3"
erow="$4"
seq=$(printf '\033[<8;%s;%sM' "$scol" "$srow")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 0.2
seq=$(printf '\033[<40;%s;%sM' "$ecol" "$erow")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 0.2
seq=$(printf '\033[<8;%s;%sm' "$ecol" "$erow")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 1
}
cleanup
check_ok new-session -d -s modal -x 80 -y 24 'cat'
@@ -127,12 +145,16 @@ case "$(fmt "$modal" '#{pane_flags}')" in
*O*) ;;
*) fail "modal pane flags do not include O" ;;
esac
case "$(fmt "$modal" '#{pane_flags}')" in
*A*) ;;
*) fail "modal pane flags do not include A" ;;
esac
case "$(fmt modal:0 '#{window_flags}')" in
*O*) ;;
*) fail "modal window flags do not include O" ;;
esac
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 0
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
check_fail "window already has a modal pane" \
new-pane -O -x 10 -y 4 'cat'
@@ -159,12 +181,14 @@ under=$($TMUX split-window -PF '#{pane_id}' -t "$p0" 'cat') ||
sleep 1
must_equal "$(fmt modal:0 '#{pane_id}')" "$modal"
must_equal "$(fmt "$under" '#{pane_active}')" 0
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
float=$($TMUX new-pane -PF '#{pane_id}' -x 10 -y 4 -X 5 -Y 3 'cat') ||
fail "new floating pane under modal failed"
sleep 1
must_equal "$(fmt modal:0 '#{pane_id}')" "$modal"
must_equal "$(fmt "$float" '#{pane_active}')" 0
must_equal "$(fmt modal:0 '#{window_zoomed_flag}')" 1
check_ok new-window -d -t modal: -n other 'cat'
check_ok select-window -t modal:other
@@ -303,6 +327,164 @@ check_ok kill-pane -t "$modal"
sleep 1
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
must_equal "$(fmt modal:0 '#{pane_id}')" "$p0"
must_equal "$(fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 0:0
ignored=$($TMUX new-pane -KdPF '#{pane_id}' -t "$p0" 'cat') ||
fail "new-pane -K without -O failed"
check_ok kill-pane -t "$ignored"
$TMUX set -g @modal-prefix no
$TMUX set -g @modal-root no
$TMUX bind -n z set -g @modal-root yes
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
-x 20 -y 5 -X 20 -Y 10 'cat') ||
fail "new-pane -OK failed"
sleep 1
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
sleep 1
must_equal "$($TMUX show -gv @modal-prefix)" no
must_equal "$($TMUX show -gv @modal-root)" no
case "$($TMUX capture-pane -pt "$modal")" in
*xz*) ;;
*) fail "keys did not reach key-capturing modal pane" ;;
esac
left=$(fmt "$modal" '#{pane_left}')
top=$(fmt "$modal" '#{pane_top}')
meta_drag $((left + 2)) $((top + 2)) $((left + 7)) $((top + 4))
new_left=$(fmt "$modal" '#{pane_left}')
new_top=$(fmt "$modal" '#{pane_top}')
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
fail "key-capturing modal pane did not move"
check_ok kill-pane -t "$modal"
sleep 1
# A nonmodal floating pane may remain above zoom, and switching between it and
# the zoomed tiled pane must not unzoom the window.
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
base=$(fmt modal:float-over-zoom '#{pane_id}')
check_ok split-window -dh -t "$base" 'cat'
check_ok resize-pane -Z -t "$base"
over=$($TMUX new-pane -APF '#{pane_id}' -t "$base" \
-x 20 -y 5 -X 20 -Y 10 'cat') ||
fail "new-pane -A failed"
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_active}')" 1:1
case "$(fmt "$over" '#{pane_flags}')" in
*A*) ;;
*) fail "float-over-zoom pane flags do not include A" ;;
esac
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
check_ok select-pane -t "$base"
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_active}')" 1:1
check_ok select-pane -t "$over"
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
client=$($TMUX list-clients -F '#{client_name}' | head -1)
[ -n "$client" ] || fail "no client for switch-client test"
check_ok switch-client -c "$client" -t "$base"
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_active}')" 1:1
check_ok switch-client -c "$client" -t "$over"
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
check_ok kill-pane -t "$over"
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
check_ok resize-pane -Z -t "$base"
ignored=$($TMUX new-pane -ALdPF '#{pane_id}' -t "$base" 'cat') ||
fail "new-pane -A -L failed"
case "$(fmt "$ignored" '#{pane_flags}')" in
*A*) fail "tiled pane flags include A" ;;
*) ;;
esac
# Existing floating panes are filtered when zoom begins: -A panes remain in the
# visible layout and ordinary floating panes do not.
check_ok new-window -d -t modal: -n existing-over-zoom 'cat'
base=$(fmt modal:existing-over-zoom '#{pane_id}')
check_ok split-window -dh -t "$base" 'cat'
over=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
-x 20 -y 5 -X 20 -Y 10 'cat') ||
fail "pre-existing new-pane -A failed"
under=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
-x 15 -y 4 -X 2 -Y 2 'cat') ||
fail "pre-existing ordinary new-pane failed"
check_ok select-window -t modal:existing-over-zoom
check_ok select-pane -t "$base"
check_ok resize-pane -Z -t "$base"
must_equal "$(fmt "$over" '#{pane_floating_flag}')" 1
must_equal "$(fmt "$under" '#{pane_floating_flag}')" 0
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
# Geometry changed in the visible zoom layout is copied back when unzooming.
check_ok select-pane -t "$over"
left=$(fmt "$over" '#{pane_left}')
top=$(fmt "$over" '#{pane_top}')
meta_drag $((left + 2)) $((top + 2)) $((left + 7)) $((top + 4))
new_left=$(fmt "$over" '#{pane_left}')
new_top=$(fmt "$over" '#{pane_top}')
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
fail "float-over-zoom pane did not move"
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
check_ok resize-pane -Z -t "$base"
must_equal "$(fmt "$over" '#{pane_left}:#{pane_top}')" \
"$new_left:$new_top"
must_equal "$(fmt "$under" '#{pane_floating_flag}')" 1
# Resizing with the over-zoom pane active restores the original zoom target.
check_ok resize-pane -Z -t "$base"
check_ok select-pane -t "$over"
check_ok resize-window -t modal:existing-over-zoom -x 90 -y 30
must_equal "$(fmt "$base" \
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}')" \
90x30:1:1
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_active}')" 1:1
# Natural pane exit uses a different removal path from kill-pane and must also
# preserve zoom.
dying=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
-x 12 -y 4 -X 4 -Y 3 'true') ||
fail "short-lived new-pane -A failed"
i=0
while $TMUX list-panes -a -F '#{pane_id}' | grep -qx "$dying"; do
i=$((i + 1))
[ $i -gt 50 ] && fail "short-lived float-over-zoom pane did not exit"
sleep 0.1
done
must_equal "$(fmt "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}')" 1:1
# A pane with -A is also above a zoom target which was itself floating. The
# temporary tiled target must sit behind retained floating panes, then return
# to the normal floating z order when unzoomed.
check_ok new-window -d -t modal: -n floating-zoom-target 'cat'
base=$(fmt modal:floating-zoom-target '#{pane_id}')
check_ok split-window -dh -t "$base" 'cat'
target=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
-x 30 -y 10 -X 10 -Y 5 'cat') ||
fail "floating zoom target creation failed"
over=$($TMUX new-pane -AdPF '#{pane_id}' -t "$base" \
-x 15 -y 5 -X 15 -Y 8 'cat') ||
fail "float-over-zoom pane creation failed"
check_ok select-window -t modal:floating-zoom-target
check_ok select-pane -t "$target"
must_equal "$(fmt "$target" '#{pane_z}')" 0
must_equal "$(fmt "$over" '#{pane_z}')" 1
check_ok resize-pane -Z -t "$target"
must_equal "$(fmt "$target" \
'#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_floating_flag}:#{pane_z}')" \
1:1:0:2
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_z}')" 1:0
check_ok select-pane -t "$over"
must_equal "$(fmt "$over" '#{window_zoomed_flag}:#{pane_active}')" 1:1
check_ok resize-pane -Z -t "$target"
must_equal "$(fmt "$over" '#{pane_active}:#{pane_z}')" 1:0
must_equal "$(fmt "$target" '#{pane_floating_flag}:#{pane_z}')" 1:1
# If the target remains active, it returns to the front on unzoom.
check_ok select-pane -t "$target"
check_ok resize-pane -Z -t "$target"
must_equal "$(fmt "$over" '#{pane_floating_flag}:#{pane_z}')" 1:0
check_ok resize-pane -Z -t "$target"
must_equal "$(fmt "$target" '#{pane_active}:#{pane_z}')" 1:0
must_equal "$(fmt "$over" '#{pane_z}')" 1
cleanup
exit 0

File diff suppressed because one or more lines are too long

View File

@@ -19,7 +19,10 @@
# - respawn-pane/respawn-window refusing a live pane without -k, working on a
# dead pane (remain-on-exit) and killing with -k;
# - resize-pane -x/-y in cells and percent, -L/-R/-U/-D adjustments and -Z
# zoom/unzoom (including implicit unzoom on split).
# zoom/unzoom, layout round trips, zoom target selection, pane removal,
# window resizing and implicit unzoom on layout changes;
# - swap-pane preserving one or two zoomed windows, including cross-window
# swaps of hidden panes and zoom targets.
#
# window-ops.sh covers window-level commands and buffers.sh paste buffers.
@@ -144,7 +147,8 @@ check_panes P:0 "0:$p3 1:$p0 2:$p2 3:$p1"
# break-pane and join-pane.
# break-pane moves a pane to a new window; -P -F prints where it went and -n
# names the new window.
# names the new window. Moving a pane out of a zoomed window unzooms it first.
check_ok resize-pane -Z -t "$p0"
out=$($TMUX break-pane -d -P -F '#{window_index}:#{pane_id}' -n broken \
-s "$p1" -t P:)
if [ "$out" != "1:$p1" ]; then
@@ -153,10 +157,14 @@ if [ "$out" != "1:$p1" ]; then
fi
check_fmt 'P:1' '#{window_name}:#{window_panes}' 'broken:1'
check_fmt 'P:0' '#{window_panes}' '3'
check_fmt 'P:0' '#{window_zoomed_flag}' '0'
# join-pane -v moves it back (the source window, left empty, is destroyed).
# join-pane -v moves it back (the source window, left empty, is destroyed) and
# also unzooms the destination before changing its layout.
check_ok resize-pane -Z -t "$p0"
check_ok join-pane -d -v -s P:broken.0 -t "$p2"
check_fmt 'P:0' '#{window_panes}' '4'
check_fmt 'P:0' '#{window_zoomed_flag}' '0'
if $TMUX has-session -t P:broken 2>/dev/null; then
echo "Window 'broken' still exists after join-pane."
exit 1
@@ -299,6 +307,122 @@ check_fmt 'P:0' '#{window_panes}' '5'
p6=$($TMUX display-message -p -t P:0.2 '#{pane_id}')
check_ok kill-pane -t "$p6"
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
# without -Z unzooms, while -Z transfers zoom to the selected pane.
layout=$($TMUX display-message -p -t P:0 '#{window_layout}')
check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0"
check_ok select-pane -t "$p2"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
check_fmt P:0 '#{window_layout}' "$layout"
check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0"
check_ok select-pane -Z -t "$p2"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1'
check_ok resize-pane -Z -t "$p2"
check_fmt P:0 '#{window_layout}' "$layout"
# Directional selection temporarily restores the full layout to find its
# neighbour, then follows the same unzoom or -Z transfer rules.
check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0"
check_ok select-pane -t "$p0"
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1'
check_ok select-pane -D -t "$p0"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
check_ok select-pane -t "$p0"
check_ok resize-pane -Z -t "$p0"
check_ok select-pane -D -Z -t "$p0"
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1'
check_ok resize-pane -Z -t "$p2"
check_fmt P:0 '#{window_layout}' "$layout"
# The last-pane path has separate zoom handling, both with and without -Z.
check_ok select-pane -t "$p0"
check_ok select-pane -t "$p2"
check_ok resize-pane -Z -t "$p2"
check_ok select-pane -l -t P:0
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'0:0:1'
check_ok select-pane -t "$p0"
check_ok select-pane -t "$p2"
check_ok resize-pane -Z -t "$p2"
check_ok select-pane -l -Z -t P:0
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1'
check_ok resize-pane -Z -t "$p0"
check_fmt P:0 '#{window_layout}' "$layout"
# Killing either a hidden ordinary pane or the zoom target unzooms.
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
zk0=$($TMUX display-message -p -t P:12.0 '#{pane_id}')
zk1=$($TMUX split-window -d -P -F '#{pane_id}' -t P:12.0 'cat')
zk2=$($TMUX split-window -d -P -F '#{pane_id}' -t P:12.0 'cat')
check_ok resize-pane -Z -t "$zk0"
check_ok kill-pane -t "$zk1"
check_fmt "$zk0" '#{window_panes}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
'2:0:0'
check_ok resize-pane -Z -t "$zk0"
check_ok kill-pane -t "$zk0"
check_fmt "$zk2" '#{window_panes}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
'1:0:0'
check_ok kill-window -t P:12
# The same cases through natural process exit exercise server_destroy_pane.
check_ok new-window -d -t P:13 -n zoom-exit 'cat'
ze0=$($TMUX display-message -p -t P:13.0 '#{pane_id}')
ze1=$($TMUX split-window -d -P -F '#{pane_id}' -t P:13.0 'cat')
ze2=$($TMUX split-window -d -P -F '#{pane_id}' -t P:13.0 'cat')
check_ok resize-pane -Z -t "$ze0"
check_ok send-keys -t "$ze1" C-d
i=0
while [ "$($TMUX display-message -p -t P:13 '#{window_panes}')" != 2 ]; do
i=$((i + 1))
[ $i -gt 50 ] && { echo "Hidden pane did not exit."; exit 1; }
sleep 0.1
done
check_fmt "$ze0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
check_ok resize-pane -Z -t "$ze2"
check_ok send-keys -t "$ze2" C-d
i=0
while [ "$($TMUX display-message -p -t P:13 '#{window_panes}')" != 1 ]; do
i=$((i + 1))
[ $i -gt 50 ] && { echo "Zoomed pane did not exit."; exit 1; }
sleep 0.1
done
check_fmt "$ze0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
check_ok kill-window -t P:13
# resize-window restores both tiled and floating zoom targets after rebuilding
# the layout at the new size.
check_ok new-window -d -t P:14 -n zoom-resize 'cat'
zr0=$($TMUX display-message -p -t P:14.0 '#{pane_id}')
check_ok split-window -d -t P:14.0 'cat'
check_ok resize-pane -Z -t "$zr0"
check_ok resize-window -t P:14 -x 90 -y 30
check_fmt "$zr0" \
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
'90x30:1:1'
check_ok resize-pane -Z -t "$zr0"
zrf=$($TMUX new-pane -dP -F '#{pane_id}' -t P:14 -x 20 -y 6 'cat')
zrf_size=$($TMUX display-message -p -t "$zrf" \
'#{pane_width}x#{pane_height}')
check_ok resize-pane -Z -t "$zrf"
check_ok resize-window -t P:14 -x 100 -y 32
check_fmt "$zrf" \
'#{window_width}x#{window_height}:#{window_zoomed_flag}:#{pane_zoomed_flag}' \
'100x32:1:1'
check_ok resize-pane -Z -t "$zrf"
check_fmt "$zrf" '#{pane_floating_flag}:#{pane_width}x#{pane_height}' \
"1:$zrf_size"
check_ok kill-window -t P:14
# ---------------------------------------------------------------------------
# kill-pane.
@@ -307,8 +431,10 @@ check_ok kill-pane -t "$p3"
check_panes P:0 "0:$p0 1:$p2 2:$p1"
# -a kills every pane except the target.
check_ok resize-pane -Z -t "$p0"
check_ok kill-pane -a -t "$p0"
check_panes P:0 "0:$p0"
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:0'
# Killing the last pane in a window kills the window.
check_ok new-window -d -t P:7 -n goner
@@ -516,6 +642,10 @@ check_panes P:3 "0:$r0 1:$r1 2:$r2"
# Swapping a pane with itself quietly does nothing.
check_ok swap-pane -d -s "$r1" -t "$r1"
check_panes P:3 "0:$r0 1:$r1 2:$r2"
check_ok resize-pane -Z -t "$r1"
check_ok swap-pane -d -Z -s "$r1" -t "$r1"
check_fmt "$r1" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
check_ok resize-pane -Z -t "$r1"
# Panes can be swapped between different windows.
check_ok swap-pane -d -s "$o0" -t "$r1"
@@ -525,10 +655,38 @@ check_ok swap-pane -d -s "$r1" -t "$o0"
check_panes P:3 "0:$r0 1:$r1 2:$r2"
check_panes P:5 "0:$o0"
# With both windows zoomed, -Z preserves each window's zoom when hidden panes
# are exchanged and when the zoom targets themselves are exchanged.
o1=$($TMUX split-window -dP -F '#{pane_id}' -t P:5.0)
check_ok resize-pane -Z -t "$r0"
check_ok resize-pane -Z -t "$o0"
check_ok swap-pane -d -Z -s "$r1" -t "$o1"
check_fmt "$r0" \
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'swaps:1:1'
check_fmt "$o0" \
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'other:1:1'
check_fmt "$r1" '#{window_name}' 'other'
check_fmt "$o1" '#{window_name}' 'swaps'
check_ok swap-pane -d -Z -s "$r0" -t "$o0"
check_fmt "$o0" \
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'swaps:1:1'
check_fmt "$r0" \
'#{window_name}:#{window_zoomed_flag}:#{pane_zoomed_flag}' 'other:1:1'
check_ok resize-pane -Z -t "$o0"
check_ok resize-pane -Z -t "$r0"
# Restore the original panes before the remaining swap tests.
check_ok swap-pane -d -s "$r0" -t "$o0"
check_ok swap-pane -d -s "$r1" -t "$o1"
check_ok kill-pane -t "$o1"
check_panes P:3 "0:$r0 1:$r1 2:$r2"
check_panes P:5 "0:$o0"
# -Z keeps the window zoomed across the swap.
check_ok resize-pane -Z -t "$r0"
check_ok swap-pane -d -Z -s "$r0" -t "$r1"
check_fmt 'P:3' '#{window_zoomed_flag}' '1'
check_fmt "$r0" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
check_ok resize-pane -Z -t P:3
check_panes P:3 "0:$r1 1:$r0 2:$r2"

View File

@@ -0,0 +1,87 @@
#!/bin/sh
# respawn-pane frees the pane's buffer and creates an empty one. The pane
# offsets and each control client's offsets used to keep pointing into the old
# buffer, so if an attached control client had not caught up on the pane's
# output, the next read from the new process ran off the end of the new buffer
# and the server crashed in input_parse.
#
# Two ways for a client to be behind are tested: a control client whose output
# goes down a fifo that is never read (a second, healthy control client keeps
# the pane being read), and a healthy control client viewing a session that
# the pane's window has been moved out of.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest"
$TMUX kill-server 2>/dev/null
DIR=$(mktemp -d)
FIFO=$DIR/fifo
SERVER=
mkfifo "$FIFO" || exit 1
cleanup() {
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
$TMUX kill-server 2>/dev/null
exec 8<&- 2>/dev/null
rm -rf "$DIR"
}
trap cleanup 0 1 15
alive() {
if ! kill -0 "$SERVER" 2>/dev/null; then
SERVER=
echo "server died after $1"
exit 1
fi
$TMUX has -t rt || exit 1
}
wait_clients() {
n=0
while [ $n -lt 50 ]; do
[ "$($TMUX lsc 2>/dev/null | wc -l)" -ge $1 ] && return
sleep 0.1
n=$((n + 1))
done
echo "control clients did not attach"; exit 1
}
# A detached session with a pane that writes a line every 10 milliseconds.
$TMUX -f/dev/null new -d -x 80 -y 24 -s rt \
'while :; do echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; sleep 0.01; done' ||
exit 1
SERVER=$($TMUX display -pt rt '#{pid}')
# One control client with output down the unread fifo and one reading
# normally; stdin of both held open by sleep so they stay attached.
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >"$FIFO" 2>&1 &
exec 8<"$FIFO"
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >/dev/null 2>&1 &
wait_clients 2
# Let the first client fall behind, then respawn the pane. The new process
# writes at once and the server must survive reading it.
sleep 3
$TMUX respawn-pane -k -t rt:0 'echo respawned; sleep 60' || exit 1
sleep 1
alive "respawn-pane with a lagging control client"
# Now a pane whose window is moved out of the session both clients view: their
# offsets for it stop advancing, so they fall behind until it is respawned.
$TMUX neww -d -t rt \
'while :; do echo yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy; sleep 0.01; done' ||
exit 1
sleep 1
$TMUX new -d -s other || exit 1
$TMUX movew -d -s rt:1 -t other: || exit 1
sleep 2
$TMUX respawn-pane -k -t other:1 'echo respawned; sleep 60' || exit 1
sleep 1
alive "respawn-pane on a window moved out of the clients' session"
exit 0

View File

@@ -14,8 +14,8 @@ DIR=$(mktemp -d) || exit 1
TMUX_TMPDIR=$DIR
export TMUX_TMPDIR
INNER="$TEST_TMUX -Lsync-output-inner-$$ -f/dev/null"
OUTER="$TEST_TMUX -Lsync-output-outer-$$ -f/dev/null"
INNER="$TEST_TMUX -Li$$ -f/dev/null"
OUTER="$TEST_TMUX -Lo$$ -f/dev/null"
CLIENT_BYTES=$DIR/client-bytes
CONTROL=$DIR/control
EMITTER=$DIR/emitter.pl
@@ -112,7 +112,7 @@ $INNER set-option -g window-size manual || exit 1
$INNER set-option -as terminal-features '*:sync' || exit 1
$OUTER new-session -d -s outer -x 80 -y 24 \
"$TEST_TMUX -Lsync-output-inner-$$ -f/dev/null attach-session -t inner" ||
"$TEST_TMUX -Li$$ -f/dev/null attach-session -t inner" ||
exit 1
$OUTER set-option -g status off || exit 1
$OUTER set-option -g window-size manual || exit 1

View File

@@ -105,6 +105,21 @@ sleep 1
CLIENT=$($TMUX2 list-clients -F '#{client_name}' | head -1)
[ -n "$CLIENT" ] || fail "no inner client"
# A variation selector which widens the previous character must use window
# coordinates when checking visibility in a pane with a nonzero x offset.
$TMUX2 set -s variation-selector-always-wide on || exit 1
WINDOW=$($TMUX2 neww -dPF '#{window_id}' "exec sleep 100") || exit 1
PANE=$($TMUX2 splitw -dhPF '#{pane_id}' -t "$WINDOW" \
"exec sleep 100") || exit 1
$TMUX2 selectw -t "$WINDOW" || exit 1
$TMUX2 respawnp -k -t "$PANE" \
"printf '\nA\342\234\217\357\270\217B'; exec sleep 100" || exit 1
sleep 1
$TMUX2 capturep -p -t "$PANE" >$TMP || exit 1
EXPECTED=$(printf 'A\342\234\217\357\270\217B')
check_line 2 "$EXPECTED"
$TMUX2 killw -t "$WINDOW" || exit 1
# Long line, then short line: default cells after cellsize must clear stale text.
capture
check_line 1 "abcdefghijklmnopqrst"

View File

@@ -17,7 +17,8 @@
# unlink the last link without -k;
# - swap-window within and between sessions, -d keeping the active window,
# and the grouped-sessions error;
# - rotate-window -U/-D rotating pane positions;
# - rotate-window -U/-D rotating pane positions, with and without preserving
# zoom using -Z;
# - kill-window switching to the last (previously current) window, kill-window
# -a killing all other windows and the "-f only valid with -a" guard.
#
@@ -306,6 +307,24 @@ check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p1"
check_ok rotate-window -D -t R:0
check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p0"
# Rotation without -Z unzooms. With -Z it preserves zoom and transfers it to
# the pane which arrives at the active position.
layout=$($TMUX display-message -p -t R:0 '#{window_layout}')
check_ok resize-pane -Z -t "$p0"
check_ok rotate-window -U -t R:0
check_fmt "$p1" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'0:0:1'
check_ok rotate-window -D -t R:0
check_fmt 'R:0' '#{window_layout}' "$layout"
check_ok resize-pane -Z -t "$p0"
check_ok rotate-window -U -Z -t R:0
check_fmt "$p1" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
'1:1:1'
check_ok resize-pane -Z -t "$p1"
check_ok rotate-window -D -t R:0
check_fmt 'R:0' '#{window_layout}' "$layout"
# ---------------------------------------------------------------------------
# kill-window.

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: resize.c,v 1.58 2026/07/17 08:37:29 nicm Exp $ */
/* $OpenBSD: resize.c,v 1.59 2026/08/20 09:19:24 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -42,8 +42,8 @@ resize_fire_window_resized(struct window *w, u_int old_sx, u_int old_sy)
void
resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
{
u_int old_sx = w->sx, old_sy = w->sy;
int zoomed;
struct window_pane *zwp;
u_int old_sx = w->sx, old_sy = w->sy;
/* Check size limits. */
if (sx < WINDOW_MINIMUM)
@@ -56,8 +56,8 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
sy = WINDOW_MAXIMUM;
/* If the window is zoomed, unzoom. */
zoomed = w->flags & WINDOW_ZOOMED;
if (zoomed)
zwp = window_zoomed_pane(w);
if (zwp != NULL)
window_unzoom(w, 1);
/* Resize the layout first. */
@@ -73,8 +73,8 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
sx, sy, w->layout_root->g.sx, w->layout_root->g.sy);
/* Restore the window zoom state. */
if (zoomed)
window_zoom(w->active);
if (zwp != NULL && window_has_pane(w, zwp))
window_zoom(zwp);
tty_update_window_offset(w);
server_redraw_window(w);
@@ -348,10 +348,10 @@ recalculate_size_skip_client(struct client *loop, __unused int type,
* is not the current window - this is used for aggressive-resize.
* Otherwise skip any session that doesn't contain the window.
*/
if (loop->session->curw == NULL)
if (active_get_effective_window(loop, loop->session) == NULL)
return (1);
if (current)
return (loop->session->curw->window != w);
return (active_get_effective_window(loop, loop->session) != w);
return (session_has(loop->session, w) == 0);
}
@@ -386,12 +386,10 @@ recalculate_size(struct window *w, int now)
* got a resize scheduled, then use the new size; otherwise the old.
*/
if (w->flags & WINDOW_RESIZE) {
if (!now && changed && w->new_sx == sx && w->new_sy == sy &&
w->new_xpixel == xpixel && w->new_ypixel == ypixel)
if (!now && changed && w->new_sx == sx && w->new_sy == sy)
changed = 0;
} else {
if (!now && changed && w->sx == sx && w->sy == sy &&
w->xpixel == xpixel && w->ypixel == ypixel)
if (!now && changed && w->sx == sx && w->sy == sy)
changed = 0;
}

View File

@@ -99,13 +99,6 @@ enum redraw_span_type {
#define REDRAW_ALL 0x7fffffff
#define REDRAW_IS_ALL(flags) ((flags) == REDRAW_ALL)
enum redraw_image_phase {
REDRAW_IMAGES_BEFORE,
REDRAW_TEXT,
REDRAW_IMAGES_AFTER,
REDRAW_CHROME
};
/* UTF-8 isolate characters. */
#define REDRAW_START_ISOLATE "\342\201\246"
#define REDRAW_END_ISOLATE "\342\201\251"
@@ -302,7 +295,7 @@ static void
redraw_set_context(struct client *c, struct redraw_build_ctx *bctx)
{
struct session *s = c->session;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
memset(bctx, 0, sizeof *bctx);
bctx->c = c;
@@ -976,7 +969,7 @@ static struct redraw_scene *
redraw_make_scene(struct client *c)
{
struct session *s = c->session;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct redraw_build_ctx bctx;
struct redraw_scene *scene;
struct redraw_build_cell *bc, *last;
@@ -1086,7 +1079,7 @@ static struct redraw_scene *
redraw_get_scene(struct client *c)
{
struct redraw_scene *scene = c->redraw_scene;
struct window *w = c->session->curw->window;
struct window *w = active_get_effective_window(c, c->session);
const char *reason = NULL;
u_int ox, oy, sx, sy;
@@ -1113,8 +1106,7 @@ redraw_get_scene(struct client *c)
/* Draw a pane span. */
static void
redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
struct redraw_span *span, u_int x, u_int y, u_int n,
enum redraw_image_phase phase)
struct redraw_span *span, u_int x, u_int y, u_int n)
{
struct redraw_scene *scene = dctx->scene;
struct client *c = scene->c;
@@ -1125,21 +1117,14 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
struct tty_style_ctx style_ctx;
u_int px, py;
tty_default_colours(&defaults, wp, &style_ctx.dim);
tty_default_colours(&defaults, wp, dctx->active, &style_ctx.dim);
style_ctx.defaults = &defaults;
style_ctx.palette = &wp->palette;
style_ctx.hyperlinks = s->hyperlinks;
px = span->data.p.px + (x - span->x);
py = span->data.p.py;
#ifdef ENABLE_IMAGES
if (phase != REDRAW_TEXT)
image_draw_line(tty, s, px, py, n, x, y,
phase == REDRAW_IMAGES_BEFORE, &style_ctx);
else
#endif
if (phase == REDRAW_TEXT)
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
}
/* Get default border style for spans without a pane. */
@@ -1155,7 +1140,7 @@ redraw_get_default_border_style(struct redraw_draw_ctx *dctx,
struct grid_cell *dgc = &dctx->default_gc;
if (~dctx->flags & REDRAW_DEFAULT_SET) {
ft = format_create_defaults(NULL, c, s, s->curw, NULL);
ft = format_create_defaults(NULL, c, s, NULL, NULL);
memcpy(dgc, &grid_default_cell, sizeof *dgc);
style_add(dgc, oo, "pane-border-style", ft);
format_free(ft);
@@ -1349,7 +1334,7 @@ redraw_draw_scrollbar_span(struct redraw_draw_ctx *dctx,
memcpy(&slgc, &gc, sizeof slgc);
slgc.fg = gc.bg;
slgc.bg = gc.fg;
tty_default_colours(&pad_gc, wp, NULL);
tty_default_colours(&pad_gc, wp, dctx->active, NULL);
sb_w = sb_style->width;
sb_pad = sb_style->pad;
@@ -1394,7 +1379,7 @@ redraw_draw_menu_span(struct redraw_draw_ctx *dctx,
/* Draw a span. */
static void
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
u_int y, enum redraw_image_phase phase)
u_int y)
{
struct redraw_scene *scene = dctx->scene;
struct redraw_span_data *data = &span->data;
@@ -1415,15 +1400,10 @@ redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
continue;
x = rr->px;
n = rr->nx;
if (type == REDRAW_SPAN_PANE) {
if (phase == REDRAW_CHROME)
continue;
} else if (phase != REDRAW_CHROME)
continue;
switch (span->data.type) {
case REDRAW_SPAN_PANE:
redraw_draw_pane_span(dctx, span, x, y, n, phase);
redraw_draw_pane_span(dctx, span, x, y, n);
break;
case REDRAW_SPAN_BORDER:
case REDRAW_SPAN_EMPTY:
@@ -1452,11 +1432,6 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
struct redraw_line *line;
struct redraw_spans *spans;
struct redraw_span *span;
#ifdef ENABLE_IMAGES
struct visible_ranges *r;
struct visible_range *rr;
u_int i;
#endif
u_int cy;
int y, top, bottom;
@@ -1469,53 +1444,24 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
if (bottom > (int)scene->sy)
bottom = scene->sy;
#ifdef ENABLE_IMAGES
if (flags & REDRAW_PANE) {
for (y = top; y < bottom; y++) {
line = &scene->lines[y];
for (y = top; y < bottom; y++) {
line = &scene->lines[y];
if (dctx->flags & REDRAW_STATUS_TOP)
cy = dctx->status_lines + y;
else
cy = y;
if (dctx->flags & REDRAW_STATUS_TOP)
cy += dctx->status_lines;
if (flags & REDRAW_PANE) {
spans = &line->spans[REDRAW_SPAN_PANE];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.p.wp != wp)
continue;
r = tty_check_overlay_range(&scene->c->tty,
span->x, cy, span->width);
for (i = 0; i < r->used; i++) {
rr = &r->ranges[i];
if (rr->nx != 0) {
image_redraw_start(&scene->c->tty,
rr->px, cy, rr->nx, 1);
}
}
if (span->data.p.wp == wp)
redraw_draw_span(dctx, span, cy);
}
}
}
#endif
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
phase <= REDRAW_CHROME; phase++) {
for (y = top; y < bottom; y++) {
line = &scene->lines[y];
if (dctx->flags & REDRAW_STATUS_TOP)
cy = dctx->status_lines + y;
else
cy = y;
if (flags & REDRAW_PANE) {
spans = &line->spans[REDRAW_SPAN_PANE];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.p.wp == wp)
redraw_draw_span(dctx, span, cy, phase);
}
}
if (phase == REDRAW_CHROME &&
(flags & REDRAW_PANE_SCROLLBAR)) {
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.sb.wp == wp)
redraw_draw_span(dctx, span, cy, phase);
}
if (flags & REDRAW_PANE_SCROLLBAR) {
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.sb.wp == wp)
redraw_draw_span(dctx, span, cy);
}
}
}
@@ -1531,15 +1477,13 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
struct redraw_span *span;
u_int y, cy, type;
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
phase <= REDRAW_CHROME; phase++) {
for (y = 0; y < scene->sy; y++) {
line = &scene->lines[y];
if (dctx->flags & REDRAW_STATUS_TOP)
cy = dctx->status_lines + y;
else
cy = y;
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
for (y = 0; y < scene->sy; y++) {
line = &scene->lines[y];
if (dctx->flags & REDRAW_STATUS_TOP)
cy = dctx->status_lines + y;
else
cy = y;
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
if (!REDRAW_IS_ALL(flags)) {
switch (type) {
case REDRAW_SPAN_PANE:
@@ -1576,8 +1520,7 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
}
spans = &line->spans[type];
TAILQ_FOREACH(span, spans, entry)
redraw_draw_span(dctx, span, cy, phase);
}
redraw_draw_span(dctx, span, cy);
}
}
}
@@ -1598,7 +1541,7 @@ redraw_draw_menu_lines(struct redraw_draw_ctx *dctx)
else
cy = y;
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry)
redraw_draw_span(dctx, span, cy, REDRAW_TEXT);
redraw_draw_span(dctx, span, cy);
}
}
@@ -1660,14 +1603,16 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx,
struct session *s = c->session;
struct options *oo = s->options;
struct tty *tty = &c->tty;
struct winlink *wl;
u_int lines;
memset(dctx, 0, sizeof *dctx);
dctx->scene = scene;
if (server_is_marked(s, s->curw, marked_pane.wp))
wl = active_get_effective_winlink(c, s);
if (server_is_marked(s, wl, marked_pane.wp))
dctx->marked = marked_pane.wp;
dctx->active = s->curw->window->active;
dctx->active = active_get_effective_pane(c, wl->window);
lines = status_line_size(c);
if (options_get_number(oo, "status-position") == 0)
@@ -1740,7 +1685,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
{
struct redraw_draw_ctx dctx;
struct session *s = c->session;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct tty *tty = &c->tty;
struct screen *sl;
struct redraw_scene *scene;
@@ -1788,10 +1733,9 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
}
if (flags & REDRAW_PANE_STATUS) {
redraw = !!(flags & REDRAW_PANE_BORDER);
redraw = 0;
TAILQ_FOREACH(loop, &scene->w->panes, entry) {
if (REDRAW_IS_ALL(flags) ||
(flags & REDRAW_PANE_BORDER))
if (REDRAW_IS_ALL(flags))
loop->flags |= PANE_NEWSTATUS;
else
loop->flags &= ~PANE_NEWSTATUS;
@@ -1829,14 +1773,6 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
}
}
tty_sync_start(tty);
#ifdef ENABLE_IMAGES
if (wp == NULL && (flags & REDRAW_PANE)) {
y = 0;
if (dctx.flags & REDRAW_STATUS_TOP)
y = dctx.status_lines;
image_redraw_start(tty, 0, y, scene->sx, scene->sy);
}
#endif
tty_update_mode(tty, tty->mode & ~CURSOR_MODES, NULL);
if (wp != NULL)
@@ -1883,6 +1819,15 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
tty_reset(tty);
tty_sync_end(tty);
#ifdef ENABLE_SIXEL
if (wp != NULL)
tty_draw_images(c, wp);
else {
TAILQ_FOREACH(loop, &scene->w->panes, entry)
tty_draw_images(c, loop);
}
#endif
log_debug("%s: finished @%u redraw", c->name, scene->w->id);
}
@@ -1940,7 +1885,7 @@ redraw_screen(struct client *c)
flags |= REDRAW_OVERLAY;
if (c->flags & CLIENT_REDRAWMENU)
flags |= REDRAW_MENU;
if (c->session->curw->window->menu != NULL)
if (active_get_effective_window(c, c->session)->menu != NULL)
flags |= REDRAW_MENU;
if (flags != 0)
redraw_draw(c, NULL, flags);
@@ -1952,7 +1897,7 @@ void
redraw_pane(struct client *c, struct window_pane *wp)
{
redraw_draw(c, wp, REDRAW_PANE|REDRAW_PANE_SCROLLBAR);
if (c->session->curw->window->menu != NULL)
if (active_get_effective_window(c, c->session)->menu != NULL)
redraw_draw(c, NULL, REDRAW_MENU);
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: screen-write.c,v 1.286 2026/08/03 12:58:53 nicm Exp $ */
/* $OpenBSD: screen-write.c,v 1.290 2026/08/24 15:05:26 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -34,10 +34,6 @@ static void screen_write_collect_clear(struct screen_write_ctx *, u_int,
static void screen_write_collect_scroll(struct screen_write_ctx *, u_int);
static void screen_write_collect_flush(struct screen_write_ctx *, int,
const char *);
#ifdef ENABLE_IMAGES
static int screen_write_image_damage(struct screen_write_ctx *, u_int,
u_int, u_int, u_int);
#endif
static int screen_write_overwrite(struct screen_write_ctx *,
struct grid_cell *, u_int);
static int screen_write_combine(struct screen_write_ctx *,
@@ -138,9 +134,7 @@ screen_write_redraw_cb(const struct tty_ctx *ttyctx)
static int
screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
{
struct window_pane *wp = ttyctx->arg;
u_int sx, sb_w = 0;
int xoff;
struct window_pane *wp = ttyctx->arg, *active;
if (ttyctx->flags & TTY_CTX_INVISIBLE_PANES) {
if (session_has(c->session, wp->window))
@@ -148,7 +142,7 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
return (0);
}
if (c->session->curw->window != wp->window)
if (active_get_effective_window(c, c->session) != wp->window)
return (0);
if (wp->layout_cell == NULL)
return (0);
@@ -166,29 +160,17 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
return (-1);
}
active = active_get_effective_pane(c, wp->window);
if (tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx,
&ttyctx->wsy))
ttyctx->flags |= TTY_CTX_WINDOW_BIGGER;
else
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
tty_default_colours(&ttyctx->defaults, wp, active,
&ttyctx->style_ctx.dim);
ttyctx->xoff = ttyctx->rxoff = wp->xoff;
ttyctx->yoff = ttyctx->ryoff = wp->yoff;
ttyctx->flags &= ~TTY_CTX_PANE_FULL_WIDTH;
if (~ttyctx->flags & TTY_CTX_WINDOW_BIGGER &&
!window_has_floating_panes(wp->window)) {
xoff = wp->xoff;
sx = wp->sx;
if (window_pane_scrollbar_reserve(wp)) {
sb_w = wp->scrollbar_style.width +
wp->scrollbar_style.pad;
sx += sb_w;
if (wp->window->sb_pos == PANE_SCROLLBARS_LEFT)
xoff -= sb_w;
}
if (xoff == 0 && sx >= c->tty.sx)
ttyctx->flags |= TTY_CTX_PANE_FULL_WIDTH;
}
if (status_at_line(c) == 0)
ttyctx->yoff += status_line_size(c);
@@ -317,7 +299,7 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
ttyctx->redraw_cb = screen_write_redraw_cb;
if (ctx->wp != NULL) {
tty_default_colours(&ttyctx->defaults, ctx->wp,
&ttyctx->style_ctx.dim);
ctx->wp->window->active, &ttyctx->style_ctx.dim);
ttyctx->style_ctx.palette = &ctx->wp->palette;
ttyctx->set_client_cb = screen_write_set_client_cb;
ttyctx->arg = ctx->wp;
@@ -326,12 +308,14 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
if (~ctx->flags & SCREEN_WRITE_SYNC) {
/*
* For the active pane or for an overlay (no pane), we want to
* only use synchronized updates if requested (commands that
* move the cursor); for other panes, always use it, since the
* cursor will have to move.
* For the active pane showing its base screen or for an
* overlay (no pane), only use synchronized updates if
* requested (commands that move the cursor); for other panes
* or a pane in a mode, always use it, since the cursor will
* have to move.
*/
if (ctx->wp != NULL && ctx->wp != ctx->wp->window->active)
if (ctx->wp != NULL && (ctx->wp != ctx->wp->window->active ||
ctx->wp->screen != &ctx->wp->base))
ttyctx->flags |= TTY_CTX_SYNC;
else {
if (ctx->wp == NULL)
@@ -359,10 +343,18 @@ screen_write_make_list(struct screen *s)
void
screen_write_free_list(struct screen *s)
{
u_int y;
struct screen_write_cline *cl;
struct screen_write_citem *ci, *ci1;
u_int y;
for (y = 0; y < screen_size_y(s); y++)
free(s->write_list[y].data);
for (y = 0; y < screen_size_y(s); y++) {
cl = &s->write_list[y];
TAILQ_FOREACH_SAFE(ci, &cl->items, entry, ci1) {
TAILQ_REMOVE(&cl->items, ci, entry);
screen_write_free_citem(ci);
}
free(cl->data);
}
free(s->write_list);
}
@@ -382,23 +374,6 @@ screen_write_init(struct screen_write_ctx *ctx, struct screen *s)
ctx->bg = 8;
}
#ifdef ENABLE_IMAGES
/* Damage temporal images when application input writes text cells. */
static int
screen_write_image_damage(struct screen_write_ctx *ctx, u_int x, u_int y,
u_int sx, u_int sy)
{
int found;
if (~ctx->flags & SCREEN_WRITE_INPUT)
return (0);
found = image_grid_check_area(ctx->s->grid, x,
ctx->s->grid->hsize + y, sx, sy);
image_grid_damage(ctx->s->grid, x, ctx->s->grid->hsize + y, sx, sy);
return (found);
}
#endif
/* Initialize writing with a pane. */
void
screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp,
@@ -1097,6 +1072,19 @@ screen_write_stop_sync(struct window_pane *wp)
log_debug("%s: %%%u stopped sync mode", __func__, wp->id);
}
/* Flush pending output before clearing sync mode. */
void
screen_write_end_sync(struct screen_write_ctx *ctx)
{
struct window_pane *wp = ctx->wp;
if (wp == NULL)
return;
if (wp->base.mode & MODE_SYNC)
screen_write_collect_flush(ctx, 0, __func__);
screen_write_stop_sync(wp);
}
/* Cursor up by ny. */
void
screen_write_cursorup(struct screen_write_ctx *ctx, u_int ny)
@@ -1323,14 +1311,15 @@ screen_write_clear_dirty(struct window_pane *wp)
}
}
/* Redraw an obscured pane through the scene to preserve floating panes. */
/* Redraw all visible cells in a pane. */
static void
screen_write_redraw_pane(struct screen_write_ctx *ctx)
screen_write_redraw_pane(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
{
if (ctx->wp != NULL) {
ctx->wp->flags |= PANE_REDRAW;
server_redraw_window_borders(ctx->wp->window);
}
struct screen *s = ctx->s;
u_int yy;
for (yy = 0; yy < screen_size_y(s); yy++)
screen_write_redraw_line(ctx, ttyctx, yy);
}
/* VT100 alignment test. */
@@ -1345,11 +1334,9 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
memcpy(&gc, &grid_default_cell, sizeof gc);
utf8_set(&gc.data, 'E');
#ifdef ENABLE_IMAGES
image_redraw_all(ctx);
if (ctx->flags & SCREEN_WRITE_INPUT)
image_grid_damage(s->grid, 0, s->grid->hsize,
screen_size_x(s), screen_size_y(s));
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
for (yy = 0; yy < screen_size_y(s); yy++) {
@@ -1373,7 +1360,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
return;
}
screen_write_redraw_pane(ctx);
screen_write_redraw_pane(ctx, &ttyctx);
}
/* Insert nx characters. */
@@ -1394,8 +1381,9 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
@@ -1434,8 +1422,9 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
@@ -1474,8 +1463,9 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
@@ -1508,6 +1498,11 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0)
ny = 1;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
if (s->cy < s->rupper || s->cy > s->rlower) {
if (ny > sy - s->cy)
ny = sy - s->cy;
@@ -1529,7 +1524,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
return;
}
screen_write_redraw_pane(ctx);
screen_write_redraw_pane(ctx, &ttyctx);
return;
}
@@ -1556,7 +1551,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
return;
}
screen_write_redraw_pane(ctx);
screen_write_redraw_pane(ctx, &ttyctx);
}
/* Delete ny lines. */
@@ -1571,6 +1566,11 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0)
ny = 1;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
if (s->cy < s->rupper || s->cy > s->rlower) {
if (ny > sy - s->cy)
ny = sy - s->cy;
@@ -1593,7 +1593,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
return;
}
screen_write_redraw_pane(ctx);
screen_write_redraw_pane(ctx, &ttyctx);
return;
}
@@ -1621,7 +1621,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
return;
}
screen_write_redraw_pane(ctx);
screen_write_redraw_pane(ctx, &ttyctx);
}
/* Clear line at cursor. */
@@ -1639,8 +1639,9 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
return;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
flags = gl->flags & GRID_LINE_OSC133_FLAGS;
@@ -1677,8 +1678,9 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
return;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg);
@@ -1703,8 +1705,9 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
return;
}
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
if (s->cx > sx - 1)
@@ -1749,9 +1752,6 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
struct screen *s = ctx->s;
struct tty_ctx ttyctx;
u_int ry;
#ifdef ENABLE_IMAGES
int image_scroll;
#endif
if (s->cy != s->rupper) {
if (s->cy > 0)
@@ -1759,20 +1759,16 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
return;
}
#ifdef ENABLE_IMAGES
image_scroll = image_grid_check_area(s->grid, 0,
s->grid->hsize + s->rupper, screen_size_x(s),
s->rlower - s->rupper + 1);
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
screen_write_collect_flush(ctx, 0, __func__);
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
if (image_scroll)
ttyctx.flags |= TTY_CTX_IMAGE_SCROLL;
#endif
ry = s->rlower + 1 - s->rupper;
if (!screen_write_should_draw_lines(ctx, s->rupper, ry))
@@ -1782,7 +1778,7 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
return;
}
screen_write_redraw_pane(ctx);
screen_write_redraw_pane(ctx, &ttyctx);
}
/* Set scroll region. */
@@ -1815,6 +1811,9 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
struct screen *s = ctx->s;
struct grid *gd = s->grid;
struct grid_line *gl;
#ifdef ENABLE_SIXEL
int redraw = 0;
#endif
u_int rupper = s->rupper, rlower = s->rlower;
gl = grid_get_line(gd, gd->hsize + s->cy);
@@ -1835,10 +1834,13 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
return;
}
#ifdef ENABLE_IMAGES
if (image_grid_check_area(gd, 0, gd->hsize + rupper,
screen_size_x(s), rlower - rupper + 1))
ctx->scrolled_images = 1;
#ifdef ENABLE_SIXEL
if (rlower == screen_size_y(s) - 1)
redraw = image_scroll_up(s, 1);
else
redraw = image_check_line(s, rupper, rlower - rupper);
if (redraw && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
@@ -1864,10 +1866,9 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg)
ctx->bg = bg;
}
#ifdef ENABLE_IMAGES
if (image_grid_check_area(gd, 0, gd->hsize + s->rupper,
screen_size_x(s), s->rlower - s->rupper + 1))
ctx->scrolled_images = 1;
#ifdef ENABLE_SIXEL
if (image_scroll_up(s, lines) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
for (i = 0; i < lines; i++) {
@@ -1894,10 +1895,9 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
else if (lines > s->rlower - s->rupper + 1)
lines = s->rlower - s->rupper + 1;
#ifdef ENABLE_IMAGES
if (image_grid_check_area(gd, 0, gd->hsize + s->rupper,
screen_size_x(s), s->rlower - s->rupper + 1))
ttyctx.flags |= TTY_CTX_IMAGE_SCROLL;
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
for (i = 0; i < lines; i++)
@@ -1914,7 +1914,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
return;
}
screen_write_redraw_pane(ctx);
screen_write_redraw_pane(ctx, &ttyctx);
}
/* Carriage return (cursor to start of line). */
@@ -1936,8 +1936,9 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
@@ -2016,8 +2017,9 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, 0, screen_size_x(s), s->cy + 1);
#ifdef ENABLE_SIXEL
if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
@@ -2088,8 +2090,9 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_IMAGES
image_redraw_all(ctx);
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
@@ -2297,7 +2300,7 @@ screen_write_collect_flush_scrolled(struct screen_write_ctx *ctx)
screen_write_initctx(ctx, &ttyctx, 1, 1);
if (ttyctx.flags & TTY_CTX_PANE_OBSCURED && wp != NULL) {
screen_write_redraw_pane(ctx);
screen_write_redraw_pane(ctx, &ttyctx);
return 0;
}
if (wp != NULL && window_pane_scrollbar_overlay_visible(wp)) {
@@ -2314,10 +2317,6 @@ screen_write_collect_flush_scrolled(struct screen_write_ctx *ctx)
ttyctx.orlower -= (wp->yoff + wp->sy - wp->window->sy);
ttyctx.n = ctx->scrolled;
ttyctx.bg = ctx->bg;
#ifdef ENABLE_IMAGES
if (ctx->scrolled_images)
ttyctx.flags |= TTY_CTX_IMAGE_SCROLL;
#endif
tty_write(tty_cmd_scrollup, &ttyctx);
if (wp != NULL)
@@ -2447,9 +2446,6 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only,
if (!screen_write_collect_flush_scrolled(ctx))
goto discard;
ctx->scrolled = 0;
#ifdef ENABLE_IMAGES
ctx->scrolled_images = 0;
#endif
}
ctx->bg = 8;
@@ -2473,9 +2469,6 @@ discard:
}
}
ctx->scrolled = 0;
#ifdef ENABLE_IMAGES
ctx->scrolled_images = 0;
#endif
ctx->bg = 8;
}
@@ -2602,8 +2595,9 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
}
}
#ifdef ENABLE_IMAGES
screen_write_image_damage(ctx, s->cx, s->cy, ci->used, 1);
#ifdef ENABLE_SIXEL
if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x,
@@ -2693,9 +2687,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
u_int sx = screen_size_x(s), sy = screen_size_y(s);
u_int width = ud->width, xx, not_wrap, i, n, vis;
int selected, skip = 1, redraw = 0;
#ifdef ENABLE_IMAGES
int selection_image = 0;
#endif
int yoff = 0, xoff = 0;
struct visible_ranges *r;
struct visible_range *ri;
@@ -2736,12 +2727,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
return;
screen_write_initctx(ctx, &ttyctx, 0, 0);
#ifdef ENABLE_IMAGES
/* Input semantics decide which image layers this text damages. */
if (screen_write_image_damage(ctx, s->cx, s->cy, width, 1))
skip = 0;
#endif
/* Handle overwriting of UTF-8 characters. */
gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
if (gl->flags & GRID_LINE_EXTENDED) {
@@ -2789,14 +2774,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
/* Update the selected flag and set the cell. */
selected = screen_check_selection(s, s->cx, s->cy);
#ifdef ENABLE_IMAGES
/* Selection highlighting must not overwrite image-covered cells. */
if (selected && image_grid_check_area(gd, s->cx,
gd->hsize + s->cy, width, 1)) {
selected = 0;
selection_image = 1;
}
#endif
if (selected && (~gc->flags & GRID_FLAG_SELECTED)) {
memcpy(&tmp_gc, gc, sizeof tmp_gc);
tmp_gc.flags |= GRID_FLAG_SELECTED;
@@ -2807,11 +2784,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
grid_view_set_cell(gd, s->cx, s->cy, &tmp_gc);
} else if (!skip)
grid_view_set_cell(gd, s->cx, s->cy, gc);
#ifdef ENABLE_IMAGES
if (selection_image)
skip = 1;
else
#endif
if (selected)
skip = 0;
@@ -2893,10 +2865,11 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
struct grid *gd = s->grid;
const struct utf8_data *ud = &gc->data;
struct options *oo = global_options;
u_int i, n, cx = s->cx, cy = s->cy, vis, yoff = 0;
u_int i, n, cx = s->cx, cy = s->cy, vis;
struct grid_cell last;
struct tty_ctx ttyctx;
int force_wide = 0, zero_width = 0;
int xoff = 0, yoff = 0;
struct visible_ranges *r;
/* Ignore U+3164 HANGUL_FILLER entirely. */
@@ -2981,9 +2954,6 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
force_wide = 0;
/* Set the new cell. */
#ifdef ENABLE_IMAGES
screen_write_image_damage(ctx, cx - n, cy, n, 1);
#endif
grid_view_set_cell(gd, cx - n, cy, &last);
if (force_wide)
grid_view_set_padding(gd, cx - 1, cy, last.bg);
@@ -2993,9 +2963,11 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
* obscured in the middle, only on left or right, but there could be an
* empty range in the visible ranges so we add them all up.
*/
if (wp != NULL)
if (wp != NULL) {
xoff = wp->xoff;
yoff = wp->yoff;
r = window_visible_ranges(wp, cx - n, cy + yoff, n, NULL);
}
r = window_visible_ranges(wp, xoff + cx - n, cy + yoff, n, NULL);
for (i = 0, vis = 0; i < r->used; i++)
vis += r->ranges[i].nx;
if (vis < n) {
@@ -3117,21 +3089,63 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len,
tty_write(tty_cmd_rawstring, &ttyctx);
}
#ifdef ENABLE_IMAGES
#ifdef ENABLE_SIXEL
/* Write a SIXEL image. */
void
screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
u_int bg)
{
struct image *im;
struct screen *s = ctx->s;
struct grid *gd = s->grid;
struct tty_ctx ttyctx;
u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines;
struct sixel_image *new;
im = sixel_to_image(si);
if (im == NULL) {
sixel_free(si);
if (screen_size_y(s) == 1)
return;
sixel_size_in_cells(si, &x, &y);
if (x > screen_size_x(s) || y > screen_size_y(s) - 1) {
if (x > screen_size_x(s) - cx)
sx = screen_size_x(s) - cx;
else
sx = x;
if (y > screen_size_y(s) - 1)
sy = screen_size_y(s) - 1;
else
sy = y;
new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1);
sixel_free(si);
if (new == NULL)
return;
sixel_size_in_cells(new, &x, &y);
si = new;
}
image_write_sixel(ctx, im, bg);
image_free(image_get_id(im));
sy = screen_size_y(s) - cy;
if (sy <= y) {
lines = y - sy + 1;
if (image_scroll_up(s, lines) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
for (i = 0; i < lines; i++) {
grid_view_scroll_region_up(gd, 0, screen_size_y(s) - 1,
bg);
screen_write_collect_scroll(ctx, bg);
}
ctx->scrolled += lines;
if (lines > cy)
screen_write_cursormove(ctx, -1, 0, 0);
else
screen_write_cursormove(ctx, -1, cy - lines, 0);
}
screen_write_collect_flush(ctx, 0, __func__);
screen_write_initctx(ctx, &ttyctx, 0, 0);
ttyctx.image = image_store(s, si);
tty_write(tty_cmd_sixelimage, &ttyctx);
screen_write_cursormove(ctx, 0, cy + y, 0);
}
#endif

View File

@@ -93,6 +93,11 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
s->tabs = NULL;
s->sel = NULL;
#ifdef ENABLE_SIXEL
TAILQ_INIT(&s->images);
TAILQ_INIT(&s->saved_images);
#endif
s->write_list = NULL;
s->hyperlinks = NULL;
@@ -126,6 +131,11 @@ screen_reinit(struct screen *s, int check)
screen_clear_selection(s);
screen_free_titles(s);
#ifdef ENABLE_SIXEL
image_free_all(s);
#endif
screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0);
screen_reset_hyperlinks(s);
}
@@ -144,6 +154,10 @@ screen_reset_hyperlinks(struct screen *s)
void
screen_free(struct screen *s)
{
#ifdef ENABLE_SIXEL
struct image *im;
#endif
free(s->sel);
free(s->tabs);
free(s->path);
@@ -160,6 +174,17 @@ screen_free(struct screen *s)
hyperlinks_free(s->hyperlinks);
screen_free_titles(s);
#ifdef ENABLE_SIXEL
/*
* Images saved when entering the alternate screen stay linked in the
* global list; move them back so they are freed and unlinked here, or
* a later eviction would write through this freed screen.
*/
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
TAILQ_FOREACH(im, &s->images, entry)
im->list = &s->images;
image_free_all(s);
#endif
}
/* Reset tabs to default, eight spaces apart. */
@@ -352,6 +377,10 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow,
if (sy != screen_size_y(s))
screen_resize_y(s, sy, eat_empty, &cy);
#ifdef ENABLE_SIXEL
image_free_all(s);
#endif
if (reflow)
screen_reflow(s, sx, &cx, &cy, cursor);
@@ -663,6 +692,10 @@ int
screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
{
u_int sx, sy;
#ifdef ENABLE_SIXEL
struct image *im;
#endif
if (SCREEN_IS_ALTERNATE(s))
return 0;
sx = screen_size_x(s);
@@ -676,6 +709,12 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
}
memcpy(&s->saved_cell, gc, sizeof s->saved_cell);
#ifdef ENABLE_SIXEL
TAILQ_CONCAT(&s->saved_images, &s->images, entry);
TAILQ_FOREACH(im, &s->saved_images, entry)
im->list = &s->saved_images;
#endif
grid_view_clear(s->grid, 0, 0, sx, sy, 8);
s->saved_flags = s->grid->flags;
@@ -689,6 +728,10 @@ int
screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
{
u_int sx = screen_size_x(s), sy = screen_size_y(s);
#ifdef ENABLE_SIXEL
struct image *im;
#endif
/*
* If the current size is different, temporarily resize to the old size
* before copying back.
@@ -731,6 +774,13 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
grid_destroy(s->saved_grid);
s->saved_grid = NULL;
#ifdef ENABLE_SIXEL
image_free_all(s);
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
TAILQ_FOREACH(im, &s->images, entry)
im->list = &s->images;
#endif
if (s->cx > screen_size_x(s) - 1)
s->cx = screen_size_x(s) - 1;
if (s->cy > screen_size_y(s) - 1)

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server-client.c,v 1.503 2026/08/17 07:56:56 nicm Exp $ */
/* $OpenBSD: server-client.c,v 1.509 2026/08/28 07:36:01 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -45,6 +45,7 @@ static void server_client_set_path(struct client *);
static void server_client_set_progress_bar(struct client *);
static void server_client_reset_state(struct client *);
static void server_client_update_latest(struct client *);
static int server_client_handle_dead_key(struct window_pane *, key_code);
static void server_client_dispatch(struct imsg *, void *);
static int server_client_dispatch_command(struct client *, struct imsg *);
static int server_client_dispatch_identify(struct client *, struct imsg *);
@@ -106,7 +107,7 @@ server_client_set_overlay(struct client *c, u_int delay,
c->tty.flags |= TTY_FREEZE;
if (c->overlay_mode == NULL)
c->tty.flags |= TTY_NOCURSOR;
window_update_focus(c->session->curw->window);
window_update_focus(active_get_effective_window(c, c->session));
server_redraw_client(c);
}
@@ -133,7 +134,7 @@ server_client_clear_overlay(struct client *c)
c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR);
if (c->session != NULL)
window_update_focus(c->session->curw->window);
window_update_focus(active_get_effective_window(c, c->session));
server_redraw_client(c);
}
@@ -378,7 +379,8 @@ server_client_attached_lost(struct client *c)
found = NULL;
TAILQ_FOREACH(loop, &clients, entry) {
s = loop->session;
if (loop == c || s == NULL || s->curw->window != w)
if (loop == c || s == NULL ||
active_get_effective_window(loop, s) != w)
continue;
if (found == NULL || timercmp(&loop->activity_time,
&found->activity_time, >))
@@ -459,15 +461,15 @@ server_client_set_session(struct client *c, struct session *s)
c->flags |= CLIENT_FOCUSED;
if (old != NULL && old->curw != NULL)
window_update_focus(old->curw->window);
window_update_focus(active_get_effective_window(c, old));
if (s != NULL) {
s->curw->window->latest = c;
active_get_effective_window(c, s)->latest = c;
recalculate_sizes();
window_update_focus(s->curw->window);
window_update_focus(active_get_effective_window(c, s));
session_update_activity(s, NULL);
session_theme_changed(s);
gettimeofday(&s->last_attached_time, NULL);
s->curw->flags &= ~WINLINK_ALERTFLAGS;
active_get_effective_winlink(c, s)->flags &= ~WINLINK_ALERTFLAGS;
alerts_check_session(s);
tty_update_client_offset(c);
status_timer_start(c);
@@ -492,6 +494,7 @@ server_client_lost(struct client *c)
server_client_clear_overlay(c);
status_prompt_clear(c);
status_message_clear(c);
active_remove_client(c);
RB_FOREACH_SAFE(cf, client_files, &c->files, cf1) {
cf->error = EINTR;
@@ -505,6 +508,7 @@ server_client_lost(struct client *c)
server_client_attached_lost(c);
events_fire_client("client-detached", c);
}
active_check_clients();
if (c->name != NULL && (c->flags & (CLIENT_CONTROL|CLIENT_TERMINAL)))
events_fire_client("client-closed", c);
@@ -523,7 +527,10 @@ server_client_lost(struct client *c)
input_cancel_requests(c);
free(c->title);
free(c->path);
free((void *)c->cwd);
free(c->exit_session);
free(c->exit_message);
evtimer_del(&c->repeat_timer);
evtimer_del(&c->click_timer);
@@ -680,7 +687,7 @@ server_client_in_scrollbar_area(struct window_pane *wp, int px, int py)
static void
server_client_update_scrollbar_hover(struct client *c, int type, int px, int py)
{
struct window *w = c->session->curw->window;
struct window *w = active_get_effective_window(c, c->session);
struct window_pane *wp;
if (type != KEYC_TYPE_MOUSEMOVE)
@@ -800,8 +807,7 @@ server_client_check_mouse_in_pane(struct window_pane *wp, int px, int py,
} else {
/* Try the pane borders. */
TAILQ_FOREACH(fwp, &w->panes, entry) {
if ((w->flags & WINDOW_ZOOMED) &&
(~fwp->flags & PANE_ZOOMED))
if (!window_pane_is_visible(fwp))
continue;
if (window_pane_is_floating(fwp) &&
window_pane_get_pane_lines(fwp) == PANE_LINES_NONE)
@@ -853,7 +859,7 @@ server_client_check_mouse(struct client *c, struct key_event *event)
{
struct mouse_event *m = &event->m;
struct session *s = c->session, *fs;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct winlink *fwl;
struct window_pane *wp, *fwp, *lwp = NULL;
u_int x, y, sx, sy, px, py, n, sl_mpos = 0;
@@ -1356,7 +1362,7 @@ server_client_update_latest(struct client *c)
if (c->session == NULL)
return;
w = c->session->curw->window;
w = active_get_effective_window(c, c->session);
if (w->latest == c)
return;
@@ -1388,6 +1394,21 @@ server_client_repeat_time(struct client *c, struct key_binding *bd)
return (repeat);
}
/* Handle a key press on a dead pane waiting for a key. */
static int
server_client_handle_dead_key(struct window_pane *wp, key_code key)
{
if (wp == NULL ||
(~wp->flags & PANE_EXITED) ||
KEYC_IS_MOUSE(key) ||
KEYC_IS_PASTE(key) ||
options_get_number(wp->options, "remain-on-exit") != 3)
return (0);
options_set_number(wp->options, "remain-on-exit", 0);
server_destroy_pane(wp, 0);
return (1);
}
/*
* Handle data key input from client. This owns and can modify the key event it
* is given and is responsible for freeing it.
@@ -1420,7 +1441,7 @@ server_client_key_callback(struct cmdq_item *item, void *data)
/* Check the client is good to accept input. */
if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
goto out;
wl = s->curw;
wl = active_get_effective_winlink(c, s);
/* Update the activity timer. */
memcpy(&c->last_activity_time, &c->activity_time,
@@ -1473,6 +1494,18 @@ server_client_key_callback(struct cmdq_item *item, void *data)
server_client_is_assume_paste(c))
goto paste_key;
/* Forward keys directly if this pane is capturing all keys. */
if (wp != NULL &&
(wp->flags & PANE_CAPTUREALLKEYS) &&
(~wp->flags & PANE_EXITED) &&
!KEYC_IS_MOUSE(key) &&
TAILQ_EMPTY(&wp->modes))
goto forward_key;
/* Focus events are not keys and cannot be bound. */
if (key == KEYC_FOCUS_IN || key == KEYC_FOCUS_OUT)
goto forward_key;
/*
* Work out the current key table. If the pane is in a mode, use
* the mode table instead of the default key table.
@@ -1637,15 +1670,8 @@ try_again:
}
forward_key:
if (wp != NULL &&
(wp->flags & PANE_EXITED) &&
!KEYC_IS_MOUSE(key) &&
!KEYC_IS_PASTE(key) &&
options_get_number(wp->options, "remain-on-exit") == 3) {
options_set_number(wp->options, "remain-on-exit", 0);
server_destroy_pane(wp, 0);
if (server_client_handle_dead_key(wp, key))
goto out;
}
if (c->flags & CLIENT_READONLY)
goto out;
if (wp != NULL)
@@ -1674,7 +1700,7 @@ out:
static int
server_client_handle_menu_key(struct client *c, struct key_event *event)
{
struct window *w = c->session->curw->window;
struct window *w = active_get_effective_window(c, c->session);
struct key_event new_event;
struct mouse_event *m;
u_int ox, oy, sx, sy;
@@ -1713,6 +1739,7 @@ server_client_handle_key0(struct client *c, struct key_event *event,
{
struct session *s = c->session;
struct cmdq_item *item;
struct window *w;
struct window_pane *wp;
/* Check the client is good to accept input. */
@@ -1733,9 +1760,9 @@ server_client_handle_key0(struct client *c, struct key_event *event,
}
/*
* Key presses in overlay mode and the command prompt are a special
* case. The queue might be blocked so they need to be processed
* immediately rather than queued.
* Key presses in overlay mode, for panes capturing all keys and in the
* command prompt are a special case. The queue might be blocked so they
* need to be processed immediately rather than queued.
*/
if (~c->flags & CLIENT_READONLY) {
if (c->message_string != NULL) {
@@ -1755,6 +1782,21 @@ server_client_handle_key0(struct client *c, struct key_event *event,
}
server_client_clear_overlay(c);
wp = s->curw->window->active;
if (wp != NULL &&
(wp->flags & PANE_CAPTUREALLKEYS) &&
TAILQ_EMPTY(&wp->modes) &&
!KEYC_IS_MOUSE(event->key)) {
if (server_client_handle_dead_key(wp, event->key))
return (0);
if (~wp->flags & PANE_EXITED) {
window_pane_key(wp, c, s, s->curw, event->key,
&event->m);
return (0);
}
}
if (server_client_handle_menu_key(c, event))
return (0);
if (c->prompt != NULL) {
@@ -1768,9 +1810,10 @@ server_client_handle_key0(struct client *c, struct key_event *event,
}
}
wp = s->curw->window->active;
w = active_get_effective_window(c, s);
wp = w->active;
if (wp == NULL || !window_pane_has_prompt(wp)) {
TAILQ_FOREACH(wp, &s->curw->window->panes, entry) {
TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_has_prompt(wp) &&
window_pane_is_visible(wp))
break;
@@ -2112,8 +2155,8 @@ static void
server_client_reset_state(struct client *c)
{
struct tty *tty = &c->tty;
struct window *w = c->session->curw->window;
struct window_pane *wp = w->active, *loop;
struct window *w = active_get_effective_window(c, c->session);
struct window_pane *wp, *loop;
struct screen *s = NULL;
struct options *oo = c->session->options;
int mode = 0, cursor, flags, pane_mode = 0;
@@ -2121,6 +2164,7 @@ server_client_reset_state(struct client *c)
u_int sb_w;
struct visible_ranges *r;
wp = active_get_effective_pane(c, w);
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
return;
@@ -2375,8 +2419,6 @@ server_client_check_exit(struct client *c, int force)
proc_send(c->peer, c->exit_msgtype, -1, name, strlen(name) + 1);
break;
}
free(c->exit_session);
free(c->exit_message);
}
/* Redraw timer callback. */
@@ -2394,7 +2436,7 @@ server_client_redraw_timer(__unused int fd, __unused short events,
static void
server_client_check_modes(struct client *c)
{
struct window *w = c->session->curw->window;
struct window *w;
struct window_pane *wp;
struct window_mode_entry *wme;
@@ -2402,6 +2444,7 @@ server_client_check_modes(struct client *c)
return;
if (~c->flags & CLIENT_REDRAWSTATUS)
return;
w = active_get_effective_window(c, c->session);
TAILQ_FOREACH(wp, &w->panes, entry) {
wme = TAILQ_FIRST(&wp->modes);
if (wme != NULL && wme->mode->update != NULL)
@@ -2414,7 +2457,7 @@ static int
server_client_any_pane_redraw(struct client *c)
{
struct session *s = c->session;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct window_pane *wp;
if (c->flags & CLIENT_REDRAWWINDOW)
@@ -2432,7 +2475,7 @@ server_client_check_redraw(struct client *c)
{
struct session *s = c->session;
struct tty *tty = &c->tty;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct window_pane *wp;
int needed, tflags, mode = tty->mode;
struct timeval tv = { .tv_usec = 1000 };
@@ -2441,14 +2484,13 @@ server_client_check_redraw(struct client *c)
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
return;
if (c->flags & (CLIENT_ALLREDRAWFLAGS|CLIENT_REDRAWSCROLLBARS)) {
log_debug("%s: redraw%s%s%s%s%s%s", c->name,
if (c->flags & CLIENT_ALLREDRAWFLAGS) {
log_debug("%s: redraw%s%s%s%s%s", c->name,
(c->flags & CLIENT_REDRAWWINDOW) ? " window" : "",
(c->flags & CLIENT_REDRAWSTATUS) ? " status" : "",
(c->flags & CLIENT_REDRAWBORDERS) ? " borders" : "",
(c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : "",
(c->flags & CLIENT_REDRAWMENU) ? " menu" : "",
(c->flags & CLIENT_REDRAWSCROLLBARS) ? " scrollbars" : "");
(c->flags & CLIENT_REDRAWMENU) ? " menu" : "");
}
/* Work out if a redraw is actually needed. */
@@ -2572,14 +2614,15 @@ static void
server_client_set_path(struct client *c)
{
struct session *s = c->session;
struct window *w = active_get_effective_window(c, s);
const char *path;
if (s->curw == NULL || s->curw->window->active == NULL)
if (w == NULL || w->active == NULL)
return;
if (s->curw->window->active->base.path == NULL)
if (w->active->base.path == NULL)
path = "";
else
path = s->curw->window->active->base.path;
path = w->active->base.path;
if (c->path == NULL || strcmp(path, c->path) != 0) {
free(c->path);
c->path = xstrdup(path);
@@ -2592,11 +2635,12 @@ static void
server_client_set_progress_bar(struct client *c)
{
struct session *s = c->session;
struct window *w = active_get_effective_window(c, s);
struct progress_bar *pane_pb;
if (s->curw == NULL || s->curw->window->active == NULL)
if (w == NULL || w->active == NULL)
return;
pane_pb = &s->curw->window->active->base.progress_bar;
pane_pb = &w->active->base.progress_bar;
if (pane_pb->state == c->progress_bar.state &&
pane_pb->progress == c->progress_bar.progress)
return;
@@ -3133,6 +3177,7 @@ server_client_remove_pane(struct window_pane *wp)
{
struct client *c;
active_remove_pane(wp);
TAILQ_FOREACH(c, &clients, entry) {
if (c->tty.mouse_last_pane == (int)wp->id) {
c->tty.mouse_last_pane = -1;
@@ -3148,6 +3193,7 @@ server_client_print(struct client *c, int parse, struct evbuffer *evb)
{
void *data = EVBUFFER_DATA(evb);
size_t size = EVBUFFER_LENGTH(evb);
struct window *w;
struct window_pane *wp;
struct window_mode_entry *wme;
char *sanitized, *msg, *line, empty = '\0';
@@ -3186,7 +3232,8 @@ server_client_print(struct client *c, int parse, struct evbuffer *evb)
goto out;
}
wp = c->session->curw->window->active;
w = active_get_effective_window(c, c->session);
wp = active_get_effective_pane(c, w);
wme = TAILQ_FIRST(&wp->modes);
if (wme == NULL || wme->mode != &window_view_mode)
window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL,

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server-fn.c,v 1.149 2026/07/27 14:25:46 nicm Exp $ */
/* $OpenBSD: server-fn.c,v 1.151 2026/08/20 09:19:24 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -122,8 +122,7 @@ server_redraw_window(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->curw != NULL &&
c->session->curw->window == w)
active_get_effective_window(c, c->session) == w)
server_redraw_client(c);
}
}
@@ -135,8 +134,7 @@ server_redraw_window_menu(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->curw != NULL &&
c->session->curw->window == w)
active_get_effective_window(c, c->session) == w)
c->flags |= CLIENT_REDRAWMENU;
}
}
@@ -148,8 +146,7 @@ server_redraw_window_borders(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->curw != NULL &&
c->session->curw->window == w)
active_get_effective_window(c, c->session) == w)
c->flags |= CLIENT_REDRAWBORDERS;
}
}
@@ -226,10 +223,11 @@ server_kill_pane(struct window_pane *wp)
server_kill_window(w, 1);
recalculate_sizes();
} else {
server_unzoom_window(w);
window_push_zoom(w, 0, wp->flags & PANE_FLOATOVERZOOM);
server_client_remove_pane(wp);
layout_close_pane(wp);
window_remove_pane(w, wp);
window_pop_zoom(w);
server_redraw_window(w);
}
}
@@ -421,15 +419,17 @@ server_destroy_pane(struct window_pane *wp, int notify)
if (notify)
server_fire_pane_exit("pane-exited", wp);
server_unzoom_window(w);
window_push_zoom(w, 0, wp->flags & PANE_FLOATOVERZOOM);
server_client_remove_pane(wp);
layout_close_pane(wp);
window_remove_pane(w, wp);
if (TAILQ_EMPTY(&w->panes))
server_kill_window(w, 1);
else
else {
window_pop_zoom(w);
server_redraw_window(w);
}
}
static void
@@ -551,6 +551,7 @@ server_check_unattached(void)
continue;
break;
}
server_destroy_session(s);
session_destroy(s, 1, __func__);
}
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server.c,v 1.215 2026/08/03 10:07:57 nicm Exp $ */
/* $OpenBSD: server.c,v 1.216 2026/08/18 07:24:49 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -129,6 +129,7 @@ server_create_socket(uint64_t flags, char **cause)
mask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
saved_errno = errno;
umask(mask);
close(fd);
errno = saved_errno;
goto fail;

View File

@@ -203,6 +203,7 @@ session_destroy(struct session *s, int notify, const char *from)
if (s->curw == NULL)
return;
s->curw = NULL;
active_remove_session(s);
RB_REMOVE(sessions, &sessions, s);
if (notify)

4
sort.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: sort.c,v 1.9 2026/06/29 07:45:09 nicm Exp $ */
/* $OpenBSD: sort.c,v 1.10 2026/08/25 07:23:30 nicm Exp $ */
/*
* Copyright (c) 2026 Dane Jensen <dhcjensen@gmail.com>
@@ -32,7 +32,7 @@ sort_qsort(void *l, u_int len, u_int size, int (*cmp)(const void *,
u_int i;
void *tmp, **ll;
if (sort_crit->order == SORT_END)
if (len < 2 || sort_crit->order == SORT_END)
return;
if (sort_crit->order == SORT_ORDER) {

29
spawn.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: spawn.c,v 1.50 2026/07/23 09:38:27 nicm Exp $ */
/* $OpenBSD: spawn.c,v 1.52 2026/08/20 09:19:24 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -243,7 +243,7 @@ struct window_pane *
spawn_pane(struct spawn_context *sc, char **cause)
{
struct cmdq_item *item = sc->item;
struct client *c;
struct client *c, *loop;
struct session *s = sc->s;
struct session *ts;
struct window *w = sc->wl->window;
@@ -331,6 +331,20 @@ spawn_pane(struct spawn_context *sc, char **cause)
input_free(sc->wp0->ictx);
sc->wp0->ictx = NULL;
}
/*
* The old buffer is gone and the new one starts empty, so
* offsets into the old buffer no longer mean anything. Reset
* them, and drop output control clients still had queued.
*/
sc->wp0->offset.used = 0;
sc->wp0->base_offset = 0;
sc->wp0->pipe_offset.used = 0;
TAILQ_FOREACH(loop, &clients, entry) {
if (loop->flags & CLIENT_CONTROL)
control_reset_pane(loop, sc->wp0);
}
new_wp = sc->wp0;
new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN);
} else {
@@ -346,6 +360,8 @@ spawn_pane(struct spawn_context *sc, char **cause)
}
if (sc->flags & SPAWN_FLOATING)
new_wp->layout_cell->flags |= LAYOUT_CELL_FLOATING;
if (sc->flags & SPAWN_FLOATOVERZOOM)
new_wp->flags |= PANE_FLOATOVERZOOM;
/*
* If window currently zoomed, window_set_active_pane calls
@@ -731,10 +747,10 @@ spawn_editor(struct client *c, const char *buf, size_t len,
lg.sy = w->sy * 9 / 10;
lg.xoff = w->sx / 2 - lg.sx / 2;
lg.yoff = w->sy / 2 - lg.sy / 2;
window_push_modal_zoom(w);
window_push_zoom(w, 0, 1);
lc = layout_floating_pane(w, NULL, &lg);
if (lc == NULL) {
window_pop_modal_zoom(w);
window_pop_zoom(w);
spawn_editor_free(es);
return (NULL);
}
@@ -751,17 +767,18 @@ spawn_editor(struct client *c, const char *buf, size_t len,
sc.environ = env;
sc.idx = -1;
sc.cwd = _PATH_TMP;
sc.flags = SPAWN_FLOATING|SPAWN_MODAL;
sc.flags = SPAWN_FLOATING|SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
wp = spawn_pane(&sc, &cause);
free(cmd);
environ_free(env);
if (wp == NULL) {
free(cause);
window_pop_modal_zoom(w);
window_pop_zoom(w);
spawn_editor_free(es);
return (NULL);
}
window_pop_zoom(w);
options_set_number(wp->options, "remain-on-exit", 0);
es->pid = wp->pid;
wp->editor = es;

View File

@@ -30,8 +30,7 @@
/* Default style. */
static struct style style_default = {
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0
},
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 },
0,
0,

81
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1156 2026/08/17 14:47:41 nicm Exp $
.\" $OpenBSD: tmux.1,v 1.1163 2026/09/01 12:49:49 nicm Exp $
.\"
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
.\"
@@ -14,7 +14,7 @@
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: August 17 2026 $
.Dd $Mdocdate: September 1 2026 $
.Dt TMUX 1
.Os
.Sh NAME
@@ -2208,14 +2208,14 @@ Move to the end of the next word.
.Xc
Same as
.Ic next\-word
but use a space alone as the word separator.
but treat all non-whitespace characters as part of a word.
.It Xo
.Ic next\-space\-end
(vi: E)
.Xc
Same as
.Ic next\-word\-end
but use a space alone as the word separator.
but treat all non-whitespace characters as part of a word.
.It Xo
.Ic other\-end
(vi: o)
@@ -2289,7 +2289,7 @@ Move to the previous word.
.Xc
Same as
.Ic previous\-word
but use a space alone as the word separator.
but treat all non-whitespace characters as part of a word.
.It Xo
.Ic rectangle\-on
.Xc
@@ -2574,8 +2574,8 @@ Word separators can be customized with the
session option.
Next word moves to the start of the next word, next word end to the end of the
next word and previous word to the start of the previous word.
The three next and previous space keys work similarly but use a space alone as
the word separator.
The three next and previous space keys work similarly but treat all
non-whitespace characters as part of a word.
Setting
.Em word\-separators
to the empty string makes next/previous word equivalent to next/previous space.
@@ -3644,7 +3644,7 @@ but a different format may be specified with
.Fl F .
.Tg newp
.It Xo Ic new\-pane
.Op Fl bCdefhIkLMOPvWZ
.Op Fl AbCdefhIkKLMOPvWZ
.Op Fl B Ar border\-lines
.Op Fl c Ar start\-directory
.Op Fl e Ar environment
@@ -3693,14 +3693,17 @@ sets the pane border lines for floating panes; see
.Fl M
may be used when bound to a mouse drag key; the new floating pane is resized to
follow the mouse drag.
It has no effect with
.Fl L .
.Fl A
creates a floating pane which remains visible above a zoomed pane.
.Fl O
creates a modal pane.
A modal pane is always the active pane and prevents interaction with any other
panes while it is active.
A window can only have one modal pane and it must be a floating pane.
With
.Fl K ,
all keys, including the prefix key, are passed directly to the modal pane.
With
.Fl C ,
the modal pane is closed when the mouse is clicked outside it.
.Pp
@@ -3990,7 +3993,7 @@ applies the last set layout if possible (undoes the most recent layout change).
spreads the current pane and any panes next to it out evenly.
.Tg selectp
.It Xo Ic select\-pane
.Op Fl DdeLlMmRUZ
.Op Fl ADdeLlMmRsSUZ
.Op Fl T Ar title
.Op Fl t Ar target\-pane
.Xc
@@ -3998,6 +4001,8 @@ spreads the current pane and any panes next to it out evenly.
Make pane
.Ar target\-pane
the active pane in its window.
.Fl A
raises the pane if it is floating.
If one of
.Fl D ,
.Fl L ,
@@ -4012,6 +4017,15 @@ keeps the window zoomed if it was zoomed.
is the same as using the
.Ic last\-pane
command.
.Fl s
switches the client to local pane selection for the target window when more
than one client is attached; the pane currently active for the client is
retained.
Local pane selection is disabled automatically when fewer than two clients
remain attached.
.Fl S
switches the client back to shared pane selection for the target window, so the
window's active pane is used.
.Fl e
enables or
.Fl d
@@ -4035,12 +4049,22 @@ and
.Ic swap\-window .
.Tg selectw
.It Xo Ic select\-window
.Op Fl lnpT
.Op Fl LlnpST
.Op Fl t Ar target\-window
.Xc
.D1 Pq alias: Ic selectw
Select the window at
.Ar target\-window .
.Fl L
makes the attached client use a local current window for the target session.
Window navigation from that client then changes only that client's effective
window and leaves the session current window unchanged.
.Fl S
returns the client to the shared session current window.
Without
.Fl L ,
the current window is shared by the session and stored in the session.
.Pp
.Fl l ,
.Fl n
and
@@ -4751,6 +4775,15 @@ for backspace.
Set the number of buffers; as new buffers are added to the top of the stack,
old ones are removed from the bottom if necessary to maintain this maximum
length.
.It Xo Ic clear\-on\-attach
.Op Ic on | off
.Xc
When enabled (the default), the client terminal switches to the alternate
screen and clears it when a client is attached.
When disabled,
.Nm
does not enter the alternate screen; instead the existing terminal content is
scrolled into the scrollback buffer so it remains accessible.
.It Xo Ic command\-alias[]
.Ar name=value
.Xc
@@ -5073,10 +5106,6 @@ Ignore function keys from
and use the
.Nm
internal set only.
.It kitty
Supports Kitty graphics using Unicode placeholders.
The terminal must support the Kitty Unicode placeholder extension, not only
the basic Kitty graphics protocol.
.It margins
Supports DECSLRM margins.
.It mouse
@@ -5105,6 +5134,8 @@ Supports
title setting.
.It usstyle
Allows underscore style and colour to be set.
.It utf8
Supports UTF\-8 output.
.El
.It Ic terminal\-overrides[] Ar string
Allow terminal descriptions read using
@@ -5639,6 +5670,11 @@ If set to both, a bell and a message are produced.
Sets the session's conception of what characters are considered word
separators, for the purposes of the next and previous word commands in
copy mode.
A space in
.Ar string
matches any character with the Unicode
.Em White_Space
property.
.El
.Pp
Available window options are:
@@ -7251,7 +7287,10 @@ shows the four frames in turn, changing every 200 milliseconds.
The following variables are available, where appropriate:
.Bl -column "XXXXXXXXXXXXXXXXXXX" "XXXXX"
.It Sy "Variable name" Ta Sy "Alias" Ta Sy "Replaced with"
.It Li "active_window_index" Ta "" Ta "Index of active window in session"
.It Li "active_window_id" Ta "" Ta "ID of effective active window"
.It Li "active_window_index" Ta "" Ta "Index of effective active window"
.It Li "local_active_window" Ta "" Ta "1 if client has local window selection"
.It Li "active_window_mode" Ta "" Ta "Window selection mode, shared or local"
.It Li "alternate_on" Ta "" Ta "1 if pane is in alternate screen"
.It Li "alternate_saved_x" Ta "" Ta "Saved cursor X in alternate screen"
.It Li "alternate_saved_y" Ta "" Ta "Saved cursor Y in alternate screen"
@@ -7308,7 +7347,10 @@ The following variables are available, where appropriate:
.It Li "cursor_very_visible" Ta "" Ta "1 if the cursor is in very visible mode"
.It Li "cursor_x" Ta "" Ta "Cursor X position in pane"
.It Li "cursor_y" Ta "" Ta "Cursor Y position in pane"
.It Li "history_added" Ta "" Ta "Number of lines scrolled into pane history"
.It Li "history_bytes" Ta "" Ta "Number of bytes in window history"
.It Li "history_collected" Ta "" Ta "Number of oldest pane history lines discarded at the history limit"
.It Li "history_generation" Ta "" Ta "Generation incremented when pane history is cleared or reflowed"
.It Li "history_limit" Ta "" Ta "Maximum window history lines"
.It Li "history_size" Ta "" Ta "Size of history in lines"
.It Li "hook" Ta "" Ta "Name of running hook, if any"
@@ -7402,11 +7444,14 @@ The following variables are available, where appropriate:
.It Li "pane_last" Ta "" Ta "1 if last pane"
.It Li "pane_last_output_time" Ta "" Ta "Time pane last produced output"
.It Li "pane_last_prompt_time" Ta "" Ta "Time most recent OSC 133 prompt began"
.It Li "pane_local_active" Ta "" Ta "1 if active pane is client-local"
.It Li "pane_local_mode" Ta "" Ta "1 if client has local pane selection"
.It Li "pane_left" Ta "" Ta "Left of pane"
.It Li "pane_marked" Ta "" Ta "1 if this is the marked pane"
.It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set"
.It Li "pane_modal_flag" Ta "" Ta "1 if pane is modal"
.It Li "pane_mode" Ta "" Ta "Name of pane mode, if any"
.It Li "pane_output_generation" Ta "" Ta "Generation incremented when pane output is processed"
.It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)"
.It Li "pane_pid" Ta "" Ta "PID of first process in pane"
.It Li "pane_pipe" Ta "" Ta "1 if pane is being piped"
@@ -7484,7 +7529,7 @@ The following variables are available, where appropriate:
.It Li "uid" Ta "" Ta "Server UID"
.It Li "user" Ta "" Ta "Server user"
.It Li "version" Ta "" Ta "Server version"
.It Li "window_active" Ta "" Ta "1 if window active"
.It Li "window_active" Ta "" Ta "1 if window is the effective active window"
.It Li "window_active_clients" Ta "" Ta "Number of clients viewing this window"
.It Li "window_active_clients_list" Ta "" Ta "List of clients viewing this window"
.It Li "window_active_sessions" Ta "" Ta "Number of sessions on which this window is active"

231
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1424 2026/08/17 14:47:41 nicm Exp $ */
/* $OpenBSD: tmux.h,v 1.1433 2026/09/01 12:49:49 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -75,12 +75,7 @@ struct screen_write_cline;
struct screen_write_ctx;
struct session;
#ifdef ENABLE_IMAGES
struct image;
struct image_backend;
struct image_rect;
struct image_line;
struct image_store;
#ifdef ENABLE_SIXEL
struct sixel_image;
#endif
@@ -488,6 +483,7 @@ enum tty_code_code {
TTYC_ICH1,
TTYC_IL,
TTYC_IL1,
TTYC_IND,
TTYC_INDN,
TTYC_INVIS,
TTYC_KCBT,
@@ -863,7 +859,7 @@ struct colour_palette {
struct grid_cell {
struct utf8_data data;
u_short attr;
u_short flags;
u_char flags;
int fg;
int bg;
int us;
@@ -874,7 +870,7 @@ struct grid_cell {
struct grid_extd_entry {
utf8_char data;
u_short attr;
u_short flags;
u_char flags;
int fg;
int bg;
int us;
@@ -892,7 +888,7 @@ struct grid_cell_entry {
u_char data;
} data;
};
u_short flags;
u_char flags;
} __packed;
/* OSC 133 data for a grid line. */
@@ -916,9 +912,6 @@ struct grid_line {
u_int time;
struct osc133_data osc133_data;
u_short flags;
#ifdef ENABLE_IMAGES
struct image_line *images;
#endif
};
/* Entire grid of cells. */
@@ -938,9 +931,6 @@ struct grid {
u_int scroll_generation;
struct grid_line *linedata;
#ifdef ENABLE_IMAGES
struct image_store *images;
#endif
};
/* Virtual cursor in a grid. */
@@ -1032,8 +1022,24 @@ struct style {
u_int link;
};
#ifdef ENABLE_IMAGES
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
#ifdef ENABLE_SIXEL
/* Image. */
struct image {
struct screen *s;
struct sixel_image *data;
char *fallback;
u_int px;
u_int py;
u_int sx;
u_int sy;
struct images *list;
TAILQ_ENTRY (image) entry;
TAILQ_ENTRY (image) all_entry;
};
TAILQ_HEAD(images, image);
#endif
/* Cursor style. */
@@ -1092,6 +1098,11 @@ struct screen {
bitstr_t *tabs;
struct screen_sel *sel;
#ifdef ENABLE_SIXEL
struct images images;
struct images saved_images;
#endif
struct screen_write_cline *write_list;
struct hyperlinks *hyperlinks;
@@ -1109,16 +1120,12 @@ struct screen_write_ctx {
#define SCREEN_WRITE_SYNC 0x1
#define SCREEN_WRITE_OBSCURED 0x2
#define SCREEN_WRITE_CHECKED_IF_OBSCURED 0x4
#define SCREEN_WRITE_INPUT 0x8
screen_write_init_ctx_cb init_ctx_cb;
void *arg;
struct screen_write_citem *item;
u_int scrolled;
#ifdef ENABLE_IMAGES
int scrolled_images;
#endif
u_int bg;
};
@@ -1329,6 +1336,8 @@ struct window_pane {
#define PANE_CMDRUNNING 0x20000
#define PANE_ACTIVITY 0x40000
#define PANE_CLOSEONCLICK 0x80000
#define PANE_CAPTUREALLKEYS 0x100000
#define PANE_FLOATOVERZOOM 0x200000
bitstr_t *sync_dirty;
u_int sync_dirty_size;
@@ -1348,9 +1357,10 @@ struct window_pane {
char tty[TTY_NAME_MAX];
int status;
struct timeval dead_time;
struct cmdq_item *wait_item; /* new-pane -W: waiting for pane exit */
struct cmdq_item *wait_item;
struct spawn_editor_state *editor;
uint64_t output_generation;
time_t last_output_time;
time_t last_prompt_time;
time_t cmd_start_time;
@@ -1435,6 +1445,7 @@ struct window {
struct window_pane *active;
struct window_pane *modal;
struct window_pane *modal_last;
struct window_pane *was_zoomed;
struct window_panes last_panes;
struct window_panes z_index;
struct window_panes panes;
@@ -1477,7 +1488,6 @@ struct window {
#define WINDOW_ZOOMED 0x8
#define WINDOW_WASZOOMED 0x10
#define WINDOW_RESIZE 0x20
#define WINDOW_WASMODALZOOMED 0x40
#define WINDOW_ALERTFLAGS (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_SILENCE)
int alerts_queued;
@@ -1725,8 +1735,7 @@ struct tty_term {
#define TERM_RGBCOLOURS 0x10
#define TERM_VT100LIKE 0x20
#define TERM_SIXEL 0x40
#define TERM_KITTY 0x80
#define TERM_INVALIDMS 0x100
#define TERM_INVALIDMS 0x80
int flags;
LIST_ENTRY(tty_term) entry;
@@ -1806,8 +1815,9 @@ struct tty {
#define TTY_WAITFG 0x2000
#define TTY_WAITBG 0x4000
#define TTY_BRACKETPASTE 0x8000
#define TTY_HAVESYNC 0x10000
#define TTY_ALL_REQUEST_FLAGS \
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA)
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA|TTY_HAVESYNC)
int flags;
struct tty_term *term;
@@ -1828,10 +1838,6 @@ struct tty {
struct event key_timer;
struct tty_key *key_tree;
#ifdef ENABLE_IMAGES
const struct image_backend *image_backend;
void *image_data;
#endif
};
/* Terminal command context. */
@@ -1853,8 +1859,6 @@ struct tty_ctx {
#define TTY_CTX_OVERLAY_SYNC 0x10
#define TTY_CTX_CELL_INVALIDATE 0x20
#define TTY_CTX_PANE_OBSCURED 0x40
#define TTY_CTX_IMAGE_SCROLL 0x80
#define TTY_CTX_PANE_FULL_WIDTH 0x100
union {
u_int n;
@@ -1870,7 +1874,7 @@ struct tty_ctx {
size_t size;
} sel;
#ifdef ENABLE_IMAGES
#ifdef ENABLE_SIXEL
struct image *image;
#endif
};
@@ -2534,6 +2538,7 @@ struct spawn_context {
#define SPAWN_HORIZONTAL 0x200
#define SPAWN_SPLIT 0x400
#define SPAWN_MODAL 0x800
#define SPAWN_FLOATOVERZOOM 0x1000
};
/* Paste buffer. */
@@ -2772,6 +2777,42 @@ void events_fire_window(const char *, struct window *);
void events_fire_pane(const char *, struct window_pane *);
void events_fire_winlink(const char *, struct winlink *);
/* active.c */
enum active_mode {
ACTIVE_SHARED,
ACTIVE_LOCAL
};
int active_is_local_window(struct client *, struct session *);
void active_set_local_window(struct client *, struct session *,
enum active_mode);
struct winlink *active_get_effective_winlink(struct client *,
struct session *);
struct window *active_get_effective_window(struct client *,
struct session *);
int active_select_window(struct client *, struct session *,
struct winlink *);
int active_select_window_index(struct client *, struct session *, int);
int active_next_window(struct client *, struct session *, int);
int active_previous_window(struct client *, struct session *, int);
int active_last_window(struct client *, struct session *);
void active_remove_client(struct client *);
void active_remove_session(struct session *);
void active_remove_window(struct window *);
int active_is_effective_window(struct client *, struct session *,
struct winlink *);
int active_is_last_window(struct client *, struct session *,
struct winlink *);
int active_has_local_pane(struct client *, struct window *);
void active_set_pane_mode(struct client *, struct window *,
enum active_mode);
struct window_pane *active_get_effective_pane(struct client *,
struct window *);
struct window_pane *active_get_last_pane(struct client *, struct window *);
int active_set_pane(struct client *, struct window *,
struct window_pane *, int);
void active_check_clients(void);
void active_remove_pane(struct window_pane *);
/* format-draw.c */
void format_draw(struct screen_write_ctx *, const struct grid_cell *,
u_int, const char *, struct style_ranges *, int);
@@ -2989,8 +3030,13 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *);
void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *);
void tty_cmd_setselection(struct tty *, const struct tty_ctx *);
void tty_cmd_rawstring(struct tty *, const struct tty_ctx *);
#ifdef ENABLE_SIXEL
void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *);
void tty_draw_images(struct client *, struct window_pane *);
#endif
void tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *);
void tty_default_colours(struct grid_cell *, struct window_pane *,
struct window_pane *, u_int *);
/* tty-term.c */
extern struct tty_terms tty_terms;
@@ -3597,6 +3643,7 @@ void screen_write_mode_set(struct screen_write_ctx *, int);
void screen_write_mode_clear(struct screen_write_ctx *, int);
void screen_write_start_sync(struct window_pane *);
void screen_write_stop_sync(struct window_pane *);
void screen_write_end_sync(struct screen_write_ctx *);
void screen_write_clear_dirty(struct window_pane *);
void screen_write_cursorup(struct screen_write_ctx *, u_int);
void screen_write_cursordown(struct screen_write_ctx *, u_int);
@@ -3631,7 +3678,7 @@ void screen_write_setselection(struct screen_write_ctx *, const char *,
u_char *, u_int);
void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
int);
#ifdef ENABLE_IMAGES
#ifdef ENABLE_SIXEL
void screen_write_sixelimage(struct screen_write_ctx *,
struct sixel_image *, u_int);
#endif
@@ -3726,8 +3773,8 @@ void window_resize(struct window *, u_int, u_int, int, int);
void window_pane_send_resize(struct window_pane *, u_int, u_int);
int window_zoom(struct window_pane *);
int window_unzoom(struct window *, int);
void window_push_modal_zoom(struct window *);
int window_pop_modal_zoom(struct window *);
int window_active_pane_is_over_zoom(struct window *);
struct window_pane *window_zoomed_pane(struct window *);
int window_push_zoom(struct window *, int, int);
int window_pop_zoom(struct window *);
void window_lost_pane(struct window *, struct window_pane *);
@@ -3771,7 +3818,7 @@ int window_pane_is_visible(struct window_pane *);
int window_pane_exited(struct window_pane *);
u_int window_pane_search(struct window_pane *, const char *, int,
int);
const char *window_printable_flags(struct winlink *, int);
const char *window_printable_flags(struct client *, struct winlink *, int);
const char *window_pane_printable_flags(struct window_pane *);
struct window_pane *window_pane_find_up(struct window_pane *);
struct window_pane *window_pane_find_down(struct window_pane *);
@@ -4032,6 +4079,7 @@ void control_set_pane_on(struct client *, struct window_pane *);
void control_set_pane_off(struct client *, struct window_pane *);
void control_continue_pane(struct client *, struct window_pane *);
void control_pause_pane(struct client *, struct window_pane *);
void control_reset_pane(struct client *, struct window_pane *);
void control_set_window_size(struct client *, u_int, u_int, u_int);
int control_get_window_size(struct client *, u_int, u_int *, u_int *);
void control_clear_window_size(struct client *, u_int);
@@ -4096,6 +4144,7 @@ void session_update_history(struct session *);
/* utf8.c */
enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *);
enum utf8_state utf8_fromwc(wchar_t wc, struct utf8_data *);
int utf8_has_whitespace(const struct utf8_data *);
void utf8_update_width_cache(void);
utf8_char utf8_build_one(u_char);
enum utf8_state utf8_from_data(const struct utf8_data *, utf8_char *);
@@ -4217,106 +4266,17 @@ void spawn_editor_finish(struct window_pane *);
/* regsub.c */
char *regsub(const char *, const char *, const char *, int);
#ifdef ENABLE_IMAGES
#ifdef ENABLE_SIXEL
/* image.c */
#define IMAGE_BACKEND_GRAPHICAL 0x1
#define IMAGE_BACKEND_SCROLLS 0x2
int image_free_all(struct screen *);
struct image *image_store(struct screen *, struct sixel_image *);
int image_check_line(struct screen *, u_int, u_int);
int image_check_area(struct screen *, u_int, u_int, u_int, u_int);
int image_scroll_up(struct screen *, u_int);
struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int,
u_char *);
struct image *image_create_view(struct image *, u_int, u_int, u_int,
u_int, u_int, u_int, u_int, u_int, u_int, u_int);
struct image *image_find(u_int);
u_int image_get_id(const struct image *);
void image_get_size(const struct image *, u_int *, u_int *);
void image_get_canvas_size(const struct image *, u_int *, u_int *);
void image_get_size_in_cells(const struct image *, u_int *, u_int *);
const u_char *image_get_pixels(const struct image *, size_t *, size_t *);
void image_set_no_cursor(struct image *);
struct sixel_image *image_get_sixel(const struct image *);
void image_set_sixel(struct image *, struct sixel_image *);
void image_ref(u_int);
void image_free(u_int);
void image_write_sixel(struct screen_write_ctx *, struct image *,
u_int);
void image_write_kitty(struct screen_write_ctx *, struct image *,
u_int, u_int, u_int, int32_t);
void image_get_pixel_rect(const struct image *, u_int, u_int,
u_int, u_int, u_int *, u_int *, u_int *, u_int *);
void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *,
u_int *);
u_char *image_base64_decode(const char *, size_t, size_t, size_t *);
u_char *image_png_decode(const u_char *, size_t, size_t, u_int *,
u_int *);
void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
u_int, u_int);
void image_redraw_all(struct screen_write_ctx *);
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
int image_backend_flags(struct tty *);
void image_tty_update(struct tty *);
void image_tty_geometry_changed(struct tty *);
void image_tty_free(struct tty *, int);
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
u_int, u_int, u_int, int, const struct tty_style_ctx *);
void image_draw_after_text(struct tty *, struct screen *, u_int,
u_int, u_int, u_int, u_int, const struct tty_style_ctx *);
void image_get_fallback_cell(struct tty *, struct image *, u_int,
u_int, const struct grid_cell *, struct grid_cell *,
const struct tty_style_ctx *);
int image_get_fallback_at(struct tty *, struct screen *, u_int,
u_int, const struct grid_cell *, struct grid_cell *,
const struct tty_style_ctx *);
struct image *image_rect_get_image(const struct image_rect *);
const struct grid_cell *image_rect_get_cell(
const struct image_rect *);
void image_rect_get_coords(const struct image_rect *,
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
int32_t image_rect_get_z(const struct image_rect *);
void image_clear(struct screen_write_ctx *, u_int);
void image_clear_kitty(struct screen_write_ctx *, char, u_int,
u_int, int32_t);
void image_grid_damage(struct grid *, u_int, u_int, u_int, u_int);
void image_grid_free_line(struct grid *, struct grid_line *);
void image_grid_free(struct grid *);
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
u_int);
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
u_int, u_int);
void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
u_int, u_int, u_int, u_int);
int image_grid_line_has_images(const struct grid_line *);
int image_grid_check_area(struct grid *, u_int, u_int, u_int,
u_int);
int image_grid_get_source(struct grid *, u_int, u_int,
struct image *, u_int *, u_int *);
void image_place_cell_kitty(struct screen_write_ctx *, struct image *,
u_int, u_int, u_int, u_int, u_int, u_int, int32_t);
#define KITTY_PARSE_ERROR -1
#define KITTY_PARSE_OK 0
#define KITTY_PARSE_MORE 1
#define KITTY_PARSE_MISSING 2
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
u_int, u_int *, u_int *, u_int *, char *, char *, u_int *,
int32_t *, int *);
int kitty_placeholder_to_image(void *, struct grid *,
struct grid_cell *, u_int, u_int, struct image **, u_int *,
u_int *, u_int *, u_int *, int32_t *);
void kitty_free_state(void *);
void kitty_draw_rect(struct tty *,
const struct image_rect *, const struct tty_style_ctx *);
void kitty_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void kitty_free_output_state(struct tty *, int);
#endif
#ifdef ENABLE_IMAGES
/* image-sixel.c */
#define SIXEL_COLOUR_REGISTERS 1024
void sixel_draw_rect(struct tty *,
const struct image_rect *, const struct tty_style_ctx *);
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void sixel_free_output(struct tty *, int);
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,
u_int);
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int);
void sixel_free(struct sixel_image *);
void sixel_log(struct sixel_image *);
void sixel_size_in_cells(struct sixel_image *, u_int *, u_int *);
@@ -4325,7 +4285,6 @@ struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int,
char *sixel_print(struct sixel_image *, struct sixel_image *,
size_t *);
struct screen *sixel_to_screen(struct sixel_image *);
struct image *sixel_to_image(struct sixel_image *);
#endif
/* server-acl.c */

View File

@@ -1,51 +0,0 @@
#!/bin/sh
# Generate the Unicode diacritic table used by Kitty image placeholders.
#
# Input must be UnicodeData.txt from Unicode 6.0.0:
# https://www.unicode.org/Public/6.0.0/ucd/UnicodeData.txt
# SHA-256: 90b45b777346bef027556f9c6cb3ea5d7d745bd60d6762855c08d8a22d34f771
#
# The selection follows the public Kitty graphics protocol: nonspacing marks
# with canonical combining class 230, NSM bidi class, no decomposition, minus
# characters which may be combined or normalized in ways unsuitable for a
# row or column index.
if [ "$#" -ne 1 ]; then
echo "usage: $0 UnicodeData.txt" >&2
exit 1
fi
awk -F ';' '
BEGIN {
omit["0300"] = omit["0301"] = omit["0302"] = 1
omit["0303"] = omit["0304"] = omit["0306"] = 1
omit["0307"] = omit["0308"] = omit["0309"] = 1
omit["030A"] = omit["030B"] = omit["030C"] = 1
omit["030F"] = omit["0311"] = omit["0313"] = 1
omit["0314"] = omit["0342"] = omit["0653"] = 1
omit["0654"] = 1
}
$3 == "Mn" && $4 == "230" && $5 == "NSM" && $6 == "" && !omit[$1] {
point[++count] = $1
}
END {
if (count != 297) {
printf "expected 297 diacritics, found %u\n", count > "/dev/stderr"
exit 1
}
print "static const uint32_t kitty_diacritics[] = {"
for (i = 1; i <= count; i++) {
if ((i - 1) % 8 == 0)
printf "\t"
printf "0x%s", point[i]
if (i != count)
printf ","
if (i == count || i % 8 == 0)
printf "\n"
else
printf " "
}
print "};"
}
' "$1"

View File

@@ -1,104 +0,0 @@
#!/bin/sh
# Compact visual check for image subcell geometry and layering. Run inside a
# tmux pane on Kitty or a SIXEL terminal; it needs at least 90 columns/24 rows.
if [ -z "$TMUX" ]; then
echo "Run this script inside tmux." >&2
exit 1
fi
columns=$(tput cols 2>/dev/null || echo 0)
lines=$(tput lines 2>/dev/null || echo 0)
if [ "$columns" -lt 90 ] || [ "$lines" -lt 24 ]; then
echo "This test needs at least 90 columns and 24 rows." >&2
exit 1
fi
at()
{
printf '\033[%s;%sH' "$1" "$2"
}
kitty()
{
printf '\033_G%s\033\\' "$1"
}
kitty_solid()
{
id=$1
rgba=$2
payload=$(i=0; while [ "$i" -lt 256 ]; do
printf "$rgba"
i=$((i + 1))
done | base64 | tr -d '\n')
kitty "a=t,q=2,f=32,s=16,v=16,i=$id;$payload"
}
sixel_folder()
{
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\\'
}
feature_info=$(tmux display-message -p \
'term=#{client_termname} features=#{client_termfeatures} cells=#{client_cell_width}x#{client_cell_height}')
printf '\033[2J\033[H\033[?25l'
at 1 2
printf 'tmux image edge-case check'
at 2 2
printf '%s' "$feature_info"
kitty_solid 201 '\377\000\000\377'
kitty_solid 202 '\000\377\000\377'
kitty_solid 203 '\000\000\377\377'
# The 26-pixel folder occupies whole grid cells, but its visible right and
# bottom edges must remain partial rather than stretching to those boundaries.
at 4 2
printf '1. partial SIXEL: folder should be the same size as direct output'
at 4 72
sixel_folder
# Chawan emits X/Y for its image padding. The red square must begin slightly
# below and right of the [img] anchor, never over it.
at 8 2
printf '2. Kitty X/Y: RED begins below/right of the [img] anchor'
at 8 72
printf '[img]'
at 9 72
kitty 'a=p,q=2,C=1,i=201,p=1,x=0,y=0,w=16,h=16,c=3,r=2,X=6,Y=6'
# Positive Kitty z overlays text and other lower z images; negative z is
# below text. The shared red/blue edge makes any wrong order obvious.
at 12 2
printf '3. Kitty z: BLUE covers RED; GREEN stays behind TEXT'
at 12 72
printf 'TEXT'
at 12 72
kitty 'a=p,q=2,C=1,i=202,p=2,x=0,y=0,w=16,h=16,c=4,r=2,z=-1'
at 14 72
kitty 'a=p,q=2,C=1,i=201,p=3,x=0,y=0,w=16,h=16,c=4,r=2,z=1'
at 14 74
kitty 'a=p,q=2,C=1,i=203,p=4,x=0,y=0,w=16,h=16,c=4,r=2,z=2'
# SIXEL is temporal: a later image covers text, while later text destroys the
# affected image cells. Both results should match on Kitty and SIXEL output.
at 18 2
printf '4. SIXEL after text: folder covers FOLDER'
at 18 72
printf 'FOLDER'
at 18 72
sixel_folder
at 21 2
printf '5. SIXEL before text: TEXT WINS, no folder remnant'
at 21 72
sixel_folder
at 21 72
printf 'TEXT WINS'
at 23 2
printf 'Take one screenshot, then press Enter to return to the shell.'
printf '\033[?25h'
IFS= read -r _image_edge_reply

View File

@@ -1,109 +0,0 @@
#!/bin/sh
# One-screen visual check for tmux image input and layering. Run this inside a
# tmux pane attached to Kitty, WezTerm, Microsoft Terminal, or a basic terminal.
if [ -z "$TMUX" ]; then
echo "Run this script inside tmux." >&2
exit 1
fi
columns=$(tput cols 2>/dev/null || echo 0)
lines=$(tput lines 2>/dev/null || echo 0)
if [ "$columns" -lt 72 ] || [ "$lines" -lt 23 ]; then
echo "This test needs at least 72 columns and 23 rows." >&2
exit 1
fi
at()
{
printf '\033[%s;%sH' "$1" "$2"
}
kitty()
{
printf '\033_G%s\033\\' "$1"
}
# Transmit a 16x16 solid RGBA texture. A one-pixel texture is unsuitable here
# because Kitty filters its transparent texture border into scaled edges.
kitty_solid()
{
id=$1
rgba=$2
payload=$(i=0; while [ "$i" -lt 256 ]; do
printf "$rgba"
i=$((i + 1))
done | base64 | tr -d '\n')
kitty "a=t,q=2,f=32,s=16,v=16,i=$id;$payload"
}
# A small 26x26 transparent SIXEL folder, used by the supplied bptato tests.
sixel_folder()
{
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\\'
}
feature_info=$(tmux display-message -p \
'term=#{client_termname} features=#{client_termfeatures} cells=#{client_cell_width}x#{client_cell_height}')
printf '\033[2J\033[H\033[?25l'
at 1 2
printf 'tmux image visual check'
at 2 2
printf '%s' "$feature_info"
# Red is placed after its text at a positive Kitty z-index, so it must cover
# the text. Green is placed below the text at a negative Kitty z-index.
kitty_solid 101 '\377\000\000\377'
kitty_solid 102 '\000\377\000\377'
kitty_solid 103 '\000\000\377\377'
kitty_solid 104 '\377\000\000\177'
at 4 2
printf '1. positive Kitty z: RED must hide TEXT UNDER at right'
at 4 62
printf 'TEXT UNDER'
at 4 60
kitty 'a=p,q=2,C=1,i=101,p=1,x=1,y=1,w=14,h=14,c=18,r=2,z=1'
at 7 2
printf '2. negative Kitty z: GREEN remains behind TEXT ON TOP'
at 7 60
kitty 'a=p,q=2,C=1,i=102,p=2,x=1,y=1,w=14,h=14,c=18,r=2,z=-1'
at 7 62
printf 'TEXT ON TOP'
at 10 2
printf '3. Kitty z overlap: RED left, BLUE on top at right'
at 11 55
kitty 'a=p,q=2,C=1,i=101,p=3,x=1,y=1,w=14,h=14,c=18,r=3,z=1'
at 11 64
kitty 'a=p,q=2,C=1,i=103,p=4,x=1,y=1,w=14,h=14,c=9,r=3,z=2'
at 15 2
printf '4. alpha: half-transparent RED, dithered on SIXEL'
at 15 60
kitty 'a=p,q=2,C=1,i=104,p=5,x=1,y=1,w=14,h=14,c=18,r=2,z=1'
# SIXEL semantics are temporal: later text destroys a SIXEL image, while a
# later SIXEL image appears over existing text.
at 18 2
printf '5. SIXEL after text: folder covers FOLDER at right'
at 18 65
printf 'FOLDER'
at 18 65
sixel_folder
at 21 2
printf '6. SIXEL before text: no folder remains behind TEXT WINS'
at 21 65
sixel_folder
at 21 65
printf 'TEXT WINS'
at 23 2
printf 'Take one screenshot, then press Enter to return to the shell.'
at 23 66
printf '\033[?25h'
IFS= read -r _image_visual_reply

View File

@@ -123,10 +123,6 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
struct grid *gd = s->grid;
const struct grid_cell *gcp;
struct grid_cell gc, ngc, last;
#ifdef ENABLE_IMAGES
struct grid_cell image_gc;
int image_status;
#endif
struct grid_line *gl;
u_int i, j, last_i, cx, ex, width;
u_int cellsize, bg;
@@ -251,35 +247,20 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
if (px >= ex || i >= ex - px) {
/* Outside the area being drawn. */
empty = nx - i;
gcp = &grid_default_cell;
#ifdef ENABLE_IMAGES
image_status = image_get_fallback_at(tty, s, px + i,
py, gcp, &image_gc, style_ctx);
if (image_status == 1) {
gcp = &image_gc;
empty = 0;
} else if (image_status == -1)
empty = 1;
else
#endif
empty = nx - i;
} else {
/* Get the current cell. */
grid_view_get_cell(gd, px + i, py, &gc);
gcp = &gc;
#ifdef ENABLE_IMAGES
if (image_get_fallback_at(tty, s, px + i, py, &gc,
&image_gc, style_ctx) == 1)
gcp = &image_gc;
#endif
/* Work out empty cells. */
empty = tty_draw_line_get_empty(gcp, &last, nx - i);
empty = tty_draw_line_get_empty(&gc, &last,
nx - i);
if (empty != 0)
;
gcp = &gc;
else {
/* Update for codeset if needed. */
gcp = tty_check_codeset(tty, gcp);
gcp = tty_check_codeset(tty, &gc);
/* And for selection. */
if (gcp->flags & GRID_FLAG_SELECTED) {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tty-features.c,v 1.42 2026/08/17 14:47:41 nicm Exp $ */
/* $OpenBSD: tty-features.c,v 1.43 2026/08/31 12:41:03 kirill Exp $ */
/*
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -224,11 +224,9 @@ static const struct tty_feature tty_feature_strikethrough = {
0
};
#define TTY_FEATURE_SYNC "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;"
/* Terminal supports synchronized updates. */
static const char *const tty_feature_sync_capabilities[] = {
TTY_FEATURE_SYNC,
"Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;",
NULL
};
static const struct tty_feature tty_feature_sync = {
@@ -359,18 +357,6 @@ static const struct tty_feature tty_feature_sixel = {
TERM_SIXEL
};
/* Terminal has Kitty graphics protocol Unicode placeholder capability. */
static const char *const tty_feature_kitty_capabilities[] = {
"Kty",
TTY_FEATURE_SYNC,
NULL
};
static const struct tty_feature tty_feature_kitty = {
"kitty",
tty_feature_kitty_capabilities,
TERM_KITTY
};
/* Terminal supports the OSC 9;4 progress bar. */
static const char *const tty_feature_progressbar_capabilities[] = {
"Spb=\\E]9;4;%p1%d;%p2%d\\E\\\\",
@@ -382,6 +368,13 @@ static const struct tty_feature tty_feature_progressbar = {
0
};
/* Terminal supports UTF-8. */
static const struct tty_feature tty_feature_utf8 = {
"utf8",
NULL,
0
};
/* Available terminal features. */
static const struct tty_feature *const tty_features[] = {
&tty_feature_256,
@@ -393,7 +386,6 @@ static const struct tty_feature *const tty_features[] = {
&tty_feature_extkeys,
&tty_feature_focus,
&tty_feature_ignorefkeys,
&tty_feature_kitty,
&tty_feature_margins,
&tty_feature_mouse,
&tty_feature_osc7,
@@ -405,7 +397,8 @@ static const struct tty_feature *const tty_features[] = {
&tty_feature_strikethrough,
&tty_feature_sync,
&tty_feature_title,
&tty_feature_usstyle
&tty_feature_usstyle,
&tty_feature_utf8
};
/* Parse features for client. */
@@ -488,6 +481,9 @@ tty_feature_present(struct tty_term *term, const char *name)
u_int i;
char *copy;
if (strcmp(name, "utf8") == 0)
return ((term->tty->client->flags & CLIENT_UTF8) != 0);
for (i = 0; i < nitems(tty_features); i++) {
tf = tty_features[i];
if (strcmp(tf->name, name) == 0) {
@@ -501,7 +497,8 @@ tty_feature_present(struct tty_term *term, const char *name)
* We don't just have the feature flag set. Check if the capabilities
* supported by the client are actual set instead.
*/
if (tf == NULL || strcmp(name, "ignorefkeys") == 0)
if (tf == NULL || tf->capabilities == NULL ||
strcmp(name, "ignorefkeys") == 0)
return (0);
if (tf->flags != 0 && (term->flags & tf->flags) != tf->flags)
return (0);
@@ -549,6 +546,8 @@ tty_apply_features(struct tty_term *term)
}
}
term->flags |= tf->flags;
if (tf == &tty_feature_utf8)
c->flags |= CLIENT_UTF8;
}
if ((term->applied_features|feat) == term->applied_features)
return (0);
@@ -624,7 +623,6 @@ tty_default_features(struct client *c, const char *name, u_int version)
"extkeys,"
"focus,"
"hyperlinks,"
"sixel,"
"usstyle"
},
{ .name = "ghostty",
@@ -638,20 +636,7 @@ tty_default_features(struct client *c, const char *name, u_int version)
"osc7,"
"sync,"
"usstyle,"
"progressbar,"
"kitty"
},
{ .name = "kitty",
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
"ccolour,"
"cstyle,"
"extkeys,"
"focus,"
"hyperlinks,"
"osc7,"
"sync,"
"usstyle,"
"kitty"
"progressbar"
},
{ .name = "Rio",
.features = TTY_FEATURES_BASE_MODERN_XTERM ","

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tty-keys.c,v 1.213 2026/08/17 14:47:41 nicm Exp $ */
/* $OpenBSD: tty-keys.c,v 1.214 2026/08/18 09:01:20 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -59,6 +59,7 @@ static int tty_keys_device_attributes2(struct tty *, const char *, size_t,
size_t *);
static int tty_keys_extended_device_attributes(struct tty *, const char *,
size_t, size_t *);
static int tty_keys_sync(struct tty *, const char *, size_t, size_t *);
static int tty_keys_palette(struct tty *, const char *, size_t, size_t *);
/* A key tree entry. */
@@ -728,7 +729,6 @@ tty_keys_winsz(struct tty *tty, const char *buf, size_t len, size_t *size)
char_y = (ypixel && tty->sy) ? ypixel / tty->sy : 0;
tty_set_size(tty, tty->sx, tty->sy, char_x, char_y);
tty_invalidate(tty);
recalculate_sizes();
tty->flags &= ~TTY_WINSIZEQUERY;
*size = end + 1;
@@ -772,6 +772,17 @@ tty_keys_next(struct tty *tty)
goto partial_key;
}
/* Is this a synchronized update mode response? */
switch (tty_keys_sync(tty, buf, len, &size)) {
case 0: /* yes */
key = KEYC_UNKNOWN;
goto complete_key;
case -1: /* no, or not valid */
break;
case 1: /* partial */
goto partial_key;
}
/* Is this a primary device attributes response? */
switch (tty_keys_device_attributes(tty, buf, len, &size)) {
case 0: /* yes */
@@ -1009,12 +1020,12 @@ complete_key:
/* Check for focus events. */
if (key == KEYC_FOCUS_OUT) {
c->flags &= ~CLIENT_FOCUSED;
window_update_focus(c->session->curw->window);
window_update_focus(active_get_effective_window(c, c->session));
events_fire_client("client-focus-out", c);
} else if (key == KEYC_FOCUS_IN) {
c->flags |= CLIENT_FOCUSED;
events_fire_client("client-focus-in", c);
window_update_focus(c->session->curw->window);
window_update_focus(active_get_effective_window(c, c->session));
}
/* Fire the key. */
@@ -1522,6 +1533,54 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
return (0);
}
/*
* Handle a synchronized update mode response. Returns 0 for success, -1 for
* failure, 1 for partial.
*/
static int
tty_keys_sync(struct tty *tty, const char *buf, size_t len, size_t *size)
{
struct client *c = tty->client;
static const char prefix[] = "\033[?2026;";
size_t i;
int status;
*size = 0;
if (tty->flags & TTY_HAVESYNC)
return (-1);
/* The response is always \033[?2026;Ps$y. */
for (i = 0; i < (sizeof prefix) - 1; i++) {
if (i == len)
return (1);
if (buf[i] != prefix[i])
return (-1);
}
if (i == len)
return (1);
if (buf[i] < '0' || buf[i] > '4')
return (-1);
status = buf[i++] - '0';
if (i == len)
return (1);
if (buf[i++] != '$')
return (-1);
if (i == len)
return (1);
if (buf[i++] != 'y')
return (-1);
*size = i;
if (status == 1 || status == 2 || status == 3) {
tty_parse_client_features(c, "sync", ",");
tty_update_features(tty);
}
log_debug("%s: received DECRPM %.*s", c->name, (int)*size, buf);
tty->flags |= TTY_HAVESYNC;
return (0);
}
/*
* Handle secondary device attributes input. Returns 0 for success, -1 for
* failure, 1 for partial.

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tty-term.c,v 1.108 2026/08/17 14:47:41 nicm Exp $ */
/* $OpenBSD: tty-term.c,v 1.109 2026/08/25 08:37:08 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -111,6 +111,7 @@ static const struct tty_term_code_entry tty_term_codes[] = {
[TTYC_ICH] = { TTYCODE_STRING, "ich" },
[TTYC_IL1] = { TTYCODE_STRING, "il1" },
[TTYC_IL] = { TTYCODE_STRING, "il" },
[TTYC_IND] = { TTYCODE_STRING, "ind" },
[TTYC_INDN] = { TTYCODE_STRING, "indn" },
[TTYC_INVIS] = { TTYCODE_STRING, "invis" },
[TTYC_KCBT] = { TTYCODE_STRING, "kcbt" },
@@ -524,7 +525,6 @@ tty_term_apply_overrides(struct tty_term *term)
tty_term_validate(term);
}
/* Mark terminals whose Ms capability does not contain a clipboard argument. */
static void
tty_term_validate(struct tty_term *term)
{

336
tty.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tty.c,v 1.478 2026/08/17 14:47:41 nicm Exp $ */
/* $OpenBSD: tty.c,v 1.480 2026/08/25 08:37:08 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -62,15 +62,17 @@ static void tty_region(struct tty *, u_int, u_int);
static void tty_margin_pane(struct tty *, const struct tty_ctx *);
static void tty_margin(struct tty *, u_int, u_int);
static int tty_large_region(struct tty *, const struct tty_ctx *);
static int tty_scroll_images(struct tty *, const struct tty_ctx *, int *);
static void tty_redraw_region(struct tty *, const struct tty_ctx *);
static void tty_emulate_repeat(struct tty *, enum tty_code_code,
enum tty_code_code, u_int);
static void tty_draw_pane_line(struct tty *, const struct tty_ctx *, u_int,
u_int, u_int, u_int, u_int);
static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int);
static int tty_check_overlay(struct tty *, u_int, u_int);
#ifdef ENABLE_SIXEL
static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *),
struct client *, struct tty_ctx *);
#endif
#define tty_use_margin(tty) \
(tty->term->flags & TERM_DECSLRM)
#define tty_full_width(tty, ctx) \
@@ -161,21 +163,10 @@ tty_resize(struct tty *tty)
void
tty_set_size(struct tty *tty, u_int sx, u_int sy, u_int xpixel, u_int ypixel)
{
#ifdef ENABLE_IMAGES
int geometry_changed;
geometry_changed = (tty->xpixel != xpixel || tty->ypixel != ypixel);
#endif
tty->sx = sx;
tty->sy = sy;
tty->xpixel = xpixel;
tty->ypixel = ypixel;
#ifdef ENABLE_IMAGES
if (geometry_changed) {
image_tty_geometry_changed(tty);
server_redraw_client(tty->client);
}
#endif
}
static void
@@ -293,10 +284,6 @@ tty_open(struct tty *tty, char **cause)
}
tty->flags |= TTY_OPENED;
#ifdef ENABLE_IMAGES
image_tty_update(tty);
#endif
tty->flags &= ~(TTY_NOCURSOR|TTY_FREEZE|TTY_BLOCK|TTY_TIMER);
event_set(&tty->event_in, c->fd, EV_PERSIST|EV_READ,
@@ -351,6 +338,7 @@ tty_start_tty(struct tty *tty)
{
struct client *c = tty->client;
struct termios tio;
u_int i;
setblocking(c->fd, 0);
event_add(&tty->event_in, NULL);
@@ -366,10 +354,21 @@ tty_start_tty(struct tty *tty)
if (tcsetattr(c->fd, TCSANOW, &tio) == 0)
tcflush(c->fd, TCOFLUSH);
tty_putcode(tty, TTYC_SMCUP);
if (options_get_number(global_options, "clear-on-attach")) {
tty_putcode(tty, TTYC_SMCUP);
tty_putcode(tty, TTYC_CLEAR);
} else {
tty_putcode_ii(tty, TTYC_CSR, 0, tty->sy - 1);
tty_putcode_ii(tty, TTYC_CUP, 0, tty->sy - 1);
if (tty_term_has(tty->term, TTYC_INDN))
tty_putcode_i(tty, TTYC_INDN, tty->sy + 1);
else if (tty_term_has(tty->term, TTYC_IND)) {
for (i = 0; i < tty->sy + 1; i++)
tty_putcode(tty, TTYC_IND);
} else
tty_putcode(tty, TTYC_CLEAR);
}
tty_putcode(tty, TTYC_SMKX);
tty_putcode(tty, TTYC_CLEAR);
if (tty_acs_needed(tty)) {
log_debug("%s: using capabilities for ACS", c->name);
@@ -416,6 +415,8 @@ tty_send_requests(struct tty *tty)
tty_puts(tty, "\033[>c");
if (~tty->flags & TTY_HAVEXDA)
tty_puts(tty, "\033[>q");
if (~tty->flags & TTY_HAVESYNC)
tty_puts(tty, "\033[?2026$p");
tty_puts(tty, "\033]10;?\033\\\033]11;?\033\\");
tty->flags |= (TTY_WAITBG|TTY_WAITFG);
} else
@@ -483,7 +484,8 @@ tty_stop_tty(struct tty *tty)
tty_raw(tty, tty_term_string(tty->term, TTYC_RMACS));
tty_raw(tty, tty_term_string(tty->term, TTYC_SGR0));
tty_raw(tty, tty_term_string(tty->term, TTYC_RMKX));
tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
if (options_get_number(global_options, "clear-on-attach"))
tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
if (tty->cstyle != SCREEN_CURSOR_DEFAULT) {
if (tty_term_has(tty->term, TTYC_SE))
tty_raw(tty, tty_term_string(tty->term, TTYC_SE));
@@ -508,7 +510,10 @@ tty_stop_tty(struct tty *tty)
if (tty_use_margin(tty))
tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG));
tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP));
if (options_get_number(global_options, "clear-on-attach"))
tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP));
else
tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
if (tty->term->flags & TERM_VT100LIKE)
tty_raw(tty, "\033[?2031l");
@@ -524,9 +529,6 @@ tty_close(struct tty *tty)
tty_stop_tty(tty);
if (tty->flags & TTY_OPENED) {
#ifdef ENABLE_IMAGES
image_tty_free(tty, 1);
#endif
evbuffer_free(tty->in);
event_del(&tty->event_in);
evbuffer_free(tty->out);
@@ -543,9 +545,6 @@ void
tty_free(struct tty *tty)
{
tty_close(tty);
#ifdef ENABLE_IMAGES
image_tty_free(tty, 0);
#endif
free(tty->r.ranges);
}
@@ -557,9 +556,6 @@ tty_update_features(struct tty *tty)
if (tty_apply_features(tty->term))
tty_term_apply_overrides(tty->term);
#ifdef ENABLE_IMAGES
image_tty_update(tty);
#endif
if (tty_use_margin(tty))
tty_putcode(tty, TTYC_ENMG);
@@ -959,7 +955,7 @@ int
tty_window_bigger(struct tty *tty)
{
struct client *c = tty->client;
struct window *w = c->session->curw->window;
struct window *w = active_get_effective_window(c, c->session);
return (tty->sx < w->sx || tty->sy - status_line_size(c) < w->sy);
}
@@ -981,10 +977,11 @@ static int
tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy)
{
struct client *c = tty->client;
struct window *w = c->session->curw->window;
struct window_pane *wp = w->active;
struct window *w = active_get_effective_window(c, c->session);
struct window_pane *wp;
u_int cx, cy, lines;
wp = active_get_effective_pane(c, w);
lines = status_line_size(c);
if (tty->sx >= w->sx && tty->sy - lines >= w->sy) {
@@ -1048,8 +1045,7 @@ tty_update_window_offset(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->curw != NULL &&
c->session->curw->window == w)
active_get_effective_window(c, c->session) == w)
tty_update_client_offset(c);
}
}
@@ -1093,26 +1089,6 @@ tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx)
return (ctx->orlower - ctx->orupper >= ctx->sy / 2);
}
/* Work out whether and how the terminal can scroll image pixels. */
static int
tty_scroll_images(struct tty *tty, const struct tty_ctx *ctx, int *full_width)
{
*full_width = 0;
#ifdef ENABLE_IMAGES
if (~ctx->flags & TTY_CTX_IMAGE_SCROLL)
return (1);
if (image_backend_flags(tty) & IMAGE_BACKEND_SCROLLS)
return (1);
if (ctx->flags & TTY_CTX_PANE_FULL_WIDTH) {
*full_width = 1;
return (1);
}
return (0);
#else
return (1);
#endif
}
/*
* Return if BCE is needed but the terminal doesn't have it - it'll need to be
* emulated.
@@ -1446,34 +1422,11 @@ tty_clear_pane_area(struct tty *tty, const struct tty_ctx *ctx, u_int py,
tty_clear_area(tty, ctx, y, ry, x, rx, bg);
}
/* Redraw part of a pane line, including its ordered image layers. */
static void
tty_draw_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int px,
u_int py, u_int nx, u_int atx, u_int aty)
{
#ifdef ENABLE_IMAGES
int has_images;
has_images = image_grid_check_area(ctx->s->grid, px,
ctx->s->grid->hsize + py, nx, 1);
if (has_images) {
image_redraw_start(tty, atx, aty, nx, 1);
image_draw_line(tty, ctx->s, px, py, nx, atx, aty, 1,
&ctx->style_ctx);
}
#endif
tty_draw_line(tty, ctx->s, px, py, nx, atx, aty, &ctx->style_ctx);
#ifdef ENABLE_IMAGES
if (has_images)
image_draw_line(tty, ctx->s, px, py, nx, atx, aty, 0,
&ctx->style_ctx);
#endif
}
/* Redraw a line of a screen at py. */
static void
tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py)
{
struct screen *s = ctx->s;
u_int nx = ctx->sx, i, x, rx, ry, j;
struct visible_ranges *r;
struct visible_range *rr;
@@ -1486,8 +1439,8 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py)
rr = &r->ranges[j];
if (rr->nx == 0)
continue;
tty_draw_pane_line(tty, ctx, rr->px - ctx->xoff, py,
rr->nx, rr->px, ctx->yoff + py);
tty_draw_line(tty, s, rr->px - ctx->xoff, py, rr->nx,
rr->px, ctx->yoff + py, &ctx->style_ctx);
}
return;
}
@@ -1497,8 +1450,8 @@ tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py)
rr = &r->ranges[j];
if (rr->nx == 0)
continue;
tty_draw_pane_line(tty, ctx, i + rr->px - x, py,
rr->nx, rr->px, ry);
tty_draw_line(tty, s, i + rr->px - x, py, rr->nx,
rr->px, ry, &ctx->style_ctx);
}
}
}
@@ -1587,6 +1540,60 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx)
return (c->overlay_check(c, c->overlay_data, px, py, nx));
}
#ifdef ENABLE_SIXEL
/* Update context for client. */
static int
tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
{
struct window_pane *wp = ttyctx->arg;
if (active_get_effective_window(c, c->session) != wp->window)
return (0);
if (wp->layout_cell == NULL)
return (0);
if (tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx,
&ttyctx->wsy))
ttyctx->flags |= TTY_CTX_WINDOW_BIGGER;
else
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
ttyctx->yoff = ttyctx->ryoff = wp->yoff;
if (status_at_line(c) == 0)
ttyctx->yoff += status_line_size(c);
return (1);
}
void
tty_draw_images(struct client *c, struct window_pane *wp)
{
struct image *im;
struct tty_ctx ttyctx;
TAILQ_FOREACH(im, &wp->screen->images, entry) {
memset(&ttyctx, 0, sizeof ttyctx);
/* Set the client independent properties. */
ttyctx.ocx = im->px;
ttyctx.ocy = im->py;
ttyctx.orlower = wp->screen->rlower;
ttyctx.orupper = wp->screen->rupper;
ttyctx.xoff = ttyctx.rxoff = wp->xoff;
ttyctx.sx = wp->sx;
ttyctx.sy = wp->sy;
ttyctx.image = im;
ttyctx.arg = wp;
ttyctx.set_client_cb = tty_set_client_cb;
ttyctx.flags |= TTY_CTX_INVISIBLE_PANES;
tty_write_one(tty_cmd_sixelimage, c, &ttyctx);
}
}
#endif
void
tty_sync_start(struct tty *tty)
{
@@ -1660,6 +1667,19 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *),
}
}
#ifdef ENABLE_SIXEL
/* Only write to the incoming tty instead of every client. */
static void
tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *),
struct client *c, struct tty_ctx *ctx)
{
if (ctx->set_client_cb == NULL)
return;
if ((ctx->set_client_cb(ctx, c)) == 1)
cmdfn(&c->tty, ctx);
}
#endif
void
tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx)
{
@@ -1707,19 +1727,9 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx)
void
tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx)
{
#ifdef ENABLE_IMAGES
u_int px, py;
#endif
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->n, ctx->bg);
#ifdef ENABLE_IMAGES
px = ctx->xoff + ctx->ocx - ctx->wox;
py = ctx->yoff + ctx->ocy - ctx->woy;
image_draw_after_text(tty, ctx->s, ctx->ocx, ctx->ocy, ctx->n,
px, py, &ctx->style_ctx);
#endif
}
void
@@ -1728,8 +1738,7 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx)
struct client *c = tty->client;
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!tty_full_width(tty, ctx) &&
(~ctx->flags & TTY_CTX_PANE_FULL_WIDTH)) ||
!tty_full_width(tty, ctx) ||
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
!tty_term_has(tty->term, TTYC_CSR) ||
!tty_term_has(tty->term, TTYC_IL1) ||
@@ -1756,8 +1765,7 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx)
struct client *c = tty->client;
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!tty_full_width(tty, ctx) &&
(~ctx->flags & TTY_CTX_PANE_FULL_WIDTH)) ||
!tty_full_width(tty, ctx) ||
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
!tty_term_has(tty->term, TTYC_CSR) ||
!tty_term_has(tty->term, TTYC_DL1) ||
@@ -1808,15 +1816,12 @@ void
tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
{
struct client *c = tty->client;
int full_width;
if (ctx->ocy != ctx->orupper)
return;
if (!tty_scroll_images(tty, ctx, &full_width) ||
(ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!full_width && !tty_full_width(tty, ctx) &&
!tty_use_margin(tty)) ||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
tty_fake_bce(tty, &ctx->defaults, 8) ||
!tty_term_has(tty->term, TTYC_CSR) ||
(!tty_term_has(tty->term, TTYC_RI) &&
@@ -1831,10 +1836,7 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
if (full_width)
tty_margin_off(tty);
else
tty_margin_pane(tty, ctx);
tty_margin_pane(tty, ctx);
tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper);
if (tty_term_has(tty->term, TTYC_RI))
@@ -1847,15 +1849,12 @@ void
tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
{
struct client *c = tty->client;
int full_width;
if (ctx->ocy != ctx->orlower)
return;
if (!tty_scroll_images(tty, ctx, &full_width) ||
(ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!full_width && !tty_full_width(tty, ctx) &&
!tty_use_margin(tty)) ||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
tty_fake_bce(tty, &ctx->defaults, 8) ||
!tty_term_has(tty->term, TTYC_CSR) ||
ctx->sx == 1 ||
@@ -1868,10 +1867,7 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
if (full_width)
tty_margin_off(tty);
else
tty_margin_pane(tty, ctx);
tty_margin_pane(tty, ctx);
/*
* If we want to wrap a pane while using margins, the cursor needs to
@@ -1896,12 +1892,9 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
{
struct client *c = tty->client;
u_int i;
int full_width;
if (!tty_scroll_images(tty, ctx, &full_width) ||
(ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!full_width && !tty_full_width(tty, ctx) &&
!tty_use_margin(tty)) ||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
tty_fake_bce(tty, &ctx->defaults, 8) ||
!tty_term_has(tty->term, TTYC_CSR) ||
ctx->sx == 1 ||
@@ -1914,10 +1907,7 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
if (full_width)
tty_margin_off(tty);
else
tty_margin_pane(tty, ctx);
tty_margin_pane(tty, ctx);
if (ctx->n == 1 || !tty_term_has(tty->term, TTYC_INDN)) {
if (!tty_use_margin(tty))
@@ -1940,12 +1930,9 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
{
u_int i;
struct client *c = tty->client;
int full_width;
if (!tty_scroll_images(tty, ctx, &full_width) ||
(ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!full_width && !tty_full_width(tty, ctx) &&
!tty_use_margin(tty)) ||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
tty_fake_bce(tty, &ctx->defaults, 8) ||
!tty_term_has(tty->term, TTYC_CSR) ||
(!tty_term_has(tty->term, TTYC_RI) &&
@@ -1960,10 +1947,7 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
if (full_width)
tty_margin_off(tty);
else
tty_margin_pane(tty, ctx);
tty_margin_pane(tty, ctx);
tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper);
if (tty_term_has(tty->term, TTYC_RIN))
@@ -2086,16 +2070,6 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
if (vis < gcp->data.width) {
tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width,
px, py, &ctx->style_ctx);
#ifdef ENABLE_IMAGES
for (i = 0; i < r->used; i++) {
if (r->ranges[i].nx == 0)
continue;
image_draw_after_text(tty, s,
ctx->ocx + r->ranges[i].px - px, ctx->ocy,
r->ranges[i].nx, r->ranges[i].px, py,
&ctx->style_ctx);
}
#endif
return;
}
}
@@ -2112,11 +2086,6 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
tty_cell(tty, ctx->cell, &ctx->style_ctx);
#ifdef ENABLE_IMAGES
image_draw_after_text(tty, s, ctx->ocx, ctx->ocy, gcp->data.width,
px, py, &ctx->style_ctx);
#endif
if (ctx->flags & TTY_CTX_CELL_INVALIDATE)
tty_invalidate(tty);
}
@@ -2164,10 +2133,6 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
cx = ri->px - ctx->xoff + ctx->wox;
tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy);
tty_putn(tty, cp + ri->px - px, ri->nx, ri->nx);
#ifdef ENABLE_IMAGES
image_draw_after_text(tty, ctx->s, cx, ctx->ocy,
ri->nx, ri->px, py, &ctx->style_ctx);
#endif
}
}
}
@@ -2208,6 +2173,58 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx)
tty_invalidate(tty);
}
#ifdef ENABLE_SIXEL
void
tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx)
{
struct image *im = ctx->image;
struct sixel_image *si = im->data;
struct sixel_image *new;
char *data;
size_t size;
u_int cx = ctx->ocx, cy = ctx->ocy, sx, sy;
u_int i, j, x, y, rx, ry;
int fallback = 0;
if ((~tty->term->flags & TERM_SIXEL) &&
!tty_term_has(tty->term, TTYC_SXL))
fallback = 1;
if (tty->xpixel == 0 || tty->ypixel == 0)
fallback = 1;
sixel_size_in_cells(si, &sx, &sy);
log_debug("%s: image is %ux%u", __func__, sx, sy);
if (!tty_clamp_area(tty, ctx, cx, cy, sx, sy, &i, &j, &x, &y, &rx, &ry))
return;
log_debug("%s: clamping to %u,%u-%u,%u", __func__, i, j, rx, ry);
if (fallback == 1) {
data = xstrdup(im->fallback);
size = strlen(data);
} else {
new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry, 0);
if (new == NULL)
return;
data = sixel_print(new, si, &size);
}
if (data != NULL) {
log_debug("%s: %zu bytes: %s", __func__, size, data);
tty_region_off(tty);
tty_margin_off(tty);
tty_cursor(tty, x, y);
tty->flags |= TTY_NOBLOCK;
tty_add(tty, data, size);
tty_invalidate(tty);
free(data);
}
if (fallback == 0)
sixel_free(new);
}
#endif
void
tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
{
@@ -3142,23 +3159,24 @@ tty_style_changed(struct window_pane *wp)
}
void
tty_default_colours(struct grid_cell *gc, struct window_pane *wp, u_int *dim)
tty_default_colours(struct grid_cell *gc, struct window_pane *wp,
struct window_pane *active, u_int *dim)
{
if (wp->flags & PANE_STYLECHANGED)
tty_style_changed (wp);
memcpy(gc, &grid_default_cell, sizeof *gc);
if (wp == wp->window->active && wp->cached_active_gc.fg != 8)
if (wp == active && wp->cached_active_gc.fg != 8)
gc->fg = wp->cached_active_gc.fg;
else
gc->fg = wp->cached_gc.fg;
if (wp == wp->window->active && wp->cached_active_gc.bg != 8)
if (wp == active && wp->cached_active_gc.bg != 8)
gc->bg = wp->cached_active_gc.bg;
else
gc->bg = wp->cached_gc.bg;
if (dim != NULL) {
if (wp == wp->window->active)
if (wp == active)
*dim = wp->cached_active_dim;
else
*dim = wp->cached_dim;

68
utf8.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: utf8.c,v 1.71 2026/05/12 09:37:25 nicm Exp $ */
/* $OpenBSD: utf8.c,v 1.72 2026/09/01 12:49:49 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -603,6 +603,72 @@ utf8_towc(const struct utf8_data *ud, wchar_t *wc)
return (UTF8_DONE);
}
/* Check for a Unicode whitespace character. */
int
utf8_has_whitespace(const struct utf8_data *ud)
{
struct utf8_data tmp;
wchar_t wc;
u_int offset = 0, size;
u_char ch;
while (offset < ud->size) {
ch = ud->data[offset];
if (ch < 0x80) {
wc = ch;
size = 1;
} else {
if (ch >= 0xc2 && ch <= 0xdf)
size = 2;
else if (ch >= 0xe0 && ch <= 0xef)
size = 3;
else if (ch >= 0xf0 && ch <= 0xf4)
size = 4;
else
return (0);
if (size > ud->size - offset)
return (0);
memset(&tmp, 0, sizeof tmp);
memcpy(tmp.data, ud->data + offset, size);
tmp.size = tmp.have = size;
if (utf8_towc(&tmp, &wc) != UTF8_DONE)
return (0);
}
offset += size;
switch (wc) {
case 0x0009:
case 0x000A:
case 0x000B:
case 0x000C:
case 0x000D:
case 0x0020:
case 0x0085:
case 0x00A0:
case 0x1680:
case 0x2000:
case 0x2001:
case 0x2002:
case 0x2003:
case 0x2004:
case 0x2005:
case 0x2006:
case 0x2007:
case 0x2008:
case 0x2009:
case 0x200A:
case 0x2028:
case 0x2029:
case 0x202F:
case 0x205F:
case 0x3000:
return (1);
}
}
return (0);
}
/* Convert wide character to UTF-8 character. */
enum utf8_state
utf8_fromwc(wchar_t wc, struct utf8_data *ud)

View File

@@ -155,9 +155,14 @@ window_pane_get_border_style(struct window_pane *wp, struct client *c,
struct format_tree *ft;
const char *option;
struct grid_cell *saved;
int *flag;
struct window *w;
struct window_pane *active;
int local, *flag;
if (wp == c->session->curw->window->active) {
w = active_get_effective_window(c, c->session);
active = active_get_effective_pane(c, w);
local = (wp == active && active_has_local_pane(c, wp->window));
if (wp == active) {
flag = &wp->active_border_gc_set;
saved = &wp->active_border_gc;
option = "pane-active-border-style";
@@ -167,8 +172,15 @@ window_pane_get_border_style(struct window_pane *wp, struct client *c,
option = "pane-border-style";
}
if (!*flag) {
if (local) {
ft = format_create_defaults(NULL, c, s, s->curw, wp);
style_apply(gc, wp->options, option, ft);
format_free(ft);
return;
}
if (!*flag) {
ft = format_create_defaults(NULL, c, s, NULL, wp);
style_apply(saved, wp->options, option, ft);
format_free(ft);
*flag = 1;
@@ -197,7 +209,7 @@ window_make_pane_status(struct window_pane *wp, struct client *c, u_int width,
return (0);
ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS);
format_defaults(ft, c, c->session, c->session->curw, wp);
format_defaults(ft, c, c->session, NULL, wp);
fmt = options_get_string(wp->options, "pane-border-format");
expanded = format_expand_time(ft, fmt);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-buffer.c,v 1.51 2026/07/15 12:45:39 nicm Exp $ */
/* $OpenBSD: window-buffer.c,v 1.52 2026/08/24 21:19:40 nicm Exp $ */
/*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -567,9 +567,7 @@ window_buffer_edit_close_cb(char *buf, size_t len, void *arg)
}
oldbuf = paste_buffer_data(pb, &oldlen);
if (oldlen != '\0' &&
oldbuf[oldlen - 1] != '\n' &&
buf[len - 1] == '\n')
if (oldlen != 0 && oldbuf[oldlen - 1] != '\n' && buf[len - 1] == '\n')
len--;
if (len != 0)
paste_replace(pb, buf, len);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-client.c,v 1.48 2026/08/05 08:54:56 nicm Exp $ */
/* $OpenBSD: window-client.c,v 1.49 2026/08/31 12:41:03 kirill Exp $ */
/*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -102,7 +102,8 @@ static const char *window_client_info_lines[] = {
WINDOW_CLIENT_FEATURE(sync) " "
WINDOW_CLIENT_FEATURE(title),
" #[#{E:tree-mode-border-style},acs]x#[default] "
WINDOW_CLIENT_FEATURE(usstyle),
WINDOW_CLIENT_FEATURE(usstyle) " "
WINDOW_CLIENT_FEATURE(utf8),
"#[#{E:tree-mode-border-style},acs]qqqqqqqqqqqqqqn#{R:q,#{window_width}}#[default]",
"#[fg=themelightgrey]prefix #[#{E:tree-mode-border-style},acs]x#[default] "
@@ -268,12 +269,13 @@ window_client_draw_info(__unused void *modedata, void *itemdata,
struct window_client_itemdata *item = itemdata;
struct client *c = item->c;
struct screen *s = ctx->s;
struct window *w = c->session->curw->window;
struct window *w;
struct grid_cell gc;
u_int cx = s->cx, cy = s->cy, i;
struct format_tree *ft;
char *expanded;
w = active_get_effective_window(c, c->session);
ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
if (c->tty.term->flags & TERM_INVALIDMS)
format_add(ft, "clipboard_invalid", "1");

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-copy.c,v 1.425 2026/08/05 12:23:25 nicm Exp $ */
/* $OpenBSD: window-copy.c,v 1.429 2026/09/01 13:04:29 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -2207,15 +2207,76 @@ window_copy_cmd_selection_mode(struct window_copy_cmd_state *cs)
struct window_mode_entry *wme = cs->wme;
struct options *so = cs->s->options;
struct window_copy_mode_data *data = wme->data;
struct grid_reader gr;
const char *s = args_string(cs->wargs, 0);
u_int sx, sy, ex, ey, fx, fy, x, y;
if (s == NULL || strcasecmp(s, "char") == 0 || strcasecmp(s, "c") == 0)
data->selflag = SEL_CHAR;
else if (strcasecmp(s, "word") == 0 || strcasecmp(s, "w") == 0) {
data->separators = options_get_string(so, "word-separators");
data->selflag = SEL_WORD;
} else if (strcasecmp(s, "line") == 0 || strcasecmp(s, "l") == 0)
} else if (strcasecmp(s, "line") == 0 || strcasecmp(s, "l") == 0) {
data->selflag = SEL_LINE;
if (data->screen.sel == NULL)
return (WINDOW_COPY_CMD_MOVE);
/*
* Line selection normally starts with select-line, which sets
* up the reset positions used when the cursor changes
* direction. Do the same when changing an existing selection
* to line mode.
*/
if (data->cursordrag == CURSORDRAG_SEL) {
fx = data->endselx;
fy = data->endsely;
} else {
fx = data->selx;
fy = data->sely;
}
sx = data->selx;
sy = data->sely;
ex = data->endselx;
ey = data->endsely;
if (ey < sy || (ey == sy && ex < sx)) {
x = sx; sx = ex; ex = x;
y = sy; sy = ey; ey = y;
}
grid_reader_start(&gr, data->backing->grid, sx, sy);
grid_reader_cursor_start_of_line(&gr, 1);
grid_reader_get_cursor(&gr, &sx, &sy);
grid_reader_start(&gr, data->backing->grid, ex, ey);
grid_reader_cursor_end_of_line(&gr, 1, 0);
grid_reader_get_cursor(&gr, &ex, &ey);
data->rectflag = 0;
data->selrx = data->selx = sx;
data->selry = data->sely = sy;
data->endselrx = data->endselx = ex;
data->endselry = data->endsely = ey;
x = data->cx;
y = screen_hsize(data->backing) + data->cy - data->oy;
data->dx = fx;
data->dy = fy;
if (data->cursordrag != CURSORDRAG_NONE &&
(y < fy || (y == fy && x < fx))) {
data->lineflag = LINE_SEL_RIGHT_LEFT;
data->cursordrag = CURSORDRAG_SEL;
window_copy_scroll_to(wme, sx, sy, 1);
} else {
data->lineflag = LINE_SEL_LEFT_RIGHT;
if (data->cursordrag != CURSORDRAG_NONE) {
data->cursordrag = CURSORDRAG_ENDSEL;
x = window_copy_cursor_limit(wme, ey, 0);
window_copy_scroll_to(wme, x, ey, 1);
}
}
if (data->cursordrag == CURSORDRAG_NONE)
window_copy_set_selection(wme, 0, 0);
return (WINDOW_COPY_CMD_REDRAW);
}
return (WINDOW_COPY_CMD_MOVE);
}
@@ -4093,7 +4154,7 @@ window_copy_search_lr_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py,
endline = gd->hsize + gd->sy - 1;
pywrap = py;
while (buf != NULL &&
pywrap <= endline &&
pywrap < endline &&
len < WINDOW_COPY_SEARCH_MAX_LINE) {
gl = grid_get_line(gd, pywrap);
if (~gl->flags & GRID_LINE_WRAPPED)
@@ -4152,7 +4213,7 @@ window_copy_search_rl_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py,
endline = gd->hsize + gd->sy - 1;
pywrap = py;
while (buf != NULL &&
pywrap <= endline &&
pywrap < endline &&
len < WINDOW_COPY_SEARCH_MAX_LINE) {
gl = grid_get_line(gd, pywrap);
if (~gl->flags & GRID_LINE_WRAPPED)
@@ -4301,7 +4362,7 @@ window_copy_stringify(struct grid *gd, u_int py, u_int first, u_int last,
}
if (dlen == 1)
buf[bx++] = *d;
else {
else if (dlen != 0) {
memcpy(buf + bx, d, dlen);
bx += dlen;
}
@@ -4353,6 +4414,7 @@ window_copy_cstrtocellpos(struct grid *gd, u_int ncells, u_int *ppx, u_int *ppy,
break;
}
}
ncells = cell;
/* Locate starting cell. */
cell = 0;
@@ -4850,6 +4912,8 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp,
cflags |= REG_ICASE;
if (regcomp(&reg, sbuf, cflags) != 0) {
free(sbuf);
free(data->searchmark);
data->searchmark = NULL;
return (0);
}
free(sbuf);
@@ -5363,14 +5427,6 @@ window_copy_write_line(struct window_mode_entry *wme,
else
content_sx = sx;
#ifdef ENABLE_IMAGES
/* Make the backing line's image layers available before drawing text. */
image_grid_free_line(s->grid,
&s->grid->linedata[s->grid->hsize + py]);
image_grid_copy_area(s->grid, width, s->grid->hsize + py,
data->backing->grid, 0, hsize - data->oy + py, content_sx, 1);
#endif
screen_write_cursormove(ctx, 0, py, 0);
ft = format_create_defaults(NULL, NULL, NULL, NULL, wp);
@@ -5414,6 +5470,7 @@ window_copy_write_line(struct window_mode_entry *wme,
window_copy_write_one(wme, ctx, width, py, hsize - data->oy + py,
content_sx, &mgc, &cgc, &mkgc, &clgc);
if (py == 0 && s->rupper < s->rlower && !data->hide_position) {
value = options_get_string(oo, "copy-mode-position-format");
if (*value != '\0') {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-panes.c,v 1.4 2026/07/29 14:06:32 nicm Exp $ */
/* $OpenBSD: window-panes.c,v 1.5 2026/08/25 06:04:33 nicm Exp $ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1009,6 +1009,7 @@ window_panes_run_command(struct window_panes_modedata *data, struct client *c,
} else {
new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item);
cmd_list_free(cmdlist);
}
free(expanded);
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-switch.c,v 1.2 2026/07/14 17:17:18 nicm Exp $ */
/* $OpenBSD: window-switch.c,v 1.3 2026/08/25 07:23:30 nicm Exp $ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -227,7 +227,8 @@ window_switch_build(struct window_switch_modedata *data)
m = xreallocarray(m, n + 1, sizeof *m);
m[n++] = item;
}
qsort(m, n, sizeof *m, window_switch_compare);
if (n > 1)
qsort(m, n, sizeof *m, window_switch_compare);
free(data->matches);
data->matches = m;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-tree.c,v 1.94 2026/07/14 17:17:18 nicm Exp $ */
/* $OpenBSD: window-tree.c,v 1.96 2026/08/25 07:23:30 nicm Exp $ */
/*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -642,6 +642,7 @@ window_tree_draw_session(struct window_tree_modedata *data, struct session *s,
}
free(label);
}
format_free(ft);
if (loop != end - 1) {
screen_write_cursormove(ctx, cx + offset + width, cy,
@@ -790,6 +791,7 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s,
}
free(label);
}
format_free(ft);
if (loop != end - 1) {
screen_write_cursormove(ctx, cx + offset + width, cy,
@@ -1020,6 +1022,10 @@ window_tree_swap(void *cur_itemdata, void *other_itemdata,
window_tree_pull_item(other, &other_session, &other_winlink,
&other_pane);
if (cur_session == NULL || cur_winlink == NULL)
return (0);
if (other_session == NULL || other_winlink == NULL)
return (0);
if (cur_session != other_session)
return (0);

157
window.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window.c,v 1.369 2026/07/29 14:06:32 nicm Exp $ */
/* $OpenBSD: window.c,v 1.373 2026/08/24 21:17:19 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -451,6 +451,7 @@ window_destroy(struct window *w)
{
log_debug("window @%u destroyed (%d references)", w->id, w->references);
active_remove_window(w);
window_unzoom(w, 0);
RB_REMOVE(windows, &windows, w);
@@ -672,6 +673,7 @@ void
window_pane_update_focus(struct window_pane *wp)
{
struct client *c;
struct window *w;
int focused = 0;
if (wp != NULL && (~wp->flags & PANE_EXITED)) {
@@ -679,10 +681,12 @@ window_pane_update_focus(struct window_pane *wp)
focused = 0;
else {
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->attached != 0 &&
if (c->session == NULL)
continue;
w = active_get_effective_window(c, c->session);
if (c->session->attached != 0 &&
(c->flags & CLIENT_FOCUSED) &&
c->session->curw->window == wp->window &&
w == wp->window &&
c->overlay_draw == NULL &&
wp->window->menu == NULL) {
focused = 1;
@@ -718,7 +722,7 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
return (0);
if (w->modal != NULL && wp != w->modal)
return (0);
if (w->flags & WINDOW_ZOOMED)
if ((w->flags & WINDOW_ZOOMED) && !window_pane_is_visible(wp))
window_unzoom(w, 1);
lastwp = w->active;
@@ -788,7 +792,8 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp)
break;
/* If the pane is floating, move to the front. */
if (window_pane_is_floating(wp)) {
if (window_pane_is_floating(wp) &&
TAILQ_FIRST(&w->z_index) != wp) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
wp->flags |= PANE_REDRAW;
@@ -918,13 +923,18 @@ window_zoom(struct window_pane *wp)
{
struct window *w = wp->window;
struct window_pane *wp1;
struct layout_cell *lc;
struct layout_geometry lg;
if (w->flags & WINDOW_ZOOMED)
return (-1);
if (window_count_panes(w, 1) == 1)
return (-1);
if (w->active != wp)
if (w->active != wp &&
(w->active == NULL ||
(~w->active->flags & PANE_FLOATOVERZOOM) ||
!window_pane_is_floating(w->active)))
window_set_active_pane(w, wp, 1);
wp->flags |= PANE_ZOOMED;
@@ -935,6 +945,22 @@ window_zoom(struct window_pane *wp)
w->saved_layout_root = w->layout_root;
layout_init(w, wp);
TAILQ_FOREACH(wp1, &w->panes, entry) {
lc = wp1->saved_layout_cell;
if (wp1 == wp ||
(~wp1->flags & PANE_FLOATOVERZOOM) ||
lc == NULL ||
(~lc->flags & LAYOUT_CELL_FLOATING))
continue;
memcpy(&lg, &lc->g, sizeof lg);
lc = layout_floating_pane(w, wp, &lg);
layout_assign_pane(lc, wp1, 0);
}
/* A floating zoom target is now tiled, so put it behind the floats. */
if (wp->saved_layout_cell->flags & LAYOUT_CELL_FLOATING) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
}
w->flags |= WINDOW_ZOOMED;
events_fire_window("window-zoomed", w);
events_fire_window("window-layout-changed", w);
@@ -946,11 +972,26 @@ window_zoom(struct window_pane *wp)
int
window_unzoom(struct window *w, int notify)
{
struct window_pane *wp;
struct window_pane *wp, *zoomed = NULL;
struct layout_cell *slc;
if (!(w->flags & WINDOW_ZOOMED))
if (~w->flags & WINDOW_ZOOMED)
return (-1);
TAILQ_FOREACH(wp, &w->panes, entry) {
if (wp->flags & PANE_ZOOMED)
zoomed = wp;
if (~wp->flags & PANE_FLOATOVERZOOM)
continue;
if (wp->flags & PANE_ZOOMED)
continue;
slc = wp->saved_layout_cell;
if (slc == NULL || wp->layout_cell == NULL)
continue;
memcpy(&slc->g, &wp->layout_cell->g, sizeof slc->g);
memcpy(&slc->fg, &wp->layout_cell->fg, sizeof slc->fg);
}
w->flags &= ~WINDOW_ZOOMED;
layout_free(w, 0);
w->layout_root = w->saved_layout_root;
@@ -961,6 +1002,22 @@ window_unzoom(struct window *w, int notify)
wp->saved_layout_cell = NULL;
wp->flags &= ~PANE_ZOOMED;
}
/* Put a floating zoom target back into the floating part of the list. */
if (zoomed != NULL && window_pane_is_floating(zoomed)) {
TAILQ_REMOVE(&w->z_index, zoomed, zentry);
if (zoomed == w->active)
TAILQ_INSERT_HEAD(&w->z_index, zoomed, zentry);
else {
TAILQ_FOREACH(wp, &w->z_index, zentry) {
if (!window_pane_is_floating(wp))
break;
}
if (wp == NULL)
TAILQ_INSERT_TAIL(&w->z_index, zoomed, zentry);
else
TAILQ_INSERT_BEFORE(wp, zoomed, zentry);
}
}
layout_fix_panes(w, NULL);
if (notify) {
@@ -972,48 +1029,69 @@ window_unzoom(struct window *w, int notify)
return (0);
}
void
window_push_modal_zoom(struct window *w)
struct window_pane *
window_zoomed_pane(struct window *w)
{
if (w->flags & WINDOW_ZOOMED)
w->flags |= WINDOW_WASMODALZOOMED;
else
w->flags &= ~WINDOW_WASMODALZOOMED;
window_unzoom(w, 1);
struct window_pane *wp;
if (~w->flags & WINDOW_ZOOMED)
return (NULL);
TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) {
if (wp->layout_cell != NULL && !window_pane_is_floating(wp))
return (wp);
}
return (NULL);
}
int
window_pop_modal_zoom(struct window *w)
window_active_pane_is_over_zoom(struct window *w)
{
struct window_pane *wp = w->active;
if (~w->flags & WINDOW_WASMODALZOOMED)
if (~w->flags & WINDOW_ZOOMED)
return (0);
w->flags &= ~WINDOW_WASMODALZOOMED;
if (wp != NULL && window_has_pane(w, wp))
return (window_zoom(wp) == 0);
return (0);
if (w->active == NULL)
return (0);
if (~w->active->flags & PANE_FLOATOVERZOOM)
return (0);
return (window_pane_is_floating(w->active));
}
int
window_push_zoom(struct window *w, int always, int flag)
{
struct window_pane *wp = window_zoomed_pane(w);
log_debug("%s: @%u %d", __func__, w->id,
flag && (w->flags & WINDOW_ZOOMED));
if (flag && (always || (w->flags & WINDOW_ZOOMED)))
w->flags |= WINDOW_WASZOOMED;
else
w->flags &= ~WINDOW_WASZOOMED;
if (w->flags & WINDOW_WASZOOMED)
w->was_zoomed = wp;
else
w->was_zoomed = NULL;
return (window_unzoom(w, 1) == 0);
}
int
window_pop_zoom(struct window *w)
{
struct window_pane *wp = w->was_zoomed;
log_debug("%s: @%u %d", __func__, w->id,
!!(w->flags & WINDOW_WASZOOMED));
if (w->flags & WINDOW_WASZOOMED)
return (window_zoom(w->active) == 0);
if (w->flags & WINDOW_WASZOOMED) {
w->flags &= ~WINDOW_WASZOOMED;
w->was_zoomed = NULL;
if (w->active != NULL &&
((~w->active->flags & PANE_FLOATOVERZOOM) ||
!window_pane_is_floating(w->active)))
wp = w->active;
if (wp == NULL || !window_has_pane(w, wp))
wp = w->active;
if (wp != NULL)
return (window_zoom(wp) == 0);
}
return (0);
}
@@ -1065,8 +1143,8 @@ window_lost_pane(struct window *w, struct window_pane *wp)
server_clear_marked();
if (wp == w->modal_last)
w->modal_last = NULL;
if (w->modal_last == NULL)
w->flags &= ~WINDOW_WASMODALZOOMED;
if (wp == w->was_zoomed)
w->was_zoomed = NULL;
window_pane_stack_remove(&w->last_panes, wp);
if (wp == w->active) {
@@ -1093,7 +1171,6 @@ window_lost_pane(struct window *w, struct window_pane *wp)
}
} else if (wp == w->modal) {
w->modal = w->modal_last = NULL;
w->flags &= ~WINDOW_WASMODALZOOMED;
}
redraw_invalidate_scene(w);
}
@@ -1101,13 +1178,9 @@ window_lost_pane(struct window *w, struct window_pane *wp)
void
window_remove_pane(struct window *w, struct window_pane *wp)
{
int pop = (wp == w->modal);
window_lost_pane(w, wp);
TAILQ_REMOVE(&w->panes, wp, entry);
TAILQ_REMOVE(&w->z_index, wp, zentry);
if (pop && window_pop_modal_zoom(w))
server_redraw_window(w);
redraw_invalidate_scene(w);
window_pane_destroy(wp);
}
@@ -1219,7 +1292,7 @@ window_destroy_panes(struct window *w)
}
const char *
window_printable_flags(struct winlink *wl, int escape)
window_printable_flags(struct client *c, struct winlink *wl, int escape)
{
struct session *s = wl->session;
static char flags[32];
@@ -1234,9 +1307,9 @@ window_printable_flags(struct winlink *wl, int escape)
flags[pos++] = '!';
if (wl->flags & WINLINK_SILENCE)
flags[pos++] = '~';
if (wl == s->curw)
if (active_is_effective_window(c, s, wl))
flags[pos++] = '*';
if (wl == TAILQ_FIRST(&s->lastw))
if (active_is_last_window(c, s, wl))
flags[pos++] = '-';
if (server_check_marked() && wl == marked_pane.wl)
flags[pos++] = 'M';
@@ -1263,6 +1336,8 @@ window_pane_printable_flags(struct window_pane *wp)
flags[pos++] = 'Z';
if (window_pane_is_floating(wp))
flags[pos++] = 'F';
if (wp->flags & PANE_FLOATOVERZOOM)
flags[pos++] = 'A';
if (wp == w->modal)
flags[pos++] = 'O';
flags[pos] = '\0';
@@ -1408,6 +1483,8 @@ window_pane_scrollbar_overlay_visible(struct window_pane *wp)
void
window_pane_scrollbar_redraw(struct window_pane *wp)
{
if (!window_pane_scrollbar_visible(wp))
return;
if (window_pane_scrollbar_overlay_visible(wp)) {
wp->flags |= PANE_REDRAW;
return;
@@ -1475,16 +1552,16 @@ window_pane_free(struct window_pane *wp)
log_debug("pane %%%u freed (%d references)", wp->id, wp->references);
free(wp->searchstr);
screen_free(&wp->status_screen);
screen_free(&wp->base);
free(wp->r.ranges);
options_free(wp->options);
free((void *)wp->cwd);
free(wp->shell);
cmd_free_argv(wp->argc, wp->argv);
colour_palette_free(&wp->palette);
style_ranges_free(&wp->border_status_line.ranges);
free(wp->border_status_line.expanded);
free(wp);
}
@@ -1969,7 +2046,7 @@ window_pane_is_visible(struct window_pane *wp)
{
if (~wp->window->flags & WINDOW_ZOOMED)
return (1);
return (wp == wp->window->active);
return (wp->layout_cell != NULL);
}
int
@@ -2562,7 +2639,7 @@ window_pane_get_bg(struct window_pane *wp)
c = window_pane_get_bg_control_client(wp);
if (c == -1) {
tty_default_colours(&defaults, wp, NULL);
tty_default_colours(&defaults, wp, wp->window->active, NULL);
if (COLOUR_DEFAULT(defaults.bg))
c = window_get_bg_client(wp);
else