mirror of
https://github.com/tmux/tmux.git
synced 2026-09-25 14:08:23 +00:00
Compare commits
92
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a17365322 | ||
|
|
6933bc417c | ||
|
|
238067acbf | ||
|
|
20f76fb069 | ||
|
|
5e860c4e6c | ||
|
|
183c3432b0 | ||
|
|
d1ee699dd1 | ||
|
|
e7618f41c2 | ||
|
|
d4adce7e00 | ||
|
|
04dbc6d89b | ||
|
|
83c8962cf9 | ||
|
|
fca57a5fc9 | ||
|
|
83f62d0acc | ||
|
|
19b7660778 | ||
|
|
4624bc2129 | ||
|
|
0e1f6ed80d | ||
|
|
104a0cee99 | ||
|
|
a7f74730d7 | ||
|
|
4a1e445b38 | ||
|
|
9a3aa26a33 | ||
|
|
1ce64bd087 | ||
|
|
6f65c31887 | ||
|
|
21ea1c91c5 | ||
|
|
cb9f9b588f | ||
|
|
d6db719b19 | ||
|
|
bc20fc6da4 | ||
|
|
9860e60afa | ||
|
|
ca5c28d224 | ||
|
|
d74b980d80 | ||
|
|
ac4daa57c9 | ||
|
|
c3326194b6 | ||
|
|
6a8c21fb41 | ||
|
|
326b9a4f16 | ||
|
|
6871fcb157 | ||
|
|
1d9943c6e6 | ||
|
|
f5c4998db3 | ||
|
|
cc81429d8a | ||
|
|
3a6c2e7877 | ||
|
|
21b3da3bab | ||
|
|
a7bd2415ba | ||
|
|
dda0e4d489 | ||
|
|
e361b8f8ff | ||
|
|
9be3a35178 | ||
|
|
324c636c5c | ||
|
|
166851bf87 | ||
|
|
5aa17a0cfa | ||
|
|
34cd5da4e3 | ||
|
|
3d5f946f35 | ||
|
|
81794f3047 | ||
|
|
541ca0e844 | ||
|
|
c612f35fd8 | ||
|
|
56b36d671e | ||
|
|
eee95e6479 | ||
|
|
bbd00148de | ||
|
|
2e9189c01d | ||
|
|
d4dc032599 | ||
|
|
d1d07f9790 | ||
|
|
b25af0a08c | ||
|
|
1eed85c344 | ||
|
|
73042686d8 | ||
|
|
313ad6fdc5 | ||
|
|
594465fbe7 | ||
|
|
07d40d5fa9 | ||
|
|
e880cf63e0 | ||
|
|
325b40e8c4 | ||
|
|
f95363aa83 | ||
|
|
f8fc53b6f8 | ||
|
|
d215280b00 | ||
|
|
d859a0f410 | ||
|
|
10d00cc7cb | ||
|
|
14a01dd337 | ||
|
|
a958883625 | ||
|
|
d202aa7ac1 | ||
|
|
d9a5e82678 | ||
|
|
13c10f672c | ||
|
|
b7c5030004 | ||
|
|
80e87aaf16 | ||
|
|
4839123510 | ||
|
|
9b3268a2a0 | ||
|
|
f41b983e04 | ||
|
|
48e33179d4 | ||
|
|
aed1209c02 | ||
|
|
6739bd03f5 | ||
|
|
f66eeef8a5 | ||
|
|
2d220d9a7f | ||
|
|
da4895d559 | ||
|
|
b254f557e4 | ||
|
|
b26eeb5229 | ||
|
|
48cdd85886 | ||
|
|
f255f089fc | ||
|
|
d6074895bb | ||
|
|
faba411289 |
@@ -27,10 +27,10 @@ jobs:
|
|||||||
runner: ubuntu-24.04
|
runner: ubuntu-24.04
|
||||||
make: make
|
make: make
|
||||||
configure: --enable-utf8proc --enable-asan
|
configure: --enable-utf8proc --enable-asan
|
||||||
#- name: ubuntu-24.04-arm64
|
- name: ubuntu-24.04-arm64
|
||||||
# runner: ubuntu-24.04-arm
|
runner: ubuntu-24.04-arm
|
||||||
# make: make
|
make: make
|
||||||
# configure: --enable-utf8proc --enable-asan
|
configure: --enable-utf8proc --enable-asan
|
||||||
- name: macos-26-arm64
|
- name: macos-26-arm64
|
||||||
runner: macos-26
|
runner: macos-26
|
||||||
make: gmake
|
make: gmake
|
||||||
@@ -72,14 +72,14 @@ jobs:
|
|||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
run: |
|
run: |
|
||||||
sh autogen.sh
|
sh autogen.sh
|
||||||
./configure ${{ matrix.configure }}
|
./configure ${{ matrix.configure }}
|
||||||
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
|
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
run: |
|
run: |
|
||||||
cd regress
|
cd regress
|
||||||
${{ matrix.make }}
|
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
|
||||||
|
|
||||||
- name: logs
|
- name: logs
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
line;
|
line;
|
||||||
|
|
||||||
- pane-border-status has top-floating and bottom-floating, and there are new
|
- pane-border-status has top-floating and bottom-floating, and there are new
|
||||||
default bindings under C-b g for common move and resize operations;
|
default bindings under C-b g for common move and resize operations.
|
||||||
|
|
||||||
- floating panes are unzoomed before creation to avoid a crash.
|
|
||||||
|
|
||||||
* Layout strings now use a JSON subset format which includes floating panes.
|
* Layout strings now use a JSON subset format which includes floating panes.
|
||||||
The old format is still accepted; control mode clients receive old layouts
|
The old format is still accepted; control mode clients receive old layouts
|
||||||
@@ -132,6 +130,8 @@ CHANGES FROM 3.7c TO 3.8
|
|||||||
|
|
||||||
* Add capture-pane -I to include the time each line entered history.
|
* Add capture-pane -I to include the time each line entered history.
|
||||||
|
|
||||||
|
* Various memory leak fixes from Heon Jeong.
|
||||||
|
|
||||||
* Add a default C-b T binding to change the current pane title.
|
* Add a default C-b T binding to change the current pane title.
|
||||||
|
|
||||||
* Menus now belong to the window, so appear on all clients.
|
* Menus now belong to the window, so appear on all clients.
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ dist_tmux_SOURCES = \
|
|||||||
options-table.c \
|
options-table.c \
|
||||||
options.c \
|
options.c \
|
||||||
paste.c \
|
paste.c \
|
||||||
popup.c \
|
|
||||||
proc.c \
|
proc.c \
|
||||||
prompt-history.c \
|
prompt-history.c \
|
||||||
prompt.c \
|
prompt.c \
|
||||||
|
|||||||
+71
-8
@@ -35,7 +35,7 @@ The usual local layout is:
|
|||||||
```sh
|
```sh
|
||||||
cd /some/where/useful
|
cd /some/where/useful
|
||||||
git clone https://github.com/tmux/tmux.git tmux-portable
|
git clone https://github.com/tmux/tmux.git tmux-portable
|
||||||
git clone https://github.com/ThomasAdam/tmux-obsd.git tmux-openbsd-cutover
|
git clone https://github.com/tmux/tmux-openbsd-cutover.git tmux-openbsd-cutover
|
||||||
```
|
```
|
||||||
|
|
||||||
The exact directory names do not matter, but the examples below use:
|
The exact directory names do not matter, but the examples below use:
|
||||||
@@ -56,18 +56,19 @@ The cutover repository has three important branches:
|
|||||||
|
|
||||||
# Adding the OpenBSD remote to portable
|
# Adding the OpenBSD remote to portable
|
||||||
|
|
||||||
In the portable repository, add the cutover repository as a remote:
|
In the portable repository, add the published cutover repository as a remote.
|
||||||
|
This works regardless of which branch is checked out in a local cutover clone:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /path/to/tmux-portable
|
cd /path/to/tmux-portable
|
||||||
git remote add tmux-openbsd /path/to/tmux-openbsd-cutover
|
git remote add tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
||||||
git config remote.tmux-openbsd.tagOpt --no-tags
|
git config remote.tmux-openbsd.tagOpt --no-tags
|
||||||
```
|
```
|
||||||
|
|
||||||
If the remote already exists, update it instead:
|
If the remote already exists, update it instead:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git remote set-url tmux-openbsd /path/to/tmux-openbsd-cutover
|
git remote set-url tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
||||||
git config remote.tmux-openbsd.tagOpt --no-tags
|
git config remote.tmux-openbsd.tagOpt --no-tags
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -77,6 +78,32 @@ Fetch the cutover master branch explicitly:
|
|||||||
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To merge unpublished changes from a local cutover clone instead, first ensure
|
||||||
|
it has an up-to-date local `master` branch. A normal clone may check out
|
||||||
|
`automation` and have only `origin/master`; fetching `master` from that clone
|
||||||
|
will then fail with `couldn't find remote ref master`.
|
||||||
|
|
||||||
|
With a clean cutover working tree:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /path/to/tmux-openbsd-cutover
|
||||||
|
git fetch --no-tags origin
|
||||||
|
git switch master
|
||||||
|
git merge --ff-only origin/master
|
||||||
|
```
|
||||||
|
|
||||||
|
`git switch master` creates a tracking branch from `origin/master` if there is
|
||||||
|
no local `master` yet. If the fast-forward fails, reconcile the local cutover
|
||||||
|
changes before continuing; do not reset them away.
|
||||||
|
|
||||||
|
Then, in portable, point the remote at that clone and fetch its local `master`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /path/to/tmux-portable
|
||||||
|
git remote set-url tmux-openbsd /path/to/tmux-openbsd-cutover
|
||||||
|
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
||||||
|
```
|
||||||
|
|
||||||
# Automated syncing
|
# Automated syncing
|
||||||
|
|
||||||
The normal sync is performed by the GitHub Actions workflow in the
|
The normal sync is performed by the GitHub Actions workflow in the
|
||||||
@@ -99,7 +126,9 @@ OpenBSD changes.
|
|||||||
If the workflow fails while merging into portable, do the merge locally and
|
If the workflow fails while merging into portable, do the merge locally and
|
||||||
push the result.
|
push the result.
|
||||||
|
|
||||||
Start from an up-to-date portable master:
|
Start with a clean working tree and an up-to-date portable master. If a merge
|
||||||
|
is already in progress, skip to resolving conflicts, or abort it before
|
||||||
|
starting again:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /path/to/tmux-portable
|
cd /path/to/tmux-portable
|
||||||
@@ -108,10 +137,15 @@ git checkout master
|
|||||||
git pull --ff-only origin master
|
git pull --ff-only origin master
|
||||||
```
|
```
|
||||||
|
|
||||||
Fetch the cutover branch:
|
Fetch the published cutover branch. Update an existing remote as well, since
|
||||||
|
it may point at a local clone without a `master` branch or with a stale one:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git remote add tmux-openbsd /path/to/tmux-openbsd-cutover 2>/dev/null || true
|
if git remote get-url tmux-openbsd >/dev/null 2>&1; then
|
||||||
|
git remote set-url tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
||||||
|
else
|
||||||
|
git remote add tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
||||||
|
fi
|
||||||
git config remote.tmux-openbsd.tagOpt --no-tags
|
git config remote.tmux-openbsd.tagOpt --no-tags
|
||||||
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
||||||
```
|
```
|
||||||
@@ -122,7 +156,22 @@ Merge it:
|
|||||||
git merge --no-ff --log refs/remotes/tmux-openbsd/master
|
git merge --no-ff --log refs/remotes/tmux-openbsd/master
|
||||||
```
|
```
|
||||||
|
|
||||||
Resolve conflicts by deciding whether portable or OpenBSD owns the file.
|
If merging a local cutover branch instead, use the local-clone preparation
|
||||||
|
and fetch commands above in place of this fetch block.
|
||||||
|
|
||||||
|
When the merge reports conflicts, it leaves the merge in progress. List the
|
||||||
|
unresolved files, edit the conflict markers to combine the required portable
|
||||||
|
and OpenBSD changes, then stage each resolved file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git diff --name-only --diff-filter=U
|
||||||
|
git diff -- path/to/file
|
||||||
|
git add path/to/file
|
||||||
|
```
|
||||||
|
|
||||||
|
For files that should come entirely from one side, decide whether portable or
|
||||||
|
OpenBSD owns the file before using the commands below. They replace the whole
|
||||||
|
file, including changes outside the conflicting hunks.
|
||||||
|
|
||||||
Useful commands:
|
Useful commands:
|
||||||
|
|
||||||
@@ -140,11 +189,25 @@ git add path/to/file
|
|||||||
|
|
||||||
This takes the OpenBSD/cutover version of a conflicted file.
|
This takes the OpenBSD/cutover version of a conflicted file.
|
||||||
|
|
||||||
|
For a modify/delete conflict, the side that deleted the file has no version
|
||||||
|
to check out. For example, portable generates `Makefile` using autotools and
|
||||||
|
does not track OpenBSD's `Makefile`. If Git reports that `Makefile` was deleted
|
||||||
|
in HEAD and modified in cutover, keep the portable deletion with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git rm -- Makefile
|
||||||
|
```
|
||||||
|
|
||||||
|
This removes the OpenBSD file left by the merge; regenerate the portable
|
||||||
|
`Makefile` with your usual configure command before building. Use this only
|
||||||
|
for the unmerged OpenBSD file, not an existing generated build file.
|
||||||
|
|
||||||
Before committing, inspect the result:
|
Before committing, inspect the result:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git status
|
git status
|
||||||
git diff --check
|
git diff --check
|
||||||
|
git diff --cached --check
|
||||||
git diff --cached --stat
|
git diff --cached --stat
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -91,8 +91,10 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
|
|||||||
wp = target.wp;
|
wp = target.wp;
|
||||||
|
|
||||||
if (wl != NULL) {
|
if (wl != NULL) {
|
||||||
if (wp != NULL)
|
if (wp != NULL) {
|
||||||
|
window_redraw_active_switch(wp->window, wp);
|
||||||
window_set_active_pane(wp->window, wp, 1);
|
window_set_active_pane(wp->window, wp, 1);
|
||||||
|
}
|
||||||
session_set_current(s, wl);
|
session_set_current(s, wl);
|
||||||
if (wp != NULL)
|
if (wp != NULL)
|
||||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||||
|
|||||||
+188
-317
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-display-menu.c,v 1.53 2026/08/31 07:46:55 nicm Exp $ */
|
/* $OpenBSD: cmd-display-menu.c,v 1.55 2026/09/21 12:14:32 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -54,8 +54,8 @@ const struct cmd_entry cmd_display_popup_entry = {
|
|||||||
.name = "display-popup",
|
.name = "display-popup",
|
||||||
.alias = "popup",
|
.alias = "popup",
|
||||||
|
|
||||||
.args = { "Bb:Cc:d:e:Eh:kNs:S:t:T:w:x:y:", 0, -1, NULL },
|
.args = { "Bb:Cc:d:e:Eh:ks:S:t:T:w:x:y:", 0, -1, NULL },
|
||||||
.usage = "[-BCEkN] [-b border-lines] [-c target-client] "
|
.usage = "[-BCEk] [-b border-lines] [-c target-client] "
|
||||||
"[-d start-directory] [-e environment] [-h height] "
|
"[-d start-directory] [-e environment] [-h height] "
|
||||||
"[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
|
"[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
|
||||||
" [-T title] [-w width] [-x position] [-y position] "
|
" [-T title] [-w width] [-x position] [-y position] "
|
||||||
@@ -91,203 +91,6 @@ cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause)
|
|||||||
return (type);
|
return (type);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
cmd_display_menu_get_popup_pos(struct client *tc, struct cmdq_item *item,
|
|
||||||
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
|
|
||||||
{
|
|
||||||
struct tty *tty = &tc->tty;
|
|
||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
|
||||||
struct key_event *event = cmdq_get_event(item);
|
|
||||||
struct session *s = tc->session;
|
|
||||||
struct winlink *wl = target->wl;
|
|
||||||
struct window_pane *wp = target->wp;
|
|
||||||
struct style_ranges *ranges = NULL;
|
|
||||||
struct style_range *sr = NULL;
|
|
||||||
const char *xp, *yp;
|
|
||||||
char *p;
|
|
||||||
int top;
|
|
||||||
u_int line, ox, oy, sx, sy, lines, position;
|
|
||||||
long n;
|
|
||||||
struct format_tree *ft;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Work out the position from the -x and -y arguments. This is the
|
|
||||||
* bottom-left position.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* If the popup is too big, stop now. */
|
|
||||||
if (w > tty->sx || h > tty->sy)
|
|
||||||
return (0);
|
|
||||||
|
|
||||||
/* Create format with mouse position if any. */
|
|
||||||
ft = format_create_from_target(item);
|
|
||||||
if (event->m.valid) {
|
|
||||||
format_add(ft, "popup_mouse_x", "%u", event->m.x);
|
|
||||||
format_add(ft, "popup_mouse_y", "%u", event->m.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Position of the previous menu, for -x/-y L. */
|
|
||||||
format_add(ft, "popup_last_x", "%u", target->w->menu_last_px);
|
|
||||||
format_add(ft, "popup_last_y", "%u", target->w->menu_last_py + h);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If there are any status lines, add this window position and the
|
|
||||||
* status line position.
|
|
||||||
*/
|
|
||||||
top = status_at_line(tc);
|
|
||||||
if (top != -1) {
|
|
||||||
lines = status_line_size(tc);
|
|
||||||
if (top == 0)
|
|
||||||
top = lines;
|
|
||||||
else
|
|
||||||
top = 0;
|
|
||||||
position = options_get_number(s->options, "status-position");
|
|
||||||
|
|
||||||
for (line = 0; line < lines; line++) {
|
|
||||||
ranges = &tc->status.entries[line].ranges;
|
|
||||||
TAILQ_FOREACH(sr, ranges, entry) {
|
|
||||||
if (sr->type != STYLE_RANGE_WINDOW)
|
|
||||||
continue;
|
|
||||||
if (sr->argument == (u_int)wl->idx)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (sr != NULL)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sr != NULL) {
|
|
||||||
format_add(ft, "popup_window_status_line_x", "%u",
|
|
||||||
sr->start);
|
|
||||||
if (position == 0) {
|
|
||||||
format_add(ft, "popup_window_status_line_y",
|
|
||||||
"%u", line + 1 + h);
|
|
||||||
} else {
|
|
||||||
format_add(ft, "popup_window_status_line_y",
|
|
||||||
"%u", tty->sy - lines + line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position == 0)
|
|
||||||
format_add(ft, "popup_status_line_y", "%u", lines + h);
|
|
||||||
else {
|
|
||||||
format_add(ft, "popup_status_line_y", "%u",
|
|
||||||
tty->sy - lines);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
top = 0;
|
|
||||||
|
|
||||||
/* Popup width and height. */
|
|
||||||
format_add(ft, "popup_width", "%u", w);
|
|
||||||
format_add(ft, "popup_height", "%u", h);
|
|
||||||
|
|
||||||
/* Position so popup is in the centre. */
|
|
||||||
n = (long)(tty->sx - 1) / 2 - w / 2;
|
|
||||||
if (n < 0)
|
|
||||||
format_add(ft, "popup_centre_x", "%u", 0);
|
|
||||||
else
|
|
||||||
format_add(ft, "popup_centre_x", "%ld", n);
|
|
||||||
n = (tty->sy - 1) / 2 + h / 2;
|
|
||||||
if (n >= tty->sy)
|
|
||||||
format_add(ft, "popup_centre_y", "%u", tty->sy - h);
|
|
||||||
else
|
|
||||||
format_add(ft, "popup_centre_y", "%ld", n);
|
|
||||||
|
|
||||||
/* Position of popup relative to mouse. */
|
|
||||||
if (event->m.valid) {
|
|
||||||
n = (long)event->m.x - w / 2;
|
|
||||||
if (n < 0)
|
|
||||||
format_add(ft, "popup_mouse_centre_x", "%u", 0);
|
|
||||||
else
|
|
||||||
format_add(ft, "popup_mouse_centre_x", "%ld", n);
|
|
||||||
n = event->m.y - h / 2;
|
|
||||||
if (n + h >= tty->sy) {
|
|
||||||
format_add(ft, "popup_mouse_centre_y", "%u",
|
|
||||||
tty->sy - h);
|
|
||||||
} else
|
|
||||||
format_add(ft, "popup_mouse_centre_y", "%ld", n);
|
|
||||||
n = (long)event->m.y + h;
|
|
||||||
if (n >= tty->sy)
|
|
||||||
format_add(ft, "popup_mouse_top", "%u", tty->sy - 1);
|
|
||||||
else
|
|
||||||
format_add(ft, "popup_mouse_top", "%ld", n);
|
|
||||||
n = event->m.y - h;
|
|
||||||
if (n < 0)
|
|
||||||
format_add(ft, "popup_mouse_bottom", "%u", 0);
|
|
||||||
else
|
|
||||||
format_add(ft, "popup_mouse_bottom", "%ld", n);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Position in pane. */
|
|
||||||
tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy);
|
|
||||||
n = top + wp->yoff - oy + h;
|
|
||||||
if (n >= tty->sy)
|
|
||||||
format_add(ft, "popup_pane_top", "%u", tty->sy - h);
|
|
||||||
else
|
|
||||||
format_add(ft, "popup_pane_top", "%ld", n);
|
|
||||||
format_add(ft, "popup_pane_bottom", "%u", top + wp->yoff + wp->sy - oy);
|
|
||||||
format_add(ft, "popup_pane_left", "%u", wp->xoff - ox);
|
|
||||||
n = (long)wp->xoff + wp->sx - ox - w;
|
|
||||||
if (n < 0)
|
|
||||||
format_add(ft, "popup_pane_right", "%u", 0);
|
|
||||||
else
|
|
||||||
format_add(ft, "popup_pane_right", "%ld", n);
|
|
||||||
|
|
||||||
/* Expand horizontal position. */
|
|
||||||
xp = args_get(args, 'x');
|
|
||||||
if (xp == NULL || strcmp(xp, "C") == 0)
|
|
||||||
xp = "#{popup_centre_x}";
|
|
||||||
else if (strcmp(xp, "R") == 0)
|
|
||||||
xp = "#{popup_pane_right}";
|
|
||||||
else if (strcmp(xp, "P") == 0)
|
|
||||||
xp = "#{popup_pane_left}";
|
|
||||||
else if (strcmp(xp, "M") == 0)
|
|
||||||
xp = "#{popup_mouse_centre_x}";
|
|
||||||
else if (strcmp(xp, "L") == 0)
|
|
||||||
xp = "#{popup_last_x}";
|
|
||||||
else if (strcmp(xp, "W") == 0)
|
|
||||||
xp = "#{popup_window_status_line_x}";
|
|
||||||
p = format_expand(ft, xp);
|
|
||||||
n = strtol(p, NULL, 10);
|
|
||||||
if (n + w >= tty->sx)
|
|
||||||
n = tty->sx - w;
|
|
||||||
else if (n < 0)
|
|
||||||
n = 0;
|
|
||||||
*px = n;
|
|
||||||
log_debug("%s: -x: %s = %s = %u (-w %u)", __func__, xp, p, *px, w);
|
|
||||||
free(p);
|
|
||||||
|
|
||||||
/* Expand vertical position */
|
|
||||||
yp = args_get(args, 'y');
|
|
||||||
if (yp == NULL || strcmp(yp, "C") == 0)
|
|
||||||
yp = "#{popup_centre_y}";
|
|
||||||
else if (strcmp(yp, "P") == 0)
|
|
||||||
yp = "#{popup_pane_bottom}";
|
|
||||||
else if (strcmp(yp, "M") == 0)
|
|
||||||
yp = "#{popup_mouse_top}";
|
|
||||||
else if (strcmp(yp, "L") == 0)
|
|
||||||
yp = "#{popup_last_y}";
|
|
||||||
else if (strcmp(yp, "S") == 0)
|
|
||||||
yp = "#{popup_status_line_y}";
|
|
||||||
else if (strcmp(yp, "W") == 0)
|
|
||||||
yp = "#{popup_window_status_line_y}";
|
|
||||||
p = format_expand(ft, yp);
|
|
||||||
n = strtol(p, NULL, 10);
|
|
||||||
if (n < h)
|
|
||||||
n = 0;
|
|
||||||
else
|
|
||||||
n -= h;
|
|
||||||
if (n + h >= tty->sy)
|
|
||||||
n = tty->sy - h;
|
|
||||||
else if (n < 0)
|
|
||||||
n = 0;
|
|
||||||
*py = n;
|
|
||||||
log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h);
|
|
||||||
free(p);
|
|
||||||
|
|
||||||
format_free(ft);
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
cmd_display_menu_get_menu_pos(struct client *tc, struct cmdq_item *item,
|
cmd_display_menu_get_menu_pos(struct client *tc, struct cmdq_item *item,
|
||||||
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
|
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
|
||||||
@@ -482,8 +285,8 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
enum box_lines lines = BOX_LINES_DEFAULT;
|
enum box_lines lines = BOX_LINES_DEFAULT;
|
||||||
char *title, *cause = NULL;
|
char *title, *cause = NULL;
|
||||||
int flags = 0, starting_choice = 0;
|
int flags = 0, starting_choice = 0;
|
||||||
u_int px, py, i, count = args_count(args);
|
u_int px, py, sx, sy, i, count = args_count(args);
|
||||||
struct options *o = target->s->curw->window->options;
|
struct options *o = target->w->options;
|
||||||
struct options_entry *oe;
|
struct options_entry *oe;
|
||||||
|
|
||||||
if (args_has(args, 'C')) {
|
if (args_has(args, 'C')) {
|
||||||
@@ -531,9 +334,6 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
}
|
}
|
||||||
if (menu->count == 0)
|
if (menu->count == 0)
|
||||||
goto out;
|
goto out;
|
||||||
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py,
|
|
||||||
menu->width + 4, menu->count + 2))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
value = args_get(args, 'b');
|
value = args_get(args, 'b');
|
||||||
if (value != NULL) {
|
if (value != NULL) {
|
||||||
@@ -544,7 +344,11 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cmdq_error(item, "menu-border-lines %s", cause);
|
cmdq_error(item, "menu-border-lines %s", cause);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
} else
|
||||||
|
lines = options_get_number(o, "menu-border-lines");
|
||||||
|
menu_get_size(menu, lines, &sx, &sy);
|
||||||
|
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py, sx, sy))
|
||||||
|
goto out;
|
||||||
|
|
||||||
if (args_has(args, 'O'))
|
if (args_has(args, 'O'))
|
||||||
flags |= MENU_STAYOPEN;
|
flags |= MENU_STAYOPEN;
|
||||||
@@ -565,6 +369,22 @@ fail:
|
|||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static enum pane_lines
|
||||||
|
cmd_display_popup_get_lines(const char *value, char **cause)
|
||||||
|
{
|
||||||
|
const struct options_table_entry *oe;
|
||||||
|
|
||||||
|
if (value == NULL)
|
||||||
|
value = "single";
|
||||||
|
else if (strcmp(value, "rounded") == 0)
|
||||||
|
value = "single";
|
||||||
|
else if (strcmp(value, "padded") == 0)
|
||||||
|
value = "spaces";
|
||||||
|
|
||||||
|
oe = options_search("pane-border-lines");
|
||||||
|
return (options_find_choice(oe, value, cause));
|
||||||
|
}
|
||||||
|
|
||||||
static enum cmd_retval
|
static enum cmd_retval
|
||||||
cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
||||||
{
|
{
|
||||||
@@ -572,148 +392,199 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
struct cmd_find_state *target = cmdq_get_target(item);
|
struct cmd_find_state *target = cmdq_get_target(item);
|
||||||
struct session *s = target->s;
|
struct session *s = target->s;
|
||||||
struct client *tc = cmdq_get_target_client(item);
|
struct client *tc = cmdq_get_target_client(item);
|
||||||
struct tty *tty = &tc->tty;
|
struct winlink *wl = target->wl;
|
||||||
const char *value, *shell, *shellcmd = NULL;
|
struct window *w = wl->window;
|
||||||
const char *style = args_get(args, 's');
|
struct window_pane *wp = target->wp, *new_wp = NULL;
|
||||||
|
struct spawn_context sc = { 0 };
|
||||||
|
struct layout_cell *lc = NULL;
|
||||||
|
struct layout_geometry lg;
|
||||||
|
struct event_payload *ep;
|
||||||
|
struct cmd_find_state fs;
|
||||||
|
const char *value, *style = args_get(args, 's');
|
||||||
const char *border_style = args_get(args, 'S');
|
const char *border_style = args_get(args, 'S');
|
||||||
char *cwd = NULL, *cause = NULL, **argv = NULL;
|
char *cause = NULL, *title = NULL;
|
||||||
char *title = NULL;
|
enum pane_lines lines = PANE_LINES_SINGLE;
|
||||||
int modify = popup_present(tc);
|
u_int px, py, sx, sy, count = args_count(args);
|
||||||
int flags = -1, argc = 0;
|
|
||||||
enum box_lines lines = BOX_LINES_DEFAULT;
|
|
||||||
u_int px, py, w, h, count = args_count(args);
|
|
||||||
struct args_value *av;
|
struct args_value *av;
|
||||||
struct environ *env = NULL;
|
long long ll;
|
||||||
struct options *o = s->curw->window->options;
|
|
||||||
struct options_entry *oe;
|
|
||||||
|
|
||||||
if (args_has(args, 'C')) {
|
if (args_has(args, 'C')) {
|
||||||
server_client_clear_overlay(tc);
|
if (w->modal != NULL)
|
||||||
|
server_kill_pane(w->modal);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
if (tc->flags & CLIENT_CONTROL)
|
if (tc->flags & CLIENT_CONTROL)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
if (!modify && tc->overlay_draw != NULL)
|
if (w->modal != NULL)
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
|
|
||||||
if (!modify) {
|
|
||||||
h = tty->sy / 2;
|
|
||||||
if (args_has(args, 'h')) {
|
|
||||||
h = args_percentage(args, 'h', 1, tty->sy, tty->sy,
|
|
||||||
&cause);
|
|
||||||
if (cause != NULL) {
|
|
||||||
cmdq_error(item, "height %s", cause);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w = tty->sx / 2;
|
|
||||||
if (args_has(args, 'w')) {
|
|
||||||
w = args_percentage(args, 'w', 1, tty->sx, tty->sx,
|
|
||||||
&cause);
|
|
||||||
if (cause != NULL) {
|
|
||||||
cmdq_error(item, "width %s", cause);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (w > tty->sx)
|
|
||||||
w = tty->sx;
|
|
||||||
if (h > tty->sy)
|
|
||||||
h = tty->sy;
|
|
||||||
if (!cmd_display_menu_get_popup_pos(tc, item, args, &px, &py,
|
|
||||||
w, h))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
value = args_get(args, 'd');
|
|
||||||
if (value != NULL)
|
|
||||||
cwd = format_single_from_target(item, value);
|
|
||||||
else
|
|
||||||
cwd = xstrdup(server_client_get_cwd(tc, s));
|
|
||||||
if (count == 0) {
|
|
||||||
shellcmd = options_get_string(s->options,
|
|
||||||
"default-command");
|
|
||||||
} else if (count == 1)
|
|
||||||
shellcmd = args_string(args, 0);
|
|
||||||
if (count <= 1 && (shellcmd == NULL || *shellcmd == '\0')) {
|
|
||||||
shellcmd = NULL;
|
|
||||||
shell = options_get_string(s->options, "default-shell");
|
|
||||||
if (!checkshell(shell))
|
|
||||||
shell = _PATH_BSHELL;
|
|
||||||
cmd_append_argv(&argc, &argv, shell);
|
|
||||||
} else
|
|
||||||
args_to_vector(args, &argc, &argv);
|
|
||||||
|
|
||||||
if (args_has(args, 'e') >= 1) {
|
|
||||||
env = environ_create();
|
|
||||||
av = args_first_value(args, 'e');
|
|
||||||
while (av != NULL) {
|
|
||||||
environ_put(env, av->string, 0);
|
|
||||||
av = args_next_value(av);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value = args_get(args, 'b');
|
value = args_get(args, 'b');
|
||||||
if (args_has(args, 'B'))
|
if (args_has(args, 'B'))
|
||||||
lines = BOX_LINES_NONE;
|
lines = PANE_LINES_NONE;
|
||||||
else if (value != NULL) {
|
else if (value != NULL) {
|
||||||
oe = options_get(o, "popup-border-lines");
|
lines = cmd_display_popup_get_lines(value, &cause);
|
||||||
lines = options_find_choice(options_table_entry(oe), value,
|
|
||||||
&cause);
|
|
||||||
if (cause != NULL) {
|
if (cause != NULL) {
|
||||||
cmdq_error(item, "popup-border-lines %s", cause);
|
cmdq_error(item, "pane-border-lines %s", cause);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sy = w->sy / 2;
|
||||||
|
if (args_has(args, 'h')) {
|
||||||
|
ll = args_percentage(args, 'h', 1, w->sy, w->sy, &cause);
|
||||||
|
if (cause != NULL) {
|
||||||
|
cmdq_error(item, "height %s", cause);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
sy = ll;
|
||||||
|
}
|
||||||
|
|
||||||
|
sx = w->sx / 2;
|
||||||
|
if (args_has(args, 'w')) {
|
||||||
|
ll = args_percentage(args, 'w', 1, w->sx, w->sx, &cause);
|
||||||
|
if (cause != NULL) {
|
||||||
|
cmdq_error(item, "width %s", cause);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
sx = ll;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sx > w->sx)
|
||||||
|
sx = w->sx;
|
||||||
|
if (sy > w->sy)
|
||||||
|
sy = w->sy;
|
||||||
|
if ((lines == PANE_LINES_NONE && (sx < 1 || sy < 1)) ||
|
||||||
|
(lines != PANE_LINES_NONE && (sx < 3 || sy < 3)))
|
||||||
|
goto out;
|
||||||
|
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py, sx, sy))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
lg.sx = sx;
|
||||||
|
lg.sy = sy;
|
||||||
|
lg.xoff = px;
|
||||||
|
lg.yoff = py;
|
||||||
|
if (lines != PANE_LINES_NONE) {
|
||||||
|
lg.sx -= 2;
|
||||||
|
lg.sy -= 2;
|
||||||
|
lg.xoff++;
|
||||||
|
lg.yoff++;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_push_zoom(w, 0, 1);
|
||||||
|
lc = layout_floating_pane(w, wp, &lg);
|
||||||
|
if (lc == NULL) {
|
||||||
|
window_pop_zoom(w);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.item = item;
|
||||||
|
sc.s = s;
|
||||||
|
sc.wl = wl;
|
||||||
|
sc.tc = tc;
|
||||||
|
sc.wp0 = wp;
|
||||||
|
sc.lc = lc;
|
||||||
|
sc.idx = -1;
|
||||||
|
sc.cwd = args_get(args, 'd');
|
||||||
|
sc.flags = (SPAWN_FLOATING|SPAWN_MODAL|SPAWN_FLOATOVERZOOM);
|
||||||
|
|
||||||
|
if (count != 1 || *args_string(args, 0) != '\0')
|
||||||
|
args_to_vector(args, &sc.argc, &sc.argv);
|
||||||
|
sc.environ = environ_create();
|
||||||
|
av = args_first_value(args, 'e');
|
||||||
|
while (av != NULL) {
|
||||||
|
environ_put(sc.environ, av->string, 0);
|
||||||
|
av = args_next_value(av);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_wp = spawn_pane(&sc, &cause);
|
||||||
|
if (new_wp == NULL) {
|
||||||
|
cmdq_error(item, "create pane failed: %s", cause);
|
||||||
|
free(cause);
|
||||||
|
cause = NULL;
|
||||||
|
window_pop_zoom(w);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
window_pop_zoom(w);
|
||||||
|
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||||
|
if (!args_has(args, 'E'))
|
||||||
|
new_wp->flags |= PANE_CLOSEONCANCEL;
|
||||||
|
|
||||||
|
options_set_number(new_wp->options, "pane-border-lines", lines);
|
||||||
|
if (args_has(args, 'E') > 1) {
|
||||||
|
if (args_has(args, 'k'))
|
||||||
|
options_set_number(new_wp->options, "remain-on-exit", 4);
|
||||||
|
else
|
||||||
|
options_set_number(new_wp->options, "remain-on-exit", 2);
|
||||||
|
} else if (args_has(args, 'E'))
|
||||||
|
options_set_number(new_wp->options, "remain-on-exit", 0);
|
||||||
|
else if (args_has(args, 'k'))
|
||||||
|
options_set_number(new_wp->options, "remain-on-exit", 3);
|
||||||
|
else
|
||||||
|
options_set_number(new_wp->options, "remain-on-exit", 1);
|
||||||
|
options_set_string(new_wp->options, "remain-on-exit-format", 0, "%s", "");
|
||||||
|
|
||||||
|
if (style != NULL) {
|
||||||
|
if (options_set_string(new_wp->options, "window-style", 0,
|
||||||
|
"%s", style) == NULL) {
|
||||||
|
cmdq_error(item, "bad style: %s", style);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
options_set_string(new_wp->options, "window-active-style", 0,
|
||||||
|
"%s", style);
|
||||||
|
new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|
|
||||||
|
PANE_THEMECHANGED);
|
||||||
|
}
|
||||||
|
if (border_style != NULL) {
|
||||||
|
if (options_set_string(new_wp->options, "pane-border-style", 0,
|
||||||
|
"%s", border_style) == NULL) {
|
||||||
|
cmdq_error(item, "bad border style: %s", border_style);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
options_set_string(new_wp->options, "pane-active-border-style",
|
||||||
|
0, "%s", border_style);
|
||||||
|
}
|
||||||
if (args_has(args, 'T'))
|
if (args_has(args, 'T'))
|
||||||
title = format_single_from_target(item, args_get(args, 'T'));
|
title = format_single_from_target(item, args_get(args, 'T'));
|
||||||
else
|
if (title != NULL) {
|
||||||
title = xstrdup("");
|
options_set_number(new_wp->options, "pane-border-status",
|
||||||
|
PANE_STATUS_TOP);
|
||||||
if (args_has(args, 'N') || !modify)
|
options_set_string(new_wp->options, "pane-border-format", 0,
|
||||||
flags = 0;
|
"%s", "#{pane_title}");
|
||||||
if (args_has(args, 'E') > 1) {
|
screen_set_title(&new_wp->base, title, 0);
|
||||||
if (flags == -1)
|
ep = event_payload_create();
|
||||||
flags = 0;
|
cmd_find_from_pane(&fs, new_wp, 0);
|
||||||
flags |= POPUP_CLOSEEXITZERO;
|
event_payload_set_target(ep, &fs);
|
||||||
} else if (args_has(args, 'E')) {
|
event_payload_set_pane(ep, "pane", new_wp);
|
||||||
if (flags == -1)
|
event_payload_set_window(ep, "window", new_wp->window);
|
||||||
flags = 0;
|
event_payload_set_string(ep, "new_title", "%s", title);
|
||||||
flags |= POPUP_CLOSEEXIT;
|
events_fire("pane-title-changed", ep);
|
||||||
}
|
|
||||||
if (args_has(args, 'k')) {
|
|
||||||
if (flags == -1)
|
|
||||||
flags = 0;
|
|
||||||
flags |= POPUP_CLOSEANYKEY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modify) {
|
new_wp->wait_item = item;
|
||||||
popup_modify(tc, title, style, border_style, lines, flags);
|
server_redraw_session(s);
|
||||||
goto out;
|
if (sc.argv != NULL)
|
||||||
}
|
cmd_free_argv(sc.argc, sc.argv);
|
||||||
if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
|
environ_free(sc.environ);
|
||||||
argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0)
|
|
||||||
goto out;
|
|
||||||
environ_free(env);
|
|
||||||
free(cwd);
|
|
||||||
free(title);
|
free(title);
|
||||||
cmd_free_argv(argc, argv);
|
|
||||||
return (CMD_RETURN_WAIT);
|
return (CMD_RETURN_WAIT);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
cmd_free_argv(argc, argv);
|
if (sc.argv != NULL)
|
||||||
environ_free(env);
|
cmd_free_argv(sc.argc, sc.argv);
|
||||||
free(cwd);
|
environ_free(sc.environ);
|
||||||
free(title);
|
free(title);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
free(cause);
|
free(cause);
|
||||||
cmd_free_argv(argc, argv);
|
if (new_wp != NULL) {
|
||||||
environ_free(env);
|
server_client_remove_pane(new_wp);
|
||||||
free(cwd);
|
layout_close_pane(new_wp);
|
||||||
|
window_remove_pane(new_wp->window, new_wp);
|
||||||
|
}
|
||||||
|
if (sc.argv != NULL)
|
||||||
|
cmd_free_argv(sc.argc, sc.argv);
|
||||||
|
environ_free(sc.environ);
|
||||||
free(title);
|
free(title);
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-6
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-join-pane.c,v 1.74 2026/08/03 20:29:52 nicm Exp $ */
|
/* $OpenBSD: cmd-join-pane.c,v 1.75 2026/09/21 10:33:16 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 George Nachman <tmux@georgester.com>
|
* Copyright (c) 2011 George Nachman <tmux@georgester.com>
|
||||||
@@ -133,7 +133,7 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
|||||||
} else if (strcmp(position, "back") == 0) {
|
} else if (strcmp(position, "back") == 0) {
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||||
if (!window_pane_is_floating(owp))
|
if (!window_pane_is_floating_with_hidden(owp))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (owp != NULL)
|
if (owp != NULL)
|
||||||
@@ -142,24 +142,30 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
|||||||
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
||||||
} else if (strcmp(position, "forward") == 0) {
|
} else if (strcmp(position, "forward") == 0) {
|
||||||
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
||||||
|
while (owp != NULL && owp->layout_cell == NULL)
|
||||||
|
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
|
||||||
if (owp != NULL) {
|
if (owp != NULL) {
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||||
}
|
}
|
||||||
} else if (strcmp(position, "backward") == 0) {
|
} else if (strcmp(position, "backward") == 0) {
|
||||||
owp = TAILQ_NEXT(wp, zentry);
|
owp = TAILQ_NEXT(wp, zentry);
|
||||||
|
while (owp != NULL && owp->layout_cell == NULL)
|
||||||
|
owp = TAILQ_NEXT(owp, zentry);
|
||||||
if (owp != NULL && window_pane_is_floating(owp)) {
|
if (owp != NULL && window_pane_is_floating(owp)) {
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
||||||
}
|
}
|
||||||
} else if (strcmp(position, "forward-loop") == 0) {
|
} else if (strcmp(position, "forward-loop") == 0) {
|
||||||
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
||||||
|
while (owp != NULL && owp->layout_cell == NULL)
|
||||||
|
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
if (owp != NULL)
|
if (owp != NULL)
|
||||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||||
else {
|
else {
|
||||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||||
if (!window_pane_is_floating(owp))
|
if (!window_pane_is_floating_with_hidden(owp))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (owp != NULL)
|
if (owp != NULL)
|
||||||
@@ -169,6 +175,8 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
|||||||
}
|
}
|
||||||
} else if (strcmp(position, "backward-loop") == 0) {
|
} else if (strcmp(position, "backward-loop") == 0) {
|
||||||
owp = TAILQ_NEXT(wp, zentry);
|
owp = TAILQ_NEXT(wp, zentry);
|
||||||
|
while (owp != NULL && owp->layout_cell == NULL)
|
||||||
|
owp = TAILQ_NEXT(owp, zentry);
|
||||||
if (owp != NULL && window_pane_is_floating(owp)) {
|
if (owp != NULL && window_pane_is_floating(owp)) {
|
||||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||||
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
||||||
@@ -300,6 +308,7 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
|||||||
struct window_pane *wp;
|
struct window_pane *wp;
|
||||||
struct layout_cell *lc;
|
struct layout_cell *lc;
|
||||||
int y, ly, x, lx;
|
int y, ly, x, lx;
|
||||||
|
int old_xoff, old_yoff, old_sx, old_sy;
|
||||||
|
|
||||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
@@ -321,10 +330,17 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
|||||||
ly = m->statusat - 1;
|
ly = m->statusat - 1;
|
||||||
|
|
||||||
if (x != lx || y != ly) {
|
if (x != lx || y != ly) {
|
||||||
|
old_xoff = wp->xoff;
|
||||||
|
old_yoff = wp->yoff;
|
||||||
|
old_sx = wp->sx;
|
||||||
|
old_sy = wp->sy;
|
||||||
|
|
||||||
lc->g.xoff += x - lx;
|
lc->g.xoff += x - lx;
|
||||||
lc->g.yoff += y - ly;
|
lc->g.yoff += y - ly;
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
server_redraw_window(w);
|
|
||||||
|
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||||
|
old_sy);
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,8 +363,10 @@ cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl,
|
|||||||
|
|
||||||
n = 0;
|
n = 0;
|
||||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||||
if (!window_pane_is_floating(owp))
|
if (!window_pane_is_floating_with_hidden(owp))
|
||||||
break;
|
break;
|
||||||
|
if (owp->layout_cell == NULL)
|
||||||
|
continue;
|
||||||
if (n >= z)
|
if (n >= z)
|
||||||
break;
|
break;
|
||||||
n++;
|
n++;
|
||||||
@@ -443,7 +461,6 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
cmdq_error(item, "pane is not floating");
|
cmdq_error(item, "pane is not floating");
|
||||||
return (CMD_RETURN_ERROR);
|
return (CMD_RETURN_ERROR);
|
||||||
}
|
}
|
||||||
server_unzoom_window(dst_w);
|
|
||||||
if ((s = args_get(args, 'P')) != NULL)
|
if ((s = args_get(args, 'P')) != NULL)
|
||||||
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
|
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
|
||||||
if ((s = args_get(args, 'z')) != NULL)
|
if ((s = args_get(args, 'z')) != NULL)
|
||||||
|
|||||||
+4
-1
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-queue.c,v 1.123 2026/08/24 20:34:26 nicm Exp $ */
|
/* $OpenBSD: cmd-queue.c,v 1.124 2026/09/22 06:46:50 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -580,6 +580,9 @@ cmdq_fire_command(struct cmdq_item *item)
|
|||||||
int flags, quiet = 0;
|
int flags, quiet = 0;
|
||||||
char *tmp;
|
char *tmp;
|
||||||
|
|
||||||
|
if (item->client != NULL && (item->client->flags & CLIENT_DEAD))
|
||||||
|
return (CMD_RETURN_ERROR);
|
||||||
|
|
||||||
if (cfg_finished)
|
if (cfg_finished)
|
||||||
cmdq_add_message(item);
|
cmdq_add_message(item);
|
||||||
if (log_get_level() > 1) {
|
if (log_get_level() > 1) {
|
||||||
|
|||||||
+10
-3
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-resize-pane.c,v 1.68 2026/08/31 07:44:39 nicm Exp $ */
|
/* $OpenBSD: cmd-resize-pane.c,v 1.69 2026/09/21 10:33:16 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -91,7 +91,8 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
server_redraw_window(w);
|
server_redraw_window(w);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
server_unzoom_window(w);
|
if (!window_pane_is_floating(wp))
|
||||||
|
server_unzoom_window(w);
|
||||||
lc = wp->layout_cell; /* may have been replaced by unzoom */
|
lc = wp->layout_cell; /* may have been replaced by unzoom */
|
||||||
|
|
||||||
if (args_has(args, 'x')) {
|
if (args_has(args, 'x')) {
|
||||||
@@ -238,6 +239,7 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
||||||
int left, right;
|
int left, right;
|
||||||
int new_xoff, new_yoff, resizes = 0;
|
int new_xoff, new_yoff, resizes = 0;
|
||||||
|
int old_xoff, old_yoff, old_sx, old_sy;
|
||||||
|
|
||||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
@@ -248,6 +250,10 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
lc = wp->layout_cell;
|
lc = wp->layout_cell;
|
||||||
sx = wp->sx;
|
sx = wp->sx;
|
||||||
sy = wp->sy;
|
sy = wp->sy;
|
||||||
|
old_xoff = wp->xoff;
|
||||||
|
old_yoff = wp->yoff;
|
||||||
|
old_sx = (int)wp->sx;
|
||||||
|
old_sy = (int)wp->sy;
|
||||||
left = wp->xoff - 1;
|
left = wp->xoff - 1;
|
||||||
right = wp->xoff + sx;
|
right = wp->xoff + sx;
|
||||||
if (window_pane_scrollbar_reserve(wp) &&
|
if (window_pane_scrollbar_reserve(wp) &&
|
||||||
@@ -347,7 +353,8 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
|||||||
}
|
}
|
||||||
if (resizes != 0) {
|
if (resizes != 0) {
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
server_redraw_window(w);
|
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||||
|
old_sy);
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-run-shell.c,v 1.94 2026/08/25 06:04:33 nicm Exp $ */
|
/* $OpenBSD: cmd-run-shell.c,v 1.95 2026/09/20 07:59:55 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||||
@@ -164,7 +164,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
if (cdata->client != NULL)
|
if (cdata->client != NULL)
|
||||||
cdata->client->references++;
|
cdata->client->references++;
|
||||||
if (args_has(args, 'c'))
|
if (args_has(args, 'c'))
|
||||||
cdata->cwd = xstrdup(args_get(args, 'c'));
|
cdata->cwd = format_single_from_target(item, args_get(args, 'c'));
|
||||||
else
|
else
|
||||||
cdata->cwd = xstrdup(server_client_get_cwd(c, s));
|
cdata->cwd = xstrdup(server_client_get_cwd(c, s));
|
||||||
|
|
||||||
|
|||||||
+14
-4
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-split-window.c,v 1.150 2026/08/20 09:19:24 nicm Exp $ */
|
/* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
|
|||||||
.name = "new-pane",
|
.name = "new-pane",
|
||||||
.alias = "newp",
|
.alias = "newp",
|
||||||
|
|
||||||
.args = { "AbB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
.args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||||
.usage = "[-AbCdefhIkKLMOPvWZ] [-B border-lines] "
|
.usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] "
|
||||||
"[-c start-directory] [-e environment] "
|
"[-c start-directory] [-e environment] "
|
||||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||||
"[-s style] [-S active-border-style] "
|
"[-s style] [-S active-border-style] "
|
||||||
@@ -219,6 +219,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||||
if (args_has(args, 'C') && args_has(args, 'O'))
|
if (args_has(args, 'C') && args_has(args, 'O'))
|
||||||
new_wp->flags |= PANE_CLOSEONCLICK;
|
new_wp->flags |= PANE_CLOSEONCLICK;
|
||||||
|
if (args_has(args, 'D') && args_has(args, 'O'))
|
||||||
|
new_wp->flags |= PANE_CLOSEONCANCEL;
|
||||||
|
|
||||||
style = args_get(args, 's');
|
style = args_get(args, 's');
|
||||||
if (style != NULL) {
|
if (style != NULL) {
|
||||||
@@ -360,6 +362,7 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
|||||||
enum pane_lines lines;
|
enum pane_lines lines;
|
||||||
u_int sx, sy;
|
u_int sx, sy;
|
||||||
int x, y, xoff, yoff, border;
|
int x, y, xoff, yoff, border;
|
||||||
|
int old_xoff, old_yoff, old_sx, old_sy;
|
||||||
|
|
||||||
if (c->tty.mouse_last_pane == -1)
|
if (c->tty.mouse_last_pane == -1)
|
||||||
return;
|
return;
|
||||||
@@ -415,8 +418,15 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
|||||||
if (sy < PANE_MINIMUM)
|
if (sy < PANE_MINIMUM)
|
||||||
sy = PANE_MINIMUM;
|
sy = PANE_MINIMUM;
|
||||||
|
|
||||||
|
old_xoff = wp->xoff;
|
||||||
|
old_yoff = wp->yoff;
|
||||||
|
old_sx = wp->sx;
|
||||||
|
old_sy = wp->sy;
|
||||||
|
|
||||||
layout_set_size(lc, sx, sy, xoff, yoff);
|
layout_set_size(lc, sx, sy, xoff, yoff);
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
server_redraw_window(w);
|
|
||||||
|
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||||
|
old_sy);
|
||||||
server_redraw_window_borders(w);
|
server_redraw_window_borders(w);
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-1
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-wait-for.c,v 1.23 2026/07/10 13:38:45 nicm Exp $ */
|
/* $OpenBSD: cmd-wait-for.c,v 1.24 2026/09/22 06:46:50 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -480,6 +480,40 @@ cmd_wait_for_unlock(struct cmdq_item *item, const char *name,
|
|||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cmd_wait_for_client_lost(struct client *c)
|
||||||
|
{
|
||||||
|
struct wait_channel *wc, *wc1;
|
||||||
|
struct wait_item *wi, *wi1;
|
||||||
|
struct wait_event_item *wei, *wei1;
|
||||||
|
|
||||||
|
TAILQ_FOREACH_SAFE(wei, &wait_event_items, entry, wei1) {
|
||||||
|
if (cmdq_get_client(wei->item) == c) {
|
||||||
|
TAILQ_REMOVE(&wait_event_items, wei, entry);
|
||||||
|
cmdq_continue(wei->item);
|
||||||
|
cmd_wait_for_event_free(wei);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
|
||||||
|
TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
|
||||||
|
if (cmdq_get_client(wi->item) == c) {
|
||||||
|
cmdq_continue(wi->item);
|
||||||
|
TAILQ_REMOVE(&wc->waiters, wi, entry);
|
||||||
|
free(wi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
|
||||||
|
if (cmdq_get_client(wi->item) == c) {
|
||||||
|
cmdq_continue(wi->item);
|
||||||
|
TAILQ_REMOVE(&wc->lockers, wi, entry);
|
||||||
|
free(wi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd_wait_for_remove_empty(wc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
cmd_wait_for_flush(void)
|
cmd_wait_for_flush(void)
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# configure.ac
|
# configure.ac
|
||||||
|
|
||||||
AC_INIT([tmux], next-3.8)
|
AC_INIT([tmux], next-3.9)
|
||||||
AC_PREREQ([2.60])
|
AC_PREREQ([2.60])
|
||||||
|
|
||||||
AC_CONFIG_AUX_DIR(etc)
|
AC_CONFIG_AUX_DIR(etc)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: format.c,v 1.417 2026/09/08 15:42:26 nicm Exp $ */
|
/* $OpenBSD: format.c,v 1.418 2026/09/20 08:19:31 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -2876,10 +2876,10 @@ format_cb_session_activity_flag(struct format_tree *ft)
|
|||||||
|
|
||||||
if (ft->s != NULL) {
|
if (ft->s != NULL) {
|
||||||
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
||||||
if (ft->wl->flags & WINLINK_ACTIVITY)
|
if (wl->flags & WINLINK_ACTIVITY)
|
||||||
return (xstrdup("1"));
|
return (xstrdup("1"));
|
||||||
return (xstrdup("0"));
|
|
||||||
}
|
}
|
||||||
|
return (xstrdup("0"));
|
||||||
}
|
}
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
@@ -2894,8 +2894,8 @@ format_cb_session_bell_flag(struct format_tree *ft)
|
|||||||
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
||||||
if (wl->flags & WINLINK_BELL)
|
if (wl->flags & WINLINK_BELL)
|
||||||
return (xstrdup("1"));
|
return (xstrdup("1"));
|
||||||
return (xstrdup("0"));
|
|
||||||
}
|
}
|
||||||
|
return (xstrdup("0"));
|
||||||
}
|
}
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
@@ -2908,10 +2908,10 @@ format_cb_session_silence_flag(struct format_tree *ft)
|
|||||||
|
|
||||||
if (ft->s != NULL) {
|
if (ft->s != NULL) {
|
||||||
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
||||||
if (ft->wl->flags & WINLINK_SILENCE)
|
if (wl->flags & WINLINK_SILENCE)
|
||||||
return (xstrdup("1"));
|
return (xstrdup("1"));
|
||||||
return (xstrdup("0"));
|
|
||||||
}
|
}
|
||||||
|
return (xstrdup("0"));
|
||||||
}
|
}
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
|||||||
w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0);
|
w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0);
|
||||||
wp = window_add_pane(w, NULL, 0, 0);
|
wp = window_add_pane(w, NULL, 0, 0);
|
||||||
bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty);
|
bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty);
|
||||||
wp->ictx = input_init(wp, vpty[0], NULL, NULL);
|
wp->ictx = input_init(wp, vpty[0], NULL);
|
||||||
window_add_ref(w, __func__);
|
window_add_ref(w, __func__);
|
||||||
|
|
||||||
wp->fd = open("/dev/null", O_WRONLY);
|
wp->fd = open("/dev/null", O_WRONLY);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: input.c,v 1.271 2026/08/31 19:34:09 nicm Exp $ */
|
/* $OpenBSD: input.c,v 1.272 2026/09/21 10:22:31 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -101,7 +101,6 @@ struct input_ctx {
|
|||||||
struct bufferevent *event;
|
struct bufferevent *event;
|
||||||
struct screen_write_ctx ctx;
|
struct screen_write_ctx ctx;
|
||||||
struct colour_palette *palette;
|
struct colour_palette *palette;
|
||||||
struct client *c;
|
|
||||||
|
|
||||||
struct input_cell cell;
|
struct input_cell cell;
|
||||||
struct input_cell old_cell;
|
struct input_cell old_cell;
|
||||||
@@ -874,7 +873,7 @@ input_restore_state(struct input_ctx *ictx)
|
|||||||
/* Initialise input parser. */
|
/* Initialise input parser. */
|
||||||
struct input_ctx *
|
struct input_ctx *
|
||||||
input_init(struct window_pane *wp, struct bufferevent *bev,
|
input_init(struct window_pane *wp, struct bufferevent *bev,
|
||||||
struct colour_palette *palette, struct client *c)
|
struct colour_palette *palette)
|
||||||
{
|
{
|
||||||
struct input_ctx *ictx;
|
struct input_ctx *ictx;
|
||||||
|
|
||||||
@@ -882,7 +881,6 @@ input_init(struct window_pane *wp, struct bufferevent *bev,
|
|||||||
ictx->wp = wp;
|
ictx->wp = wp;
|
||||||
ictx->event = bev;
|
ictx->event = bev;
|
||||||
ictx->palette = palette;
|
ictx->palette = palette;
|
||||||
ictx->c = c;
|
|
||||||
|
|
||||||
ictx->input_space = INPUT_BUF_START;
|
ictx->input_space = INPUT_BUF_START;
|
||||||
ictx->input_buf = xmalloc(INPUT_BUF_START);
|
ictx->input_buf = xmalloc(INPUT_BUF_START);
|
||||||
@@ -3425,15 +3423,9 @@ input_osc_52(struct input_ctx *ictx, const char *p)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (wp == NULL) {
|
if (wp == NULL) {
|
||||||
/* Popup window. */
|
free(out);
|
||||||
if (ictx->c == NULL) {
|
return;
|
||||||
free(out);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tty_set_selection(&ictx->c->tty, clip, out, outlen);
|
|
||||||
paste_add(NULL, out, outlen);
|
|
||||||
} else {
|
} else {
|
||||||
/* Normal window. */
|
|
||||||
screen_write_start_pane(&ctx, wp, NULL);
|
screen_write_start_pane(&ctx, wp, NULL);
|
||||||
screen_write_setselection(&ctx, clip, out, outlen);
|
screen_write_setselection(&ctx, clip, out, outlen);
|
||||||
screen_write_stop(&ctx);
|
screen_write_stop(&ctx);
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: key-bindings.c,v 1.192 2026/08/06 09:05:04 nicm Exp $ */
|
/* $OpenBSD: key-bindings.c,v 1.193 2026/09/21 20:45:48 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -523,7 +523,7 @@ key_bindings_init(void)
|
|||||||
"bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }",
|
"bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }",
|
||||||
|
|
||||||
/* Mouse button 1 on border. */
|
/* Mouse button 1 on border. */
|
||||||
"bind -n MouseDown1Border { select-pane -M }",
|
"bind -n MouseDown1Border { select-pane -t= }",
|
||||||
|
|
||||||
/* Mouse button 1 drag on border. */
|
/* Mouse button 1 drag on border. */
|
||||||
"bind -n MouseDrag1Border { resize-pane -M }",
|
"bind -n MouseDrag1Border { resize-pane -M }",
|
||||||
|
|||||||
+68
-27
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: layout-custom.c,v 1.40 2026/09/09 07:03:39 nicm Exp $ */
|
/* $OpenBSD: layout-custom.c,v 1.43 2026/09/22 06:48:01 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
#include "tmux.h"
|
#include "tmux.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Layouts can be represented as strings in a JSON format (v2). The legacy
|
* Layouts can be represented as strings in a JSON format (v2). The version 1
|
||||||
* format (v1) will be removed in the future and should no longer be used.
|
* format will be removed in the future and should no longer be used.
|
||||||
*
|
*
|
||||||
* The current (v2) format is JSON. The top level has two keys:
|
* The current (v2) format is JSON. The top level has two keys:
|
||||||
* "V": version number, currently 2
|
* "V": version number, currently 2
|
||||||
@@ -57,6 +57,9 @@
|
|||||||
* "z": z-index, if a floating pane
|
* "z": z-index, if a floating pane
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Maximum nesting depth for version 1 layouts. */
|
||||||
|
#define LAYOUT_V1_MAX_DEPTH 1000
|
||||||
|
|
||||||
/* Layout string. */
|
/* Layout string. */
|
||||||
struct layout_string {
|
struct layout_string {
|
||||||
char *dat;
|
char *dat;
|
||||||
@@ -280,26 +283,59 @@ layout_checksum(const char *layout)
|
|||||||
char *
|
char *
|
||||||
layout_dump(__unused struct window *w, struct layout_cell *lcroot, int flags)
|
layout_dump(__unused struct window *w, struct layout_cell *lcroot, int flags)
|
||||||
{
|
{
|
||||||
struct layout_string layout_string;
|
struct layout_string layout_string = { 0 };
|
||||||
char *out = NULL;
|
char *out;
|
||||||
|
|
||||||
if (lcroot == NULL)
|
if (lcroot == NULL)
|
||||||
return NULL;
|
goto bad;
|
||||||
|
|
||||||
layout_string_init(&layout_string);
|
layout_string_init(&layout_string);
|
||||||
|
if (layout_append(lcroot, &layout_string, flags) != 0)
|
||||||
if (layout_append(lcroot, &layout_string, flags) == 0) {
|
goto bad;
|
||||||
if (flags & LAYOUT_CUSTOM_OLD_FORMAT)
|
if (~flags & LAYOUT_CUSTOM_OLD_FORMAT)
|
||||||
xasprintf(&out, "%04hx,%s",
|
xasprintf(&out, "{\"V\":2,\"L\":%s}", layout_string.dat);
|
||||||
layout_checksum(layout_string.dat),
|
else {
|
||||||
layout_string.dat);
|
xasprintf(&out, "%04hx,%s", layout_checksum(layout_string.dat),
|
||||||
else
|
layout_string.dat);
|
||||||
xasprintf(&out, "{\"V\":2,\"L\":%s}",
|
|
||||||
layout_string.dat);
|
|
||||||
}
|
}
|
||||||
layout_string_free(&layout_string);
|
layout_string_free(&layout_string);
|
||||||
|
|
||||||
return (out);
|
return (out);
|
||||||
|
|
||||||
|
bad:
|
||||||
|
layout_string_free(&layout_string);
|
||||||
|
if (~flags & LAYOUT_CUSTOM_OLD_FORMAT)
|
||||||
|
return (NULL);
|
||||||
|
return (xstrdup("0000,"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get a floating pane cell's z-index in the layout being dumped. */
|
||||||
|
static u_int
|
||||||
|
layout_cell_zindex(struct layout_cell *lc)
|
||||||
|
{
|
||||||
|
struct window_pane *wp = lc->wp, *wq;
|
||||||
|
struct window *w = wp->window;
|
||||||
|
struct layout_cell *other;
|
||||||
|
int saved = (lc == wp->saved_layout_cell);
|
||||||
|
u_int i = 0;
|
||||||
|
|
||||||
|
if (saved &&
|
||||||
|
w->active != NULL &&
|
||||||
|
(w->active->flags & PANE_ZOOMED) &&
|
||||||
|
(w->active->saved_layout_cell->flags & LAYOUT_CELL_FLOATING)) {
|
||||||
|
if (wp == w->active)
|
||||||
|
return (0);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
TAILQ_FOREACH(wq, &w->z_index, zentry) {
|
||||||
|
if (wq == wp)
|
||||||
|
break;
|
||||||
|
if (saved)
|
||||||
|
other = wq->saved_layout_cell;
|
||||||
|
else
|
||||||
|
other = wq->layout_cell;
|
||||||
|
if (other != NULL && (other->flags & LAYOUT_CELL_FLOATING))
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return (i);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Append information for a single cell in a JSON (v2) format. */
|
/* Append information for a single cell in a JSON (v2) format. */
|
||||||
@@ -310,7 +346,7 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
|
|||||||
struct window_pane *wp;
|
struct window_pane *wp;
|
||||||
enum layout_type type;
|
enum layout_type type;
|
||||||
char c;
|
char c;
|
||||||
u_int i, n;
|
u_int i, n, z;
|
||||||
|
|
||||||
if (lc == NULL)
|
if (lc == NULL)
|
||||||
return (-1);
|
return (-1);
|
||||||
@@ -351,9 +387,10 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
|
|||||||
if (window_pane_index(wp, &i) != 0)
|
if (window_pane_index(wp, &i) != 0)
|
||||||
return (-1);
|
return (-1);
|
||||||
layout_string_write(ls, ",\"i\":%u", i);
|
layout_string_write(ls, ",\"i\":%u", i);
|
||||||
if ((lc->flags & LAYOUT_CELL_FLOATING) &&
|
if (lc->flags & LAYOUT_CELL_FLOATING) {
|
||||||
window_pane_zindex(wp, &i) == 0)
|
z = layout_cell_zindex(lc);
|
||||||
layout_string_write(ls, ",\"z\":%u", i);
|
layout_string_write(ls, ",\"z\":%u", z);
|
||||||
|
}
|
||||||
layout_string_write(ls, ",\"I\":\"%%%u\"", wp->id);
|
layout_string_write(ls, ",\"I\":\"%%%u\"", wp->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +399,7 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
|
|||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Append information for a single cell in the legacy (v1) format. */
|
/* Append information for a single cell in the version 1 format. */
|
||||||
static int
|
static int
|
||||||
layout_append_v1(struct layout_cell *lc, struct layout_string *ls)
|
layout_append_v1(struct layout_cell *lc, struct layout_string *ls)
|
||||||
{
|
{
|
||||||
@@ -778,7 +815,7 @@ layout_assign(struct window *w, struct layout_parse_ctx *pctx)
|
|||||||
layout_assign_fallback(w, w->layout_root);
|
layout_assign_fallback(w, w->layout_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Construct a cell from the legacy (v1) format. */
|
/* Construct a cell from the version 1 format. */
|
||||||
static struct layout_cell *
|
static struct layout_cell *
|
||||||
layout_construct_cell(struct layout_cell *lcparent, const char **layout)
|
layout_construct_cell(struct layout_cell *lcparent, const char **layout)
|
||||||
{
|
{
|
||||||
@@ -827,12 +864,15 @@ layout_construct_cell(struct layout_cell *lcparent, const char **layout)
|
|||||||
return (lc);
|
return (lc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Construct a layout from the legacy (v1) format. */
|
/* Construct a layout from the version 1 format. */
|
||||||
static struct layout_cell *
|
static struct layout_cell *
|
||||||
layout_construct_v1(struct layout_cell *lcparent, const char **layout)
|
layout_construct_v1(struct layout_cell *lcparent, const char **layout, u_int depth)
|
||||||
{
|
{
|
||||||
struct layout_cell *lc, *lcchild;
|
struct layout_cell *lc, *lcchild;
|
||||||
|
|
||||||
|
if (depth > LAYOUT_V1_MAX_DEPTH)
|
||||||
|
return (NULL);
|
||||||
|
|
||||||
lc = layout_construct_cell(lcparent, layout);
|
lc = layout_construct_cell(lcparent, layout);
|
||||||
if (lc == NULL)
|
if (lc == NULL)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
@@ -855,7 +895,7 @@ layout_construct_v1(struct layout_cell *lcparent, const char **layout)
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
(*layout)++;
|
(*layout)++;
|
||||||
lcchild = layout_construct_v1(lc, layout);
|
lcchild = layout_construct_v1(lc, layout, depth + 1);
|
||||||
if (lcchild == NULL)
|
if (lcchild == NULL)
|
||||||
goto fail;
|
goto fail;
|
||||||
TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
|
TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
|
||||||
@@ -1068,7 +1108,8 @@ layout_construct(const char *input, struct layout_parse_ctx *pctx)
|
|||||||
*pctx->cause = xstrdup("invalid layout checksum");
|
*pctx->cause = xstrdup("invalid layout checksum");
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
if ((pctx->root = layout_construct_v1(NULL, &input)) == NULL) {
|
pctx->root = layout_construct_v1(NULL, &input, 0);
|
||||||
|
if (pctx->root == NULL) {
|
||||||
*pctx->cause = xstrdup("invalid layout");
|
*pctx->cause = xstrdup("invalid layout");
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: layout.c,v 1.99 2026/09/09 07:03:39 nicm Exp $ */
|
/* $OpenBSD: layout.c,v 1.101 2026/09/20 08:42:46 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -469,7 +469,6 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
|||||||
sx = PANE_MINIMUM;
|
sx = PANE_MINIMUM;
|
||||||
else
|
else
|
||||||
sx = sx - sb_w - sb_pad;
|
sx = sx - sb_w - sb_pad;
|
||||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window_pane_resize(wp, sx, sy);
|
window_pane_resize(wp, sx, sy);
|
||||||
@@ -477,8 +476,11 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
|||||||
if (wp->xoff != old_xoff ||
|
if (wp->xoff != old_xoff ||
|
||||||
wp->yoff != old_yoff ||
|
wp->yoff != old_yoff ||
|
||||||
wp->sx != old_sx ||
|
wp->sx != old_sx ||
|
||||||
wp->sy != old_sy)
|
wp->sy != old_sy) {
|
||||||
changed = 1;
|
changed = 1;
|
||||||
|
if (window_pane_scrollbar_reserve(wp))
|
||||||
|
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (changed)
|
if (changed)
|
||||||
redraw_invalidate_scene(w);
|
redraw_invalidate_scene(w);
|
||||||
@@ -768,6 +770,49 @@ layout_free(struct window *w, int only_nodes)
|
|||||||
layout_free_cell(w->layout_root, only_nodes);
|
layout_free_cell(w->layout_root, only_nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Move and resize floating panes so they stay inside the window. */
|
||||||
|
static void
|
||||||
|
layout_clamp_floating_panes(struct window *w, u_int sx, u_int sy)
|
||||||
|
{
|
||||||
|
struct window_pane *wp;
|
||||||
|
struct layout_cell *lc;
|
||||||
|
u_int pad, avail, csx, csy;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(wp, &w->z_index, zentry) {
|
||||||
|
lc = wp->layout_cell;
|
||||||
|
if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING))
|
||||||
|
continue;
|
||||||
|
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
|
||||||
|
pad = 0;
|
||||||
|
else
|
||||||
|
pad = 1;
|
||||||
|
|
||||||
|
csx = lc->g.sx;
|
||||||
|
avail = (sx > 2 * pad) ? sx - 2 * pad : 0;
|
||||||
|
if (csx > avail)
|
||||||
|
csx = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
||||||
|
csy = lc->g.sy;
|
||||||
|
avail = (sy > 2 * pad) ? sy - 2 * pad : 0;
|
||||||
|
if (csy > avail)
|
||||||
|
csy = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
||||||
|
if (csx != lc->g.sx || csy != lc->g.sy)
|
||||||
|
layout_set_size(lc, csx, csy, lc->g.xoff, lc->g.yoff);
|
||||||
|
|
||||||
|
if (lc->g.xoff + lc->g.sx + pad > sx) {
|
||||||
|
if (lc->g.sx + 2 * pad >= sx)
|
||||||
|
lc->g.xoff = pad;
|
||||||
|
else
|
||||||
|
lc->g.xoff = sx - lc->g.sx - pad;
|
||||||
|
}
|
||||||
|
if (lc->g.yoff + lc->g.sy + pad > sy) {
|
||||||
|
if (lc->g.sy + 2 * pad >= sy)
|
||||||
|
lc->g.yoff = pad;
|
||||||
|
else
|
||||||
|
lc->g.yoff = sy - lc->g.sy - pad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Resize the entire layout after window resize. */
|
/* Resize the entire layout after window resize. */
|
||||||
void
|
void
|
||||||
layout_resize(struct window *w, u_int sx, u_int sy)
|
layout_resize(struct window *w, u_int sx, u_int sy)
|
||||||
@@ -788,8 +833,11 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
|||||||
* out proportionately - this should leave the layout fitting the new
|
* out proportionately - this should leave the layout fitting the new
|
||||||
* window size.
|
* window size.
|
||||||
*/
|
*/
|
||||||
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING))
|
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) {
|
||||||
|
layout_clamp_floating_panes(w, sx, sy);
|
||||||
|
layout_fix_panes(w, NULL);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
xchange = sx - lc->g.sx;
|
xchange = sx - lc->g.sx;
|
||||||
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
|
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
|
||||||
if (xchange < 0 && xchange < -xlimit)
|
if (xchange < 0 && xchange < -xlimit)
|
||||||
@@ -819,6 +867,7 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
|||||||
|
|
||||||
/* Fix cell offsets. */
|
/* Fix cell offsets. */
|
||||||
layout_fix_offsets(w);
|
layout_fix_offsets(w);
|
||||||
|
layout_clamp_floating_panes(w, sx, sy);
|
||||||
layout_fix_panes(w, NULL);
|
layout_fix_panes(w, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1681,7 +1730,7 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
|||||||
enum pane_lines lines, struct window *w, struct layout_geometry *lg,
|
enum pane_lines lines, struct window *w, struct layout_geometry *lg,
|
||||||
char **cause)
|
char **cause)
|
||||||
{
|
{
|
||||||
int sx, sy, ox, oy;
|
int sx, sy, ox, oy, pad;
|
||||||
char *error = NULL;
|
char *error = NULL;
|
||||||
|
|
||||||
sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx;
|
sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx;
|
||||||
@@ -1730,12 +1779,20 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!window_has_floating_panes(w)) {
|
||||||
|
w->last_new_pane_x = 0;
|
||||||
|
w->last_new_pane_y = 0;
|
||||||
|
}
|
||||||
if (ox == INT_MAX) {
|
if (ox == INT_MAX) {
|
||||||
if (w->last_new_pane_x == 0)
|
if (w->last_new_pane_x == 0)
|
||||||
ox = 4;
|
ox = 4;
|
||||||
else {
|
else {
|
||||||
|
if (lines != PANE_LINES_NONE)
|
||||||
|
pad = 1;
|
||||||
|
else
|
||||||
|
pad = 0;
|
||||||
ox = w->last_new_pane_x + 4;
|
ox = w->last_new_pane_x + 4;
|
||||||
if (w->last_new_pane_x > w->sx)
|
if (ox + sx + pad > (int)w->sx)
|
||||||
ox = 4;
|
ox = 4;
|
||||||
}
|
}
|
||||||
w->last_new_pane_x = ox;
|
w->last_new_pane_x = ox;
|
||||||
@@ -1746,8 +1803,12 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
|||||||
if (w->last_new_pane_y == 0)
|
if (w->last_new_pane_y == 0)
|
||||||
oy = 2;
|
oy = 2;
|
||||||
else {
|
else {
|
||||||
|
if (lines != PANE_LINES_NONE)
|
||||||
|
pad = 1;
|
||||||
|
else
|
||||||
|
pad = 0;
|
||||||
oy = w->last_new_pane_y + 2;
|
oy = w->last_new_pane_y + 2;
|
||||||
if (w->last_new_pane_y > w->sy)
|
if (oy + sy + pad > (int)w->sy)
|
||||||
oy = 2;
|
oy = 2;
|
||||||
}
|
}
|
||||||
w->last_new_pane_y = oy;
|
w->last_new_pane_y = oy;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: menu.c,v 1.70 2026/08/17 07:33:55 nicm Exp $ */
|
/* $OpenBSD: menu.c,v 1.71 2026/09/21 12:14:32 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -51,6 +51,18 @@ struct menu_data {
|
|||||||
void *data;
|
void *data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
menu_get_size(struct menu *menu, enum box_lines lines, u_int *sx, u_int *sy)
|
||||||
|
{
|
||||||
|
if (lines == BOX_LINES_NONE) {
|
||||||
|
*sx = menu->item_width + 2;
|
||||||
|
*sy = menu->count;
|
||||||
|
} else {
|
||||||
|
*sx = menu->width + 4;
|
||||||
|
*sy = menu->count + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
menu_add_items(struct menu *menu, const struct menu_item *items,
|
menu_add_items(struct menu *menu, const struct menu_item *items,
|
||||||
struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
|
struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
|
||||||
@@ -142,6 +154,8 @@ menu_add_item(struct menu *menu, const struct menu_item *item,
|
|||||||
width = format_width(new_item->name);
|
width = format_width(new_item->name);
|
||||||
if (*new_item->name == '-')
|
if (*new_item->name == '-')
|
||||||
width--;
|
width--;
|
||||||
|
if (width > menu->item_width)
|
||||||
|
menu->item_width = width;
|
||||||
if (width > menu->width)
|
if (width > menu->width)
|
||||||
menu->width = width;
|
menu->width = width;
|
||||||
}
|
}
|
||||||
@@ -238,7 +252,7 @@ menu_update(struct menu_data *md)
|
|||||||
screen_write_clearscreen(&ctx, 8);
|
screen_write_clearscreen(&ctx, 8);
|
||||||
|
|
||||||
if (md->border_lines != BOX_LINES_NONE) {
|
if (md->border_lines != BOX_LINES_NONE) {
|
||||||
screen_write_box(&ctx, menu->width + 4, menu->count + 2,
|
screen_write_box(&ctx, menu_width(md), menu_height(md),
|
||||||
md->border_lines, &md->border_style_gc, menu->title);
|
md->border_lines, &md->border_style_gc, menu->title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,11 +300,13 @@ menu_destroy(struct window *w)
|
|||||||
void
|
void
|
||||||
menu_get_cursor(struct menu_data *md, u_int *cx, u_int *cy)
|
menu_get_cursor(struct menu_data *md, u_int *cx, u_int *cy)
|
||||||
{
|
{
|
||||||
*cx = md->px + 2;
|
u_int border = (md->border_lines != BOX_LINES_NONE);
|
||||||
|
|
||||||
|
*cx = md->px + 1 + border;
|
||||||
if (md->choice == -1)
|
if (md->choice == -1)
|
||||||
*cy = md->py;
|
*cy = md->py;
|
||||||
else
|
else
|
||||||
*cy = md->py + 1 + md->choice;
|
*cy = md->py + border + md->choice;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct screen *
|
struct screen *
|
||||||
@@ -302,13 +318,19 @@ menu_screen(struct menu_data *md)
|
|||||||
u_int
|
u_int
|
||||||
menu_width(struct menu_data *md)
|
menu_width(struct menu_data *md)
|
||||||
{
|
{
|
||||||
return (md->menu->width + 4);
|
u_int sx, sy;
|
||||||
|
|
||||||
|
menu_get_size(md->menu, md->border_lines, &sx, &sy);
|
||||||
|
return (sx);
|
||||||
}
|
}
|
||||||
|
|
||||||
u_int
|
u_int
|
||||||
menu_height(struct menu_data *md)
|
menu_height(struct menu_data *md)
|
||||||
{
|
{
|
||||||
return (md->menu->count + 2);
|
u_int sx, sy;
|
||||||
|
|
||||||
|
menu_get_size(md->menu, md->border_lines, &sx, &sy);
|
||||||
|
return (sy);
|
||||||
}
|
}
|
||||||
|
|
||||||
u_int
|
u_int
|
||||||
@@ -338,6 +360,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
|||||||
enum cmd_parse_status status;
|
enum cmd_parse_status status;
|
||||||
char *error;
|
char *error;
|
||||||
key_code key;
|
key_code key;
|
||||||
|
u_int border;
|
||||||
|
|
||||||
if (KEYC_IS_MOUSE(event->key)) {
|
if (KEYC_IS_MOUSE(event->key)) {
|
||||||
/*
|
/*
|
||||||
@@ -353,10 +376,10 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
|||||||
return (1);
|
return (1);
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
if (m->x < md->px ||
|
border = (md->border_lines != BOX_LINES_NONE);
|
||||||
m->x > md->px + 4 + menu->width ||
|
if (m->x < md->px || m->x >= md->px + menu_width(md) ||
|
||||||
m->y < md->py + 1 ||
|
m->y < md->py + border ||
|
||||||
m->y > md->py + 1 + n - 1) {
|
m->y >= md->py + border + n) {
|
||||||
if (~md->flags & MENU_STAYOPEN) {
|
if (~md->flags & MENU_STAYOPEN) {
|
||||||
if (!move && MOUSE_RELEASE(m->b))
|
if (!move && MOUSE_RELEASE(m->b))
|
||||||
return (1);
|
return (1);
|
||||||
@@ -379,7 +402,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
|||||||
if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
|
if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
|
||||||
goto chosen;
|
goto chosen;
|
||||||
}
|
}
|
||||||
md->choice = m->y - (md->py + 1);
|
md->choice = m->y - (md->py + border);
|
||||||
if (md->choice != old)
|
if (md->choice != old)
|
||||||
server_redraw_window_menu(md->w);
|
server_redraw_window_menu(md->w);
|
||||||
return (0);
|
return (0);
|
||||||
@@ -546,20 +569,16 @@ menu_resize(struct menu_data *md, struct window *w)
|
|||||||
|
|
||||||
if (md == NULL)
|
if (md == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
nx = md->px;
|
nx = md->px;
|
||||||
ny = md->py;
|
ny = md->py;
|
||||||
|
|
||||||
sx = md->menu->width + 4;
|
menu_get_size(md->menu, md->border_lines, &sx, &sy);
|
||||||
sy = md->menu->count + 2;
|
|
||||||
|
|
||||||
if (nx + sx > w->sx) {
|
if (nx + sx > w->sx) {
|
||||||
if (w->sx <= sx)
|
if (w->sx <= sx)
|
||||||
nx = 0;
|
nx = 0;
|
||||||
else
|
else
|
||||||
nx = w->sx - sx;
|
nx = w->sx - sx;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ny + sy > w->sy) {
|
if (ny + sy > w->sy) {
|
||||||
if (w->sy <= sy)
|
if (w->sy <= sy)
|
||||||
ny = 0;
|
ny = 0;
|
||||||
@@ -591,8 +610,9 @@ menu_display(struct menu *menu, int flags, int starting_choice,
|
|||||||
w = fs->w;
|
w = fs->w;
|
||||||
o = w->options;
|
o = w->options;
|
||||||
|
|
||||||
sx = menu->width + 4;
|
if (lines == BOX_LINES_DEFAULT)
|
||||||
sy = menu->count + 2;
|
lines = options_get_number(o, "menu-border-lines");
|
||||||
|
menu_get_size(menu, lines, &sx, &sy);
|
||||||
if (sx >= w->sx)
|
if (sx >= w->sx)
|
||||||
px = 0;
|
px = 0;
|
||||||
else if (px + sx > w->sx)
|
else if (px + sx > w->sx)
|
||||||
@@ -604,9 +624,6 @@ menu_display(struct menu *menu, int flags, int starting_choice,
|
|||||||
w->menu_last_px = px;
|
w->menu_last_px = px;
|
||||||
w->menu_last_py = py;
|
w->menu_last_py = py;
|
||||||
|
|
||||||
if (lines == BOX_LINES_DEFAULT)
|
|
||||||
lines = options_get_number(o, "menu-border-lines");
|
|
||||||
|
|
||||||
md = xcalloc(1, sizeof *md);
|
md = xcalloc(1, sizeof *md);
|
||||||
md->w = w;
|
md->w = w;
|
||||||
md->flags = flags;
|
md->flags = flags;
|
||||||
|
|||||||
+11
-6
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: mode-tree.c,v 1.101 2026/08/05 07:50:21 nicm Exp $ */
|
/* $OpenBSD: mode-tree.c,v 1.102 2026/09/21 12:14:32 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1375,12 +1375,14 @@ static void
|
|||||||
mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
|
mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
|
||||||
u_int y, int outside)
|
u_int y, int outside)
|
||||||
{
|
{
|
||||||
|
struct window *w = mtd->wp->window;
|
||||||
struct mode_tree_item *mti;
|
struct mode_tree_item *mti;
|
||||||
struct menu *menu;
|
struct menu *menu;
|
||||||
const struct menu_item *items;
|
const struct menu_item *items;
|
||||||
struct mode_tree_menu *mtm;
|
struct mode_tree_menu *mtm;
|
||||||
char *title;
|
char *title;
|
||||||
u_int line;
|
enum box_lines lines;
|
||||||
|
u_int line, sx, sy;
|
||||||
|
|
||||||
if (mtd->offset + y > mtd->line_size - 1)
|
if (mtd->offset + y > mtd->line_size - 1)
|
||||||
line = mtd->current;
|
line = mtd->current;
|
||||||
@@ -1405,14 +1407,17 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
|
|||||||
mtm->line = line;
|
mtm->line = line;
|
||||||
mtd->references++;
|
mtd->references++;
|
||||||
|
|
||||||
if (x >= (menu->width + 4) / 2)
|
lines = options_get_number(w->options, "menu-border-lines");
|
||||||
x -= (menu->width + 4) / 2;
|
menu_get_size(menu, lines, &sx, &sy);
|
||||||
|
if (x >= sx / 2)
|
||||||
|
x -= sx / 2;
|
||||||
else
|
else
|
||||||
x = 0;
|
x = 0;
|
||||||
x += mtd->wp->xoff;
|
x += mtd->wp->xoff;
|
||||||
y += mtd->wp->yoff;
|
y += mtd->wp->yoff;
|
||||||
if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL,
|
|
||||||
NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0) {
|
if (menu_display(menu, 0, 0, NULL, x, y, c, lines, NULL, NULL, NULL,
|
||||||
|
NULL, mode_tree_menu_callback, mtm) != 0) {
|
||||||
mode_tree_remove_ref(mtd);
|
mode_tree_remove_ref(mtd);
|
||||||
free(mtm);
|
free(mtm);
|
||||||
menu_free(menu);
|
menu_free(menu);
|
||||||
|
|||||||
+8
-33
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
|
/* $OpenBSD: options-table.c,v 1.247 2026/09/21 10:22:31 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -76,9 +76,10 @@ static const char *options_table_pane_border_indicators_list[] = {
|
|||||||
"off", "colour", "arrows", "both", NULL
|
"off", "colour", "arrows", "both", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_pane_border_lines_list[] = {
|
static const char *options_table_pane_border_lines_list[] = {
|
||||||
"single", "double", "heavy", "simple", "number", "spaces", "none", NULL
|
"single", "double", "heavy", "simple", "number", "spaces", "none",
|
||||||
|
"rounded", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_popup_border_lines_list[] = {
|
static const char *options_table_menu_border_lines_list[] = {
|
||||||
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
|
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_set_clipboard_list[] = {
|
static const char *options_table_set_clipboard_list[] = {
|
||||||
@@ -91,7 +92,7 @@ static const char *options_table_window_size_list[] = {
|
|||||||
"largest", "smallest", "manual", "latest", NULL
|
"largest", "smallest", "manual", "latest", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_remain_on_exit_list[] = {
|
static const char *options_table_remain_on_exit_list[] = {
|
||||||
"off", "on", "failed", "key", NULL
|
"off", "on", "failed", "key", "failed-key", NULL
|
||||||
};
|
};
|
||||||
static const char *options_table_destroy_unattached_list[] = {
|
static const char *options_table_destroy_unattached_list[] = {
|
||||||
"off", "on", "keep-last", "keep-group", NULL
|
"off", "on", "keep-last", "keep-group", NULL
|
||||||
@@ -492,7 +493,7 @@ const struct options_table_entry options_table[] = {
|
|||||||
{ .name = "menu-border-lines",
|
{ .name = "menu-border-lines",
|
||||||
.type = OPTIONS_TABLE_CHOICE,
|
.type = OPTIONS_TABLE_CHOICE,
|
||||||
.scope = OPTIONS_TABLE_WINDOW,
|
.scope = OPTIONS_TABLE_WINDOW,
|
||||||
.choices = options_table_popup_border_lines_list,
|
.choices = options_table_menu_border_lines_list,
|
||||||
.default_num = BOX_LINES_SINGLE,
|
.default_num = BOX_LINES_SINGLE,
|
||||||
.text = "Type of characters used to draw menu border lines. Some of "
|
.text = "Type of characters used to draw menu border lines. Some of "
|
||||||
"these are only supported on terminals with UTF-8 support."
|
"these are only supported on terminals with UTF-8 support."
|
||||||
@@ -1665,40 +1666,14 @@ const struct options_table_entry options_table[] = {
|
|||||||
.text = "Pane scrollbar position."
|
.text = "Pane scrollbar position."
|
||||||
},
|
},
|
||||||
|
|
||||||
{ .name = "popup-style",
|
|
||||||
.type = OPTIONS_TABLE_STRING,
|
|
||||||
.scope = OPTIONS_TABLE_WINDOW,
|
|
||||||
.default_str = "bg=themedarkgrey,fg=themewhite",
|
|
||||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
|
||||||
.separator = ",",
|
|
||||||
.text = "Default style of popups."
|
|
||||||
},
|
|
||||||
|
|
||||||
{ .name = "popup-border-style",
|
|
||||||
.type = OPTIONS_TABLE_STRING,
|
|
||||||
.scope = OPTIONS_TABLE_WINDOW,
|
|
||||||
.default_str = "bg=themedarkgrey,fg=themelightgrey",
|
|
||||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
|
||||||
.separator = ",",
|
|
||||||
.text = "Default style of popup borders."
|
|
||||||
},
|
|
||||||
|
|
||||||
{ .name = "popup-border-lines",
|
|
||||||
.type = OPTIONS_TABLE_CHOICE,
|
|
||||||
.scope = OPTIONS_TABLE_WINDOW,
|
|
||||||
.choices = options_table_popup_border_lines_list,
|
|
||||||
.default_num = BOX_LINES_SINGLE,
|
|
||||||
.text = "Type of characters used to draw popup border lines. Some of "
|
|
||||||
"these are only supported on terminals with UTF-8 support."
|
|
||||||
},
|
|
||||||
|
|
||||||
{ .name = "remain-on-exit",
|
{ .name = "remain-on-exit",
|
||||||
.type = OPTIONS_TABLE_CHOICE,
|
.type = OPTIONS_TABLE_CHOICE,
|
||||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||||
.choices = options_table_remain_on_exit_list,
|
.choices = options_table_remain_on_exit_list,
|
||||||
.default_num = 0,
|
.default_num = 0,
|
||||||
.text = "Whether panes should remain ('on'), remain until a key is "
|
.text = "Whether panes should remain ('on'), remain until a key is "
|
||||||
"pressed ('key') or be automatically killed ('off' or "
|
"pressed after any exit ('key') or after a failure "
|
||||||
|
"('failed-key'), or be automatically killed ('off' or "
|
||||||
"'failed') when the program inside exits."
|
"'failed') when the program inside exits."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,661 +0,0 @@
|
|||||||
/* $OpenBSD: popup.c,v 1.77 2026/08/28 08:02:16 nicm Exp $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
||||||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "tmux.h"
|
|
||||||
|
|
||||||
struct popup_data {
|
|
||||||
struct client *c;
|
|
||||||
struct cmdq_item *item;
|
|
||||||
int flags;
|
|
||||||
char *title;
|
|
||||||
|
|
||||||
char *style;
|
|
||||||
char *border_style;
|
|
||||||
struct grid_cell border_cell;
|
|
||||||
enum box_lines border_lines;
|
|
||||||
|
|
||||||
struct screen s;
|
|
||||||
struct grid_cell defaults;
|
|
||||||
struct colour_palette palette;
|
|
||||||
|
|
||||||
struct visible_ranges r;
|
|
||||||
|
|
||||||
struct job *job;
|
|
||||||
struct input_ctx *ictx;
|
|
||||||
int status;
|
|
||||||
popup_close_cb cb;
|
|
||||||
void *arg;
|
|
||||||
|
|
||||||
int close;
|
|
||||||
|
|
||||||
/* Current position and size. */
|
|
||||||
u_int px;
|
|
||||||
u_int py;
|
|
||||||
u_int sx;
|
|
||||||
u_int sy;
|
|
||||||
|
|
||||||
/* Preferred position and size. */
|
|
||||||
u_int ppx;
|
|
||||||
u_int ppy;
|
|
||||||
u_int psx;
|
|
||||||
u_int psy;
|
|
||||||
|
|
||||||
enum { OFF, MOVE, SIZE } dragging;
|
|
||||||
u_int dx;
|
|
||||||
u_int dy;
|
|
||||||
|
|
||||||
u_int lx;
|
|
||||||
u_int ly;
|
|
||||||
u_int lb;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_free(struct popup_data *pd)
|
|
||||||
{
|
|
||||||
server_client_unref(pd->c);
|
|
||||||
|
|
||||||
if (pd->job != NULL)
|
|
||||||
job_free(pd->job);
|
|
||||||
if (pd->ictx != NULL)
|
|
||||||
input_free(pd->ictx);
|
|
||||||
|
|
||||||
free(pd->r.ranges);
|
|
||||||
screen_free(&pd->s);
|
|
||||||
colour_palette_free(&pd->palette);
|
|
||||||
|
|
||||||
free(pd->title);
|
|
||||||
free(pd->style);
|
|
||||||
free(pd->border_style);
|
|
||||||
free(pd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_reapply_styles(struct popup_data *pd)
|
|
||||||
{
|
|
||||||
struct client *c = pd->c;
|
|
||||||
struct session *s = c->session;
|
|
||||||
struct options *o;
|
|
||||||
struct format_tree *ft;
|
|
||||||
struct style sytmp;
|
|
||||||
|
|
||||||
if (s == NULL)
|
|
||||||
return;
|
|
||||||
o = s->curw->window->options;
|
|
||||||
|
|
||||||
ft = format_create_defaults(NULL, c, s, s->curw, NULL);
|
|
||||||
|
|
||||||
/* Reapply popup style from options. */
|
|
||||||
memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
|
|
||||||
style_apply(&pd->defaults, o, "popup-style", ft);
|
|
||||||
if (pd->style != NULL) {
|
|
||||||
style_set(&sytmp, &grid_default_cell);
|
|
||||||
if (style_parse(&sytmp, &pd->defaults, pd->style) == 0) {
|
|
||||||
pd->defaults.fg = sytmp.gc.fg;
|
|
||||||
pd->defaults.bg = sytmp.gc.bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pd->defaults.attr = 0;
|
|
||||||
|
|
||||||
/* Reapply border style from options. */
|
|
||||||
memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
|
|
||||||
style_apply(&pd->border_cell, o, "popup-border-style", ft);
|
|
||||||
if (pd->border_style != NULL) {
|
|
||||||
style_set(&sytmp, &grid_default_cell);
|
|
||||||
if (style_parse(&sytmp, &pd->border_cell,
|
|
||||||
pd->border_style) == 0) {
|
|
||||||
pd->border_cell.fg = sytmp.gc.fg;
|
|
||||||
pd->border_cell.bg = sytmp.gc.bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pd->border_cell.attr = 0;
|
|
||||||
|
|
||||||
format_free(ft);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_redraw_cb(const struct tty_ctx *ttyctx)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = ttyctx->arg;
|
|
||||||
|
|
||||||
pd->c->flags |= CLIENT_REDRAWOVERLAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = ttyctx->arg;
|
|
||||||
|
|
||||||
if (c != pd->c)
|
|
||||||
return (0);
|
|
||||||
if (pd->c->flags & CLIENT_REDRAWOVERLAY)
|
|
||||||
return (0);
|
|
||||||
|
|
||||||
ttyctx->wox = 0;
|
|
||||||
ttyctx->woy = 0;
|
|
||||||
ttyctx->wsx = c->tty.sx;
|
|
||||||
ttyctx->wsy = c->tty.sy;
|
|
||||||
|
|
||||||
if (pd->border_lines == BOX_LINES_NONE) {
|
|
||||||
ttyctx->xoff = ttyctx->rxoff = pd->px;
|
|
||||||
ttyctx->yoff = ttyctx->ryoff = pd->py;
|
|
||||||
} else {
|
|
||||||
ttyctx->xoff = ttyctx->rxoff = pd->px + 1;
|
|
||||||
ttyctx->yoff = ttyctx->ryoff = pd->py + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = ctx->arg;
|
|
||||||
|
|
||||||
memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults);
|
|
||||||
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
|
|
||||||
ttyctx->style_ctx.defaults = &ttyctx->defaults;
|
|
||||||
ttyctx->style_ctx.palette = &pd->palette;
|
|
||||||
ttyctx->redraw_cb = popup_redraw_cb;
|
|
||||||
ttyctx->set_client_cb = popup_set_client_cb;
|
|
||||||
ttyctx->arg = pd;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct screen *
|
|
||||||
popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = data;
|
|
||||||
|
|
||||||
if (pd->border_lines == BOX_LINES_NONE) {
|
|
||||||
*cx = pd->px + pd->s.cx;
|
|
||||||
*cy = pd->py + pd->s.cy;
|
|
||||||
} else {
|
|
||||||
*cx = pd->px + 1 + pd->s.cx;
|
|
||||||
*cy = pd->py + 1 + pd->s.cy;
|
|
||||||
}
|
|
||||||
return (&pd->s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return parts of the input range which are not obstructed by the popup. */
|
|
||||||
static struct visible_ranges *
|
|
||||||
popup_check_cb(__unused struct client* c, void *data, u_int px, u_int py,
|
|
||||||
u_int nx)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = data;
|
|
||||||
struct visible_ranges *r = &pd->r;
|
|
||||||
|
|
||||||
server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx,
|
|
||||||
r);
|
|
||||||
return (r);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_draw_cb(struct client *c, void *data)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = data;
|
|
||||||
struct tty *tty = &c->tty;
|
|
||||||
struct screen s;
|
|
||||||
struct screen_write_ctx ctx;
|
|
||||||
u_int i, px = pd->px, py = pd->py;
|
|
||||||
struct grid_cell defaults;
|
|
||||||
struct tty_style_ctx style_ctx;
|
|
||||||
|
|
||||||
popup_reapply_styles(pd);
|
|
||||||
|
|
||||||
screen_init(&s, pd->sx, pd->sy, 0);
|
|
||||||
if (pd->s.hyperlinks != NULL) {
|
|
||||||
hyperlinks_free(s.hyperlinks);
|
|
||||||
s.hyperlinks = hyperlinks_copy(pd->s.hyperlinks);
|
|
||||||
}
|
|
||||||
screen_write_start(&ctx, &s);
|
|
||||||
screen_write_clearscreen(&ctx, 8);
|
|
||||||
|
|
||||||
if (pd->border_lines == BOX_LINES_NONE) {
|
|
||||||
screen_write_cursormove(&ctx, 0, 0, 0);
|
|
||||||
screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx, pd->sy);
|
|
||||||
} else if (pd->sx > 2 && pd->sy > 2) {
|
|
||||||
screen_write_box(&ctx, pd->sx, pd->sy, pd->border_lines,
|
|
||||||
&pd->border_cell, pd->title);
|
|
||||||
screen_write_cursormove(&ctx, 1, 1, 0);
|
|
||||||
screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2,
|
|
||||||
pd->sy - 2);
|
|
||||||
}
|
|
||||||
screen_write_stop(&ctx);
|
|
||||||
|
|
||||||
memcpy(&defaults, &pd->defaults, sizeof defaults);
|
|
||||||
if (defaults.fg == 8)
|
|
||||||
defaults.fg = pd->palette.fg;
|
|
||||||
if (defaults.bg == 8)
|
|
||||||
defaults.bg = pd->palette.bg;
|
|
||||||
style_ctx.defaults = &defaults;
|
|
||||||
style_ctx.palette = &pd->palette;
|
|
||||||
style_ctx.dim = 0;
|
|
||||||
style_ctx.hyperlinks = s.hyperlinks;
|
|
||||||
|
|
||||||
c->overlay_check = NULL;
|
|
||||||
c->overlay_data = NULL;
|
|
||||||
for (i = 0; i < pd->sy; i++)
|
|
||||||
tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &style_ctx);
|
|
||||||
screen_free(&s);
|
|
||||||
c->overlay_check = popup_check_cb;
|
|
||||||
c->overlay_data = pd;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_free_cb(__unused struct client *c, void *data)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = data;
|
|
||||||
struct cmdq_item *item = pd->item;
|
|
||||||
|
|
||||||
if (pd->cb != NULL)
|
|
||||||
pd->cb(pd->status, pd->arg);
|
|
||||||
|
|
||||||
if (item != NULL) {
|
|
||||||
if (cmdq_get_client(item) != NULL &&
|
|
||||||
cmdq_get_client(item)->session == NULL)
|
|
||||||
cmdq_get_client(item)->retval = pd->status;
|
|
||||||
cmdq_continue(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
popup_free(pd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_resize_cb(__unused struct client *c, void *data)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = data;
|
|
||||||
struct tty *tty = &c->tty;
|
|
||||||
|
|
||||||
if (pd == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Adjust position and size. */
|
|
||||||
if (pd->psy > tty->sy)
|
|
||||||
pd->sy = tty->sy;
|
|
||||||
else
|
|
||||||
pd->sy = pd->psy;
|
|
||||||
if (pd->psx > tty->sx)
|
|
||||||
pd->sx = tty->sx;
|
|
||||||
else
|
|
||||||
pd->sx = pd->psx;
|
|
||||||
if (pd->ppy + pd->sy > tty->sy)
|
|
||||||
pd->py = tty->sy - pd->sy;
|
|
||||||
else
|
|
||||||
pd->py = pd->ppy;
|
|
||||||
if (pd->ppx + pd->sx > tty->sx)
|
|
||||||
pd->px = tty->sx - pd->sx;
|
|
||||||
else
|
|
||||||
pd->px = pd->ppx;
|
|
||||||
|
|
||||||
/* Avoid zero size screens. */
|
|
||||||
if (pd->border_lines == BOX_LINES_NONE) {
|
|
||||||
screen_resize(&pd->s, pd->sx, pd->sy, 0);
|
|
||||||
if (pd->job != NULL)
|
|
||||||
job_resize(pd->job, pd->sx, pd->sy );
|
|
||||||
} else if (pd->sx > 2 && pd->sy > 2) {
|
|
||||||
screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
|
|
||||||
if (pd->job != NULL)
|
|
||||||
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_handle_drag(struct client *c, struct popup_data *pd,
|
|
||||||
struct mouse_event *m)
|
|
||||||
{
|
|
||||||
u_int px, py;
|
|
||||||
|
|
||||||
if (!MOUSE_DRAG(m->b))
|
|
||||||
pd->dragging = OFF;
|
|
||||||
else if (pd->dragging == MOVE) {
|
|
||||||
if (m->x < pd->dx)
|
|
||||||
px = 0;
|
|
||||||
else if (m->x - pd->dx + pd->sx > c->tty.sx)
|
|
||||||
px = c->tty.sx - pd->sx;
|
|
||||||
else
|
|
||||||
px = m->x - pd->dx;
|
|
||||||
if (m->y < pd->dy)
|
|
||||||
py = 0;
|
|
||||||
else if (m->y - pd->dy + pd->sy > c->tty.sy)
|
|
||||||
py = c->tty.sy - pd->sy;
|
|
||||||
else
|
|
||||||
py = m->y - pd->dy;
|
|
||||||
pd->px = px;
|
|
||||||
pd->py = py;
|
|
||||||
pd->dx = m->x - pd->px;
|
|
||||||
pd->dy = m->y - pd->py;
|
|
||||||
pd->ppx = px;
|
|
||||||
pd->ppy = py;
|
|
||||||
server_redraw_client(c);
|
|
||||||
} else if (pd->dragging == SIZE) {
|
|
||||||
if (pd->border_lines == BOX_LINES_NONE) {
|
|
||||||
if (m->x < pd->px + 1)
|
|
||||||
return;
|
|
||||||
if (m->y < pd->py + 1)
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (m->x < pd->px + 3)
|
|
||||||
return;
|
|
||||||
if (m->y < pd->py + 3)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pd->sx = m->x - pd->px;
|
|
||||||
pd->sy = m->y - pd->py;
|
|
||||||
pd->psx = pd->sx;
|
|
||||||
pd->psy = pd->sy;
|
|
||||||
|
|
||||||
if (pd->border_lines == BOX_LINES_NONE) {
|
|
||||||
screen_resize(&pd->s, pd->sx, pd->sy, 0);
|
|
||||||
if (pd->job != NULL)
|
|
||||||
job_resize(pd->job, pd->sx, pd->sy);
|
|
||||||
} else {
|
|
||||||
screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
|
|
||||||
if (pd->job != NULL)
|
|
||||||
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
|
||||||
}
|
|
||||||
server_redraw_client(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
popup_key_cb(struct client *c, void *data, struct key_event *event)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = data;
|
|
||||||
struct mouse_event *m = &event->m;
|
|
||||||
const char *buf;
|
|
||||||
size_t len;
|
|
||||||
u_int px, py;
|
|
||||||
enum { NONE, LEFT, RIGHT, TOP, BOTTOM } border = NONE;
|
|
||||||
|
|
||||||
if (KEYC_IS_MOUSE(event->key)) {
|
|
||||||
if (pd->dragging != OFF) {
|
|
||||||
popup_handle_drag(c, pd, m);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (m->x < pd->px ||
|
|
||||||
m->x > pd->px + pd->sx - 1 ||
|
|
||||||
m->y < pd->py ||
|
|
||||||
m->y > pd->py + pd->sy - 1) {
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
if (pd->border_lines != BOX_LINES_NONE) {
|
|
||||||
if (m->x == pd->px)
|
|
||||||
border = LEFT;
|
|
||||||
else if (m->x == pd->px + pd->sx - 1)
|
|
||||||
border = RIGHT;
|
|
||||||
else if (m->y == pd->py)
|
|
||||||
border = TOP;
|
|
||||||
else if (m->y == pd->py + pd->sy - 1)
|
|
||||||
border = BOTTOM;
|
|
||||||
}
|
|
||||||
if ((m->b & MOUSE_MASK_MODIFIERS) == 0 &&
|
|
||||||
MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3 &&
|
|
||||||
(border == LEFT || border == TOP))
|
|
||||||
goto out;
|
|
||||||
if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) ||
|
|
||||||
(border != NONE && !MOUSE_DRAG(m->lb))) {
|
|
||||||
if (!MOUSE_DRAG(m->b))
|
|
||||||
goto out;
|
|
||||||
if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_1)
|
|
||||||
pd->dragging = MOVE;
|
|
||||||
else if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_3)
|
|
||||||
pd->dragging = SIZE;
|
|
||||||
pd->dx = m->lx - pd->px;
|
|
||||||
pd->dy = m->ly - pd->py;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) ||
|
|
||||||
pd->job == NULL) &&
|
|
||||||
(event->key == '\033' || event->key == ('c'|KEYC_CTRL)))
|
|
||||||
return (1);
|
|
||||||
if (pd->job == NULL && (pd->flags & POPUP_CLOSEANYKEY) &&
|
|
||||||
!KEYC_IS_MOUSE(event->key) && !KEYC_IS_PASTE(event->key))
|
|
||||||
return (1);
|
|
||||||
if (pd->job != NULL) {
|
|
||||||
if (KEYC_IS_MOUSE(event->key)) {
|
|
||||||
/* Must be inside, checked already. */
|
|
||||||
if (pd->border_lines == BOX_LINES_NONE) {
|
|
||||||
px = m->x - pd->px;
|
|
||||||
py = m->y - pd->py;
|
|
||||||
} else {
|
|
||||||
px = m->x - pd->px - 1;
|
|
||||||
py = m->y - pd->py - 1;
|
|
||||||
}
|
|
||||||
if (!input_key_get_mouse(&pd->s, m, px, py, &buf, &len))
|
|
||||||
return (0);
|
|
||||||
bufferevent_write(job_get_event(pd->job), buf, len);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
input_key(&pd->s, job_get_event(pd->job), event->key);
|
|
||||||
}
|
|
||||||
return (0);
|
|
||||||
|
|
||||||
out:
|
|
||||||
pd->lx = m->x;
|
|
||||||
pd->ly = m->y;
|
|
||||||
pd->lb = m->b;
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_job_update_cb(struct job *job)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = job_get_data(job);
|
|
||||||
struct evbuffer *evb = job_get_event(job)->input;
|
|
||||||
struct client *c = pd->c;
|
|
||||||
struct screen *s = &pd->s;
|
|
||||||
void *data = EVBUFFER_DATA(evb);
|
|
||||||
size_t size = EVBUFFER_LENGTH(evb);
|
|
||||||
|
|
||||||
if (size == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
c->overlay_check = NULL;
|
|
||||||
c->overlay_data = NULL;
|
|
||||||
input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size);
|
|
||||||
c->overlay_check = popup_check_cb;
|
|
||||||
c->overlay_data = pd;
|
|
||||||
|
|
||||||
evbuffer_drain(evb, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
popup_job_complete_cb(struct job *job)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = job_get_data(job);
|
|
||||||
int status;
|
|
||||||
|
|
||||||
status = job_get_status(pd->job);
|
|
||||||
if (WIFEXITED(status))
|
|
||||||
pd->status = WEXITSTATUS(status);
|
|
||||||
else if (WIFSIGNALED(status))
|
|
||||||
pd->status = WTERMSIG(status);
|
|
||||||
else
|
|
||||||
pd->status = 0;
|
|
||||||
pd->job = NULL;
|
|
||||||
|
|
||||||
if ((pd->flags & POPUP_CLOSEEXIT) ||
|
|
||||||
((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
|
|
||||||
server_client_clear_overlay(pd->c);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
popup_present(struct client *c)
|
|
||||||
{
|
|
||||||
return (c->overlay_draw == popup_draw_cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
popup_modify(struct client *c, const char *title, const char *style,
|
|
||||||
const char *border_style, enum box_lines lines, int flags)
|
|
||||||
{
|
|
||||||
struct popup_data *pd = c->overlay_data;
|
|
||||||
struct style sytmp;
|
|
||||||
|
|
||||||
if (title != NULL) {
|
|
||||||
if (pd->title != NULL)
|
|
||||||
free(pd->title);
|
|
||||||
pd->title = xstrdup(title);
|
|
||||||
}
|
|
||||||
if (border_style != NULL) {
|
|
||||||
free(pd->border_style);
|
|
||||||
pd->border_style = xstrdup(border_style);
|
|
||||||
style_set(&sytmp, &pd->border_cell);
|
|
||||||
if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
|
|
||||||
pd->border_cell.fg = sytmp.gc.fg;
|
|
||||||
pd->border_cell.bg = sytmp.gc.bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (style != NULL) {
|
|
||||||
free(pd->style);
|
|
||||||
pd->style = xstrdup(style);
|
|
||||||
style_set(&sytmp, &pd->defaults);
|
|
||||||
if (style_parse(&sytmp, &pd->defaults, style) == 0) {
|
|
||||||
pd->defaults.fg = sytmp.gc.fg;
|
|
||||||
pd->defaults.bg = sytmp.gc.bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lines != BOX_LINES_DEFAULT) {
|
|
||||||
if (lines == BOX_LINES_NONE && pd->border_lines != lines) {
|
|
||||||
screen_resize(&pd->s, pd->sx, pd->sy, 1);
|
|
||||||
job_resize(pd->job, pd->sx, pd->sy);
|
|
||||||
} else if (pd->border_lines == BOX_LINES_NONE &&
|
|
||||||
pd->border_lines != lines) {
|
|
||||||
screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 1);
|
|
||||||
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
|
||||||
}
|
|
||||||
pd->border_lines = lines;
|
|
||||||
tty_resize(&c->tty);
|
|
||||||
}
|
|
||||||
if (flags != -1)
|
|
||||||
pd->flags = flags;
|
|
||||||
|
|
||||||
server_redraw_client(c);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
|
|
||||||
u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd,
|
|
||||||
int argc, char **argv, const char *cwd, const char *title, struct client *c,
|
|
||||||
struct session *s, const char *style, const char *border_style,
|
|
||||||
popup_close_cb cb, void *arg)
|
|
||||||
{
|
|
||||||
struct popup_data *pd;
|
|
||||||
u_int jx, jy;
|
|
||||||
struct options *o;
|
|
||||||
struct style sytmp;
|
|
||||||
|
|
||||||
if (s != NULL)
|
|
||||||
o = s->curw->window->options;
|
|
||||||
else
|
|
||||||
o = c->session->curw->window->options;
|
|
||||||
|
|
||||||
if (lines == BOX_LINES_DEFAULT)
|
|
||||||
lines = options_get_number(o, "popup-border-lines");
|
|
||||||
if (lines == BOX_LINES_NONE) {
|
|
||||||
if (sx < 1 || sy < 1)
|
|
||||||
return (-1);
|
|
||||||
jx = sx;
|
|
||||||
jy = sy;
|
|
||||||
} else {
|
|
||||||
if (sx < 3 || sy < 3)
|
|
||||||
return (-1);
|
|
||||||
jx = sx - 2;
|
|
||||||
jy = sy - 2;
|
|
||||||
}
|
|
||||||
if (c->tty.sx < sx || c->tty.sy < sy)
|
|
||||||
return (-1);
|
|
||||||
|
|
||||||
pd = xcalloc(1, sizeof *pd);
|
|
||||||
pd->item = item;
|
|
||||||
pd->flags = flags;
|
|
||||||
|
|
||||||
if (title != NULL)
|
|
||||||
pd->title = xstrdup(title);
|
|
||||||
if (style != NULL)
|
|
||||||
pd->style = xstrdup(style);
|
|
||||||
if (border_style != NULL)
|
|
||||||
pd->border_style = xstrdup(border_style);
|
|
||||||
|
|
||||||
pd->c = c;
|
|
||||||
pd->c->references++;
|
|
||||||
|
|
||||||
pd->cb = cb;
|
|
||||||
pd->arg = arg;
|
|
||||||
pd->status = 128 + SIGHUP;
|
|
||||||
|
|
||||||
pd->border_lines = lines;
|
|
||||||
memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
|
|
||||||
style_apply(&pd->border_cell, o, "popup-border-style", NULL);
|
|
||||||
if (border_style != NULL) {
|
|
||||||
style_set(&sytmp, &grid_default_cell);
|
|
||||||
if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
|
|
||||||
pd->border_cell.fg = sytmp.gc.fg;
|
|
||||||
pd->border_cell.bg = sytmp.gc.bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pd->border_cell.attr = 0;
|
|
||||||
|
|
||||||
screen_init(&pd->s, jx, jy, 0);
|
|
||||||
screen_set_default_cursor(&pd->s, global_w_options);
|
|
||||||
colour_palette_init(&pd->palette);
|
|
||||||
colour_palette_from_option(&pd->palette, global_w_options);
|
|
||||||
|
|
||||||
memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
|
|
||||||
style_apply(&pd->defaults, o, "popup-style", NULL);
|
|
||||||
if (style != NULL) {
|
|
||||||
style_set(&sytmp, &grid_default_cell);
|
|
||||||
if (style_parse(&sytmp, &pd->defaults, style) == 0) {
|
|
||||||
pd->defaults.fg = sytmp.gc.fg;
|
|
||||||
pd->defaults.bg = sytmp.gc.bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pd->defaults.attr = 0;
|
|
||||||
|
|
||||||
pd->px = px;
|
|
||||||
pd->py = py;
|
|
||||||
pd->sx = sx;
|
|
||||||
pd->sy = sy;
|
|
||||||
|
|
||||||
pd->ppx = px;
|
|
||||||
pd->ppy = py;
|
|
||||||
pd->psx = sx;
|
|
||||||
pd->psy = sy;
|
|
||||||
|
|
||||||
pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
|
|
||||||
popup_job_update_cb, popup_job_complete_cb, NULL, pd,
|
|
||||||
JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
|
|
||||||
if (pd->job == NULL) {
|
|
||||||
popup_free(pd);
|
|
||||||
return (-1);
|
|
||||||
}
|
|
||||||
pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette, c);
|
|
||||||
|
|
||||||
server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
|
|
||||||
popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
+23
-20
@@ -1,33 +1,17 @@
|
|||||||
TESTS!= echo *.sh
|
TESTS!= echo *.sh
|
||||||
LOGDIR=logs
|
LOGDIR=logs
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all prepare $(TESTS)
|
||||||
.NOTPARALLEL: all
|
|
||||||
|
|
||||||
all:
|
all: prepare $(TESTS)
|
||||||
@mkdir -p "$(LOGDIR)"; \
|
@failed=0; failures=; \
|
||||||
rm -f "$(LOGDIR)"/*.log; \
|
|
||||||
failed=0; failures=; \
|
|
||||||
for test in $(TESTS); do \
|
for test in $(TESTS); do \
|
||||||
base=$${test##*/}; \
|
base=$${test##*/}; \
|
||||||
log="$(LOGDIR)/$${base%.sh}.log"; \
|
log="$(LOGDIR)/$${base%.sh}.log"; \
|
||||||
rm -f "$$log"; \
|
if [ -f "$$log" ]; then \
|
||||||
printf '%-40s ' "$$test"; \
|
|
||||||
start=$$(date +%s); \
|
|
||||||
env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
|
|
||||||
sh -x "$$test" >"$$log" 2>&1; \
|
|
||||||
if [ $$? -eq 0 ]; then \
|
|
||||||
end=$$(date +%s); \
|
|
||||||
rm -f "$$log"; \
|
|
||||||
echo "PASS ($$((end - start))s)"; \
|
|
||||||
else \
|
|
||||||
end=$$(date +%s); \
|
|
||||||
echo "FAIL ($$((end - start))s)"; \
|
|
||||||
echo " log: $$log"; \
|
|
||||||
failed=1; \
|
failed=1; \
|
||||||
failures="$$failures $$test"; \
|
failures="$$failures $$test"; \
|
||||||
fi; \
|
fi; \
|
||||||
sleep 1; \
|
|
||||||
done; \
|
done; \
|
||||||
if [ "$$failed" -ne 0 ]; then \
|
if [ "$$failed" -ne 0 ]; then \
|
||||||
echo; \
|
echo; \
|
||||||
@@ -39,3 +23,22 @@ all:
|
|||||||
rmdir "$(LOGDIR)" 2>/dev/null || true; \
|
rmdir "$(LOGDIR)" 2>/dev/null || true; \
|
||||||
fi; \
|
fi; \
|
||||||
exit $$failed
|
exit $$failed
|
||||||
|
|
||||||
|
prepare:
|
||||||
|
@mkdir -p "$(LOGDIR)"
|
||||||
|
@rm -f "$(LOGDIR)"/*.log
|
||||||
|
|
||||||
|
$(TESTS): prepare
|
||||||
|
@base="$@"; base=$${base##*/}; \
|
||||||
|
log="$(LOGDIR)/$${base%.sh}.log"; \
|
||||||
|
start=$$(date +%s); \
|
||||||
|
if env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
|
||||||
|
sh -x "$@" >"$$log" 2>&1; then \
|
||||||
|
end=$$(date +%s); \
|
||||||
|
rm -f "$$log"; \
|
||||||
|
printf '%-40s PASS (%ss)\n' "$@" "$$((end - start))"; \
|
||||||
|
else \
|
||||||
|
end=$$(date +%s); \
|
||||||
|
printf '%-40s FAIL (%ss)\n log: %s\n' \
|
||||||
|
"$@" "$$((end - start))" "$$log"; \
|
||||||
|
fi
|
||||||
|
|||||||
+32
-2
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
# monitor-activity, monitor-bell and monitor-silence: both the
|
# monitor-activity, monitor-bell and monitor-silence: both the
|
||||||
# alert-activity, alert-bell and alert-silence hooks and the winlink alert
|
# alert-activity, alert-bell and alert-silence hooks and the winlink alert
|
||||||
# flags (window_activity_flag, window_bell_flag, window_silence_flag and
|
# flags (window_activity_flag, window_bell_flag, window_silence_flag,
|
||||||
# the #, !, ~ characters in window_flags). The sessions are detached so
|
# their session equivalents and the #, !, ~ characters in window_flags).
|
||||||
|
# The sessions are detached so
|
||||||
# alert flags are set even on the current window; the *-action options
|
# alert flags are set even on the current window; the *-action options
|
||||||
# still decide whether the hooks fire. Panes run cat: activity is
|
# still decide whether the hooks fire. Panes run cat: activity is
|
||||||
# generated by the tty echo of send-keys and a bell by sending a BEL and
|
# generated by the tty echo of send-keys and a bell by sending a BEL and
|
||||||
@@ -111,6 +112,24 @@ flags_have()
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_session_flag()
|
||||||
|
{
|
||||||
|
flag="#{session_${1}_flag}"
|
||||||
|
expected=$2
|
||||||
|
|
||||||
|
# Only mon exists here. Check both window contexts and list-sessions.
|
||||||
|
for target in mon:w0 "mon:$3"; do
|
||||||
|
value=$($TMUX display -pt "$target" "$flag") ||
|
||||||
|
fail "display $flag failed"
|
||||||
|
[ "$value" = "$expected" ] ||
|
||||||
|
fail "expected $flag for $target to be '$expected' but got '$value'"
|
||||||
|
done
|
||||||
|
value=$($TMUX list-sessions -F "$flag") ||
|
||||||
|
fail "list-sessions $flag failed"
|
||||||
|
[ "$value" = "$expected" ] ||
|
||||||
|
fail "expected list-sessions $flag to be '$expected' but got '$value'"
|
||||||
|
}
|
||||||
|
|
||||||
flags_lack()
|
flags_lack()
|
||||||
{
|
{
|
||||||
target=$1
|
target=$1
|
||||||
@@ -150,10 +169,12 @@ $TMUX set-hook -g alert-silence \
|
|||||||
# alert-bell and sets the bell flag, shown as ! in window_flags.
|
# alert-bell and sets the bell flag, shown as ! in window_flags.
|
||||||
$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed"
|
$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed"
|
||||||
assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0
|
assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0
|
||||||
|
assert_session_flag bell 0 bellw
|
||||||
flags_lack mon:bellw '!'
|
flags_lack mon:bellw '!'
|
||||||
bell mon:bellw
|
bell mon:bellw
|
||||||
wait_for @log '|alert-bell:mon:bellw'
|
wait_for @log '|alert-bell:mon:bellw'
|
||||||
wait_for_fmt mon:bellw '#{window_bell_flag}' 1
|
wait_for_fmt mon:bellw '#{window_bell_flag}' 1
|
||||||
|
assert_session_flag bell 1 bellw
|
||||||
flags_have mon:bellw '!'
|
flags_have mon:bellw '!'
|
||||||
|
|
||||||
# Bells are not deduplicated: a second bell fires the hook again even
|
# Bells are not deduplicated: a second bell fires the hook again even
|
||||||
@@ -164,6 +185,7 @@ wait_for @log '|alert-bell:mon:bellw|alert-bell:mon:bellw'
|
|||||||
# Selecting the window clears the alert flags.
|
# Selecting the window clears the alert flags.
|
||||||
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
||||||
wait_for_fmt mon:bellw '#{window_bell_flag}' 0
|
wait_for_fmt mon:bellw '#{window_bell_flag}' 0
|
||||||
|
assert_session_flag bell 0 bellw
|
||||||
flags_lack mon:bellw '!'
|
flags_lack mon:bellw '!'
|
||||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||||
|
|
||||||
@@ -173,6 +195,7 @@ $TMUX set -g @log '' || fail "reset @log failed"
|
|||||||
bell mon:w0
|
bell mon:w0
|
||||||
wait_for @log '|alert-bell:mon:w0'
|
wait_for @log '|alert-bell:mon:w0'
|
||||||
wait_for_fmt mon:w0 '#{window_bell_flag}' 1
|
wait_for_fmt mon:w0 '#{window_bell_flag}' 1
|
||||||
|
assert_session_flag bell 1 bellw
|
||||||
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
||||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||||
wait_for_fmt mon:w0 '#{window_bell_flag}' 0
|
wait_for_fmt mon:w0 '#{window_bell_flag}' 0
|
||||||
@@ -199,6 +222,7 @@ $TMUX neww -d -t mon: -n actw cat || fail "new-window actw failed"
|
|||||||
$TMUX set -g @log '' || fail "reset @log failed"
|
$TMUX set -g @log '' || fail "reset @log failed"
|
||||||
activity mon:actw
|
activity mon:actw
|
||||||
assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0
|
assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0
|
||||||
|
assert_session_flag activity 0 actw
|
||||||
assert_unchanged @log ''
|
assert_unchanged @log ''
|
||||||
|
|
||||||
# With monitor-activity on, output fires alert-activity and sets the
|
# With monitor-activity on, output fires alert-activity and sets the
|
||||||
@@ -208,6 +232,7 @@ $TMUX set -wt mon:actw monitor-activity on ||
|
|||||||
activity mon:actw
|
activity mon:actw
|
||||||
wait_for @log '|alert-activity:mon:actw'
|
wait_for @log '|alert-activity:mon:actw'
|
||||||
wait_for_fmt mon:actw '#{window_activity_flag}' 1
|
wait_for_fmt mon:actw '#{window_activity_flag}' 1
|
||||||
|
assert_session_flag activity 1 actw
|
||||||
flags_have mon:actw '#'
|
flags_have mon:actw '#'
|
||||||
|
|
||||||
# While the flag is set further activity does not fire the hook again.
|
# While the flag is set further activity does not fire the hook again.
|
||||||
@@ -234,6 +259,7 @@ exec 3>"$OUT/fifo"
|
|||||||
wait_for_fmt mon: '#{session_attached}' 1
|
wait_for_fmt mon: '#{session_attached}' 1
|
||||||
$TMUX selectw -t mon:actw || fail "select-window actw failed"
|
$TMUX selectw -t mon:actw || fail "select-window actw failed"
|
||||||
wait_for_fmt mon:actw '#{window_activity_flag}' 0
|
wait_for_fmt mon:actw '#{window_activity_flag}' 0
|
||||||
|
assert_session_flag activity 0 actw
|
||||||
flags_lack mon:actw '#'
|
flags_lack mon:actw '#'
|
||||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||||
exec 3>&-
|
exec 3>&-
|
||||||
@@ -250,6 +276,7 @@ $TMUX set -wt mon:w0 monitor-activity on ||
|
|||||||
$TMUX set -g @log '' || fail "reset @log failed"
|
$TMUX set -g @log '' || fail "reset @log failed"
|
||||||
activity mon:w0
|
activity mon:w0
|
||||||
wait_for_fmt mon:w0 '#{window_activity_flag}' 1
|
wait_for_fmt mon:w0 '#{window_activity_flag}' 1
|
||||||
|
assert_session_flag activity 1 actw
|
||||||
assert_unchanged @log ''
|
assert_unchanged @log ''
|
||||||
$TMUX set -wut mon:w0 monitor-activity ||
|
$TMUX set -wut mon:w0 monitor-activity ||
|
||||||
fail "unset monitor-activity w0 failed"
|
fail "unset monitor-activity w0 failed"
|
||||||
@@ -273,16 +300,19 @@ $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
|||||||
# running.
|
# running.
|
||||||
$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed"
|
$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed"
|
||||||
assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0
|
assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0
|
||||||
|
assert_session_flag silence 0 silw
|
||||||
$TMUX set -g @log '' || fail "reset @log failed"
|
$TMUX set -g @log '' || fail "reset @log failed"
|
||||||
$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed"
|
$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed"
|
||||||
wait_for @log '|alert-silence:mon:silw'
|
wait_for @log '|alert-silence:mon:silw'
|
||||||
wait_for_fmt mon:silw '#{window_silence_flag}' 1
|
wait_for_fmt mon:silw '#{window_silence_flag}' 1
|
||||||
|
assert_session_flag silence 1 silw
|
||||||
flags_have mon:silw '~'
|
flags_have mon:silw '~'
|
||||||
assert_unchanged @log '|alert-silence:mon:silw'
|
assert_unchanged @log '|alert-silence:mon:silw'
|
||||||
$TMUX set -wt mon:silw monitor-silence 0 ||
|
$TMUX set -wt mon:silw monitor-silence 0 ||
|
||||||
fail "reset monitor-silence failed"
|
fail "reset monitor-silence failed"
|
||||||
$TMUX selectw -t mon:silw || fail "select-window silw failed"
|
$TMUX selectw -t mon:silw || fail "select-window silw failed"
|
||||||
wait_for_fmt mon:silw '#{window_silence_flag}' 0
|
wait_for_fmt mon:silw '#{window_silence_flag}' 0
|
||||||
|
assert_session_flag silence 0 silw
|
||||||
flags_lack mon:silw '~'
|
flags_lack mon:silw '~'
|
||||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||||
|
|
||||||
|
|||||||
Executable
+79
@@ -0,0 +1,79 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Selecting a pane with attach-session from an already attached client must
|
||||||
|
# redraw pane contents when window-style and window-active-style differ.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lattach-redraw-outer-$$ -f/dev/null"
|
||||||
|
BEFORE=$DIR/before
|
||||||
|
AFTER=$DIR/after
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
LEFT=$($INNER new-session -dPF '#{pane_id}' -s inner -x 40 -y 8 \
|
||||||
|
"printf 'LEFT'; exec sleep 100") || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g default-terminal screen || exit 1
|
||||||
|
$INNER split-window -h -t "$LEFT" "printf 'RIGHT'; exec sleep 100" || exit 1
|
||||||
|
$INNER set-option -w -t "$LEFT" window-style bg=red || exit 1
|
||||||
|
$INNER set-option -w -t "$LEFT" window-active-style bg=blue || exit 1
|
||||||
|
$INNER bind-key -n x attach-session -t "$LEFT" || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
sleep 1
|
||||||
|
$OUTER send-keys -t outer:0.0 x || exit 1
|
||||||
|
sleep 1
|
||||||
|
[ "$($INNER display-message -p -t inner '#{pane_id}')" = "$LEFT" ] ||
|
||||||
|
fail "attach-session did not select the target pane"
|
||||||
|
$OUTER capture-pane -pe -t outer:0.0 >"$BEFORE" || exit 1
|
||||||
|
|
||||||
|
# A forced redraw produces the correct scene. It must be identical to the
|
||||||
|
# scene drawn immediately by attach-session.
|
||||||
|
$INNER refresh-client -t "$CLIENT" || exit 1
|
||||||
|
sleep 1
|
||||||
|
$OUTER capture-pane -pe -t outer:0.0 >"$AFTER" || exit 1
|
||||||
|
cmp -s "$BEFORE" "$AFTER" ||
|
||||||
|
fail "attach-session left stale active/inactive pane styles"
|
||||||
|
|
||||||
|
exit 0
|
||||||
+10
-1
@@ -5,6 +5,15 @@
|
|||||||
PATH=/bin:/usr/bin
|
PATH=/bin:/usr/bin
|
||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
|
# The pane's shell must not be the user's own interactive shell: a custom
|
||||||
|
# PS1/PROMPT_COMMAND that sets the terminal title (as many do, for tmux/xterm
|
||||||
|
# TERM types) would redraw over the titles this test sets and checks on
|
||||||
|
# every prompt, regardless of how long it waits first.
|
||||||
|
shell=
|
||||||
|
if command -v bash >/dev/null 2>&1; then
|
||||||
|
shell='bash --noprofile --norc +o history'
|
||||||
|
fi
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
@@ -30,7 +39,7 @@ must_equal()
|
|||||||
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
|
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
|
||||||
}
|
}
|
||||||
|
|
||||||
$TMUX new-session -d -x 80 -y 24 || exit 1
|
$TMUX new-session -d -x 80 -y 24 -- $shell || exit 1
|
||||||
$TMUX set-option -qg allow-set-title on || exit 1
|
$TMUX set-option -qg allow-set-title on || exit 1
|
||||||
$TMUX set-option -qg allow-rename on || exit 1
|
$TMUX set-option -qg allow-rename on || exit 1
|
||||||
$TMUX set-option -qg automatic-rename off || exit 1
|
$TMUX set-option -qg automatic-rename off || exit 1
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Width cache ranges must include their endpoint without overflowing wchar_t.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
SERVER=
|
||||||
|
WATCHDOG=
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
if [ -n "$WATCHDOG" ]; then
|
||||||
|
kill "$WATCHDOG" 2>/dev/null
|
||||||
|
wait "$WATCHDOG" 2>/dev/null
|
||||||
|
fi
|
||||||
|
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
|
||||||
|
}
|
||||||
|
trap cleanup 0
|
||||||
|
trap 'exit 1' 1 2 15
|
||||||
|
|
||||||
|
$TMUX new-session -d 'exec sleep 60' || exit 1
|
||||||
|
SERVER=$($TMUX display-message -p '#{pid}') || exit 1
|
||||||
|
|
||||||
|
# kill-server cannot stop a server stuck rebuilding the width cache.
|
||||||
|
(
|
||||||
|
sleep 5
|
||||||
|
kill -9 "$SERVER" 2>/dev/null
|
||||||
|
) &
|
||||||
|
WATCHDOG=$!
|
||||||
|
|
||||||
|
$TMUX set -g codepoint-widths 'U+1F600=2' || exit 1
|
||||||
|
|
||||||
|
# Cover both signed and unsigned 32-bit wchar_t. Values above WCHAR_MAX
|
||||||
|
# are ignored by the parser on platforms where they cannot be represented.
|
||||||
|
for value in U+7FFFFFFF=1 U+7FFFFFFE-U+7FFFFFFF=2 \
|
||||||
|
U+FFFFFFFF=1 U+FFFFFFFE-U+FFFFFFFF=2; do
|
||||||
|
$TMUX set -g codepoint-widths "$value" || exit 1
|
||||||
|
[ "$($TMUX display-message -p alive)" = alive ] || exit 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check that an ordinary range still includes both endpoints and stops there.
|
||||||
|
$TMUX set -g codepoint-widths 'U+03B1-U+03B3=2' || exit 1
|
||||||
|
$TMUX set -g @text 'αβγδ' || exit 1
|
||||||
|
[ "$($TMUX display-message -p '#{w:@text}')" = 7 ] || exit 1
|
||||||
|
$TMUX set -gu codepoint-widths || exit 1
|
||||||
|
[ "$($TMUX display-message -p '#{w:@text}')" = 4 ] || exit 1
|
||||||
|
|
||||||
|
$TMUX kill-server || exit 1
|
||||||
|
SERVER=
|
||||||
|
exit 0
|
||||||
@@ -17,7 +17,7 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -Ltest"
|
TMUX="$TEST_TMUX -Ltest$$"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
DIR=$(mktemp -d)
|
DIR=$(mktemp -d)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -Ltest"
|
TMUX="$TEST_TMUX -Ltest$$"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
DIR=$(mktemp -d)
|
DIR=$(mktemp -d)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Popups require a tty overlay and cannot be displayed by a control client.
|
# Popups require an attached terminal and cannot be displayed by a control
|
||||||
|
# client.
|
||||||
# A popup command from control mode must be ignored cleanly, leaving the
|
# A popup command from control mode must be ignored cleanly, leaving the
|
||||||
# client command queue and server usable.
|
# client command queue and server usable.
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -Ltest"
|
TMUX="$TEST_TMUX -Ltest$$"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
OUT=$(mktemp)
|
OUT=$(mktemp)
|
||||||
|
|||||||
@@ -30,8 +30,11 @@ killw
|
|||||||
EOF
|
EOF
|
||||||
sleep 1
|
sleep 1
|
||||||
$TMUX has || exit 1
|
$TMUX has || exit 1
|
||||||
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1
|
# Use a control client to request legacy layouts, keeping only pane lines
|
||||||
cat <<EOF|cmp -s $TMP - || exit 1
|
# from the control protocol output.
|
||||||
|
$TMUX -C lsp -aF '#{pane_id} #{window_layout}' |
|
||||||
|
grep '^%[0-9]' >$TMP || exit 1
|
||||||
|
cat <<EOF|cmp $TMP - || exit 1
|
||||||
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||||
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||||
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -Ltest"
|
TMUX="$TEST_TMUX -Ltest$$"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
FIFO=$(mktemp -u)
|
FIFO=$(mktemp -u)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Trimming a blank source must leave a visible line for cursor reflow.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
export PATH TERM
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
}
|
||||||
|
trap cleanup 0
|
||||||
|
trap 'exit 1' 1 2 3 15
|
||||||
|
|
||||||
|
$TMUX new-session -d -x80 -y24 -s test 'sleep 100' || exit 1
|
||||||
|
$TMUX set-option -g window-size manual || exit 1
|
||||||
|
$TMUX new-window -t test:1 'sleep 100' || exit 1
|
||||||
|
$TMUX resize-window -t test:0 -x20 -y24 || exit 1
|
||||||
|
|
||||||
|
# Exercise widening, narrowing, and equal widths with no source history.
|
||||||
|
for pair in '0 1' '1 0' '1 2'; do
|
||||||
|
set -- $pair
|
||||||
|
if [ "$2" = 2 ]; then
|
||||||
|
$TMUX new-window -t test:2 'sleep 100' || exit 1
|
||||||
|
$TMUX resize-window -t test:2 -x80 -y24 || exit 1
|
||||||
|
fi
|
||||||
|
source=test:$1.0
|
||||||
|
target=test:$2.0
|
||||||
|
[ "$($TMUX display-message -p -t "$source" '#{history_size}')" = 0 ] ||
|
||||||
|
fail "source has unexpected history"
|
||||||
|
$TMUX copy-mode -s "$source" -t "$target" || exit 1
|
||||||
|
$TMUX has-session -t test || fail "server died copying blank source"
|
||||||
|
[ "$($TMUX display-message -p -t "$target" \
|
||||||
|
'#{pane_in_mode} #{copy_cursor_x} #{copy_cursor_y}')" = '1 0 0' ] ||
|
||||||
|
fail "blank source did not enter copy mode at the first cell"
|
||||||
|
$TMUX send-keys -t "$target" -X cancel || exit 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Real content still trims trailing empty lines, clamping the cursor to beta.
|
||||||
|
$TMUX respawn-pane -k -t test:0.0 \
|
||||||
|
"printf 'alpha\r\nbeta\r\n\r\n\r\n'; exec sleep 100" || exit 1
|
||||||
|
i=0
|
||||||
|
while [ "$($TMUX display-message -p -t test:0.0 '#{cursor_y}')" != 4 ]; do
|
||||||
|
i=$((i + 1))
|
||||||
|
[ "$i" -lt 10 ] || fail "source output did not arrive"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
$TMUX copy-mode -s test:0.0 -t test:1.0 || exit 1
|
||||||
|
[ "$($TMUX display-message -p -t test:1.0 \
|
||||||
|
'#{pane_in_mode} #{copy_cursor_y} #{copy_cursor_line}')" = '1 1 beta' ] ||
|
||||||
|
fail "nonblank source content or trailing-line trimming changed"
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+108
@@ -0,0 +1,108 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Regression test for a floating-pane drag bug: cmd_resize_pane_redraw_floating()
|
||||||
|
# (cmd-resize-pane.c) reported damage for just a dragged floating pane's
|
||||||
|
# content rectangle, not the one-cell border frame drawn around it (see the
|
||||||
|
# "floating" case in screen-redraw.c, which draws that frame at
|
||||||
|
# xoff-1/yoff-1 through xoff+sx/yoff+sy - one cell outside the pane's own
|
||||||
|
# content area). Damage scoped to only the content area left the frame's
|
||||||
|
# previous position undrawn as the pane moved, so dragging it left a trail
|
||||||
|
# of un-erased border frames behind - visible as several "corners" stacked
|
||||||
|
# up rather than just the pane's current one.
|
||||||
|
#
|
||||||
|
# This bug has nothing to do with images - it reproduces with a plain
|
||||||
|
# floating pane and no image support required.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$TMUX kill-server >/dev/null 2>&1
|
||||||
|
$TMUX2 kill-server >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
cleanup
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||||
|
#
|
||||||
|
# Write a plain (unmodified) SGR button-1 press, drag update and release at
|
||||||
|
# 1-based positions to the outer pane holding the inner client - this
|
||||||
|
# matches the default MouseDown1Border/MouseDrag1Border bindings used to
|
||||||
|
# move or resize a floating pane by its border.
|
||||||
|
drag()
|
||||||
|
{
|
||||||
|
scol="$1"
|
||||||
|
srow="$2"
|
||||||
|
ecol="$3"
|
||||||
|
erow="$4"
|
||||||
|
|
||||||
|
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
TMP=$(mktemp)
|
||||||
|
trap "cleanup; rm -f $TMP" 0 1 15
|
||||||
|
|
||||||
|
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||||
|
$TMUX set -g mouse on
|
||||||
|
$TMUX set -g default-command 'sh -c "sleep 100"'
|
||||||
|
|
||||||
|
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||||
|
fail "new-pane -X -Y failed"
|
||||||
|
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
|
||||||
|
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
||||||
|
sleep 1
|
||||||
|
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||||
|
[ -n "$OUTER" ] || fail "No outer pane."
|
||||||
|
|
||||||
|
# Sanity check: exactly one floating pane, so exactly one top-left corner,
|
||||||
|
# before dragging anything.
|
||||||
|
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||||
|
n=$(grep -o '┌' $TMP | wc -l)
|
||||||
|
[ "$n" -eq 1 ] || fail "sanity: expected 1 corner before drag, found $n"
|
||||||
|
|
||||||
|
# Drag the floating pane by its top border (row FTOP-1, some column within
|
||||||
|
# its width) down several rows in a few separate steps, then release. A
|
||||||
|
# single drag() call already does press/motion/release, so call it several
|
||||||
|
# times in a row to simulate a multi-step real drag.
|
||||||
|
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||||
|
STARTROW=$FTOP
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 6 ]; do
|
||||||
|
newrow=$((STARTROW + i + 1))
|
||||||
|
drag $((GRABCOL + 1)) $((STARTROW + i)) $((GRABCOL + 1)) $newrow
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||||
|
|
||||||
|
# Exactly one top-left corner should remain - the pane's current position.
|
||||||
|
# This is expected to fail before the fix: multiple corners (a trail of
|
||||||
|
# un-erased frames) would remain from the intermediate drag positions.
|
||||||
|
n=$(grep -o '┌' $TMP | wc -l)
|
||||||
|
[ "$n" -eq 1 ] || fail "expected exactly 1 corner after drag, found $n (ghost frames left behind)"
|
||||||
|
|
||||||
|
exit 0
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# server_client_check_redraw() had `(~c->flags & CLIENT_ALLREDRAWFLAGS)` as
|
||||||
|
# a fallback condition guarding a call to redraw_client_damage() - for a
|
||||||
|
# multi-bit mask, `~x & MASK` means "at least one of these bits is unset"
|
||||||
|
# (almost always true), not "none of these bits are set" as the comment
|
||||||
|
# and surrounding logic clearly intend. Every floating-pane drag command
|
||||||
|
# unconditionally sets CLIENT_REDRAWBORDERS (server_redraw_window_borders()
|
||||||
|
# in cmd-resize-pane.c/cmd-join-pane.c/cmd-split-window.c) alongside
|
||||||
|
# reporting window damage, so this fallback fired on every single drag
|
||||||
|
# step, composing the exact same damage rectangle a second time a few
|
||||||
|
# lines later at the CLIENT_ALLREDRAWFLAGS block - wasted work, not a
|
||||||
|
# correctness issue, but a clean, deterministic signal to check for via
|
||||||
|
# the server's own -vv log.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
cd "$DIR" || exit 1
|
||||||
|
INNER="$TEST_TMUX -vv -Ldoublecomp-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Ldoublecomp-outer-$$ -f/dev/null"
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
cd /
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.15
|
||||||
|
}
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 15 -y 5 -X 5 -Y 2 \
|
||||||
|
'sleep 100') || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Ldoublecomp-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
GRABCOL=$((XOFF + 3))
|
||||||
|
BORDERROW=$YOFF
|
||||||
|
|
||||||
|
# Top-border drag (a move): each step both reports window damage and sets
|
||||||
|
# CLIENT_REDRAWBORDERS (server_redraw_window_borders() in the caller),
|
||||||
|
# which is exactly the combination the buggy fallback misfired on.
|
||||||
|
mouse 0 "$GRABCOL" "$BORDERROW" M
|
||||||
|
i=0
|
||||||
|
steps=6
|
||||||
|
while [ $i -lt $steps ]; do
|
||||||
|
GRABCOL=$((GRABCOL + 1))
|
||||||
|
mouse 32 "$GRABCOL" "$BORDERROW" M
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
mouse 0 "$GRABCOL" "$BORDERROW" m
|
||||||
|
sleep 0.3
|
||||||
|
|
||||||
|
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
[ "$NEWXOFF" != "$XOFF" ] || fail "sanity: floating pane did not move (still at $XOFF)"
|
||||||
|
|
||||||
|
LOG=$(ls tmux-server*.log 2>/dev/null | head -1)
|
||||||
|
[ -n "$LOG" ] || fail "sanity: no server -vv log was produced"
|
||||||
|
|
||||||
|
# Each drag step should compose its damage exactly once. If any rectangle
|
||||||
|
# was composed twice, the same "x,y WxH" text appears on two consecutive
|
||||||
|
# composing-damage lines - compare the position+size together, since
|
||||||
|
# distinct steps commonly share the same size (only the position differs).
|
||||||
|
dup=$(grep "composing damage" "$LOG" | awk '{print $(NF-1), $NF}' |
|
||||||
|
uniq -d | wc -l)
|
||||||
|
[ "$dup" -eq 0 ] ||
|
||||||
|
fail "$dup damage rectangle(s) were composed twice in the same pass"
|
||||||
|
|
||||||
|
exit 0
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Regression test: dragging a floating pane across another pane's ordinary
|
||||||
|
# content must not redraw that other pane's scrollbar, unless the drag
|
||||||
|
# actually crosses the scrollbar's own strip.
|
||||||
|
#
|
||||||
|
# cmd_resize_pane_redraw_floating() (cmd-resize-pane.c) used to flag
|
||||||
|
# PANE_REDRAWSCROLLBAR on any pane whose whole *body* intersected the
|
||||||
|
# floating pane's old or new rectangle, rather than just its narrow
|
||||||
|
# scrollbar strip - so dragging a floating pane back and forth over an
|
||||||
|
# ordinary tiled pane's content (never touching its scrollbar) still
|
||||||
|
# needlessly redrew that pane's scrollbar on every motion step. See
|
||||||
|
# tmux-image-redraw-known-bugs.md for the full write-up.
|
||||||
|
#
|
||||||
|
# This is checked by giving the non-dragged pane a distinctive scrollbar
|
||||||
|
# colour and counting how many times its SGR code appears in the client's
|
||||||
|
# raw output while the floating pane is dragged vertically over that pane's
|
||||||
|
# body, well clear of its scrollbar column: with the fix, it should never
|
||||||
|
# reappear after the initial draw.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$TMUX kill-server >/dev/null 2>&1
|
||||||
|
$TMUX2 kill-server >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
cleanup
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||||
|
drag()
|
||||||
|
{
|
||||||
|
scol="$1"
|
||||||
|
srow="$2"
|
||||||
|
ecol="$3"
|
||||||
|
erow="$4"
|
||||||
|
|
||||||
|
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
|
||||||
|
TMP=$(mktemp)
|
||||||
|
trap "cleanup; rm -f $TMP" 0 1 15
|
||||||
|
|
||||||
|
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||||
|
$TMUX set -g mouse on || fail "set mouse failed"
|
||||||
|
$TMUX set -g default-command 'sh -c "sleep 100"' || fail "set default-command failed"
|
||||||
|
$TMUX set -g pane-scrollbars on || fail "set pane-scrollbars failed"
|
||||||
|
|
||||||
|
$TMUX split-window -h -t inner 'sh -c "sleep 100"' || fail "split-window failed"
|
||||||
|
|
||||||
|
PANES=$($TMUX list-panes -t inner -F '#{pane_id} #{pane_left}')
|
||||||
|
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||||
|
[ -n "$LEFT" ] || fail "could not identify left pane"
|
||||||
|
|
||||||
|
# A distinctive scrollbar colour for the non-dragged (left) pane only.
|
||||||
|
$TMUX set -p -t "$LEFT" pane-scrollbars-style 'fg=colour201,bg=colour17' ||
|
||||||
|
fail "set pane-scrollbars-style failed"
|
||||||
|
|
||||||
|
ALEFT=$($TMUX display-message -p -t "$LEFT" '#{pane_left}')
|
||||||
|
ATOP=$($TMUX display-message -p -t "$LEFT" '#{pane_top}')
|
||||||
|
AWIDTH=$($TMUX display-message -p -t "$LEFT" '#{pane_width}')
|
||||||
|
AHEIGHT=$($TMUX display-message -p -t "$LEFT" '#{pane_height}')
|
||||||
|
[ "$AWIDTH" -gt 15 ] || fail "left pane too narrow for this test ($AWIDTH)"
|
||||||
|
|
||||||
|
# A small floating pane placed well inside the left pane's content area,
|
||||||
|
# clear of its (right-hand) scrollbar column by several columns.
|
||||||
|
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 8 -y 5 \
|
||||||
|
-X $((ALEFT + 2)) -Y $((ATOP + 2))) || fail "new-pane -X -Y failed"
|
||||||
|
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
[ $((FLEFT + FWIDTH + 3)) -lt $((ALEFT + AWIDTH)) ] ||
|
||||||
|
fail "sanity: floating pane too close to the scrollbar column"
|
||||||
|
|
||||||
|
# Start the outer session with a plain shell, then start capturing before
|
||||||
|
# triggering the attach - starting the attach as the outer pane's initial
|
||||||
|
# command would mean pipe-pane only starts after the attach-driven initial
|
||||||
|
# redraw (which draws the scrollbars) has already happened, missing it.
|
||||||
|
$TMUX2 new-session -d -x 60 -y 20 || exit 1
|
||||||
|
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||||
|
[ -n "$OUTER" ] || fail "No outer pane."
|
||||||
|
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||||
|
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Sanity check: the distinctive scrollbar colour reaches the client at all.
|
||||||
|
grep -qa '48;5;201' $TMP || fail "sanity: scrollbar colour never reached the client"
|
||||||
|
: >$TMP
|
||||||
|
|
||||||
|
# Drag the floating pane straight up and down by its top border, staying at
|
||||||
|
# a fixed column the whole time - this never crosses the left pane's
|
||||||
|
# scrollbar strip, only its ordinary content.
|
||||||
|
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||||
|
row=$FTOP
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 6 ]; do
|
||||||
|
newrow=$((row + 1))
|
||||||
|
drag $GRABCOL $row $GRABCOL $newrow
|
||||||
|
row=$newrow
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 6 ]; do
|
||||||
|
newrow=$((row - 1))
|
||||||
|
drag $GRABCOL $row $GRABCOL $newrow
|
||||||
|
row=$newrow
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# The scrollbar colour should never reappear - its geometry never changed,
|
||||||
|
# and the drag never crossed its column. This is expected to fail before
|
||||||
|
# the fix - see the header comment.
|
||||||
|
n=$(grep -ac '48;5;201' $TMP)
|
||||||
|
[ "$n" -eq 0 ] ||
|
||||||
|
fail "left pane's scrollbar was redrawn $n times while dragging over its body only"
|
||||||
|
|
||||||
|
exit 0
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# server_client_key_callback()'s mouse-drag dispatch opens a synchronized-
|
||||||
|
# output frame (tty_sync_start()) before running the drag callback, on
|
||||||
|
# every single drag motion event. server_client_check_redraw() then checks
|
||||||
|
# EVBUFFER_LENGTH(tty->out) != 0 later in the same pass to decide whether
|
||||||
|
# to defer this pass's redraw - nothing drains tty->out in between, so the
|
||||||
|
# frame-open sequence just queued (8 bytes: "\033[?2026h") makes that check
|
||||||
|
# see "outstanding output" and defer against itself, escalating the drag's
|
||||||
|
# damage to a full-window redraw on every motion event on any
|
||||||
|
# synchronized-output-capable terminal. This checks the server's own -vv
|
||||||
|
# log for that exact self-inflicted "8 left" deferral pattern during a
|
||||||
|
# drag, and requires it never appears.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
cd "$DIR" || exit 1
|
||||||
|
INNER="$TEST_TMUX -vv -Lsyncdefer-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lsyncdefer-outer-$$ -f/dev/null"
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
cd /
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.15
|
||||||
|
}
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 15 -y 5 -X 5 -Y 2 \
|
||||||
|
'sleep 100') || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER set-option -as terminal-features ',screen-256color:sync' || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lsyncdefer-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
GRABCOL=$((XOFF + 3))
|
||||||
|
BORDERROW=$YOFF
|
||||||
|
|
||||||
|
# Plain (non-Alt) top-border drag: "MouseDrag1Border" -> resize-pane -M ->
|
||||||
|
# a move, since grabbing the top border moves rather than resizes. Several
|
||||||
|
# small steps, each its own drag-motion event and so its own pass through
|
||||||
|
# the code under test.
|
||||||
|
mouse 0 "$GRABCOL" "$BORDERROW" M
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 6 ]; do
|
||||||
|
GRABCOL=$((GRABCOL + 1))
|
||||||
|
mouse 32 "$GRABCOL" "$BORDERROW" M
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
mouse 0 "$GRABCOL" "$BORDERROW" m
|
||||||
|
sleep 0.3
|
||||||
|
|
||||||
|
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
[ "$NEWXOFF" != "$XOFF" ] || fail "sanity: floating pane did not move (still at $XOFF)"
|
||||||
|
|
||||||
|
LOG=$(ls tmux-server*.log 2>/dev/null | head -1)
|
||||||
|
[ -n "$LOG" ] || fail "sanity: no server -vv log was produced"
|
||||||
|
|
||||||
|
n=$(grep -c "redraw deferred (8 left)" "$LOG")
|
||||||
|
[ "$n" -eq 0 ] ||
|
||||||
|
fail "drag self-deferred against its own queued sync bytes $n time(s)"
|
||||||
|
|
||||||
|
exit 0
|
||||||
+206
@@ -0,0 +1,206 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Mirror of floating-pane-drag-wide-character.sh for the *right* edge of a
|
||||||
|
# damage rectangle. redraw_damage_grow_span_clip() (screen-redraw.c) widens
|
||||||
|
# a damage rectangle's right edge by one cell whenever it isn't already at
|
||||||
|
# the span's own edge, to pull in a wide character's base half when the
|
||||||
|
# edge lands on its padding half - but it did this unconditionally, with no
|
||||||
|
# check of which half it was actually touching. When the edge instead
|
||||||
|
# already lands cleanly on a fresh character's base cell (a character fully
|
||||||
|
# outside the range), growing right pulls in just that base cell -
|
||||||
|
# tty_draw_line() sees it can't fit that character's full width in the
|
||||||
|
# remaining range (tty_draw_line_get_empty()'s gc->data.width > nx check)
|
||||||
|
# and clears it, exactly as the left-edge bug cleared a neighbouring
|
||||||
|
# character's padding half.
|
||||||
|
#
|
||||||
|
# As with the left-edge test, the exact column parity needs to be
|
||||||
|
# deterministic to actually catch it. This constructs it by creating the
|
||||||
|
# floating pane, checking its real position, and recreating it one column
|
||||||
|
# over if necessary until the vacated rectangle's right edge lands on a
|
||||||
|
# base cell.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lwidecharr-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lwidecharr-outer-$$ -f/dev/null"
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
BASE=$DIR/base
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
FLOAT=
|
||||||
|
PANEWIDTH=12
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
slice_columns()
|
||||||
|
{
|
||||||
|
# Extract terminal columns [COL1, COL2) from lines [ROW1, ROW2] of
|
||||||
|
# $1, decoding UTF-8 - the pane's own new position (well clear of
|
||||||
|
# this range) must not affect the comparison, so this only looks at
|
||||||
|
# the narrow strip actually vacated, not the whole line. capture-pane
|
||||||
|
# text has one decoded character per double-width cell pair (every
|
||||||
|
# character here is width 2), so terminal columns are converted to
|
||||||
|
# character indices by halving before slicing.
|
||||||
|
perl -CSD -e '
|
||||||
|
my ($row1, $row2, $col1, $col2, $file) = @ARGV;
|
||||||
|
open my $fh, "<:encoding(UTF-8)", $file or die $!;
|
||||||
|
my @lines = <$fh>;
|
||||||
|
my $c1 = int($col1 / 2);
|
||||||
|
my $c2 = int(($col2 + 1) / 2);
|
||||||
|
for my $n ($row1 .. $row2) {
|
||||||
|
my $line = $lines[$n - 1];
|
||||||
|
$line =~ s/\R\z//;
|
||||||
|
print substr($line, $c1, $c2 - $c1), "\n";
|
||||||
|
}
|
||||||
|
' "$ROW1" "$ROW2" "$COL1" "$COL2" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_old_rows_restored()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
slice_columns "$BASE" >"$DIR/want"
|
||||||
|
slice_columns "$CAPTURE" >"$DIR/got"
|
||||||
|
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "wide characters under the floating pane's vacated right edge were not restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
binmode STDOUT, ':encoding(UTF-8)';
|
||||||
|
$| = 1;
|
||||||
|
for my $row (1 .. 10) {
|
||||||
|
print "\e[$row;1H", chr(0x754c) x 20;
|
||||||
|
}
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
$INNER set-option -g pane-scrollbars off || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lwidecharr-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
wait_outer_has '界界界'
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||||
|
|
||||||
|
# Create the floating pane, then check its actual resulting position. Try
|
||||||
|
# adjacent starting columns until the vacated rectangle's right edge
|
||||||
|
# (xoff + PANEWIDTH + 1, the border-grown exclusive end - see
|
||||||
|
# window_pane_damage_floating(), window.c) lands on an even (base-cell)
|
||||||
|
# column.
|
||||||
|
startx=5
|
||||||
|
tries=0
|
||||||
|
while [ "$tries" -lt 2 ]; do
|
||||||
|
[ -n "$FLOAT" ] && $INNER kill-pane -t "$FLOAT" 2>/dev/null
|
||||||
|
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x "$PANEWIDTH" -y 3 \
|
||||||
|
-X "$startx" -Y 5 \
|
||||||
|
'sh -c "printf FLOATMARK; exec sleep 100"') || exit 1
|
||||||
|
sleep 0.2
|
||||||
|
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
oldright=$((XOFF + PANEWIDTH + 1))
|
||||||
|
if [ $((oldright % 2)) -eq 0 ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
startx=$((startx + 1))
|
||||||
|
tries=$((tries + 1))
|
||||||
|
done
|
||||||
|
[ $(((XOFF + PANEWIDTH + 1) % 2)) -eq 0 ] ||
|
||||||
|
fail "could not find bad-parity starting column"
|
||||||
|
|
||||||
|
wait_outer_has FLOATMARK
|
||||||
|
|
||||||
|
ROW1=$((YOFF + 1))
|
||||||
|
ROW2=$((YOFF + 3))
|
||||||
|
COL1=$((XOFF + PANEWIDTH - 2))
|
||||||
|
COL2=$((XOFF + PANEWIDTH + 2))
|
||||||
|
|
||||||
|
# Grab the pane's top border a couple of columns in (avoiding the corner
|
||||||
|
# cells) and drag it well clear of its old rectangle - far enough right
|
||||||
|
# that its new position starts past the checked columns above (which sit
|
||||||
|
# just past the pane's *original* right edge).
|
||||||
|
GRABCOL=$((XOFF + 3))
|
||||||
|
BORDERROW=$((YOFF))
|
||||||
|
|
||||||
|
seq=$(printf '\033[<0;%s;%sM' "$GRABCOL" "$BORDERROW")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
seq=$(printf '\033[<32;%s;%sM' "$((GRABCOL + 20))" "$BORDERROW")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
seq=$(printf '\033[<0;%s;%sm' "$((GRABCOL + 20))" "$BORDERROW")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
[ "$NEWXOFF" != "$XOFF" ] || fail "sanity: floating pane did not move (still at $XOFF)"
|
||||||
|
|
||||||
|
wait_old_rows_restored
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+200
@@ -0,0 +1,200 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Damage at a floating pane's vacated edge must always redraw a complete
|
||||||
|
# grid character. redraw_damage_grow_span_clip() (screen-redraw.c) widens a
|
||||||
|
# damage rectangle's left edge by one cell whenever it isn't already at the
|
||||||
|
# span's own edge, to pull in a wide character's base half when the edge
|
||||||
|
# lands on its padding half - but it did this unconditionally, with no
|
||||||
|
# check of which half it was actually touching. When the edge instead
|
||||||
|
# already lands cleanly on a fresh character's base cell, growing left
|
||||||
|
# walks into the *previous*, unrelated character's padding cell and blanks
|
||||||
|
# it (tty_draw_line() treats any leading padding cell as proof its own
|
||||||
|
# range starts mid-character).
|
||||||
|
#
|
||||||
|
# This is a general damage-composition bug, not specific to any one kind of
|
||||||
|
# pane, but the exact column parity needs to be deterministic to actually
|
||||||
|
# catch it (a lucky parity draws fine). This constructs it by creating the
|
||||||
|
# floating pane, checking its real position (the border-framing offset
|
||||||
|
# added to -X is not something to hand-compute), and recreating it one
|
||||||
|
# column over if necessary until the vacated rectangle's left edge lands on
|
||||||
|
# a base cell.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lwidechar-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lwidechar-outer-$$ -f/dev/null"
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
BASE=$DIR/base
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
FLOAT=
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
slice_columns()
|
||||||
|
{
|
||||||
|
# Extract terminal columns [COL1, COL2) from lines [ROW1, ROW2] of
|
||||||
|
# $1, decoding UTF-8 - the pane's own new position (well clear of
|
||||||
|
# this range) must not affect the comparison, so this only looks at
|
||||||
|
# the narrow strip actually vacated, not the whole line. capture-pane
|
||||||
|
# text has one decoded character per double-width cell pair (every
|
||||||
|
# character here is width 2), so terminal columns are converted to
|
||||||
|
# character indices by halving before slicing.
|
||||||
|
perl -CSD -e '
|
||||||
|
my ($row1, $row2, $col1, $col2, $file) = @ARGV;
|
||||||
|
open my $fh, "<:encoding(UTF-8)", $file or die $!;
|
||||||
|
my @lines = <$fh>;
|
||||||
|
my $c1 = int($col1 / 2);
|
||||||
|
my $c2 = int(($col2 + 1) / 2);
|
||||||
|
for my $n ($row1 .. $row2) {
|
||||||
|
my $line = $lines[$n - 1];
|
||||||
|
$line =~ s/\R\z//;
|
||||||
|
print substr($line, $c1, $c2 - $c1), "\n";
|
||||||
|
}
|
||||||
|
' "$ROW1" "$ROW2" "$COL1" "$COL2" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_old_rows_restored()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
slice_columns "$BASE" >"$DIR/want"
|
||||||
|
slice_columns "$CAPTURE" >"$DIR/got"
|
||||||
|
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "wide characters under the floating pane's vacated edge were not restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
binmode STDOUT, ':encoding(UTF-8)';
|
||||||
|
$| = 1;
|
||||||
|
for my $row (1 .. 10) {
|
||||||
|
print "\e[$row;1H", chr(0x754c) x 20;
|
||||||
|
}
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
$INNER set-option -g pane-scrollbars off || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lwidechar-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
wait_outer_has '界界界'
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||||
|
|
||||||
|
# Create the floating pane, then check its actual resulting position. Try
|
||||||
|
# adjacent starting columns until the vacated rectangle's left edge
|
||||||
|
# (xoff - 1) lands on an even (base-cell) column - the odd case is the one
|
||||||
|
# every earlier manual test happened to land on by chance.
|
||||||
|
startx=5
|
||||||
|
tries=0
|
||||||
|
while [ "$tries" -lt 2 ]; do
|
||||||
|
[ -n "$FLOAT" ] && $INNER kill-pane -t "$FLOAT" 2>/dev/null
|
||||||
|
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 12 -y 3 -X "$startx" \
|
||||||
|
-Y 5 'sh -c "printf FLOATMARK; exec sleep 100"') || exit 1
|
||||||
|
sleep 0.2
|
||||||
|
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
oldleft=$((XOFF - 1))
|
||||||
|
if [ $((oldleft % 2)) -eq 0 ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
startx=$((startx + 1))
|
||||||
|
tries=$((tries + 1))
|
||||||
|
done
|
||||||
|
[ $(((XOFF - 1) % 2)) -eq 0 ] || fail "could not find bad-parity starting column"
|
||||||
|
|
||||||
|
wait_outer_has FLOATMARK
|
||||||
|
|
||||||
|
ROW1=$((YOFF + 1))
|
||||||
|
ROW2=$((YOFF + 3))
|
||||||
|
COL1=$((XOFF - 4))
|
||||||
|
COL2=$((XOFF + 4))
|
||||||
|
|
||||||
|
# Grab the pane's top border a couple of columns in (avoiding the corner
|
||||||
|
# cells) and drag it well clear of its old rectangle.
|
||||||
|
GRABCOL=$((XOFF + 3))
|
||||||
|
BORDERROW=$((YOFF))
|
||||||
|
|
||||||
|
seq=$(printf '\033[<0;%s;%sM' "$GRABCOL" "$BORDERROW")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
seq=$(printf '\033[<32;%s;%sM' "$((GRABCOL + 15))" "$BORDERROW")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
seq=$(printf '\033[<0;%s;%sm' "$((GRABCOL + 15))" "$BORDERROW")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
[ "$NEWXOFF" != "$XOFF" ] || fail "sanity: floating pane did not move (still at $XOFF)"
|
||||||
|
|
||||||
|
wait_old_rows_restored
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -268,5 +268,149 @@ $TMUX kill-pane -t "$floating" || exit 1
|
|||||||
$TMUX kill-pane -t "$right" || exit 1
|
$TMUX kill-pane -t "$right" || exit 1
|
||||||
$TMUX kill-pane -t "$lower" || exit 1
|
$TMUX kill-pane -t "$lower" || exit 1
|
||||||
|
|
||||||
|
# --- Floating panes clamped when the window shrinks (issue #5581, PR #5582) ---
|
||||||
|
#
|
||||||
|
# layout_resize clamps floating panes back inside the window when it shrinks:
|
||||||
|
# move them and, only if they cannot fit, shrink them (never below
|
||||||
|
# PANE_MINIMUM). A floating cell is the pane's content, so with the default
|
||||||
|
# single-line border the clamp counts 1 cell of border per side, exactly as
|
||||||
|
# layout_floating_args_parse does on creation; with no border it counts 0.
|
||||||
|
# Each case below gets its own window, since resize-window fixes a window at
|
||||||
|
# a manual size for the rest of its life.
|
||||||
|
|
||||||
|
# Case 1: a lone floating pane -- break-pane -W on a window's only pane --
|
||||||
|
# takes the early-return path in layout_resize, added during PR #5582's
|
||||||
|
# review round, since there is no tiled tree to walk.
|
||||||
|
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||||
|
fail "new-window for lone float failed"
|
||||||
|
|
||||||
|
# -x 20 -y 6 -> pane 18x4; -X 60 -Y 18 -> pane_left 61, pane_top 19: with the
|
||||||
|
# border, the footprint is columns 60-79, rows 18-23, flush with the 80x24
|
||||||
|
# window's right and bottom edges, so the float fits before it is shrunk.
|
||||||
|
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||||
|
fail "break-pane -W for lone float failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
||||||
|
|
||||||
|
# Shrink to 40x16. The float still fits at its own size (18 <= 40-2, 4 <=
|
||||||
|
# 16-2) so only its position moves, flush to the new right/bottom edges:
|
||||||
|
# xoff = 40 - 18 - 1 = 21; yoff = 16 - 4 - 1 = 11.
|
||||||
|
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||||
|
fail "resize-window (lone float) failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||||
|
|
||||||
|
# Case 5: grow the window back. The clamp must not chase the window back
|
||||||
|
# outward -- it only ever pulls a float in, never restores where it was.
|
||||||
|
$TMUX resize-window -t "$win" -x 80 -y 24 ||
|
||||||
|
fail "resize-window grow (lone float) failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||||
|
$TMUX kill-window -t "$win" || exit 1
|
||||||
|
|
||||||
|
# Case 2: the same float, but with a tiled sibling surviving alongside it, so
|
||||||
|
# the window's root cell stays tiled and layout_resize takes the normal path
|
||||||
|
# (the clamp call after layout_fix_offsets) instead of the early return
|
||||||
|
# above. Same geometry as case 1, so the same numbers should come out.
|
||||||
|
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||||
|
fail "new-window for tiled sibling failed"
|
||||||
|
$TMUX split-window -t "$win" 'sleep 100' ||
|
||||||
|
fail "split-window for tiled sibling failed"
|
||||||
|
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||||
|
fail "break-pane -W for tiled sibling failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
||||||
|
|
||||||
|
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||||
|
fail "resize-window (tiled sibling) failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||||
|
$TMUX kill-window -t "$win" || exit 1
|
||||||
|
|
||||||
|
# Case 3: new-pane float, the original repro from issue #5581 and the PR
|
||||||
|
# body -- also the normal path, via the tiled base pane new-window creates.
|
||||||
|
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||||
|
fail "new-window for new-pane repro failed"
|
||||||
|
$TMUX resize-window -t "$win" -x 120 -y 40 ||
|
||||||
|
fail "resize-window to 120x40 failed"
|
||||||
|
|
||||||
|
# -x 30 -y 10 -> pane 28x8; -X 85 -Y 25 -> pane_left 86, pane_top 26.
|
||||||
|
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
||||||
|
-x 30 -y 10 -X 85 -Y 25 'sleep 100') ||
|
||||||
|
fail "new-pane for new-pane repro failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 86
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 26
|
||||||
|
|
||||||
|
# Shrink to 60x20: xoff = 60 - 28 - 1 = 31; yoff = 20 - 8 - 1 = 11.
|
||||||
|
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
||||||
|
fail "resize-window (new-pane repro) failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 31
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 11
|
||||||
|
$TMUX kill-window -t "$win" || exit 1
|
||||||
|
|
||||||
|
# Case 4: a float that already fits inside the shrunk window is left alone --
|
||||||
|
# assert position and size are both unchanged, not just one of them.
|
||||||
|
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||||
|
fail "new-window for untouched float failed"
|
||||||
|
|
||||||
|
# -x 20 -y 6 -> pane 18x4; -X 8 -Y 3 -> pane_left 9, pane_top 4 (as at the top
|
||||||
|
# of this file). Footprint columns 8-27, rows 3-8: well inside 60x20 too.
|
||||||
|
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
||||||
|
-x 20 -y 6 -X 8 -Y 3 'sleep 100') ||
|
||||||
|
fail "new-pane for untouched float failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
||||||
|
|
||||||
|
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
||||||
|
fail "resize-window (untouched float) failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
||||||
|
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
||||||
|
$TMUX kill-window -t "$win" || exit 1
|
||||||
|
|
||||||
|
# Case 6: pad = 0, via the pane-border-lines window option set to none. The
|
||||||
|
# border arithmetic differs here (no -2/+1 adjustment either on creation or
|
||||||
|
# in the clamp).
|
||||||
|
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||||
|
fail "new-window for pad=0 float failed"
|
||||||
|
$TMUX set-option -w -t "$win" pane-border-lines none ||
|
||||||
|
fail "set pane-border-lines none failed"
|
||||||
|
|
||||||
|
# No border: -x 20 -y 6 -> pane 20x6 directly; -X 60 -Y 18 -> pane_left 60,
|
||||||
|
# pane_top 18 directly. Footprint (== content, no border) is columns 60-79,
|
||||||
|
# rows 18-23: flush right/bottom of the 80x24 window, same as case 1.
|
||||||
|
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||||
|
fail "break-pane -W for pad=0 float failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 60
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 18
|
||||||
|
|
||||||
|
# Shrink to 40x16 with pad=0: xoff = 40 - 20 - 0 = 20; yoff = 16 - 6 - 0 = 10.
|
||||||
|
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||||
|
fail "resize-window (pad=0 float) failed"
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 20
|
||||||
|
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 10
|
||||||
|
$TMUX kill-window -t "$win" || exit 1
|
||||||
|
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# A floating pane positioned partly off the window's left/top edge (e.g.
|
||||||
|
# created with -X -5) has a negative wp->xoff/wp->yoff. screen_write_
|
||||||
|
# redraw_cb() (screen-write.c) used to pass these straight through as u_int
|
||||||
|
# to redraw_damage_window(), which wraps a negative offset to a huge value
|
||||||
|
# - redraw_damage_window()'s own bounds check then rejects the whole
|
||||||
|
# rectangle, so nothing gets redrawn, not even the pane's visible portion.
|
||||||
|
#
|
||||||
|
# This fires on returning from the alternate screen (screen_write_
|
||||||
|
# alternateoff()) among other paths. This test exercises exactly that:
|
||||||
|
# fills the pane's primary screen, switches it to the alternate screen and
|
||||||
|
# back, and checks the client actually receives the restored primary
|
||||||
|
# content in the pane's visible (on-screen) columns - using an attached
|
||||||
|
# client's own received bytes (via a nested outer client), not
|
||||||
|
# capture-pane, which reads the grid directly and would pass regardless of
|
||||||
|
# whether the client was ever actually told to redraw it.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Loffscreen-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Loffscreen-outer-$$ -f/dev/null"
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_visible_restored()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
sed -n "${CONTENTROW}p" "$CAPTURE" | grep -q '^AAAAA' && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "primary-screen content was not restored in the pane's visible columns after returning from the alternate screen"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
$| = 1;
|
||||||
|
print "\e[1;1H", 'A' x 15;
|
||||||
|
sleep 2;
|
||||||
|
print "\e[?1049h";
|
||||||
|
print "\e[1;1H", 'B' x 15;
|
||||||
|
sleep 2;
|
||||||
|
print "\e[?1049l";
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
|
||||||
|
# Content pane spans window columns -5..9 (partly off the left edge); only
|
||||||
|
# columns 0..9 are ever visible.
|
||||||
|
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 15 -y 5 -X -5 -Y 2 \
|
||||||
|
"perl '$EMITTER'") || exit 1
|
||||||
|
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
[ "$XOFF" -lt 0 ] || fail "sanity: floating pane is not off-screen (xoff=$XOFF)"
|
||||||
|
CONTENTROW=$((YOFF + 1))
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Loffscreen-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_outer_has AAAAA
|
||||||
|
wait_outer_has BBBBB
|
||||||
|
wait_visible_restored
|
||||||
|
|
||||||
|
exit 0
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# A damage rectangle's clip range is grown to avoid splitting a wide
|
||||||
|
# character, but redraw_damage_grow_span_clip() (screen-redraw.c) only ever
|
||||||
|
# checks the span's own pane *content* grid (wp->screen) for that. For a
|
||||||
|
# REDRAW_SPAN_PANE span, that same range is then also handed to
|
||||||
|
# redraw_damage_draw_pane_prompt() to recompose the pane's separately
|
||||||
|
# rendered prompt (wp->prompt, e.g. from "command-prompt -P") over the
|
||||||
|
# damaged sub-range - but the prompt is drawn into its own, freshly
|
||||||
|
# allocated one-line screen, unrelated to the pane's content grid, so a
|
||||||
|
# range grown (or left ungrown) against the content is not necessarily
|
||||||
|
# grown correctly for the prompt's own wide characters.
|
||||||
|
#
|
||||||
|
# This is invisible when the pane's own content is plain ASCII (as here):
|
||||||
|
# redraw_damage_grow_span_clip() never finds anything to grow against, so
|
||||||
|
# the raw, ungrown geometric range is passed straight through to the
|
||||||
|
# prompt - and if that range's edge lands mid-character in the *prompt's*
|
||||||
|
# grid, tty_draw_line() clears the character it cuts through
|
||||||
|
# (tty_draw_line_get_empty()'s gc->data.width > nx check, for a trailing
|
||||||
|
# base cell with no room left for its padding half).
|
||||||
|
#
|
||||||
|
# The trigger is a palette change (OSC 4) in a tiled pane that is one half
|
||||||
|
# of a vertical split running the full height of the window - occluded
|
||||||
|
# under the floating pane, but still geometrically triggering a redraw of
|
||||||
|
# its own rectangle. Positioned so the split boundary falls inside the
|
||||||
|
# floating pane's own CJK prompt, this reproduces exactly Codex's report:
|
||||||
|
# "a floating pane containing a CJK prompt across a tiled-pane boundary -
|
||||||
|
# a palette update in the tiled pane blanks a prompt character."
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
cd "$DIR" || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lpromptwide-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lpromptwide-outer-$$ -f/dev/null"
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
FLOAT=
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
cd /
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_prompt_row_intact()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
line=$(sed -n "${PROMPTROW}p" "$CAPTURE")
|
||||||
|
case $line in
|
||||||
|
*"$PROMPTTEXT"*) return 0 ;;
|
||||||
|
esac
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "the CJK prompt was not intact after the palette-triggered damage - got: $line"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
$| = 1;
|
||||||
|
my $line = <STDIN>;
|
||||||
|
print "\e]4;1;rgb:11/22/33\e\\";
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 60 -y 12 "perl '$EMITTER'" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
LEFT=$($INNER list-panes -t inner -F '#{pane_id}') || exit 1
|
||||||
|
|
||||||
|
# Split so the boundary between the two tiled panes falls at column 17 -
|
||||||
|
# used below to pick a floating-pane column that lands the boundary
|
||||||
|
# mid-character inside the prompt.
|
||||||
|
RIGHT=$($INNER split-window -t inner -h -l 43 -PF '#{pane_id}' \
|
||||||
|
'sleep 100') || exit 1
|
||||||
|
RX=$($INNER display-message -p -t "$RIGHT" '#{pane_left}') || exit 1
|
||||||
|
|
||||||
|
# Create the floating pane, then check its actual resulting position (the
|
||||||
|
# border-framing offset added to -X is not something to hand-compute). Try
|
||||||
|
# adjacent starting columns until the split boundary lands on an odd
|
||||||
|
# (padding-half) column of the prompt's own numbering, and within the
|
||||||
|
# prompt's 12-column width.
|
||||||
|
startx=10
|
||||||
|
tries=0
|
||||||
|
while [ "$tries" -lt 4 ]; do
|
||||||
|
[ -n "$FLOAT" ] && $INNER kill-pane -t "$FLOAT" 2>/dev/null
|
||||||
|
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 24 -y 5 -X "$startx" \
|
||||||
|
-Y 2 "sh -c 'i=0; while [ \$i -lt 10 ]; do \
|
||||||
|
printf AAAAAAAAAAAAAAAAAAAAAA\\\\n; i=\$((i+1)); done; sleep 100'") ||
|
||||||
|
exit 1
|
||||||
|
sleep 0.2
|
||||||
|
X1=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
Y1=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
H1=$($INNER display-message -p -t "$FLOAT" '#{pane_height}')
|
||||||
|
local=$((RX - X1 - 1))
|
||||||
|
if [ "$local" -ge 1 ] && [ "$local" -le 11 ] &&
|
||||||
|
[ $((local % 2)) -eq 1 ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
startx=$((startx + 1))
|
||||||
|
tries=$((tries + 1))
|
||||||
|
done
|
||||||
|
local=$((RX - X1 - 1))
|
||||||
|
[ "$local" -ge 1 ] && [ "$local" -le 11 ] && [ $((local % 2)) -eq 1 ] ||
|
||||||
|
fail "could not find bad-parity starting column"
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 60 -y 12 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lpromptwide-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
wait_outer_has AAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}') || exit 1
|
||||||
|
|
||||||
|
$INNER select-pane -t "$FLOAT" || exit 1
|
||||||
|
PROMPTTEXT=$(printf '\344\270\255' | perl -CSD -ne 'print $_ x 6')
|
||||||
|
$INNER command-prompt -b -P -t "$CLIENT" -p "$PROMPTTEXT" \
|
||||||
|
'display-message -- %1' || exit 1
|
||||||
|
wait_outer_has "$PROMPTTEXT"
|
||||||
|
|
||||||
|
PROMPTROW=$((Y1 + H1))
|
||||||
|
|
||||||
|
# Trigger the damage: unblock the emitter so it fires the palette change in
|
||||||
|
# the left tiled pane, which is occluded under (but geometrically overlaps)
|
||||||
|
# the floating pane's prompt row.
|
||||||
|
$INNER send-keys -t "$LEFT" Enter || exit 1
|
||||||
|
|
||||||
|
wait_prompt_row_intact
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+117
@@ -0,0 +1,117 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# redraw_damage_refresh_status() (screen-redraw.c) force-regenerates a
|
||||||
|
# pane's border-status title when a damage rectangle touches it, guarded
|
||||||
|
# by the per-pane PANE_NEWSTATUS flag. window_make_pane_status() formats
|
||||||
|
# pane-border-format using the requesting client's own context (so e.g.
|
||||||
|
# #{client_name} differs per client), but wp->status_screen/PANE_NEWSTATUS
|
||||||
|
# are shared by every client viewing the pane. With two clients attached
|
||||||
|
# to the same session, whichever client's damage pass runs first renders
|
||||||
|
# its own text and sets the flag; the other client's damage pass, finding
|
||||||
|
# the flag already set, used to skip rendering entirely and reuse
|
||||||
|
# whatever was already there.
|
||||||
|
#
|
||||||
|
# This checks the actual server-side decision via the -vv log rather than
|
||||||
|
# a visual capture: an unrelated periodic client status-refresh reliably
|
||||||
|
# repaints each client's title correctly again within the same tick right
|
||||||
|
# after the buggy decision is made, before anything is ever flushed to
|
||||||
|
# either terminal, so the wrong content this bug produces is never
|
||||||
|
# visible to any external capture - the log is the only place the actual
|
||||||
|
# bug (or its absence) can be observed.
|
||||||
|
#
|
||||||
|
# A floating pane with its own pane-border-status is positioned so that a
|
||||||
|
# damage rectangle from an *unrelated* palette change (OSC 4) in the
|
||||||
|
# underlying tiled pane - whose own geometry spans the whole window -
|
||||||
|
# overlaps the floating pane's title row without touching its content,
|
||||||
|
# giving a damage-only trigger with no side effect that would otherwise
|
||||||
|
# force a normal (non-buggy) full per-client status re-render in the same
|
||||||
|
# pass and mask the result either way.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
cd "$DIR" || exit 1
|
||||||
|
INNER="$TEST_TMUX -vv -Lstatuscc-inner-$$ -f/dev/null"
|
||||||
|
OUTER1="$TEST_TMUX -Lstatuscc-outer1-$$ -f/dev/null"
|
||||||
|
OUTER2="$TEST_TMUX -Lstatuscc-outer2-$$ -f/dev/null"
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER1 kill-server 2>/dev/null
|
||||||
|
$OUTER2 kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
cd /
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
BASEEMITTER=$DIR/base-emitter.pl
|
||||||
|
cat >"$BASEEMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
$| = 1;
|
||||||
|
my $line = <STDIN>;
|
||||||
|
print "\e]4;1;rgb:11/22/33\e\\";
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 "perl '$BASEEMITTER'" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g pane-border-status top || exit 1
|
||||||
|
$INNER set-option -g pane-border-format 'C=#{client_name}' || exit 1
|
||||||
|
BASE=$($INNER list-panes -t inner -F '#{pane_id}') || exit 1
|
||||||
|
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 20 -y 3 -X 5 -Y 4 \
|
||||||
|
'sleep 100') || exit 1
|
||||||
|
|
||||||
|
$OUTER1 new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER1 set-option -g status off || exit 1
|
||||||
|
$OUTER1 set-option -g window-size manual || exit 1
|
||||||
|
$OUTER1 set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER1 respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lstatuscc-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
sleep 0.5
|
||||||
|
NAME1=$($INNER list-clients -F '#{client_name}') || exit 1
|
||||||
|
|
||||||
|
$OUTER2 new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER2 set-option -g status off || exit 1
|
||||||
|
$OUTER2 set-option -g window-size manual || exit 1
|
||||||
|
$OUTER2 set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER2 respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lstatuscc-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
sleep 0.5
|
||||||
|
ALLNAMES=$($INNER list-clients -F '#{client_name}') || exit 1
|
||||||
|
NAME2=$(echo "$ALLNAMES" | grep -v "^$NAME1\$")
|
||||||
|
[ -n "$NAME2" ] || fail "sanity: could not identify the second client"
|
||||||
|
|
||||||
|
# Let any attach-driven full redraw (and its own, non-buggy, per-client
|
||||||
|
# status render) finish completely before triggering the damage-only
|
||||||
|
# palette update.
|
||||||
|
sleep 1.5
|
||||||
|
|
||||||
|
$INNER send-keys -t "$BASE" Enter || exit 1
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
LOG=$(ls tmux-server*.log 2>/dev/null | head -1)
|
||||||
|
[ -n "$LOG" ] || fail "sanity: no server -vv log was produced"
|
||||||
|
|
||||||
|
n1=$(grep -c "regenerated pane .* status for $NAME1\$" "$LOG")
|
||||||
|
n2=$(grep -c "regenerated pane .* status for $NAME2\$" "$LOG")
|
||||||
|
[ "$n1" -ge 1 ] || fail "damage pass never regenerated $NAME1's own status - it reused whatever the other client's render left behind"
|
||||||
|
[ "$n2" -ge 1 ] || fail "damage pass never regenerated $NAME2's own status - it reused whatever the other client's render left behind"
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+102
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Resizing a floating pane must refresh session status formats which depend on
|
||||||
|
# its geometry, not only the pane scene and borders.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lfloating-status-outer-$$ -f/dev/null"
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has_status()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
tail -1 "$CAPTURE" | grep -q "$marker" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client status did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 50 -y 12 'sleep 100' || exit 1
|
||||||
|
FLOAT=$($INNER new-pane -PF '#{pane_id}' -x 10 -y 5 -X 5 -Y 3 \
|
||||||
|
'sleep 100') || fail "could not create floating pane"
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
$INNER set-option -g status on || exit 1
|
||||||
|
$INNER set-option -g status-position bottom || exit 1
|
||||||
|
$INNER set-option -g status-left 'WIDTH=#{pane_width}' || exit 1
|
||||||
|
$INNER set-option -g status-right '' || exit 1
|
||||||
|
$INNER set-option -g status-interval 0 || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 50 -y 12 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
OLD_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
wait_outer_has_status "WIDTH=$OLD_WIDTH"
|
||||||
|
|
||||||
|
RIGHT=$($INNER display-message -p -t "$FLOAT" '#{pane_right}')
|
||||||
|
TOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
X=$((RIGHT + 2))
|
||||||
|
Y=$((TOP + 2))
|
||||||
|
|
||||||
|
# Grab the right frame and enlarge the floating pane.
|
||||||
|
mouse 0 "$X" "$Y" M
|
||||||
|
mouse 32 "$((X + 1))" "$Y" M
|
||||||
|
mouse 32 "$((X + 8))" "$Y" M
|
||||||
|
mouse 0 "$((X + 8))" "$Y" m
|
||||||
|
|
||||||
|
NEW_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
[ "$NEW_WIDTH" -ne "$OLD_WIDTH" ] || fail "floating pane was not resized"
|
||||||
|
wait_outer_has_status "WIDTH=$NEW_WIDTH"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Moving, resizing and reordering an over-zoom float must leave the tiled
|
||||||
|
# pane zoomed, and the changes must survive an explicit unzoom.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
trap '$TMUX kill-server 2>/dev/null' 0
|
||||||
|
trap 'exit 1' 1 2 15
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
||||||
|
{
|
||||||
|
$TMUX "$@" || fail "failed: $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
check()
|
||||||
|
{
|
||||||
|
got=$(run display-message -p -t "$1" "$2") || exit 1
|
||||||
|
[ "$got" = "$3" ] || fail "$1 $2: got '$got', expected '$3'"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_zoom()
|
||||||
|
{
|
||||||
|
check "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
|
||||||
|
check "$float" '#{pane_floating_flag}:#{pane_active}' "1:$active"
|
||||||
|
check "$base" '#{@unzoomed}' ''
|
||||||
|
}
|
||||||
|
|
||||||
|
run new-session -d -x 80 -y 24
|
||||||
|
base=$(run display-message -p '#{pane_id}') || exit 1
|
||||||
|
other=$(run split-window -dPF '#{pane_id}') || exit 1
|
||||||
|
layout=$(run display-message -p '#{window_layout}') || exit 1
|
||||||
|
run set-hook -g window-unzoomed 'set -g @unzoomed 1'
|
||||||
|
|
||||||
|
# Cover creation before and after zoom, with both the float and the tiled
|
||||||
|
# pane active. Modal panes use the same over-zoom flag.
|
||||||
|
for mode in before after detached modal; do
|
||||||
|
if [ "$mode" != before ]; then
|
||||||
|
run resize-pane -Z -t "$base"
|
||||||
|
fi
|
||||||
|
case "$mode" in
|
||||||
|
detached) flags=-Ad; active=0 ;;
|
||||||
|
modal) flags=-O; active=1 ;;
|
||||||
|
*) flags=-A; active=1 ;;
|
||||||
|
esac
|
||||||
|
float=$(run new-pane "$flags" -PF '#{pane_id}' -t "$base" \
|
||||||
|
-x 20 -y 8 -X 8 -Y 3 '') || exit 1
|
||||||
|
if [ "$mode" = before ]; then
|
||||||
|
run resize-pane -Z -t "$base"
|
||||||
|
fi
|
||||||
|
run set -gu @unzoomed
|
||||||
|
check_zoom
|
||||||
|
|
||||||
|
run move-pane -t "$float" -D
|
||||||
|
check "$float" '#{pane_top}' 5
|
||||||
|
check_zoom
|
||||||
|
run move-pane -t "$float" -U 2 -R 3 -L 1
|
||||||
|
check "$float" '#{pane_left}:#{pane_top}' '11:3'
|
||||||
|
check_zoom
|
||||||
|
run move-pane -t "$float" -X 25% -Y 25%
|
||||||
|
check "$float" '#{pane_left}:#{pane_top}' '21:7'
|
||||||
|
check_zoom
|
||||||
|
run move-pane -t "$float" -P bottom-right
|
||||||
|
check "$float" '#{pane_left}:#{pane_top}' '61:17'
|
||||||
|
check_zoom
|
||||||
|
run resize-pane -t "$float" -x 30 -y 10
|
||||||
|
check "$float" '#{pane_width}:#{pane_height}' '28:8'
|
||||||
|
check_zoom
|
||||||
|
run resize-pane -t "$float" -U 1 -L 2
|
||||||
|
check "$float" '#{pane_left}:#{pane_top}:#{pane_width}:#{pane_height}' \
|
||||||
|
'59:16:30:9'
|
||||||
|
check_zoom
|
||||||
|
run move-pane -t "$float" -P centre
|
||||||
|
check "$float" '#{pane_left}:#{pane_top}' '25:7'
|
||||||
|
check_zoom
|
||||||
|
|
||||||
|
# Failed commands must not change zoom either.
|
||||||
|
for command in 'move-pane -D invalid' 'move-pane -P invalid' \
|
||||||
|
'move-pane -z invalid' 'resize-pane -x invalid' \
|
||||||
|
'resize-pane -D invalid'; do
|
||||||
|
$TMUX $command -t "$float" 2>/dev/null &&
|
||||||
|
fail "unexpected success: $command"
|
||||||
|
check_zoom
|
||||||
|
done
|
||||||
|
|
||||||
|
run resize-pane -Z -t "$base"
|
||||||
|
check "$float" '#{pane_left}:#{pane_top}:#{pane_width}:#{pane_height}' \
|
||||||
|
'25:7:30:9'
|
||||||
|
run kill-pane -t "$float"
|
||||||
|
check "$base" '#{window_layout}' "$layout"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Put an ordinary (hidden while zoomed) float between two over-zoom floats
|
||||||
|
# in the stacking order. Reordering must skip it when counting visible
|
||||||
|
# positions, but keep all floats ahead of the tiled panes after unzoom.
|
||||||
|
back=$(run new-pane -AdPF '#{pane_id}' -t "$base" '') || exit 1
|
||||||
|
hidden=$(run new-pane -dPF '#{pane_id}' -t "$base" '') || exit 1
|
||||||
|
float=$(run new-pane -AdPF '#{pane_id}' -t "$base" '') || exit 1
|
||||||
|
active=0
|
||||||
|
run resize-pane -Z -t "$base"
|
||||||
|
run set -gu @unzoomed
|
||||||
|
|
||||||
|
for position in backward back forward-loop; do
|
||||||
|
run move-pane -t "$float" -P front
|
||||||
|
run move-pane -t "$float" -P "$position"
|
||||||
|
check "$float" '#{pane_z}' 1
|
||||||
|
check "$back" '#{pane_z}' 0
|
||||||
|
check_zoom
|
||||||
|
done
|
||||||
|
for position in forward front backward-loop; do
|
||||||
|
run move-pane -t "$float" -P back
|
||||||
|
run move-pane -t "$float" -P "$position"
|
||||||
|
check "$float" '#{pane_z}' 0
|
||||||
|
check "$back" '#{pane_z}' 1
|
||||||
|
check_zoom
|
||||||
|
done
|
||||||
|
for z in 1 0 99; do
|
||||||
|
run move-pane -t "$float" -z "$z"
|
||||||
|
want=$z
|
||||||
|
[ "$z" = 99 ] && want=1
|
||||||
|
check "$float" '#{pane_z}' "$want"
|
||||||
|
check_zoom
|
||||||
|
done
|
||||||
|
run resize-pane -Z -t "$base"
|
||||||
|
check "$float" '#{pane_z}' 2
|
||||||
|
check "$base" '#{pane_z}' 4
|
||||||
|
run kill-pane -t "$hidden"
|
||||||
|
run kill-pane -t "$back"
|
||||||
|
|
||||||
|
# Explicit zoom toggling and operations on tiled panes retain their
|
||||||
|
# existing behaviour. The over-zoom flag alone does not make a pane a float:
|
||||||
|
# zooming the float itself gives it a tiled cell until unzoom.
|
||||||
|
run resize-pane -Z -t "$float"
|
||||||
|
check "$float" '#{pane_zoomed_flag}:#{pane_floating_flag}' '1:0'
|
||||||
|
run resize-pane -t "$float" -x 25
|
||||||
|
check "$float" '#{window_zoomed_flag}:#{pane_floating_flag}:#{pane_width}' \
|
||||||
|
'0:1:23'
|
||||||
|
run resize-pane -Z -t "$base"
|
||||||
|
run resize-pane -t "$other" -D 1
|
||||||
|
check "$base" '#{window_zoomed_flag}' 0
|
||||||
|
|
||||||
|
# A hidden ordinary float must still be unzoomed before resizing it.
|
||||||
|
hidden=$(run new-pane -dPF '#{pane_id}' -t "$base" '') || exit 1
|
||||||
|
run resize-pane -Z -t "$base"
|
||||||
|
run resize-pane -t "$hidden" -x 25
|
||||||
|
check "$hidden" '#{window_zoomed_flag}:#{pane_floating_flag}:#{pane_width}' \
|
||||||
|
'0:1:23'
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -7,6 +7,8 @@ TERM=screen
|
|||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
trap '$TMUX kill-server 2>/dev/null' 0
|
||||||
|
trap 'exit 1' 1 2 15
|
||||||
|
|
||||||
# test_format $format $expected_result
|
# test_format $format $expected_result
|
||||||
test_format()
|
test_format()
|
||||||
|
|||||||
@@ -18,6 +18,24 @@ export TZ LANG LC_ALL
|
|||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
|
||||||
|
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
|
||||||
|
HOLD1=
|
||||||
|
HOLD2=
|
||||||
|
CC1=
|
||||||
|
CC2=
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
for pid in $HOLD1 $HOLD2 $CC1 $CC2; do
|
||||||
|
kill "$pid" 2>/dev/null
|
||||||
|
wait "$pid" 2>/dev/null
|
||||||
|
done
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
rm -f "$FIFO1" "$FIFO2"
|
||||||
|
}
|
||||||
|
trap cleanup 0
|
||||||
|
trap 'exit 1' 1 2 15
|
||||||
|
|
||||||
ESC=$(printf '\033')
|
ESC=$(printf '\033')
|
||||||
|
|
||||||
@@ -579,8 +597,6 @@ assert_alive "verbose loop expansion"
|
|||||||
|
|
||||||
# L loops over attached clients. Attach two control-mode clients, each held
|
# L loops over attached clients. Attach two control-mode clients, each held
|
||||||
# open by a background process keeping a FIFO's write end open.
|
# open by a background process keeping a FIFO's write end open.
|
||||||
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
|
|
||||||
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
|
|
||||||
rm -f "$FIFO1" "$FIFO2"
|
rm -f "$FIFO1" "$FIFO2"
|
||||||
mkfifo "$FIFO1" "$FIFO2" || exit 1
|
mkfifo "$FIFO1" "$FIFO2" || exit 1
|
||||||
# Hold the write ends open so the control clients stay attached.
|
# Hold the write ends open so the control clients stay attached.
|
||||||
@@ -605,9 +621,9 @@ test_format "#{L/nr:x}" "xx"
|
|||||||
test_format "#{L/r:x}" "xx"
|
test_format "#{L/r:x}" "xx"
|
||||||
# Now detach one and confirm the count drops to one.
|
# Now detach one and confirm the count drops to one.
|
||||||
kill $HOLD2 2>/dev/null
|
kill $HOLD2 2>/dev/null
|
||||||
|
wait $HOLD2 2>/dev/null
|
||||||
|
HOLD2=
|
||||||
sleep 1
|
sleep 1
|
||||||
test_format "#{L:x}" "x"
|
test_format "#{L:x}" "x"
|
||||||
kill $HOLD1 $CC1 $CC2 2>/dev/null
|
|
||||||
rm -f "$FIFO1" "$FIFO2"
|
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ TERM=screen
|
|||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
trap '$TMUX kill-server 2>/dev/null' 0
|
||||||
|
trap 'exit 1' 1 2 15
|
||||||
|
|
||||||
# test_format $format $expected_result
|
# test_format $format $expected_result
|
||||||
test_format()
|
test_format()
|
||||||
|
|||||||
@@ -76,14 +76,14 @@ $TMUX set-hook -g session-closed \
|
|||||||
fail "set-hook session-closed failed"
|
fail "set-hook session-closed failed"
|
||||||
|
|
||||||
# The only pane of the only window of a session exits: pane-exited, then
|
# The only pane of the only window of a session exits: pane-exited, then
|
||||||
# window-unlinked, then session-closed, each seeing the dead object in the
|
# session-closed, then window-unlinked, each seeing the dead object in the
|
||||||
# hook formats.
|
# hook formats.
|
||||||
pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') ||
|
pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') ||
|
||||||
fail "new-session doomed failed"
|
fail "new-session doomed failed"
|
||||||
wait_for @log \
|
wait_for @log \
|
||||||
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed"
|
"|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
|
||||||
assert_unchanged @log \
|
assert_unchanged @log \
|
||||||
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed"
|
"|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
|
||||||
|
|
||||||
# The dead pane, window and session cannot be used as targets but the
|
# The dead pane, window and session cannot be used as targets but the
|
||||||
# server survives.
|
# server survives.
|
||||||
@@ -110,14 +110,14 @@ $TMUX set-hook -g pane-exited \
|
|||||||
'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' ||
|
'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' ||
|
||||||
fail "restore pane-exited hook failed"
|
fail "restore pane-exited hook failed"
|
||||||
|
|
||||||
# kill-window on the last window: window-unlinked then session-closed but
|
# kill-window on the last window: session-closed then window-unlinked but
|
||||||
# no pane-exited for the panes in the killed window.
|
# no pane-exited for the panes in the killed window.
|
||||||
$TMUX set -g @log '' || fail "reset @log failed"
|
$TMUX set -g @log '' || fail "reset @log failed"
|
||||||
$TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed"
|
$TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed"
|
||||||
$TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed"
|
$TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed"
|
||||||
$TMUX kill-window -t doomed2:0 || fail "kill-window failed"
|
$TMUX kill-window -t doomed2:0 || fail "kill-window failed"
|
||||||
wait_for @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2'
|
wait_for @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
|
||||||
assert_unchanged @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2'
|
assert_unchanged @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
|
||||||
$TMUX has -t main || fail "server died after kill-window chain"
|
$TMUX has -t main || fail "server died after kill-window chain"
|
||||||
|
|
||||||
# kill-session: session-closed fires first, then window-unlinked for its
|
# kill-session: session-closed fires first, then window-unlinked for its
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ $TMUX kill-server 2>/dev/null
|
|||||||
|
|
||||||
TMP=$(mktemp)
|
TMP=$(mktemp)
|
||||||
OUT=$(mktemp)
|
OUT=$(mktemp)
|
||||||
trap "rm -f $TMP $OUT" 0 1 15
|
trap 'rm -f "$TMP" "$OUT"; $TMUX kill-server 2>/dev/null' 0
|
||||||
|
trap 'exit 1' 1 2 15
|
||||||
|
|
||||||
cat <<EOF >$TMP
|
cat <<EOF >$TMP
|
||||||
if 'true' 'wibble wobble'
|
if 'true' 'wibble wobble'
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ TERM=screen
|
|||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
|
||||||
|
# Keep panes alive until the next case or cleanup, without reading replies.
|
||||||
|
INPUT_HOLD='while :; do sleep 3600; done'
|
||||||
|
|
||||||
TMP=$(mktemp)
|
TMP=$(mktemp)
|
||||||
EXP=$(mktemp)
|
EXP=$(mktemp)
|
||||||
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
|
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||||
@@ -37,10 +40,10 @@ start_pane_hlimit()
|
|||||||
|
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
$TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1
|
$TMUX new-session -d -x 1 -y 1 -s test-setup "$INPUT_HOLD" || exit 1
|
||||||
$TMUX set-option -g history-limit "$hlimit" || exit 1
|
$TMUX set-option -g history-limit "$hlimit" || exit 1
|
||||||
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \
|
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \
|
||||||
"printf '$seq'; sleep 2" || exit 1
|
"printf '$seq'; $INPUT_HOLD" || exit 1
|
||||||
$TMUX kill-session -t test-setup
|
$TMUX kill-session -t test-setup
|
||||||
sleep 0.3
|
sleep 0.3
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -22,7 +22,19 @@ assert_key () {
|
|||||||
clear-history -t$W \; \
|
clear-history -t$W \; \
|
||||||
send-keys -t$W "$key" 'EOL' || exit 1
|
send-keys -t$W "$key" 'EOL' || exit 1
|
||||||
|
|
||||||
actual_code=$($TMUX capturep -pt$W | \
|
# cat echoes the keys back asynchronously, so wait for the EOL marker
|
||||||
|
# to reach the pane instead of capturing straight away.
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 50 ]; do
|
||||||
|
screen=$($TMUX capturep -pt$W)
|
||||||
|
case "$screen" in
|
||||||
|
*EOL*) break ;;
|
||||||
|
esac
|
||||||
|
i=$((i + 1))
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
actual_code=$(printf '%s\n' "$screen" | \
|
||||||
head -1 | \
|
head -1 | \
|
||||||
sed -e 's/EOL.*$//')
|
sed -e 's/EOL.*$//')
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,38 @@
|
|||||||
|
|
||||||
. ./input-common.inc
|
. ./input-common.inc
|
||||||
|
|
||||||
|
# Wait for the terminator and following text to reach the parser.
|
||||||
|
check_discard()
|
||||||
|
{
|
||||||
|
name=$1
|
||||||
|
printf 'OK\n' >"$EXP"
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 300 ]; do
|
||||||
|
capture_grid "$name" >"$TMP"
|
||||||
|
cmp -s "$TMP" "$EXP" && return 0
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "$name (timed out waiting for discard)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The missing-terminator timer may expire before the string limit is reached
|
||||||
|
# on a slow build. Clear any payload printed after the timeout, then check
|
||||||
|
# that the string did not change the title and normal output has resumed.
|
||||||
|
test_discard()
|
||||||
|
{
|
||||||
|
name=$1
|
||||||
|
prefix=$2
|
||||||
|
start_cmd "$name" 8 3 "$INPUT_HOLD"
|
||||||
|
$TMUX select-pane -T discard-test || exit 1
|
||||||
|
$TMUX respawn-pane -k \
|
||||||
|
"perl -e 'print qq{$prefix}, q{x} x 1100000, qq{\e\\\\\e[H\e[2JOK}'; $INPUT_HOLD" || exit 1
|
||||||
|
check_discard "$name"
|
||||||
|
$TMUX display-message -p -t "$name:" '#{pane_title}' >"$TMP" || exit 1
|
||||||
|
printf 'discard-test\n' >"$EXP"
|
||||||
|
cmp "$TMP" "$EXP" || fail "$name title"
|
||||||
|
}
|
||||||
|
|
||||||
start_cmd csi-param-discard 8 3 \
|
start_cmd csi-param-discard 8 3 \
|
||||||
"perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2"
|
"perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2"
|
||||||
check_capture csi-param-discard 'OK'
|
check_capture csi-param-discard 'OK'
|
||||||
@@ -10,13 +42,8 @@ start_cmd csi-interm-discard 8 3 \
|
|||||||
"perl -e 'print qq{\e[ \030OK}'; sleep 2"
|
"perl -e 'print qq{\e[ \030OK}'; sleep 2"
|
||||||
check_capture csi-interm-discard 'OK'
|
check_capture csi-interm-discard 'OK'
|
||||||
|
|
||||||
start_cmd osc-discard 8 3 \
|
test_discard osc-discard '\e]2;'
|
||||||
"perl -e 'print qq{\e]2;}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
|
test_discard apc-discard '\e_'
|
||||||
check_capture osc-discard 'OK'
|
|
||||||
|
|
||||||
start_cmd apc-discard 8 3 \
|
|
||||||
"perl -e 'print qq{\e_}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
|
|
||||||
check_capture apc-discard 'OK'
|
|
||||||
|
|
||||||
start_pane unknown-csi 8 3 '\033[?9999zOK'
|
start_pane unknown-csi 8 3 '\033[?9999zOK'
|
||||||
check_capture unknown-csi 'OK'
|
check_capture unknown-csi 'OK'
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ $TMUX kill-server 2>/dev/null
|
|||||||
sleep 0.1
|
sleep 0.1
|
||||||
$TMUX new-session -d -x 5 -y 3 -s history \; \
|
$TMUX new-session -d -x 5 -y 3 -s history \; \
|
||||||
set-option -g history-limit 3 \; \
|
set-option -g history-limit 3 \; \
|
||||||
respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; sleep 2" || exit 1
|
respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; $INPUT_HOLD" || exit 1
|
||||||
sleep 0.3
|
sleep 0.3
|
||||||
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
|
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
|
||||||
printf "%s\n" '01
|
printf "%s\n" '01
|
||||||
|
|||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Killing a session whose window is zoomed must not crash the server:
|
||||||
|
# window_destroy used to unzoom the window, which resized the panes and
|
||||||
|
# fired pane-resized with the window in the payload, and releasing that
|
||||||
|
# reference destroyed the window a second time.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
|
$TMUX -f/dev/null new -d -sfoo || exit 1
|
||||||
|
$TMUX split-window -d -h -tfoo:0 || exit 1
|
||||||
|
$TMUX resize-pane -Z -tfoo:0 || exit 1
|
||||||
|
$TMUX new -d -sbar || exit 1
|
||||||
|
$TMUX kill-session -tfoo || exit 1
|
||||||
|
$TMUX has-session -tbar || exit 1
|
||||||
|
|
||||||
|
# The same with the zoomed window in a session killed by kill-server.
|
||||||
|
$TMUX new -d -sbaz || exit 1
|
||||||
|
$TMUX split-window -d -v -tbaz:0 || exit 1
|
||||||
|
$TMUX resize-pane -Z -tbaz:0 || exit 1
|
||||||
|
$TMUX kill-server || exit 1
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -50,7 +50,8 @@
|
|||||||
# layout it names is a single cell or a split;
|
# layout it names is a single cell or a split;
|
||||||
# - a window whose only tiled pane has been killed, which leaves it with a
|
# - a window whose only tiled pane has been killed, which leaves it with a
|
||||||
# floating cell as its layout root or with a root node holding nothing but
|
# floating cell as its layout root or with a root node holding nothing but
|
||||||
# floating cells, producing no v1 dump at all, and being parsed as v1;
|
# floating cells, producing an empty v1 body with a checksum, and being
|
||||||
|
# parsed as v1;
|
||||||
# - the %layout-change notification, in both formats at once: two control
|
# - the %layout-change notification, in both formats at once: two control
|
||||||
# clients watching one layout change, only one of which has asked for new
|
# clients watching one layout change, only one of which has asked for new
|
||||||
# layouts, and the number of notifications a change produces in each format;
|
# layouts, and the number of notifications a change produces in each format;
|
||||||
@@ -376,10 +377,7 @@ must_equal 'Layout after select-pane back' "$(layout L:two)" "$SPLIT"
|
|||||||
|
|
||||||
# With nothing zoomed the two layout formats agree.
|
# With nothing zoomed the two layout formats agree.
|
||||||
#
|
#
|
||||||
# The zoomed case is deliberately not covered here. While a pane is zoomed
|
# Zoomed layouts are checked below with floating panes.
|
||||||
# #{window_layout} dumps the saved (unzoomed) layout and
|
|
||||||
# #{window_visible_layout} the zoomed one, but that depends on how zooming
|
|
||||||
# stashes the layout root rather than on anything in layout-custom.c.
|
|
||||||
must_equal 'Visible layout' "$(visible_layout L:two)" "$SPLIT"
|
must_equal 'Visible layout' "$(visible_layout L:two)" "$SPLIT"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -703,6 +701,42 @@ must_contain 'Floating layout back z-index' "$floating" '"z":1'
|
|||||||
check_ok select-layout -t L:float "$floating"
|
check_ok select-layout -t L:float "$floating"
|
||||||
must_equal 'Floating layout after round trip' "$(raw_layout L:float)" "$floating"
|
must_equal 'Floating layout after round trip' "$(raw_layout L:float)" "$floating"
|
||||||
|
|
||||||
|
# A dump taken while zoomed must retain the unzoomed floating z-indexes.
|
||||||
|
# Check hidden floats, floats above zoom, and a mixture of the two.
|
||||||
|
for flags in '' A mixed; do
|
||||||
|
check_ok new-window -d -t L: -n zoom
|
||||||
|
zt=$($TMUX display-message -p -t L:zoom '#{pane_id}')
|
||||||
|
check_ok split-window -d -h -t "$zt"
|
||||||
|
case "$flags" in
|
||||||
|
A|mixed) first=-Ad ;;
|
||||||
|
*) first=-d ;;
|
||||||
|
esac
|
||||||
|
case "$flags" in
|
||||||
|
A) second=-Ad ;;
|
||||||
|
*) second=-d ;;
|
||||||
|
esac
|
||||||
|
check_ok new-pane "$first" -t "$zt" -x 20 -y 6 ''
|
||||||
|
check_ok new-pane "$second" -t "$zt" -x 30 -y 8 ''
|
||||||
|
for pane in 0 2 3; do
|
||||||
|
check_ok select-pane -t "L:zoom.$pane"
|
||||||
|
before=$(raw_layout L:zoom)
|
||||||
|
legacy=$(v1_layout L:zoom)
|
||||||
|
check_ok resize-pane -Z -t "L:zoom.$pane"
|
||||||
|
during=$(raw_layout L:zoom)
|
||||||
|
must_equal 'Layout while zoomed' "$during" "$before"
|
||||||
|
must_equal 'Legacy layout while zoomed' \
|
||||||
|
"$(v1_layout L:zoom)" "$legacy"
|
||||||
|
must_equal 'Zoom after dumping layout' \
|
||||||
|
"$($TMUX display-message -p -t L:zoom '#{window_zoomed_flag}')" 1
|
||||||
|
must_differ 'Visible layout while zoomed' \
|
||||||
|
"$(visible_layout L:zoom)" "$(layout L:zoom)"
|
||||||
|
check_ok select-layout -t L:zoom "$during"
|
||||||
|
must_equal 'Round trip from zoomed layout' \
|
||||||
|
"$(raw_layout L:zoom)" "$before"
|
||||||
|
done
|
||||||
|
check_ok kill-window -t L:zoom
|
||||||
|
done
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Floating panes and the legacy (v1) format.
|
# Floating panes and the legacy (v1) format.
|
||||||
#
|
#
|
||||||
@@ -838,12 +872,12 @@ must_equal 'Panes left with one floating pane' \
|
|||||||
"$($TMUX display-message -p -t L:gone1 '#{window_panes}')" '1'
|
"$($TMUX display-message -p -t L:gone1 '#{window_panes}')" '1'
|
||||||
|
|
||||||
# The floating cell is the root and there is nothing tiled under it, so there is
|
# The floating cell is the root and there is nothing tiled under it, so there is
|
||||||
# no v1 dump to make. In particular the floating cell must not be written out on
|
# only an empty v1 body to dump. The floating cell must not be written out on
|
||||||
# its own, which would be a layout claiming the window is the size and position
|
# its own, which would be a layout claiming the window is the size and position
|
||||||
# of the floating pane with no pane in it at all.
|
# of the floating pane with no pane in it at all.
|
||||||
got=$(v1_layout L:gone1)
|
got=$(v1_layout L:gone1)
|
||||||
check_ok display-message -p alive
|
check_ok display-message -p alive
|
||||||
must_equal 'v1 dump with one floating pane and no tiled panes' "$got" ''
|
must_equal 'v1 dump with one floating pane and no tiled panes' "$got" '0000,'
|
||||||
|
|
||||||
# Two floating panes left, so the node keeps two children, does not collapse,
|
# Two floating panes left, so the node keeps two children, does not collapse,
|
||||||
# and stays the root with nothing but floating cells in it.
|
# and stays the root with nothing but floating cells in it.
|
||||||
@@ -856,11 +890,11 @@ must_equal 'Panes left with two floating panes' \
|
|||||||
"$($TMUX display-message -p -t L:gone2 '#{window_panes}')" '2'
|
"$($TMUX display-message -p -t L:gone2 '#{window_panes}')" '2'
|
||||||
|
|
||||||
# The node is the root this time rather than the floating cell, but it has no
|
# The node is the root this time rather than the floating cell, but it has no
|
||||||
# tiled cell anywhere under it either, so there is still no v1 dump to make -
|
# tiled cell anywhere under it either, so the v1 body is still empty -
|
||||||
# and making one must not take the server with it.
|
# and making one must not take the server with it.
|
||||||
got=$(v1_layout L:gone2)
|
got=$(v1_layout L:gone2)
|
||||||
check_ok display-message -p alive
|
check_ok display-message -p alive
|
||||||
must_equal 'v1 dump with two floating panes and no tiled panes' "$got" ''
|
must_equal 'v1 dump with two floating panes and no tiled panes' "$got" '0000,'
|
||||||
|
|
||||||
# Nor must parsing a v1 layout against it. There is no tiled pane for the
|
# Nor must parsing a v1 layout against it. There is no tiled pane for the
|
||||||
# layout to name, so whether it is applied or rejected is the format's business;
|
# layout to name, so whether it is applied or rejected is the format's business;
|
||||||
|
|||||||
+144
-2
@@ -337,9 +337,9 @@ $TMUX set -g @modal-prefix no
|
|||||||
$TMUX set -g @modal-root no
|
$TMUX set -g @modal-root no
|
||||||
$TMUX bind -n z set -g @modal-root yes
|
$TMUX bind -n z set -g @modal-root yes
|
||||||
|
|
||||||
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
|
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
|
||||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||||
fail "new-pane -OK failed"
|
fail "new-pane -ODK failed"
|
||||||
sleep 1
|
sleep 1
|
||||||
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||||
sleep 1
|
sleep 1
|
||||||
@@ -356,9 +356,151 @@ new_left=$(fmt "$modal" '#{pane_left}')
|
|||||||
new_top=$(fmt "$modal" '#{pane_top}')
|
new_top=$(fmt "$modal" '#{pane_top}')
|
||||||
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||||
fail "key-capturing modal pane did not move"
|
fail "key-capturing modal pane did not move"
|
||||||
|
$TMUX2 send-keys -t "$OUTER" Escape
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
|
||||||
|
-x 20 -y 5 -X 20 -Y 10 'trap "" INT; exec cat') ||
|
||||||
|
fail "new-pane -ODK failed"
|
||||||
|
sleep 1
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-c
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
# A dead modal does not close on Escape or C-c without -D.
|
||||||
|
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
||||||
|
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
|
||||||
|
fail "new-pane -O failed"
|
||||||
|
check_ok set-option -p -t "$modal" remain-on-exit on
|
||||||
|
sleep 2
|
||||||
|
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
|
||||||
|
$TMUX2 send-keys -t "$OUTER" Escape
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||||
check_ok kill-pane -t "$modal"
|
check_ok kill-pane -t "$modal"
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
|
# failed-key closes successful panes and retains failed panes until a key.
|
||||||
|
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
||||||
|
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
|
||||||
|
fail "new-pane -O failed"
|
||||||
|
check_ok set-option -p -t "$modal" remain-on-exit failed-key
|
||||||
|
sleep 2
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
||||||
|
-x 20 -y 5 -X 20 -Y 10 'sleep 1; exit 1') ||
|
||||||
|
fail "new-pane -O failed"
|
||||||
|
check_ok set-option -p -t "$modal" remain-on-exit failed-key
|
||||||
|
sleep 2
|
||||||
|
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
|
||||||
|
must_equal "$($TMUX show-options -pv -t "$modal" remain-on-exit)" failed-key
|
||||||
|
$TMUX2 send-keys -t "$OUTER" a
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
$TMUX bind P display-popup -E -t "$p0" -w 20 -h 5 -T popup-title 'cat'
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-b P
|
||||||
|
sleep 1
|
||||||
|
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||||
|
[ -n "$modal" ] || fail "display-popup did not create a modal pane"
|
||||||
|
must_equal "$(fmt "$modal" '#{pane_title}')" popup-title
|
||||||
|
must_equal "$($TMUX show-options -pv -t "$modal" pane-border-status)" top
|
||||||
|
must_equal "$($TMUX show-options -pv -t "$modal" pane-border-format)" \
|
||||||
|
'#{pane_title}'
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||||
|
sleep 1
|
||||||
|
must_equal "$($TMUX show -gv @modal-prefix)" no
|
||||||
|
must_equal "$($TMUX show -gv @modal-root)" no
|
||||||
|
case "$($TMUX capture-pane -pt "$modal")" in
|
||||||
|
*xz*) ;;
|
||||||
|
*) fail "keys did not reach display-popup pane" ;;
|
||||||
|
esac
|
||||||
|
$TMUX2 send-keys -t "$OUTER" Escape
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-c
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
# Creating a popup pane must not fire the split-window hook.
|
||||||
|
check_ok set-hook -t modal after-split-window \
|
||||||
|
"set-option -g @popup-after-split yes"
|
||||||
|
check_ok set-option -g @popup-after-split no
|
||||||
|
check_ok display-popup -E -t "$p0" true
|
||||||
|
must_equal "$($TMUX show-option -gv @popup-after-split)" no
|
||||||
|
check_ok set-hook -u -t modal after-split-window
|
||||||
|
|
||||||
|
# A borderless popup must not create a zero-sized pane in a tiny window.
|
||||||
|
check_ok new-window -d -t modal: -n popup-small 'cat'
|
||||||
|
check_ok set-option -w -t modal:popup-small window-size manual
|
||||||
|
check_ok resize-window -t modal:popup-small -x 1 -y 1
|
||||||
|
small=$(fmt modal:popup-small '#{pane_id}')
|
||||||
|
check_ok bind Z display-popup -B -t "$small" 'cat'
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-b Z
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:popup-small '#{window_panes}')" 1
|
||||||
|
must_equal "$(fmt modal:popup-small '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
$TMUX bind D display-popup -t "$p0" -w 20 -h 5 'printf done'
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-b D
|
||||||
|
sleep 2
|
||||||
|
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||||
|
[ -n "$modal" ] || fail "retained display-popup was not created"
|
||||||
|
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
|
||||||
|
case "$($TMUX capture-pane -pt "$modal")" in
|
||||||
|
*'Pane is dead'*) fail "display-popup showed remain-on-exit message" ;;
|
||||||
|
esac
|
||||||
|
$TMUX2 send-keys -t "$OUTER" a
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||||
|
$TMUX2 send-keys -t "$OUTER" Escape
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
$TMUX bind K display-popup -k -t "$p0" -w 20 -h 5 'printf done'
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-b K
|
||||||
|
sleep 2
|
||||||
|
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||||
|
[ -n "$modal" ] || fail "display-popup -k was not created"
|
||||||
|
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
|
||||||
|
$TMUX2 send-keys -t "$OUTER" a
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
$TMUX bind F display-popup -EE -t "$p0" -w 20 -h 5 'exit 1'
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-b F
|
||||||
|
sleep 2
|
||||||
|
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||||
|
[ -n "$modal" ] || fail "failed display-popup -EE did not remain"
|
||||||
|
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
|
||||||
|
$TMUX2 send-keys -t "$OUTER" a
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||||
|
$TMUX2 send-keys -t "$OUTER" Escape
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||||
|
check_ok kill-pane -t "$modal"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
check_ok display-popup -EE -t "$p0" true
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
$TMUX bind G display-popup -EE -k -t "$p0" -w 20 -h 5 'exit 1'
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-b G
|
||||||
|
sleep 2
|
||||||
|
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||||
|
[ -n "$modal" ] || fail "failed display-popup -EE -k did not remain"
|
||||||
|
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
|
||||||
|
must_equal "$($TMUX show-options -pv -t "$modal" remain-on-exit)" failed-key
|
||||||
|
$TMUX2 send-keys -t "$OUTER" a
|
||||||
|
sleep 1
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
|
check_ok display-popup -EE -k -t "$p0" true
|
||||||
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
|
|
||||||
# A nonmodal floating pane may remain above zoom, and switching between it and
|
# A nonmodal floating pane may remain above zoom, and switching between it and
|
||||||
# the zoomed tiled pane must not unzoom the window.
|
# the zoomed tiled pane must not unzoom the window.
|
||||||
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
|
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Chooser menus are centred on the mouse using their displayed width. A
|
||||||
|
# hidden title must not move a borderless menu away from the mouse.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$TMUX kill-server >/dev/null 2>&1
|
||||||
|
$TMUX2 kill-server >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$TMUX new-session -d -s short -x 100 -y 30 'sleep 100' || exit 1
|
||||||
|
$TMUX set -g mouse on || exit 1
|
||||||
|
$TMUX set -g status off || exit 1
|
||||||
|
$TMUX2 new-session -d -x 100 -y 30 "$TMUX attach" || exit 1
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
for name in short aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; do
|
||||||
|
$TMUX rename-session "$name" || exit 1
|
||||||
|
for lines in none single padded; do
|
||||||
|
$TMUX set -g menu-border-lines "$lines" || exit 1
|
||||||
|
$TMUX choose-tree -s || exit 1
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Right-button press at window (60, 0), away from either edge.
|
||||||
|
seq=$(printf '\033[<2;61;1M')
|
||||||
|
$TMUX2 send-keys -l "$seq" || exit 1
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
row=2
|
||||||
|
column=54
|
||||||
|
if [ "$lines" = none ]; then
|
||||||
|
row=1
|
||||||
|
elif [ "$name" != short ]; then
|
||||||
|
column=31
|
||||||
|
fi
|
||||||
|
text=$($TMUX2 capture-pane -p | awk -v row="$row" \
|
||||||
|
-v column="$column" 'NR == row {
|
||||||
|
# Keep byte offsets equal to columns with non-Unicode awk.
|
||||||
|
gsub(/│/, "|")
|
||||||
|
print substr($0, column, 7)
|
||||||
|
}')
|
||||||
|
[ "$text" = 'Select ' ] || \
|
||||||
|
fail "$lines menu for $name: expected Select at ($column, $row), got '$text'"
|
||||||
|
|
||||||
|
# Close the menu, release the button, then leave the chooser.
|
||||||
|
$TMUX2 send-keys Escape || exit 1
|
||||||
|
seq=$(printf '\033[<2;61;1m')
|
||||||
|
$TMUX2 send-keys -l "$seq" || exit 1
|
||||||
|
$TMUX2 send-keys q || exit 1
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -86,6 +86,11 @@ check_value "-gv status-keys" "vi"
|
|||||||
check_fail "unknown value: bogus" set -g status-keys bogus
|
check_fail "unknown value: bogus" set -g status-keys bogus
|
||||||
check_value "-gv status-keys" "vi"
|
check_value "-gv status-keys" "vi"
|
||||||
|
|
||||||
|
# pane-border-lines accepts rounded as a pane border style.
|
||||||
|
check_ok set -gw pane-border-lines rounded
|
||||||
|
check_value "-gwv pane-border-lines" "rounded"
|
||||||
|
check_ok set -gw pane-border-lines single
|
||||||
|
|
||||||
# --- flag options ---------------------------------------------------------
|
# --- flag options ---------------------------------------------------------
|
||||||
#
|
#
|
||||||
# focus-events is an on/off flag. Setting with no value toggles it; explicit
|
# focus-events is an on/off flag. Setting with no value toggles it; explicit
|
||||||
|
|||||||
+24
-5
@@ -104,6 +104,25 @@ assert_alive()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Compare layout geometry and pane ordering, ignoring the active pane and
|
||||||
|
# last-pane history in the JSON layout. Selection is checked separately.
|
||||||
|
pane_layout()
|
||||||
|
{
|
||||||
|
$TMUX display-message -p -t "$1" '#{window_layout}' |
|
||||||
|
sed 's/,"a":true//g; s/,"l":[0-9][0-9]*//g'
|
||||||
|
}
|
||||||
|
|
||||||
|
check_layout()
|
||||||
|
{
|
||||||
|
out=$(pane_layout "$1")
|
||||||
|
if [ "$out" != "$2" ]; then
|
||||||
|
echo "Layout for '$1' wrong."
|
||||||
|
echo "Expected: '$2'"
|
||||||
|
echo "But got: '$out'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# split-window geometry.
|
# split-window geometry.
|
||||||
|
|
||||||
@@ -309,12 +328,12 @@ check_ok kill-pane -t "$p6"
|
|||||||
|
|
||||||
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
|
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
|
||||||
# without -Z unzooms, while -Z transfers zoom to the selected pane.
|
# without -Z unzooms, while -Z transfers zoom to the selected pane.
|
||||||
layout=$($TMUX display-message -p -t P:0 '#{window_layout}')
|
layout=$(pane_layout P:0)
|
||||||
check_ok select-pane -t "$p0"
|
check_ok select-pane -t "$p0"
|
||||||
check_ok resize-pane -Z -t "$p0"
|
check_ok resize-pane -Z -t "$p0"
|
||||||
check_ok select-pane -t "$p2"
|
check_ok select-pane -t "$p2"
|
||||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
||||||
check_fmt P:0 '#{window_layout}' "$layout"
|
check_layout P:0 "$layout"
|
||||||
|
|
||||||
check_ok select-pane -t "$p0"
|
check_ok select-pane -t "$p0"
|
||||||
check_ok resize-pane -Z -t "$p0"
|
check_ok resize-pane -Z -t "$p0"
|
||||||
@@ -322,7 +341,7 @@ check_ok select-pane -Z -t "$p2"
|
|||||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||||
'1:1:1'
|
'1:1:1'
|
||||||
check_ok resize-pane -Z -t "$p2"
|
check_ok resize-pane -Z -t "$p2"
|
||||||
check_fmt P:0 '#{window_layout}' "$layout"
|
check_layout P:0 "$layout"
|
||||||
|
|
||||||
# Directional selection temporarily restores the full layout to find its
|
# Directional selection temporarily restores the full layout to find its
|
||||||
# neighbour, then follows the same unzoom or -Z transfer rules.
|
# neighbour, then follows the same unzoom or -Z transfer rules.
|
||||||
@@ -340,7 +359,7 @@ check_ok select-pane -D -Z -t "$p0"
|
|||||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||||
'1:1:1'
|
'1:1:1'
|
||||||
check_ok resize-pane -Z -t "$p2"
|
check_ok resize-pane -Z -t "$p2"
|
||||||
check_fmt P:0 '#{window_layout}' "$layout"
|
check_layout P:0 "$layout"
|
||||||
|
|
||||||
# The last-pane path has separate zoom handling, both with and without -Z.
|
# The last-pane path has separate zoom handling, both with and without -Z.
|
||||||
check_ok select-pane -t "$p0"
|
check_ok select-pane -t "$p0"
|
||||||
@@ -357,7 +376,7 @@ check_ok select-pane -l -Z -t P:0
|
|||||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||||
'1:1:1'
|
'1:1:1'
|
||||||
check_ok resize-pane -Z -t "$p0"
|
check_ok resize-pane -Z -t "$p0"
|
||||||
check_fmt P:0 '#{window_layout}' "$layout"
|
check_layout P:0 "$layout"
|
||||||
|
|
||||||
# Killing either a hidden ordinary pane or the zoom target unzooms.
|
# Killing either a hidden ordinary pane or the zoom target unzooms.
|
||||||
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
|
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
|
||||||
|
|||||||
Executable
+126
@@ -0,0 +1,126 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Damage at a popup edge must redraw complete grid characters. Drawing only a
|
||||||
|
# wide character's base or padding cell leaves a two-cell hole behind.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lpopup-wide-outer-$$ -f/dev/null"
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
BASE=$DIR/base
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
POPUP_PID=
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_old_rows_restored()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
sed -n '3,5p' "$BASE" >"$DIR/want"
|
||||||
|
sed -n '3,5p' "$CAPTURE" >"$DIR/got"
|
||||||
|
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "wide characters under the old popup edge were not restored"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
binmode STDOUT, ':encoding(UTF-8)';
|
||||||
|
$| = 1;
|
||||||
|
for my $row (1 .. 10) {
|
||||||
|
print "\e[$row;1H", chr(0x754c) x 20;
|
||||||
|
}
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
wait_outer_has '界界界'
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||||
|
|
||||||
|
$INNER display-popup -t "$CLIENT" -x 5 -y 5 -w 10 -h 3 -E \
|
||||||
|
"sh -c 'printf POPUP; exec sleep 100'" &
|
||||||
|
POPUP_PID=$!
|
||||||
|
wait_outer_has POPUP
|
||||||
|
|
||||||
|
# The first motion starts the drag; the second moves the popup away from its
|
||||||
|
# old rectangle. Its odd x coordinate bisects the underlying double-width
|
||||||
|
# cells at both edges.
|
||||||
|
mouse 0 10 3 M
|
||||||
|
mouse 32 11 3 M
|
||||||
|
mouse 32 28 7 M
|
||||||
|
mouse 0 28 7 m
|
||||||
|
wait_old_rows_restored
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+112
@@ -0,0 +1,112 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Check a redraw callback which has no accompanying client redraw flags. A
|
||||||
|
# wrapped row crossing a panned viewport cannot use the direct tty path.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Ldamage-only-outer-$$ -f/dev/null"
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
TRIGGER=$DIR/trigger
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_inner_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||||
|
grep -q "$marker" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner pane did not contain $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
$| = 1;
|
||||||
|
for my $row (1 .. 12) {
|
||||||
|
print "\e[$row;1H", 'o' x 79;
|
||||||
|
}
|
||||||
|
print "\e[1;1H";
|
||||||
|
while (!-e $ENV{TRIGGER}) {
|
||||||
|
select undef, undef, undef, 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $second = ('B' x 24) . 'DAMAGE-ONLY' . ('B' x 45);
|
||||||
|
print "\e[5;1H", ('A' x 80), $second;
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 80 -y 12 \
|
||||||
|
"TRIGGER='$TRIGGER' perl '$EMITTER'" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
$INNER refresh-client -t "$CLIENT" -R 20 || exit 1
|
||||||
|
wait_outer_has oooooooooo
|
||||||
|
|
||||||
|
: >"$TRIGGER"
|
||||||
|
wait_inner_has DAMAGE-ONLY
|
||||||
|
wait_outer_has DAMAGE-ONLY
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+186
@@ -0,0 +1,186 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Redraw a moved floating pane on both attached clients viewing the same
|
||||||
|
# window. Window redraw work must not be consumed by only one client.
|
||||||
|
#
|
||||||
|
# Uses ASCII pane borders (rather than the default UTF-8 box-drawing) because
|
||||||
|
# this test nests a real tmux client inside another tmux's pane to get a
|
||||||
|
# genuine terminal to capture from; that nested-tmux relay has been observed
|
||||||
|
# to mis-render a cell that previously held a multi-byte UTF-8 border
|
||||||
|
# character being overwritten later by plain content, on the outer instance's
|
||||||
|
# own interpretation, independent of anything the inner tmux sends. That is a
|
||||||
|
# nested-test-harness artifact, not a real tmux bug - confirmed by replaying
|
||||||
|
# the exact same drag sequence against a real terminal (xterm), where it
|
||||||
|
# never reproduces. ASCII borders avoid the artifact entirely.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lredraw-multi-outer-$$ -f/dev/null"
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_clients()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
count=$($INNER list-clients 2>/dev/null | wc -l)
|
||||||
|
[ "$count" -eq 2 ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "two inner clients did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
target=$1
|
||||||
|
marker=$2
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t "$target" >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer pane $target did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_float_left()
|
||||||
|
{
|
||||||
|
comparison=$1
|
||||||
|
limit=$2
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
left=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
if [ "$comparison" = gt ] && [ "$left" -gt "$limit" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "$comparison" = lt ] && [ "$left" -lt "$limit" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "floating pane did not move"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$2" "$3" "$4" "$5")
|
||||||
|
$OUTER send-keys -t "$1" -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
drag_float()
|
||||||
|
{
|
||||||
|
target=$1
|
||||||
|
startcol=$2
|
||||||
|
startrow=$3
|
||||||
|
endcol=$4
|
||||||
|
mouse "$target" 0 "$startcol" "$startrow" M
|
||||||
|
mouse "$target" 32 "$endcol" "$startrow" M
|
||||||
|
mouse "$target" 0 "$endcol" "$startrow" m
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_scene()
|
||||||
|
{
|
||||||
|
target=$1
|
||||||
|
base=$2
|
||||||
|
firstcol=$3
|
||||||
|
lastcol=$4
|
||||||
|
|
||||||
|
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
||||||
|
|
||||||
|
# With ASCII (simple) borders every corner and junction is the same
|
||||||
|
# '+', so one rectangular floating pane always draws exactly 4 of
|
||||||
|
# them; more means a stale frame was left behind somewhere.
|
||||||
|
corners=$(grep -o '+' "$CAPTURE" | wc -l)
|
||||||
|
[ "$corners" -eq 4 ] ||
|
||||||
|
fail "outer pane $target had $corners floating frames"
|
||||||
|
|
||||||
|
sed -n '6,11p' "$base" | cut -c"$firstcol-$lastcol" >"$DIR/want"
|
||||||
|
sed -n '6,11p' "$CAPTURE" | cut -c"$firstcol-$lastcol" >"$DIR/got"
|
||||||
|
cmp -s "$DIR/want" "$DIR/got" ||
|
||||||
|
fail "outer pane $target did not restore the old floating area"
|
||||||
|
}
|
||||||
|
|
||||||
|
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
$INNER set-option -g default-command 'sleep 100' || exit 1
|
||||||
|
$INNER set-option -g pane-border-lines simple || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen || exit 1
|
||||||
|
$OUTER split-window -h -t outer:0.0 'sleep 100' || exit 1
|
||||||
|
|
||||||
|
PANES=$($OUTER list-panes -t outer:0 -F '#{pane_id} #{pane_left}')
|
||||||
|
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||||
|
RIGHT=$(echo "$PANES" | sort -k2 -n | tail -1 | cut -d' ' -f1)
|
||||||
|
[ -n "$LEFT" ] && [ -n "$RIGHT" ] || fail "could not find outer panes"
|
||||||
|
|
||||||
|
$OUTER respawn-pane -k -t "$LEFT" \
|
||||||
|
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
$OUTER respawn-pane -k -t "$RIGHT" \
|
||||||
|
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_clients
|
||||||
|
wait_outer_has "$LEFT" BG-ROW-19
|
||||||
|
wait_outer_has "$RIGHT" BG-ROW-19
|
||||||
|
$OUTER capture-pane -p -t "$LEFT" >"$DIR/base-left" || exit 1
|
||||||
|
$OUTER capture-pane -p -t "$RIGHT" >"$DIR/base-right" || exit 1
|
||||||
|
|
||||||
|
FLOAT=$($INNER new-pane -dPF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||||
|
fail "could not create floating pane"
|
||||||
|
wait_outer_has "$LEFT" '+'
|
||||||
|
wait_outer_has "$RIGHT" '+'
|
||||||
|
|
||||||
|
FTOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||||
|
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
FWIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||||
|
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||||
|
|
||||||
|
# Move right through one client and require both clients to restore the old
|
||||||
|
# left-hand footprint.
|
||||||
|
drag_float "$LEFT" "$GRABCOL" "$FTOP" $((GRABCOL + 30))
|
||||||
|
wait_float_left gt 30
|
||||||
|
assert_scene "$LEFT" "$DIR/base-left" 1 20
|
||||||
|
assert_scene "$RIGHT" "$DIR/base-right" 1 20
|
||||||
|
|
||||||
|
# Move back through the other client and check the old right-hand footprint.
|
||||||
|
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||||
|
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||||
|
drag_float "$RIGHT" "$GRABCOL" "$FTOP" $((GRABCOL - 30))
|
||||||
|
wait_float_left lt 10
|
||||||
|
assert_scene "$LEFT" "$DIR/base-left" 35 60
|
||||||
|
assert_scene "$RIGHT" "$DIR/base-right" 35 60
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+208
@@ -0,0 +1,208 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Check that full and region screen-write fallbacks update an attached client,
|
||||||
|
# not only tmux's internal pane grid.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
INNER=
|
||||||
|
OUTER=
|
||||||
|
N=0
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||||
|
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_lacks()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" || return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client still showed $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_inner_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||||
|
grep -q "$marker" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner pane did not contain $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_inner_lacks()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$INNER capture-pane -p -t inner:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" || return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner pane still contained $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup()
|
||||||
|
{
|
||||||
|
mode=$1
|
||||||
|
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||||
|
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||||
|
N=$((N + 1))
|
||||||
|
INNER="$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lredraw-write-outer-$$-$N -f/dev/null"
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 12 \
|
||||||
|
"MODE=$mode READY='$DIR/ready-$N' TRIGGER='$DIR/trigger-$N' perl '$EMITTER'" ||
|
||||||
|
exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger()
|
||||||
|
{
|
||||||
|
: >"$DIR/trigger-$N-${1:-1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
$| = 1;
|
||||||
|
my $mode = $ENV{MODE};
|
||||||
|
my $ready = $ENV{READY};
|
||||||
|
my $trigger = $ENV{TRIGGER};
|
||||||
|
|
||||||
|
sub fill_screen {
|
||||||
|
my ($prefix) = @_;
|
||||||
|
print "\e[2J\e[H";
|
||||||
|
for my $row (0 .. 11) {
|
||||||
|
printf "\e[%d;1H%s-ROW-%02d", $row + 1, $prefix, $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($mode eq 'ris') {
|
||||||
|
fill_screen('RIS');
|
||||||
|
} elsif ($mode eq 'alternate') {
|
||||||
|
fill_screen('BASE');
|
||||||
|
} elsif ($mode eq 'scroll') {
|
||||||
|
fill_screen('SCROLL');
|
||||||
|
} else {
|
||||||
|
die "unknown mode $mode\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
open my $fh, '>', $ready or die "$ready: $!\n";
|
||||||
|
close $fh;
|
||||||
|
while (!-e "$trigger-1") {
|
||||||
|
select undef, undef, undef, 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($mode eq 'ris') {
|
||||||
|
print "\ec";
|
||||||
|
} elsif ($mode eq 'alternate') {
|
||||||
|
print "\e[?1049h";
|
||||||
|
fill_screen('ALT');
|
||||||
|
while (!-e "$trigger-2") {
|
||||||
|
select undef, undef, undef, 0.01;
|
||||||
|
}
|
||||||
|
print "\e[?1049l";
|
||||||
|
} else {
|
||||||
|
print "\e[12;1H\r\nSCROLL-NEW";
|
||||||
|
}
|
||||||
|
sleep 100;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
# RIS clears the complete screen. The source pane and attached client must both
|
||||||
|
# lose every old row.
|
||||||
|
setup ris
|
||||||
|
wait_outer_has RIS-ROW-11
|
||||||
|
trigger
|
||||||
|
wait_inner_lacks RIS-ROW
|
||||||
|
wait_outer_lacks RIS-ROW
|
||||||
|
|
||||||
|
# Leaving the alternate screen restores every row of the base screen.
|
||||||
|
setup alternate
|
||||||
|
wait_outer_has BASE-ROW-11
|
||||||
|
trigger
|
||||||
|
wait_inner_has ALT-ROW-11
|
||||||
|
wait_outer_has ALT-ROW-11
|
||||||
|
wait_outer_lacks BASE-ROW
|
||||||
|
trigger 2
|
||||||
|
wait_inner_has BASE-ROW-11
|
||||||
|
wait_outer_has BASE-ROW-11
|
||||||
|
wait_outer_lacks ALT-ROW
|
||||||
|
|
||||||
|
# Scrolling a pane which is narrower than the terminal redraws its complete
|
||||||
|
# region. Check the physical client row by row after the source grid shifts.
|
||||||
|
setup scroll
|
||||||
|
$INNER split-window -h -t inner:0 'sleep 100' || exit 1
|
||||||
|
wait_outer_has SCROLL-ROW-11
|
||||||
|
trigger
|
||||||
|
wait_inner_has SCROLL-NEW
|
||||||
|
wait_outer_has SCROLL-NEW
|
||||||
|
row=1
|
||||||
|
while [ "$row" -le 11 ]; do
|
||||||
|
expected=$(printf 'SCROLL-ROW-%02d' "$row")
|
||||||
|
actual=$(sed -n "${row}p" "$CAPTURE")
|
||||||
|
case "$actual" in
|
||||||
|
"$expected"*) ;;
|
||||||
|
*) fail "outer row $row was not redrawn as $expected" ;;
|
||||||
|
esac
|
||||||
|
row=$((row + 1))
|
||||||
|
done
|
||||||
|
actual=$(sed -n '12p' "$CAPTURE")
|
||||||
|
case "$actual" in
|
||||||
|
SCROLL-NEW*) ;;
|
||||||
|
*) fail "outer bottom row was not redrawn as SCROLL-NEW" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -15,7 +15,7 @@ PATH=/bin:/usr/bin
|
|||||||
TERM=screen
|
TERM=screen
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
TMUX="$TEST_TMUX -Ltest"
|
TMUX="$TEST_TMUX -Ltest$$"
|
||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
|
|
||||||
DIR=$(mktemp -d)
|
DIR=$(mktemp -d)
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# run-shell -c should expand formats using the target pane.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||||
|
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
trap '$TMUX kill-server 2>/dev/null; rm -rf "$TMP"' 0 1 15
|
||||||
|
TMP=$(cd "$TMP" && pwd -P)
|
||||||
|
mkdir "$TMP/first" "$TMP/second dir" || exit 1
|
||||||
|
|
||||||
|
check_directory()
|
||||||
|
{
|
||||||
|
actual=$(cat "$TMP/out")
|
||||||
|
if [ "$actual" != "$1" ]; then
|
||||||
|
echo "Expected directory '$1', got '$actual'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# The start path is available immediately, before the child is scheduled.
|
||||||
|
# Use it to test the format context in the same command queue as creation.
|
||||||
|
$TMUX new-session -d -s test -c "$TMP/first" 'sleep 60' \; \
|
||||||
|
run-shell -c '#{pane_start_path}' "pwd >'$TMP/out'" || exit 1
|
||||||
|
check_directory "$TMP/first"
|
||||||
|
|
||||||
|
# An explicit target must supply the format context, including with a delay.
|
||||||
|
pane=$($TMUX new-window -d -P -F '#{pane_id}' -t test \
|
||||||
|
-c "$TMP/second dir" 'sleep 60') || exit 1
|
||||||
|
# The current path depends on the operating system finding the child process.
|
||||||
|
i=0
|
||||||
|
while [ "$($TMUX display-message -p -t "$pane" '#{pane_current_path}')" != \
|
||||||
|
"$TMP/second dir" ]; do
|
||||||
|
if [ "$i" -ge 100 ]; then
|
||||||
|
echo "Timed out waiting for pane current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
$TMUX run-shell -t "$pane" -d 0.1 -c '#{pane_current_path}' \
|
||||||
|
"pwd >'$TMP/out'" || exit 1
|
||||||
|
check_directory "$TMP/second dir"
|
||||||
|
|
||||||
|
# Literal directories and the default client directory still work.
|
||||||
|
$TMUX run-shell -c "$TMP/second dir" "pwd >'$TMP/out'" || exit 1
|
||||||
|
check_directory "$TMP/second dir"
|
||||||
|
(cd "$TMP/first" && $TMUX run-shell "pwd >'$TMP/out'") || exit 1
|
||||||
|
check_directory "$TMP/first"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -107,6 +107,12 @@ $TMUX2 new-pane -x28 -y8 -X4 -Y1 -B double \
|
|||||||
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||||
compare floating-border-double
|
compare floating-border-double
|
||||||
|
|
||||||
|
# Larger floating pane with rounded border lines.
|
||||||
|
new_scene 40 12
|
||||||
|
$TMUX2 new-pane -x28 -y8 -X4 -Y1 -B rounded \
|
||||||
|
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||||
|
compare floating-border-rounded
|
||||||
|
|
||||||
# Floating pane with no border lines: redraw_mark_pane_borders returns early so
|
# Floating pane with no border lines: redraw_mark_pane_borders returns early so
|
||||||
# the float has no border at all, only its (clipped) content over the base pane.
|
# the float has no border at all, only its (clipped) content over the base pane.
|
||||||
new_scene 40 12
|
new_scene 40 12
|
||||||
|
|||||||
@@ -106,9 +106,28 @@ compare menu-over-split
|
|||||||
|
|
||||||
# Menu with no border lines.
|
# Menu with no border lines.
|
||||||
setup 40 14
|
setup 40 14
|
||||||
menu -b none -x6 -y8
|
$TMUX2 display-menu -T "This title must not reserve width" -C 1 \
|
||||||
|
-b none -x6 -y8 \
|
||||||
|
"Alpha item" a "" \
|
||||||
|
"Beta item" b "" \
|
||||||
|
"" "" "" \
|
||||||
|
"Gamma item" g "" || exit 1
|
||||||
|
sleep 1
|
||||||
compare menu-noborder
|
compare menu-noborder
|
||||||
|
|
||||||
|
# Menu border style follows the explicitly targeted window, not the current
|
||||||
|
# window when a different pane is selected.
|
||||||
|
setup 40 14
|
||||||
|
$TMUX2 set menu-border-lines none || exit 1
|
||||||
|
$TMUX2 neww || exit 1
|
||||||
|
$TMUX2 display-menu -t%0 -T "Menu" -C 1 \
|
||||||
|
"Alpha item" a "" \
|
||||||
|
"Beta item" b "" \
|
||||||
|
"" "" "" \
|
||||||
|
"Gamma item" g "" || exit 1
|
||||||
|
$TMUX2 selectw -t%0 || exit 1
|
||||||
|
compare menu-target-window-noborder
|
||||||
|
|
||||||
# Menu with double border lines.
|
# Menu with double border lines.
|
||||||
setup 40 14
|
setup 40 14
|
||||||
menu -b double -x6 -y8
|
menu -b double -x6 -y8
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Exercise drawing of popups (display-popup) over the window scene. A popup is an
|
|
||||||
# overlay drawn on top of the redraw scene (the overlay_draw path in
|
|
||||||
# screen-redraw.c), so this guards against regressions in how popups appear.
|
|
||||||
#
|
|
||||||
# A popup is modal and stays open until its command exits, so each scene fully
|
|
||||||
# re-creates the servers and re-attaches; the popup is opened in the background
|
|
||||||
# (display-popup blocks the client that runs it) and the outer pane is captured
|
|
||||||
# while it is open.
|
|
||||||
#
|
|
||||||
# Run with GENERATE=1 to (re)create the golden files.
|
|
||||||
|
|
||||||
PATH=/bin:/usr/bin
|
|
||||||
TERM=screen
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
export TERM LC_ALL
|
|
||||||
|
|
||||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
|
||||||
TMUX=
|
|
||||||
TMUX2=
|
|
||||||
SETUP=0
|
|
||||||
RESULTS=screen-redraw-results
|
|
||||||
|
|
||||||
TMP=$(mktemp)
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
rm -f "$TMP"
|
|
||||||
[ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null
|
|
||||||
[ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null
|
|
||||||
}
|
|
||||||
trap cleanup 0 1 15
|
|
||||||
|
|
||||||
fail() {
|
|
||||||
echo "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
compare() {
|
|
||||||
sleep 1
|
|
||||||
$TMUX capturep -p >$TMP || exit 1
|
|
||||||
if [ -n "$GENERATE" ]; then
|
|
||||||
cp $TMP "$RESULTS/$1.result" || exit 1
|
|
||||||
echo "generated $1"
|
|
||||||
else
|
|
||||||
cmp -s $TMP "$RESULTS/$1.result" || \
|
|
||||||
fail "scene $1 differs from $RESULTS/$1.result"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
C="sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"POP%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
|
||||||
|
|
||||||
# setup: fresh inner window attached inside a fresh outer pane, 40x14.
|
|
||||||
setup() {
|
|
||||||
[ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null
|
|
||||||
[ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null
|
|
||||||
SETUP=$((SETUP + 1))
|
|
||||||
TMUX="$TEST_TMUX -LtestA$$-$SETUP -f/dev/null"
|
|
||||||
TMUX2="$TEST_TMUX -LtestB$$-$SETUP -f/dev/null"
|
|
||||||
$TMUX2 new -d -x40 -y14 "$C" || exit 1
|
|
||||||
$TMUX2 set -g status off || exit 1
|
|
||||||
$TMUX2 set -g window-size manual || exit 1
|
|
||||||
$TMUX2 resizew -x40 -y14 || exit 1
|
|
||||||
$TMUX new -d -x40 -y14 || exit 1
|
|
||||||
$TMUX set -g status off || exit 1
|
|
||||||
$TMUX set -g window-size manual || exit 1
|
|
||||||
$TMUX set -g default-terminal "tmux-256color" || exit 1
|
|
||||||
$TMUX send -l "$TMUX2 attach" || exit 1
|
|
||||||
$TMUX send Enter || exit 1
|
|
||||||
sleep 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# popup <args>: open a popup running a fixed command, in the background (it stays
|
|
||||||
# open because the command sleeps; the servers are killed at the next setup).
|
|
||||||
popup() {
|
|
||||||
$TMUX2 display-popup "$@" -E "sh -c 'printf POPUP; exec sleep 100'" &
|
|
||||||
sleep 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Basic popup over a single pane.
|
|
||||||
setup
|
|
||||||
popup -w20 -h6 -x6 -y3
|
|
||||||
compare popup-basic
|
|
||||||
|
|
||||||
# Popup over a split: drawn on top of the pane border.
|
|
||||||
setup
|
|
||||||
$TMUX2 splitw -h "$C" || exit 1
|
|
||||||
popup -w24 -h8 -x8 -y3
|
|
||||||
compare popup-over-split
|
|
||||||
|
|
||||||
# Popup with no border lines (-B).
|
|
||||||
setup
|
|
||||||
popup -B -w20 -h6 -x6 -y3
|
|
||||||
compare popup-noborder
|
|
||||||
|
|
||||||
# Popup with double border lines.
|
|
||||||
setup
|
|
||||||
popup -b double -w20 -h6 -x6 -y3
|
|
||||||
compare popup-double
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
base
|
||||||
|
╭──────────────────────────╮
|
||||||
|
│FLOAT │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
╰──────────────────────────╯
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
MENU00 abcdefghij
|
MENU00 abcdefghij
|
||||||
MENU01 abcdefghij
|
MENU01 abcdefghij
|
||||||
MENU02[38;5;45m[48;5;235m Menu [39m[49m
|
MENU02 abcdefghij
|
||||||
MENU03[38;5;45m[48;5;235m [38;5;250m Alpha item (a) [38;5;45m [39m[49m
|
MENU03 abcdefghij
|
||||||
MENU04[38;5;45m[48;5;235m [38;5;16m[48;5;220m Beta item (b) [38;5;45m[48;5;235m [39m[49m
|
MENU04[38;5;250m[48;5;235m Alpha item (a) [39m[49m
|
||||||
MENU05[38;5;45m[48;5;235m [39m[49m
|
MENU05[38;5;16m[48;5;220m Beta item (b) [39m[49m
|
||||||
MENU06[38;5;45m[48;5;235m [38;5;250m Gamma item (g) [38;5;45m [39m[49m
|
MENU06[38;5;45m[48;5;235m [39m[49m
|
||||||
MENU07[38;5;45m[48;5;235m [39m[49m
|
MENU07[38;5;250m[48;5;235m Gamma item (g) [39m[49m
|
||||||
MENU08 abcdefghij
|
MENU08 abcdefghij
|
||||||
MENU09 abcdefghij
|
MENU09 abcdefghij
|
||||||
MENU10 abcdefghij
|
MENU10 abcdefghij
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
MENU00 abcdefghij
|
||||||
|
MENU01 abcdefghij
|
||||||
|
MENU02 abcdefghij
|
||||||
|
MENU03 abcdefghij
|
||||||
|
MENU04 abcd[38;5;250m[48;5;235m Alpha item (a) [39m[49m
|
||||||
|
MENU05 abcd[38;5;16m[48;5;220m Beta item (b) [39m[49m
|
||||||
|
MENU06 abcd[38;5;45m[48;5;235m [39m[49m
|
||||||
|
MENU07 abcd[38;5;250m[48;5;235m Gamma item (g) [39m[49m
|
||||||
|
MENU08 abcdefghij
|
||||||
|
MENU09 abcdefghij
|
||||||
|
MENU10 abcdefghij
|
||||||
|
MENU11 abcdefghij
|
||||||
|
MENU12 abcdefghij
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
POP00 ┌──────────────────┐
|
|
||||||
POP01 │POPUP │
|
|
||||||
POP02 │ │
|
|
||||||
POP03 │ │
|
|
||||||
POP04 │ │
|
|
||||||
POP05 └──────────────────┘
|
|
||||||
POP06 abcdefghij
|
|
||||||
POP07 abcdefghij
|
|
||||||
POP08 abcdefghij
|
|
||||||
POP09 abcdefghij
|
|
||||||
POP10 abcdefghij
|
|
||||||
POP11 abcdefghij
|
|
||||||
POP12 abcdefghij
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
POP00 ╔══════════════════╗
|
|
||||||
POP01 ║POPUP ║
|
|
||||||
POP02 ║ ║
|
|
||||||
POP03 ║ ║
|
|
||||||
POP04 ║ ║
|
|
||||||
POP05 ╚══════════════════╝
|
|
||||||
POP06 abcdefghij
|
|
||||||
POP07 abcdefghij
|
|
||||||
POP08 abcdefghij
|
|
||||||
POP09 abcdefghij
|
|
||||||
POP10 abcdefghij
|
|
||||||
POP11 abcdefghij
|
|
||||||
POP12 abcdefghij
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
POP00 POPUP
|
|
||||||
POP01
|
|
||||||
POP02
|
|
||||||
POP03
|
|
||||||
POP04
|
|
||||||
POP05
|
|
||||||
POP06 abcdefghij
|
|
||||||
POP07 abcdefghij
|
|
||||||
POP08 abcdefghij
|
|
||||||
POP09 abcdefghij
|
|
||||||
POP10 abcdefghij
|
|
||||||
POP11 abcdefghij
|
|
||||||
POP12 abcdefghij
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
POP00 ab┌──────────────────────┐fghij
|
|
||||||
POP01 ab│POPUP │fghij
|
|
||||||
POP02 ab│ │fghij
|
|
||||||
POP03 ab│ │fghij
|
|
||||||
POP04 ab│ │fghij
|
|
||||||
POP05 ab│ │fghij
|
|
||||||
POP06 ab│ │fghij
|
|
||||||
POP07 ab└──────────────────────┘fghij
|
|
||||||
POP08 abcdefghij │POP08 abcdefghij
|
|
||||||
POP09 abcdefghij │POP09 abcdefghij
|
|
||||||
POP10 abcdefghij │POP10 abcdefghij
|
|
||||||
POP11 abcdefghij │POP11 abcdefghij
|
|
||||||
POP12 abcdefghij │POP12 abcdefghij
|
|
||||||
│
|
|
||||||
@@ -95,7 +95,8 @@ layout_tee_down() { # top/bottom, bottom split: a top tee
|
|||||||
$TMUX kill-server 2>/dev/null
|
$TMUX kill-server 2>/dev/null
|
||||||
$TMUX2 kill-server 2>/dev/null
|
$TMUX2 kill-server 2>/dev/null
|
||||||
|
|
||||||
$TMUX2 new -d -x40 -y14 "sh -c 'exec sleep 100'" || exit 1
|
# Keep the anchor window alive until cleanup, even on slow test runs.
|
||||||
|
$TMUX2 new -d -x40 -y14 'exec cat' || exit 1
|
||||||
$TMUX2 set -g status off || exit 1
|
$TMUX2 set -g status off || exit 1
|
||||||
$TMUX2 set -g window-size manual || exit 1
|
$TMUX2 set -g window-size manual || exit 1
|
||||||
$TMUX2 set -g pane-border-format " #{pane_index} " || exit 1
|
$TMUX2 set -g pane-border-format " #{pane_index} " || exit 1
|
||||||
|
|||||||
Executable
+81
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Switching windows in the same session must redraw the attached client. The
|
||||||
|
# session object is shared, so its current winlink cannot be compared after it
|
||||||
|
# has already been changed.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lswitch-redraw-outer-$$ -f/dev/null"
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_outer_has()
|
||||||
|
{
|
||||||
|
marker=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
grep -q "$marker" "$CAPTURE" && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "outer client did not show $marker"
|
||||||
|
}
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 8 \
|
||||||
|
"printf '\033[2J\033[HA-WINDOW'; exec sleep 100" || exit 1
|
||||||
|
$INNER new-window -d -t inner:1 \
|
||||||
|
"printf '\033[2J\033[HB-WINDOW'; exec sleep 100" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null attach-session -t inner:0" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
wait_outer_has A-WINDOW
|
||||||
|
|
||||||
|
$INNER switch-client -c "$CLIENT" -t inner:1.0 || exit 1
|
||||||
|
[ "$($INNER display-message -p -t inner '#{window_index}')" -eq 1 ] ||
|
||||||
|
fail "server did not select window 1"
|
||||||
|
wait_outer_has B-WINDOW
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+116
@@ -0,0 +1,116 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# When only some lines are redrawn after a synchronized update, a line
|
||||||
|
# following a wrapped line must be drawn at its own position, not where the
|
||||||
|
# cursor happens to be.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
TMUX_TMPDIR=$DIR
|
||||||
|
export TMUX_TMPDIR
|
||||||
|
|
||||||
|
INNER="$TEST_TMUX -Li$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lo$$ -f/dev/null"
|
||||||
|
CONTROL=$DIR/control
|
||||||
|
EMITTER=$DIR/emitter.pl
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_file()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ] && [ ! -e "$1" ]; do
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
[ -e "$1" ] || fail "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
$INNER list-clients -F '#{client_termfeatures}' 2>/dev/null |
|
||||||
|
grep -q 'sync' && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "sync-capable client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >"$EMITTER" <<'PERL'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
my $control = $ENV{CONTROL};
|
||||||
|
(my $dir = $control) =~ s{/[^/]+$}{};
|
||||||
|
|
||||||
|
# Fill the screen with 80 column lines and no newlines so every line wraps.
|
||||||
|
my $out = "\e[H\e[2J";
|
||||||
|
for my $row (1 .. 24) {
|
||||||
|
$out .= substr(sprintf("ROW%02d_", $row) . ('x' x 80), 0, 80);
|
||||||
|
}
|
||||||
|
syswrite STDOUT, $out . "\e[1;1H";
|
||||||
|
|
||||||
|
open my $painted, '>', "$dir/painted" or die "$dir/painted: $!\n";
|
||||||
|
close $painted;
|
||||||
|
while (!-e $control) {
|
||||||
|
select undef, undef, undef, 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Change rows 2 and 6 in one synchronized update.
|
||||||
|
my $frame = "\e[?2026h" .
|
||||||
|
"\e[2;1H" . substr("NEW02_" . ('#' x 80), 0, 80) .
|
||||||
|
"\e[6;1H" . substr("NEW06_" . ('%' x 80), 0, 80) .
|
||||||
|
"\e[24;1H" .
|
||||||
|
"\e[?2026l";
|
||||||
|
my $written = syswrite STDOUT, $frame;
|
||||||
|
die "short synchronized frame write\n"
|
||||||
|
unless defined $written && $written == length $frame;
|
||||||
|
open my $done, '>', "$dir/done" or die "$dir/done: $!\n";
|
||||||
|
close $done;
|
||||||
|
select undef, undef, undef, 3;
|
||||||
|
PERL
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 80 -y 24 \
|
||||||
|
"CONTROL='$CONTROL' perl '$EMITTER'" || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -as terminal-features '*:sync' || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 80 -y 24 \
|
||||||
|
"$TEST_TMUX -Li$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
wait_for_client
|
||||||
|
|
||||||
|
wait_for_file "$DIR/painted" "application did not paint"
|
||||||
|
sleep 1
|
||||||
|
: >"$CONTROL"
|
||||||
|
wait_for_file "$DIR/done" "application did not finish"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
$OUTER capture-pane -p -t outer:0.0 >"$DIR/screen" || exit 1
|
||||||
|
sed -n 2p "$DIR/screen" | grep -q '^NEW02_#' || fail "row 2 not redrawn"
|
||||||
|
sed -n 3p "$DIR/screen" | grep -q '^ROW03_x' || fail "row 3 overwritten"
|
||||||
|
sed -n 6p "$DIR/screen" | grep -q '^NEW06_%' || fail "row 6 not redrawn"
|
||||||
|
exit 0
|
||||||
+61
-10
@@ -9,14 +9,37 @@ $TMUX kill-server 2>/dev/null
|
|||||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||||
$TMUX2 kill-server 2>/dev/null
|
$TMUX2 kill-server 2>/dev/null
|
||||||
|
|
||||||
TMP=$(mktemp)
|
TMP=$(mktemp) || exit 1
|
||||||
trap "rm -f $TMP" 0 1 15
|
prompt_pid=
|
||||||
|
|
||||||
|
cleanup () {
|
||||||
|
if [ -n "$prompt_pid" ]; then
|
||||||
|
kill "$prompt_pid" 2>/dev/null
|
||||||
|
wait "$prompt_pid" 2>/dev/null
|
||||||
|
fi
|
||||||
|
$TMUX kill-server 2>/dev/null
|
||||||
|
$TMUX2 kill-server 2>/dev/null
|
||||||
|
rm -f "$TMP"
|
||||||
|
}
|
||||||
|
trap cleanup 0
|
||||||
|
trap 'exit 1' 1 2 15
|
||||||
|
|
||||||
|
fail () {
|
||||||
|
echo "[FAIL] $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
$TMUX2 -f/dev/null new -d || exit 1
|
$TMUX2 -f/dev/null new -d || exit 1
|
||||||
$TMUX -f/dev/null new -d "$TMUX2 attach" || exit 1
|
$TMUX -f/dev/null new -d "$TMUX2 attach" || exit 1
|
||||||
sleep 1
|
i=0
|
||||||
|
while [ -z "$($TMUX2 list-clients -F '#{client_name}')" ]; do
|
||||||
|
[ "$i" -lt 100 ] || fail "inner client did not attach"
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
exit_status=0
|
exit_status=0
|
||||||
|
assertion=0
|
||||||
|
|
||||||
format_string () {
|
format_string () {
|
||||||
case $1 in
|
case $1 in
|
||||||
@@ -34,12 +57,33 @@ assert_key () {
|
|||||||
expected_name=$2
|
expected_name=$2
|
||||||
format_string=$(format_string "$expected_name")
|
format_string=$(format_string "$expected_name")
|
||||||
|
|
||||||
$TMUX2 command-prompt -k 'display-message -pl '"$format_string" > "$TMP" &
|
# Use a different prompt each time so a stale redraw cannot look ready.
|
||||||
sleep 0.05
|
assertion=$((assertion + 1))
|
||||||
|
prompt="tty-keys-$assertion:"
|
||||||
|
$TMUX2 command-prompt -k -p "$prompt" \
|
||||||
|
'display-message -pl '"$format_string" > "$TMP" &
|
||||||
|
prompt_pid=$!
|
||||||
|
i=0
|
||||||
|
while ! $TMUX capture-pane -p | grep -Fq "$prompt"; do
|
||||||
|
kill -0 "$prompt_pid" 2>/dev/null || \
|
||||||
|
fail "$keys -> $expected_name: prompt exited before becoming ready"
|
||||||
|
[ "$i" -lt 100 ] || \
|
||||||
|
fail "$keys -> $expected_name: timed out waiting for prompt"
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
$TMUX send-keys $keys
|
$TMUX send-keys $keys || fail "could not send $keys"
|
||||||
|
|
||||||
wait
|
i=0
|
||||||
|
while kill -0 "$prompt_pid" 2>/dev/null; do
|
||||||
|
[ "$i" -lt 100 ] || \
|
||||||
|
fail "$keys -> $expected_name: timed out waiting for key"
|
||||||
|
sleep 0.05
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
wait "$prompt_pid" || fail "$keys -> $expected_name: prompt failed"
|
||||||
|
prompt_pid=
|
||||||
|
|
||||||
keys=$(printf '%s' "$keys" | sed -e 's/Escape/\\\\033/g' | tr -d '[:space:]')
|
keys=$(printf '%s' "$keys" | sed -e 's/Escape/\\\\033/g' | tr -d '[:space:]')
|
||||||
actual_name=$(tr -d '[:space:]' < "$TMP")
|
actual_name=$(tr -d '[:space:]' < "$TMP")
|
||||||
@@ -293,6 +337,16 @@ assert_key 'Escape [24@' 'C-S-F12'
|
|||||||
assert_key 'Escape [I' 'FocusIn'
|
assert_key 'Escape [I' 'FocusIn'
|
||||||
assert_key 'Escape [O' 'FocusOut'
|
assert_key 'Escape [O' 'FocusOut'
|
||||||
|
|
||||||
|
# Focus events do not leave the prefix table.
|
||||||
|
$TMUX2 bind C-c set -g @focus-prefix yes
|
||||||
|
$TMUX2 set -g focus-events on
|
||||||
|
$TMUX send-keys C-b Escape '[O' Escape '[I' C-c
|
||||||
|
sleep 0.05
|
||||||
|
if [ "$($TMUX2 show -gv @focus-prefix)" != yes ]; then
|
||||||
|
echo "[FAIL] FocusIn and FocusOut left the prefix table"
|
||||||
|
exit_status=1
|
||||||
|
fi
|
||||||
|
|
||||||
# Paste keys
|
# Paste keys
|
||||||
assert_key 'Escape [200~' 'PasteStart'
|
assert_key 'Escape [200~' 'PasteStart'
|
||||||
assert_key 'Escape [201~' 'PasteEnd'
|
assert_key 'Escape [201~' 'PasteEnd'
|
||||||
@@ -355,7 +409,4 @@ assert_key 'Escape [32;2u' 'S-Space'
|
|||||||
assert_key 'Escape [9;5u' 'C-Tab'
|
assert_key 'Escape [9;5u' 'C-Tab'
|
||||||
assert_key 'Escape [1;5Z' 'C-S-Tab'
|
assert_key 'Escape [1;5Z' 'C-S-Tab'
|
||||||
|
|
||||||
$TMUX kill-server 2>/dev/null
|
|
||||||
$TMUX2 kill-server 2>/dev/null
|
|
||||||
|
|
||||||
exit $exit_status
|
exit $exit_status
|
||||||
|
|||||||
Executable
+117
@@ -0,0 +1,117 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# A pane that doesn't span the terminal's full width - because
|
||||||
|
# pane-scrollbars is on (the scrollbar occupies a column) or the pane is
|
||||||
|
# one of a side-by-side split - needs DECSLRM (left/right margin) support
|
||||||
|
# to use the fast native-scroll path (tty_cmd_linefeed()/scrollup()/
|
||||||
|
# scrolldown()/reverseindex(), tty.c:
|
||||||
|
# "(!tty_full_width(tty, ctx) && !tty_use_margin(tty))"). Without it, every
|
||||||
|
# single scroll falls back to tty_redraw_region()'s full manual repaint of
|
||||||
|
# the whole region - a real, confirmed source of flicker (and, separately,
|
||||||
|
# of image content not surviving a scroll in branches with image support).
|
||||||
|
#
|
||||||
|
# tty_default_features() (tty-features.c) grants the "margins" feature to
|
||||||
|
# several terminals it can positively identify via XTVERSION/DA2 (mintty,
|
||||||
|
# iTerm2, WezTerm, ghostty, XTerm-as-VT420) - this checks the actual
|
||||||
|
# server-side scroll decision via the -vv log for the underlying mechanism
|
||||||
|
# those table entries all rely on, using the terminal-features option
|
||||||
|
# directly (which any of them - or a user's own terminal-overrides -
|
||||||
|
# ultimately feed into) rather than simulating any one terminal's
|
||||||
|
# identification handshake.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
cd "$DIR" || exit 1
|
||||||
|
INNER="$TEST_TMUX -vv -Lmarginsscrollbar-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lmarginsscrollbar-outer-$$ -f/dev/null"
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
cd /
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
wait_for_client()
|
||||||
|
{
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||||
|
[ -n "$CLIENT" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "inner client did not attach"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_scroll_phase()
|
||||||
|
{
|
||||||
|
label=$1
|
||||||
|
margins=$2
|
||||||
|
|
||||||
|
rm -f tmux-server*.log
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 6 'exec sh' || exit 1
|
||||||
|
$INNER set -g status off || exit 1
|
||||||
|
$INNER set -g window-size manual || exit 1
|
||||||
|
$INNER set -g pane-scrollbars on || exit 1
|
||||||
|
if [ "$margins" = "on" ]; then
|
||||||
|
$INNER set -as terminal-features ',*:margins' || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
$OUTER new-session -d -x 40 -y 6 || exit 1
|
||||||
|
OUTERPANE=$($OUTER list-panes -F '#{pane_id}') || exit 1
|
||||||
|
$OUTER set -g status off || exit 1
|
||||||
|
$OUTER set -g window-size manual || exit 1
|
||||||
|
$OUTER set -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER send-keys -t "$OUTERPANE" -l "$INNER attach -t inner" || exit 1
|
||||||
|
$OUTER send-keys -t "$OUTERPANE" Enter || exit 1
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
wait_for_client
|
||||||
|
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 8 ]; do
|
||||||
|
$INNER send-keys -t inner Enter || exit 1
|
||||||
|
sleep 0.2
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
sleep 0.3
|
||||||
|
|
||||||
|
LOG=$(ls tmux-server*.log 2>/dev/null | head -1)
|
||||||
|
[ -n "$LOG" ] || fail "$label: sanity: no server -vv log was produced"
|
||||||
|
|
||||||
|
n=$(grep -c "tty_redraw_region.*large region redraw" "$LOG")
|
||||||
|
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
|
||||||
|
echo "$n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Phase 1: margins granted - must never fall back to a full region redraw.
|
||||||
|
n_with=$(run_scroll_phase "with margins" "on")
|
||||||
|
[ "$n_with" -eq 0 ] ||
|
||||||
|
fail "with margins granted, scrolling a scrollbar-enabled pane still fell back to a full region redraw ($n_with times)"
|
||||||
|
|
||||||
|
# Phase 2: sanity check - without margins, the same scenario must actually
|
||||||
|
# hit the fallback, proving phase 1 wasn't accidentally trivial.
|
||||||
|
n_without=$(run_scroll_phase "without margins" "off")
|
||||||
|
[ "$n_without" -gt 0 ] ||
|
||||||
|
fail "sanity: without margins, scrolling a scrollbar-enabled pane never fell back to a full region redraw - this scenario no longer exercises the bug this test checks for"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# A disconnected waiter must release both its wait and its command queue.
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
export TERM
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
OUT=$(mktemp -d)
|
||||||
|
TMUX="$TEST_TMUX -S$OUT/socket -f/dev/null"
|
||||||
|
pids=
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
for pid in $pids; do
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
$TMUX kill-server 2>/dev/null || true
|
||||||
|
rm -rf "$OUT"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_list()
|
||||||
|
{
|
||||||
|
expected=$1
|
||||||
|
shift
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 100 ]; do
|
||||||
|
value=$($TMUX wait-for -l "$@") || fail "list waiters failed"
|
||||||
|
[ "$value" = "$expected" ] && return
|
||||||
|
i=$((i + 1))
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
fail "expected waiters '$expected', got '$value'"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_free()
|
||||||
|
{
|
||||||
|
client_pid=$1
|
||||||
|
i=0
|
||||||
|
while [ $i -lt 100 ]; do
|
||||||
|
awk -v pid="$client_pid" '
|
||||||
|
$4 == "IDENTIFY_CLIENTPID" && $5 == pid { address = $3 }
|
||||||
|
address != "" && $2 == "free" && $3 == "client" &&
|
||||||
|
$4 == address && $5 == "(0" { freed = 1 }
|
||||||
|
END { exit !freed }
|
||||||
|
' "$OUT"/tmux-server-*.log && return
|
||||||
|
i=$((i + 1))
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
fail "client $client_pid was not freed"
|
||||||
|
}
|
||||||
|
|
||||||
|
cd "$OUT" || exit 1
|
||||||
|
$TMUX -vv new-session -d -s test || fail "new-session failed"
|
||||||
|
$TMUX set -g @continued 0 || fail "set option failed"
|
||||||
|
|
||||||
|
for signal in TERM KILL; do
|
||||||
|
for kind in channel lock event; do
|
||||||
|
name="$kind-$signal"
|
||||||
|
case $kind in
|
||||||
|
channel) args="$name"; list_args="$name" ;;
|
||||||
|
lock)
|
||||||
|
$TMUX wait-for -L "$name" || fail "lock failed"
|
||||||
|
args="-L $name"; list_args="$name"
|
||||||
|
;;
|
||||||
|
event) args="-E @$name"; list_args="-E @$name" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
$TMUX wait-for $args \; set -g @continued 1 &
|
||||||
|
dead=$!
|
||||||
|
pids="$dead"
|
||||||
|
wait_list "client-$dead" $list_args
|
||||||
|
|
||||||
|
$TMUX wait-for $args &
|
||||||
|
live=$!
|
||||||
|
pids="$dead $live"
|
||||||
|
wait_list "$(printf 'client-%s\nclient-%s' "$dead" "$live")" \
|
||||||
|
$list_args
|
||||||
|
|
||||||
|
kill -"$signal" "$dead" || fail "kill failed"
|
||||||
|
wait "$dead" 2>/dev/null || true
|
||||||
|
pids="$live"
|
||||||
|
wait_list "client-$live" $list_args
|
||||||
|
wait_free "$dead"
|
||||||
|
[ "$($TMUX show -gqv @continued)" = 0 ] ||
|
||||||
|
fail "disconnected client's next command ran"
|
||||||
|
|
||||||
|
case $kind in
|
||||||
|
channel) $TMUX wait-for -S "$name" || fail "signal failed" ;;
|
||||||
|
lock) $TMUX wait-for -U "$name" || fail "unlock failed" ;;
|
||||||
|
event)
|
||||||
|
$TMUX wait-for -E -w "client-$live" "@$name" ||
|
||||||
|
fail "wake event waiter failed"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
wait_list "" $list_args
|
||||||
|
wait_free "$live"
|
||||||
|
wait "$live" || fail "surviving waiter failed"
|
||||||
|
pids=
|
||||||
|
if [ "$kind" = lock ]; then
|
||||||
|
$TMUX wait-for -U "$name" || fail "final unlock failed"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# Removing the last waiter must remove its otherwise unused channel, and must
|
||||||
|
# not create a pending signal. Unrelated pending signals must be preserved.
|
||||||
|
$TMUX wait-for -S saved || fail "save signal failed"
|
||||||
|
$TMUX wait-for last &
|
||||||
|
dead=$!
|
||||||
|
pids="$dead"
|
||||||
|
wait_list "client-$dead" last
|
||||||
|
kill -KILL "$dead" || fail "kill last waiter failed"
|
||||||
|
wait "$dead" 2>/dev/null || true
|
||||||
|
pids=
|
||||||
|
wait_list "" last
|
||||||
|
wait_free "$dead"
|
||||||
|
grep -F 'remove empty wait channel last' "$OUT"/tmux-server-*.log \
|
||||||
|
>/dev/null || fail "empty channel was not removed"
|
||||||
|
|
||||||
|
$TMUX wait-for saved \; wait-for last &
|
||||||
|
live=$!
|
||||||
|
pids="$live"
|
||||||
|
wait_list "client-$live" last
|
||||||
|
$TMUX wait-for -S last || fail "signal last failed"
|
||||||
|
wait_free "$live"
|
||||||
|
wait "$live" || fail "pending signal was not preserved"
|
||||||
|
pids=
|
||||||
|
|
||||||
|
# Source-file leaves a cleanup callback behind the blocked command.
|
||||||
|
printf 'wait-for sourced\nset -g @continued 1\n' >"$OUT/source.conf"
|
||||||
|
$TMUX source-file "$OUT/source.conf" &
|
||||||
|
dead=$!
|
||||||
|
pids="$dead"
|
||||||
|
wait_list "client-$dead" sourced
|
||||||
|
kill -KILL "$dead" || fail "kill source-file waiter failed"
|
||||||
|
wait "$dead" 2>/dev/null || true
|
||||||
|
pids=
|
||||||
|
wait_list "" sourced
|
||||||
|
wait_free "$dead"
|
||||||
|
[ "$($TMUX show -gqv @continued)" = 0 ] ||
|
||||||
|
fail "disconnected source-file client's next command ran"
|
||||||
|
|
||||||
|
# Firing an event after cancellation must not call the removed event sink.
|
||||||
|
$TMUX wait-for -E window-renamed &
|
||||||
|
dead=$!
|
||||||
|
pids="$dead"
|
||||||
|
wait_list "client-$dead" -E window-renamed
|
||||||
|
kill -KILL "$dead" || fail "kill event waiter failed"
|
||||||
|
wait "$dead" 2>/dev/null || true
|
||||||
|
pids=
|
||||||
|
wait_list "" -E window-renamed
|
||||||
|
wait_free "$dead"
|
||||||
|
$TMUX rename-window -t test:0 renamed || fail "rename-window failed"
|
||||||
|
$TMUX display-message -p '#{pid}' >/dev/null || fail "server exited"
|
||||||
|
|
||||||
|
exit 0
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Dragging an inactive pane's scrollbar slider changes which pane is
|
||||||
|
# active (window_copy_scroll(), window-copy.c). Every other place in the
|
||||||
|
# codebase that changes the active pane pairs it with
|
||||||
|
# window_redraw_active_switch() (or falls back to a full window redraw) so
|
||||||
|
# the old/new active panes' window-active-style/window-style colours get
|
||||||
|
# repainted - window_copy_scroll() was the one caller that did neither, so
|
||||||
|
# a scrollbar-slider drag changed focus but left both panes' body colours
|
||||||
|
# stale. This used to be masked by window_set_active_pane() itself doing
|
||||||
|
# an unconditional full redraw on every active-pane change, until that was
|
||||||
|
# narrowed to borders/status-only for the common case.
|
||||||
|
#
|
||||||
|
# This creates two tiled panes with clearly distinguishable
|
||||||
|
# window-active-style/window-style backgrounds, puts the *inactive* one in
|
||||||
|
# copy mode, drags its scrollbar slider, and checks - via an attached
|
||||||
|
# client's own received bytes, since window-style is applied during
|
||||||
|
# redraw composition rather than stored in the grid, so capture-pane
|
||||||
|
# alone would not reflect it - that the newly active pane's marker text
|
||||||
|
# immediately shows the active-style colour, not the stale one.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
export PATH TERM LC_ALL
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
|
||||||
|
DIR=$(mktemp -d) || exit 1
|
||||||
|
INNER="$TEST_TMUX -Lsbfocus-inner-$$ -f/dev/null"
|
||||||
|
OUTER="$TEST_TMUX -Lsbfocus-outer-$$ -f/dev/null"
|
||||||
|
CAPTURE=$DIR/capture
|
||||||
|
ESC=$(printf '\033')
|
||||||
|
|
||||||
|
fail()
|
||||||
|
{
|
||||||
|
echo "$*" >&2
|
||||||
|
[ -s "$CAPTURE" ] && cat -A "$CAPTURE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
{
|
||||||
|
$OUTER kill-server 2>/dev/null
|
||||||
|
$INNER kill-server 2>/dev/null
|
||||||
|
rm -rf "$DIR"
|
||||||
|
}
|
||||||
|
trap cleanup 0 1 15
|
||||||
|
|
||||||
|
get_mark_color()
|
||||||
|
{
|
||||||
|
$OUTER capture-pane -e -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||||
|
LC_ALL=C grep -a "MARK1" "$CAPTURE" |
|
||||||
|
LC_ALL=C grep -aoE "${ESC}\[4[12]m" | tail -1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_mark_color()
|
||||||
|
{
|
||||||
|
want=$1
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 50 ]; do
|
||||||
|
[ "$(get_mark_color)" = "$want" ] && return 0
|
||||||
|
sleep 0.1
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
fail "MARK1's pane never showed the expected background (wanted '$want', got '$(get_mark_color)')"
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse()
|
||||||
|
{
|
||||||
|
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||||
|
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||||
|
sleep 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
$INNER new-session -d -s inner -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$INNER set-option -g status off || exit 1
|
||||||
|
$INNER set-option -g window-size manual || exit 1
|
||||||
|
$INNER set-option -g mouse on || exit 1
|
||||||
|
$INNER set-option -g pane-scrollbars on || exit 1
|
||||||
|
$INNER setw pane-scrollbars-position right || exit 1
|
||||||
|
$INNER setw pane-scrollbars-style 'width=1,pad=0' || exit 1
|
||||||
|
$INNER set-option -g window-active-style 'bg=red' || exit 1
|
||||||
|
$INNER set-option -g window-style 'bg=green' || exit 1
|
||||||
|
$INNER split-window -h -t inner -d 'printf MARK1; sleep 100' || exit 1
|
||||||
|
|
||||||
|
PANE1=$($INNER list-panes -t inner -F '#{pane_id}' | sed -n 2p)
|
||||||
|
[ "$($INNER display-message -p -t "$PANE1" '#{pane_active}')" = 0 ] ||
|
||||||
|
fail "sanity: MARK1's pane is already active before the drag"
|
||||||
|
|
||||||
|
# Put the inactive pane in copy mode without switching focus (copy-mode -t
|
||||||
|
# targets a pane without making it active).
|
||||||
|
$INNER copy-mode -t "$PANE1" || exit 1
|
||||||
|
|
||||||
|
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||||
|
$OUTER set-option -g status off || exit 1
|
||||||
|
$OUTER set-option -g window-size manual || exit 1
|
||||||
|
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||||
|
$OUTER respawn-pane -k -t outer:0.0 \
|
||||||
|
"$TEST_TMUX -Lsbfocus-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
wait_mark_color "${ESC}[42m"
|
||||||
|
|
||||||
|
XOFF=$($INNER display-message -p -t "$PANE1" '#{pane_left}')
|
||||||
|
YOFF=$($INNER display-message -p -t "$PANE1" '#{pane_top}')
|
||||||
|
SX=$($INNER display-message -p -t "$PANE1" '#{pane_width}')
|
||||||
|
SBCOL=$((XOFF + SX + 1))
|
||||||
|
SBROW=$((YOFF + 1))
|
||||||
|
|
||||||
|
# With no scrollback beyond the pane's own content, the slider fills the
|
||||||
|
# whole scrollbar track, so any point on it (here, its very first row) is
|
||||||
|
# on the slider.
|
||||||
|
mouse 0 "$SBCOL" "$SBROW" M
|
||||||
|
mouse 32 "$SBCOL" "$((SBROW + 1))" M
|
||||||
|
mouse 0 "$SBCOL" "$((SBROW + 1))" m
|
||||||
|
|
||||||
|
[ "$($INNER display-message -p -t "$PANE1" '#{pane_active}')" = 1 ] ||
|
||||||
|
fail "sanity: dragging the scrollbar slider did not change focus"
|
||||||
|
|
||||||
|
wait_mark_color "${ESC}[41m"
|
||||||
|
|
||||||
|
exit 0
|
||||||
+500
-86
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: screen-redraw.c,v 1.159 2026/09/09 08:31:42 nicm Exp $ */
|
/* $OpenBSD: screen-redraw.c,v 1.161 2026/09/22 16:56:07 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
* this is done at various points, such as when a pane is moved or resized. The
|
* this is done at various points, such as when a pane is moved or resized. The
|
||||||
* scene only includes the part of the client used for the window: panes, pane
|
* scene only includes the part of the client used for the window: panes, pane
|
||||||
* status lines, borders, scrollbars, and any area outside the window. The
|
* status lines, borders, scrollbars, and any area outside the window. The
|
||||||
* client status line and overlay are not included.
|
* client status line is not included.
|
||||||
*
|
*
|
||||||
* A scene is made from spans. A span is a horizontal run of cells on one
|
* A scene is made from spans. A span is a horizontal run of cells on one
|
||||||
* visible line that can be drawn in the same way. Each span has a type, for
|
* visible line that can be drawn in the same way. Each span has a type, for
|
||||||
@@ -93,7 +93,6 @@ enum redraw_span_type {
|
|||||||
#define REDRAW_PANE_SCROLLBAR 0x20
|
#define REDRAW_PANE_SCROLLBAR 0x20
|
||||||
#define REDRAW_STATUS 0x40
|
#define REDRAW_STATUS 0x40
|
||||||
#define REDRAW_MENU 0x80
|
#define REDRAW_MENU 0x80
|
||||||
#define REDRAW_OVERLAY 0x100
|
|
||||||
|
|
||||||
/* Draw everything. */
|
/* Draw everything. */
|
||||||
#define REDRAW_ALL 0x7fffffff
|
#define REDRAW_ALL 0x7fffffff
|
||||||
@@ -205,6 +204,22 @@ struct redraw_scene {
|
|||||||
u_int oy;
|
u_int oy;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* A single damaged window-coordinate rectangle. */
|
||||||
|
struct redraw_damage {
|
||||||
|
u_int x;
|
||||||
|
u_int y;
|
||||||
|
u_int sx;
|
||||||
|
u_int sy;
|
||||||
|
|
||||||
|
TAILQ_ENTRY(redraw_damage) entry;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cap on the number of pending damage rectangles per window before they are
|
||||||
|
* collapsed into a single rectangle covering their union.
|
||||||
|
*/
|
||||||
|
#define REDRAW_DAMAGE_MAX 16
|
||||||
|
|
||||||
/* Cell for building the scene. */
|
/* Cell for building the scene. */
|
||||||
struct redraw_build_cell {
|
struct redraw_build_cell {
|
||||||
struct redraw_span_data data;
|
struct redraw_span_data data;
|
||||||
@@ -213,6 +228,12 @@ struct redraw_build_cell {
|
|||||||
static struct redraw_build_cell *redraw_cells;
|
static struct redraw_build_cell *redraw_cells;
|
||||||
static size_t redraw_ncells;
|
static size_t redraw_ncells;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bumped once per redraw_client_damage() call (one client's one redraw
|
||||||
|
* pass) - see redraw_damage_refresh_status().
|
||||||
|
*/
|
||||||
|
static u_int redraw_status_serial;
|
||||||
|
|
||||||
/* Context for building the scene. */
|
/* Context for building the scene. */
|
||||||
struct redraw_build_ctx {
|
struct redraw_build_ctx {
|
||||||
struct client *c;
|
struct client *c;
|
||||||
@@ -264,8 +285,6 @@ redraw_flags_to_string(int flags)
|
|||||||
strlcat(s, "scrollbar ", sizeof s);
|
strlcat(s, "scrollbar ", sizeof s);
|
||||||
if (flags & REDRAW_MENU)
|
if (flags & REDRAW_MENU)
|
||||||
strlcat(s, "menu ", sizeof s);
|
strlcat(s, "menu ", sizeof s);
|
||||||
if (flags & REDRAW_OVERLAY)
|
|
||||||
strlcat(s, "overlay ", sizeof s);
|
|
||||||
if (REDRAW_IS_ALL(flags))
|
if (REDRAW_IS_ALL(flags))
|
||||||
strlcat(s, "all ", sizeof s);
|
strlcat(s, "all ", sizeof s);
|
||||||
if (*s != '\0')
|
if (*s != '\0')
|
||||||
@@ -1057,6 +1076,13 @@ redraw_free_scene(struct redraw_scene *scene)
|
|||||||
free(scene);
|
free(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Does a client's cached scene show this window? */
|
||||||
|
int
|
||||||
|
redraw_client_has_window(struct client *c, struct window *w)
|
||||||
|
{
|
||||||
|
return (c->redraw_scene != NULL && c->redraw_scene->w == w);
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark a window's cached redraw scenes as out of date. */
|
/* Mark a window's cached redraw scenes as out of date. */
|
||||||
void
|
void
|
||||||
redraw_invalidate_scene(struct window *w)
|
redraw_invalidate_scene(struct window *w)
|
||||||
@@ -1064,6 +1090,114 @@ redraw_invalidate_scene(struct window *w)
|
|||||||
w->redraw_scene_generation++;
|
w->redraw_scene_generation++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Free all pending damage for a window. */
|
||||||
|
void
|
||||||
|
redraw_free_damage(struct window *w)
|
||||||
|
{
|
||||||
|
struct redraw_damage *rd, *rd1;
|
||||||
|
|
||||||
|
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||||
|
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||||
|
free(rd);
|
||||||
|
}
|
||||||
|
w->damage_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collapse all pending damage for a window into one rectangle - its union. */
|
||||||
|
static void
|
||||||
|
redraw_collapse_damage(struct window *w)
|
||||||
|
{
|
||||||
|
struct redraw_damage *rd, *rd1, *first;
|
||||||
|
u_int x0, y0, x1, y1;
|
||||||
|
|
||||||
|
first = TAILQ_FIRST(&w->damage);
|
||||||
|
if (first == NULL)
|
||||||
|
return;
|
||||||
|
x0 = first->x;
|
||||||
|
y0 = first->y;
|
||||||
|
x1 = first->x + first->sx;
|
||||||
|
y1 = first->y + first->sy;
|
||||||
|
|
||||||
|
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||||
|
if (rd->x < x0)
|
||||||
|
x0 = rd->x;
|
||||||
|
if (rd->y < y0)
|
||||||
|
y0 = rd->y;
|
||||||
|
if (rd->x + rd->sx > x1)
|
||||||
|
x1 = rd->x + rd->sx;
|
||||||
|
if (rd->y + rd->sy > y1)
|
||||||
|
y1 = rd->y + rd->sy;
|
||||||
|
if (rd != first) {
|
||||||
|
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||||
|
free(rd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
first->x = x0;
|
||||||
|
first->y = y0;
|
||||||
|
first->sx = x1 - x0;
|
||||||
|
first->sy = y1 - y0;
|
||||||
|
w->damage_count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Record a damaged window-coordinate rectangle. Clips it to the window,
|
||||||
|
* merges it with an existing rectangle where doing so does not make the
|
||||||
|
* result substantially larger than the two combined, and collapses the
|
||||||
|
* whole list to its union once it grows past a modest cap.
|
||||||
|
*
|
||||||
|
* This only records damage - nothing consumes it yet.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
redraw_damage_window(struct window *w, u_int x, u_int y, u_int sx, u_int sy)
|
||||||
|
{
|
||||||
|
struct redraw_damage *rd;
|
||||||
|
u_int x0, y0, x1, y1, area, union_area;
|
||||||
|
|
||||||
|
if (x >= w->sx || y >= w->sy)
|
||||||
|
return;
|
||||||
|
if (x + sx > w->sx)
|
||||||
|
sx = w->sx - x;
|
||||||
|
if (y + sy > w->sy)
|
||||||
|
sy = w->sy - y;
|
||||||
|
if (sx == 0 || sy == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||||
|
/* Skip unless overlapping or directly adjacent. */
|
||||||
|
if (x > rd->x + rd->sx || rd->x > x + sx ||
|
||||||
|
y > rd->y + rd->sy || rd->y > y + sy)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
x0 = (x < rd->x) ? x : rd->x;
|
||||||
|
y0 = (y < rd->y) ? y : rd->y;
|
||||||
|
x1 = (x + sx > rd->x + rd->sx) ? x + sx : rd->x + rd->sx;
|
||||||
|
y1 = (y + sy > rd->y + rd->sy) ? y + sy : rd->y + rd->sy;
|
||||||
|
|
||||||
|
area = sx * sy + rd->sx * rd->sy;
|
||||||
|
union_area = (x1 - x0) * (y1 - y0);
|
||||||
|
if (union_area > 2 * area)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rd->x = x0;
|
||||||
|
rd->y = y0;
|
||||||
|
rd->sx = x1 - x0;
|
||||||
|
rd->sy = y1 - y0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rd = xcalloc(1, sizeof *rd);
|
||||||
|
rd->x = x;
|
||||||
|
rd->y = y;
|
||||||
|
rd->sx = sx;
|
||||||
|
rd->sy = sy;
|
||||||
|
TAILQ_INSERT_TAIL(&w->damage, rd, entry);
|
||||||
|
w->damage_count++;
|
||||||
|
|
||||||
|
if (w->damage_count > REDRAW_DAMAGE_MAX)
|
||||||
|
redraw_collapse_damage(w);
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark all cached redraw scenes as out of date. */
|
/* Mark all cached redraw scenes as out of date. */
|
||||||
void
|
void
|
||||||
redraw_invalidate_all_scenes(void)
|
redraw_invalidate_all_scenes(void)
|
||||||
@@ -1376,50 +1510,39 @@ redraw_draw_menu_span(struct redraw_draw_ctx *dctx,
|
|||||||
tty_draw_line(tty, s, px, span->data.m.py, n, x, y, NULL);
|
tty_draw_line(tty, s, px, span->data.m.py, n, x, y, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Draw a span. */
|
/*
|
||||||
|
* Draw a span, restricted to [clip_x, clip_x + clip_n) - a caller drawing
|
||||||
|
* the whole span passes the span's own x/width here; a caller drawing only
|
||||||
|
* a damaged sub-range passes that range instead.
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||||
u_int y)
|
u_int y, u_int clip_x, u_int clip_n)
|
||||||
{
|
{
|
||||||
struct redraw_scene *scene = dctx->scene;
|
|
||||||
struct redraw_span_data *data = &span->data;
|
struct redraw_span_data *data = &span->data;
|
||||||
enum redraw_span_type type = data->type;
|
enum redraw_span_type type = data->type;
|
||||||
struct client *c = scene->c;
|
|
||||||
struct tty *tty = &c->tty;
|
|
||||||
struct visible_ranges *r;
|
|
||||||
struct visible_range *rr;
|
|
||||||
u_int i, x, n;
|
|
||||||
|
|
||||||
if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS)
|
if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
r = tty_check_overlay_range(tty, span->x, y, span->width);
|
switch (span->data.type) {
|
||||||
for (i = 0; i < r->used; i++) {
|
case REDRAW_SPAN_PANE:
|
||||||
rr = &r->ranges[i];
|
redraw_draw_pane_span(dctx, span, clip_x, y, clip_n);
|
||||||
if (rr->nx == 0)
|
break;
|
||||||
continue;
|
case REDRAW_SPAN_BORDER:
|
||||||
x = rr->px;
|
case REDRAW_SPAN_EMPTY:
|
||||||
n = rr->nx;
|
case REDRAW_SPAN_OUTSIDE:
|
||||||
|
redraw_draw_border_span(dctx, span, clip_x, y, clip_n);
|
||||||
switch (span->data.type) {
|
break;
|
||||||
case REDRAW_SPAN_PANE:
|
case REDRAW_SPAN_STATUS:
|
||||||
redraw_draw_pane_span(dctx, span, x, y, n);
|
redraw_draw_status_span(dctx, span, clip_x, y, clip_n);
|
||||||
break;
|
break;
|
||||||
case REDRAW_SPAN_BORDER:
|
case REDRAW_SPAN_SCROLLBAR:
|
||||||
case REDRAW_SPAN_EMPTY:
|
redraw_draw_scrollbar_span(dctx, span, clip_x, y, clip_n);
|
||||||
case REDRAW_SPAN_OUTSIDE:
|
break;
|
||||||
redraw_draw_border_span(dctx, span, x, y, n);
|
case REDRAW_SPAN_MENU:
|
||||||
break;
|
redraw_draw_menu_span(dctx, span, clip_x, y, clip_n);
|
||||||
case REDRAW_SPAN_STATUS:
|
break;
|
||||||
redraw_draw_status_span(dctx, span, x, y, n);
|
|
||||||
break;
|
|
||||||
case REDRAW_SPAN_SCROLLBAR:
|
|
||||||
redraw_draw_scrollbar_span(dctx, span, x, y, n);
|
|
||||||
break;
|
|
||||||
case REDRAW_SPAN_MENU:
|
|
||||||
redraw_draw_menu_span(dctx, span, x, y, n);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1453,15 +1576,19 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
|||||||
if (flags & REDRAW_PANE) {
|
if (flags & REDRAW_PANE) {
|
||||||
spans = &line->spans[REDRAW_SPAN_PANE];
|
spans = &line->spans[REDRAW_SPAN_PANE];
|
||||||
TAILQ_FOREACH(span, spans, entry) {
|
TAILQ_FOREACH(span, spans, entry) {
|
||||||
if (span->data.p.wp == wp)
|
if (span->data.p.wp == wp) {
|
||||||
redraw_draw_span(dctx, span, cy);
|
redraw_draw_span(dctx, span, cy,
|
||||||
|
span->x, span->width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (flags & REDRAW_PANE_SCROLLBAR) {
|
if (flags & REDRAW_PANE_SCROLLBAR) {
|
||||||
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
||||||
TAILQ_FOREACH(span, spans, entry) {
|
TAILQ_FOREACH(span, spans, entry) {
|
||||||
if (span->data.sb.wp == wp)
|
if (span->data.sb.wp == wp) {
|
||||||
redraw_draw_span(dctx, span, cy);
|
redraw_draw_span(dctx, span, cy,
|
||||||
|
span->x, span->width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1519,8 +1646,10 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
spans = &line->spans[type];
|
spans = &line->spans[type];
|
||||||
TAILQ_FOREACH(span, spans, entry)
|
TAILQ_FOREACH(span, spans, entry) {
|
||||||
redraw_draw_span(dctx, span, cy);
|
redraw_draw_span(dctx, span, cy, span->x,
|
||||||
|
span->width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1540,8 +1669,10 @@ redraw_draw_menu_lines(struct redraw_draw_ctx *dctx)
|
|||||||
cy = dctx->status_lines + y;
|
cy = dctx->status_lines + y;
|
||||||
else
|
else
|
||||||
cy = y;
|
cy = y;
|
||||||
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry)
|
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry) {
|
||||||
redraw_draw_span(dctx, span, cy);
|
redraw_draw_span(dctx, span, cy, span->x,
|
||||||
|
span->width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1621,6 +1752,24 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx,
|
|||||||
dctx->flags |= REDRAW_ISOLATES;
|
dctx->flags |= REDRAW_ISOLATES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Build a pane prompt into a one-line screen. */
|
||||||
|
static void
|
||||||
|
redraw_make_pane_prompt(struct window_pane *wp, struct screen *screen)
|
||||||
|
{
|
||||||
|
struct screen_write_ctx ctx;
|
||||||
|
struct prompt_draw_data pdd;
|
||||||
|
|
||||||
|
screen_init(screen, wp->sx, 1, 0);
|
||||||
|
screen_write_start(&ctx, screen);
|
||||||
|
pdd.ctx = &ctx;
|
||||||
|
pdd.cursor_x = &wp->prompt_cx;
|
||||||
|
pdd.area_x = 0;
|
||||||
|
pdd.area_width = wp->sx;
|
||||||
|
pdd.prompt_line = 0;
|
||||||
|
prompt_draw(wp->prompt, &pdd);
|
||||||
|
screen_write_stop(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
/* Draw a pane's prompt over its content. */
|
/* Draw a pane's prompt over its content. */
|
||||||
static void
|
static void
|
||||||
redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||||
@@ -1629,8 +1778,6 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
|||||||
struct client *c = scene->c;
|
struct client *c = scene->c;
|
||||||
struct tty *tty = &c->tty;
|
struct tty *tty = &c->tty;
|
||||||
struct screen screen;
|
struct screen screen;
|
||||||
struct screen_write_ctx ctx;
|
|
||||||
struct prompt_draw_data pdd;
|
|
||||||
int ox = scene->ox, oy = scene->oy;
|
int ox = scene->ox, oy = scene->oy;
|
||||||
int sx = scene->sx, sy = scene->sy;
|
int sx = scene->sx, sy = scene->sy;
|
||||||
int line, cy, px, offset, width, wy;
|
int line, cy, px, offset, width, wy;
|
||||||
@@ -1663,17 +1810,8 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
|||||||
if (px + width > sx)
|
if (px + width > sx)
|
||||||
width = sx - px;
|
width = sx - px;
|
||||||
|
|
||||||
screen_init(&screen, wp->sx, 1, 0);
|
redraw_make_pane_prompt(wp, &screen);
|
||||||
screen_write_start(&ctx, &screen);
|
tty_draw_line(tty, &screen, offset, 0, width, px, cy, NULL);
|
||||||
pdd.ctx = &ctx;
|
|
||||||
pdd.cursor_x = &wp->prompt_cx;
|
|
||||||
pdd.area_x = 0;
|
|
||||||
pdd.area_width = wp->sx;
|
|
||||||
pdd.prompt_line = 0;
|
|
||||||
prompt_draw(wp->prompt, &pdd);
|
|
||||||
screen_write_stop(&ctx);
|
|
||||||
|
|
||||||
tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL);
|
|
||||||
screen_free(&screen);
|
screen_free(&screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1688,10 +1826,8 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
|||||||
struct screen *sl;
|
struct screen *sl;
|
||||||
struct redraw_scene *scene;
|
struct redraw_scene *scene;
|
||||||
struct window_pane *loop;
|
struct window_pane *loop;
|
||||||
u_int width, i, y, lines, j;
|
u_int width, i, y, lines;
|
||||||
struct redraw_span *first;
|
struct redraw_span *first;
|
||||||
struct visible_ranges *r;
|
|
||||||
struct visible_range *rr;
|
|
||||||
int redraw;
|
int redraw;
|
||||||
|
|
||||||
if (c->flags & CLIENT_SUSPENDED)
|
if (c->flags & CLIENT_SUSPENDED)
|
||||||
@@ -1704,7 +1840,8 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
|||||||
redraw = status_prompt_redraw(c);
|
redraw = status_prompt_redraw(c);
|
||||||
else
|
else
|
||||||
redraw = status_redraw(c);
|
redraw = status_redraw(c);
|
||||||
if (!redraw && !REDRAW_IS_ALL(flags)) {
|
if (!redraw && (~c->flags & CLIENT_REDRAWSTATUSALWAYS) &&
|
||||||
|
!REDRAW_IS_ALL(flags)) {
|
||||||
flags &= ~REDRAW_STATUS;
|
flags &= ~REDRAW_STATUS;
|
||||||
if (flags == 0)
|
if (flags == 0)
|
||||||
return;
|
return;
|
||||||
@@ -1799,19 +1936,9 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
|||||||
else
|
else
|
||||||
y = c->tty.sy - lines;
|
y = c->tty.sy - lines;
|
||||||
sl = c->status.active;
|
sl = c->status.active;
|
||||||
for (i = 0; i < lines; i++) {
|
for (i = 0; i < lines; i++)
|
||||||
r = tty_check_overlay_range(tty, 0, y + i, tty->sx);
|
tty_draw_line(tty, sl, 0, i, tty->sx, 0, y + i, NULL);
|
||||||
for (j = 0; j < r->used; j++) {
|
|
||||||
rr = &r->ranges[j];
|
|
||||||
if (rr->nx == 0)
|
|
||||||
continue;
|
|
||||||
tty_draw_line(tty, sl, rr->px, i, rr->nx,
|
|
||||||
rr->px, y + i, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (c->overlay_draw != NULL && (flags & REDRAW_OVERLAY))
|
|
||||||
c->overlay_draw(c, c->overlay_data);
|
|
||||||
|
|
||||||
tty_reset(tty);
|
tty_reset(tty);
|
||||||
|
|
||||||
@@ -1867,18 +1994,13 @@ redraw_screen(struct client *c)
|
|||||||
{
|
{
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
|
|
||||||
if (c->flags & CLIENT_REDRAWWINDOW) {
|
if (c->flags & CLIENT_REDRAWWINDOW)
|
||||||
if (c->flags & CLIENT_REDRAWOVERLAY)
|
redraw_draw(c, NULL, REDRAW_ALL);
|
||||||
redraw_draw(c, NULL, REDRAW_ALL);
|
else {
|
||||||
else
|
|
||||||
redraw_draw(c, NULL, REDRAW_ALL & ~REDRAW_OVERLAY);
|
|
||||||
} else {
|
|
||||||
if (c->flags & CLIENT_REDRAWBORDERS)
|
if (c->flags & CLIENT_REDRAWBORDERS)
|
||||||
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
|
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
|
||||||
if (c->flags & CLIENT_REDRAWSTATUS)
|
if (c->flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))
|
||||||
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
|
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
|
||||||
if (c->flags & CLIENT_REDRAWOVERLAY)
|
|
||||||
flags |= REDRAW_OVERLAY;
|
|
||||||
if (c->flags & CLIENT_REDRAWMENU)
|
if (c->flags & CLIENT_REDRAWMENU)
|
||||||
flags |= REDRAW_MENU;
|
flags |= REDRAW_MENU;
|
||||||
if (c->session->curw->window->menu != NULL)
|
if (c->session->curw->window->menu != NULL)
|
||||||
@@ -1903,3 +2025,295 @@ redraw_pane_scrollbar(struct client *c, struct window_pane *wp)
|
|||||||
{
|
{
|
||||||
redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR);
|
redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A REDRAW_SPAN_STATUS span within a damaged rectangle needs its content
|
||||||
|
* rebuilt and force-drawn regardless of whether that content has logically
|
||||||
|
* changed. window_make_pane_status()'s grid_compare() only tells us
|
||||||
|
* whether the *content* changed, not whether the physical cells were
|
||||||
|
* disturbed by something else (e.g. a floating pane sliding across this
|
||||||
|
* row) - and being inside a damage rectangle already proves that happened.
|
||||||
|
* Without this, redraw_draw_span() silently skips REDRAW_SPAN_STATUS spans
|
||||||
|
* whenever PANE_NEWSTATUS is not set, leaving a pane's border-status title
|
||||||
|
* blank until some unrelated redraw happens to touch it (e.g. a focus
|
||||||
|
* change or window resize).
|
||||||
|
*
|
||||||
|
* wp->status_screen/PANE_NEWSTATUS are per-pane, but the formatted content
|
||||||
|
* (window_make_pane_status() expands pane-border-format, which can read
|
||||||
|
* per-client fields like #{client_name}) is per-client. Gating purely on
|
||||||
|
* PANE_NEWSTATUS would let one client's damage pass render its own text,
|
||||||
|
* set the flag, and leave every other client's pass - this tick or any
|
||||||
|
* later one, since nothing else clears it here - reusing that stale,
|
||||||
|
* wrong-client text. redraw_status_serial (bumped once per
|
||||||
|
* redraw_client_damage() call, i.e. once per client per pass) still
|
||||||
|
* dedupes repeat calls within that same pass, but forces a fresh,
|
||||||
|
* correctly-client-formatted render on every distinct client/pass.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
redraw_damage_refresh_status(struct redraw_draw_ctx *dctx,
|
||||||
|
struct window_pane *wp)
|
||||||
|
{
|
||||||
|
struct redraw_span *first;
|
||||||
|
u_int width;
|
||||||
|
|
||||||
|
if ((wp->flags & PANE_NEWSTATUS) &&
|
||||||
|
wp->status_serial == redraw_status_serial)
|
||||||
|
return;
|
||||||
|
width = redraw_pane_status_width(dctx, wp, &first);
|
||||||
|
if (width == 0)
|
||||||
|
return;
|
||||||
|
log_debug("%s: regenerated pane %%%u status for %s", __func__, wp->id,
|
||||||
|
dctx->scene->c->name);
|
||||||
|
window_make_pane_status(wp, dctx->scene->c, width, first);
|
||||||
|
wp->flags |= PANE_NEWSTATUS;
|
||||||
|
wp->status_serial = redraw_status_serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Whether the cell at (px, py) in screen s is a padding cell. */
|
||||||
|
static int
|
||||||
|
redraw_screen_cell_is_padding(struct screen *s, u_int px, u_int py)
|
||||||
|
{
|
||||||
|
struct grid_cell gc;
|
||||||
|
|
||||||
|
if (px >= screen_size_x(s))
|
||||||
|
return (0);
|
||||||
|
grid_view_get_cell(s->grid, px, py, &gc);
|
||||||
|
return ((gc.flags & GRID_FLAG_PADDING) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whether the cell at scene x-coordinate x within this span is the second
|
||||||
|
* (padding) half of a wide character - the condition under which growing a
|
||||||
|
* damage clip's edge toward it, to pull in the rest of that character, is
|
||||||
|
* correct. True unconditionally for span types with no real backing screen
|
||||||
|
* (border, scrollbar) - these only ever draw single synthesized cells, so
|
||||||
|
* growing them is always harmless. For span types with a real screen (pane
|
||||||
|
* content, a pane's status line, a menu), only true when x is actually a
|
||||||
|
* padding cell there - if x is instead the start of an unrelated,
|
||||||
|
* already-complete character, growing toward it would walk into that
|
||||||
|
* character's opposite half and corrupt it: tty_draw_line() clears a
|
||||||
|
* leading padding cell in its draw range (proof the range starts
|
||||||
|
* mid-character) and, via a different check (tty_draw_line_get_empty()'s
|
||||||
|
* gc->data.width > nx test), also clears a trailing base cell that has no
|
||||||
|
* room left for its own padding (proof the range ends mid-character) - so
|
||||||
|
* growing either edge onto a base cell is equally destructive to whatever
|
||||||
|
* character lies just outside the range, just through a different part of
|
||||||
|
* tty_draw_line().
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
redraw_span_cell_is_padding(struct redraw_span *span, u_int x)
|
||||||
|
{
|
||||||
|
struct screen *s;
|
||||||
|
u_int px, py;
|
||||||
|
|
||||||
|
switch (span->data.type) {
|
||||||
|
case REDRAW_SPAN_PANE:
|
||||||
|
s = span->data.p.wp->screen;
|
||||||
|
px = span->data.p.px + (x - span->x);
|
||||||
|
py = span->data.p.py;
|
||||||
|
break;
|
||||||
|
case REDRAW_SPAN_STATUS:
|
||||||
|
s = &span->data.st.wp->status_screen;
|
||||||
|
px = span->data.st.offset + (x - span->x);
|
||||||
|
py = 0;
|
||||||
|
break;
|
||||||
|
case REDRAW_SPAN_MENU:
|
||||||
|
s = menu_screen(span->data.m.md);
|
||||||
|
px = span->data.m.px + (x - span->x);
|
||||||
|
py = span->data.m.py;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
return (redraw_screen_cell_is_padding(s, px, py));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Grow a clipped span range by one cell on either edge that isn't already at
|
||||||
|
* the span's own boundary. A clip edge that lands mid-character (this is a
|
||||||
|
* damage rectangle, so its edges are geometric and have no idea what's in
|
||||||
|
* the grid) may be sitting on the second, padding half of a wide character
|
||||||
|
* whose other half falls just outside the requested range - growing by one
|
||||||
|
* cell is enough to pull the whole character back in, since no grid cell is
|
||||||
|
* ever wider than two columns, and clamping to the span's own x and width
|
||||||
|
* keeps this from bleeding into a neighbouring span. Both edges need the
|
||||||
|
* same padding check before growing: growing onto a cell that isn't padding
|
||||||
|
* (an unrelated, already-complete character just outside the range) is
|
||||||
|
* destructive on either side, not just the left - see
|
||||||
|
* redraw_span_cell_is_padding().
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
redraw_damage_grow_span_clip(struct redraw_span *span, u_int *xp, u_int *endp)
|
||||||
|
{
|
||||||
|
if (*xp > span->x && redraw_span_cell_is_padding(span, *xp))
|
||||||
|
(*xp)--;
|
||||||
|
if (*endp < span->x + span->width &&
|
||||||
|
redraw_span_cell_is_padding(span, *endp))
|
||||||
|
(*endp)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* As redraw_damage_grow_span_clip(), but against an explicit screen: px0 is
|
||||||
|
* the column in that screen corresponding to span->x, py the row. Used for
|
||||||
|
* a span's separately rendered content (e.g. a pane's prompt) that isn't
|
||||||
|
* span->data.p.wp->screen (or whichever grid redraw_span_cell_is_padding()
|
||||||
|
* would otherwise consult for this span's type), and so has its own,
|
||||||
|
* unrelated wide-character boundaries at the same columns.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
redraw_damage_grow_screen_clip(struct redraw_span *span, struct screen *s,
|
||||||
|
u_int px0, u_int py, u_int *xp, u_int *endp)
|
||||||
|
{
|
||||||
|
if (*xp > span->x &&
|
||||||
|
redraw_screen_cell_is_padding(s, px0 + (*xp - span->x), py))
|
||||||
|
(*xp)--;
|
||||||
|
if (*endp < span->x + span->width &&
|
||||||
|
redraw_screen_cell_is_padding(s, px0 + (*endp - span->x), py))
|
||||||
|
(*endp)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recompose a pane's prompt over a damaged section of its display row. */
|
||||||
|
static void
|
||||||
|
redraw_damage_draw_pane_prompt(struct redraw_draw_ctx *dctx,
|
||||||
|
struct redraw_span *span, u_int y, u_int x, u_int n)
|
||||||
|
{
|
||||||
|
struct redraw_scene *scene = dctx->scene;
|
||||||
|
struct window_pane *wp = span->data.p.wp;
|
||||||
|
struct tty *tty = &scene->c->tty;
|
||||||
|
struct screen screen;
|
||||||
|
u_int px, width, prompt_y, x0, x1;
|
||||||
|
|
||||||
|
if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0)
|
||||||
|
return;
|
||||||
|
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||||
|
prompt_y = 0;
|
||||||
|
else
|
||||||
|
prompt_y = wp->sy - 1;
|
||||||
|
if (span->data.p.py != prompt_y)
|
||||||
|
return;
|
||||||
|
|
||||||
|
redraw_make_pane_prompt(wp, &screen);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* x and n were clipped and grown against wp->screen, whose character
|
||||||
|
* boundaries have nothing to do with the prompt's separately
|
||||||
|
* rendered screen - realign the range on the prompt's own grid
|
||||||
|
* instead, clamped to this span so it cannot bleed into a
|
||||||
|
* neighbouring one.
|
||||||
|
*/
|
||||||
|
x0 = x;
|
||||||
|
x1 = x + n;
|
||||||
|
redraw_damage_grow_screen_clip(span, &screen, span->data.p.px, 0, &x0,
|
||||||
|
&x1);
|
||||||
|
|
||||||
|
px = span->data.p.px + (x0 - span->x);
|
||||||
|
if (px < screen_size_x(&screen)) {
|
||||||
|
width = x1 - x0;
|
||||||
|
if (width > screen_size_x(&screen) - px)
|
||||||
|
width = screen_size_x(&screen) - px;
|
||||||
|
tty_draw_line(tty, &screen, px, 0, width, x0, y, NULL);
|
||||||
|
}
|
||||||
|
screen_free(&screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compose exactly the cells within a damaged rectangle (already in this
|
||||||
|
* client's own scene coordinates), rather than a whole pane. For each row
|
||||||
|
* in range, every span of every type whose x-range intersects the
|
||||||
|
* rectangle is drawn restricted to just the intersected sub-range.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||||
|
u_int sx, u_int sy)
|
||||||
|
{
|
||||||
|
struct redraw_scene *scene = dctx->scene;
|
||||||
|
struct redraw_line *line;
|
||||||
|
struct redraw_spans *spans;
|
||||||
|
struct redraw_span *span;
|
||||||
|
u_int cy, yy, clip_x, clip_end, type;
|
||||||
|
|
||||||
|
if (x >= scene->sx || y >= scene->sy)
|
||||||
|
return;
|
||||||
|
if (x + sx > scene->sx)
|
||||||
|
sx = scene->sx - x;
|
||||||
|
if (y + sy > scene->sy)
|
||||||
|
sy = scene->sy - y;
|
||||||
|
if (sx == 0 || sy == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (yy = y; yy < y + sy; yy++) {
|
||||||
|
line = &scene->lines[yy];
|
||||||
|
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||||
|
cy = dctx->status_lines + yy;
|
||||||
|
else
|
||||||
|
cy = yy;
|
||||||
|
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||||
|
spans = &line->spans[type];
|
||||||
|
TAILQ_FOREACH(span, spans, entry) {
|
||||||
|
clip_x = (span->x > x) ? span->x : x;
|
||||||
|
clip_end = (span->x + span->width < x + sx) ?
|
||||||
|
span->x + span->width : x + sx;
|
||||||
|
if (clip_end <= clip_x)
|
||||||
|
continue;
|
||||||
|
if (type == REDRAW_SPAN_STATUS) {
|
||||||
|
redraw_damage_refresh_status(dctx,
|
||||||
|
span->data.st.wp);
|
||||||
|
}
|
||||||
|
redraw_damage_grow_span_clip(span, &clip_x,
|
||||||
|
&clip_end);
|
||||||
|
redraw_draw_span(dctx, span, cy, clip_x,
|
||||||
|
clip_end - clip_x);
|
||||||
|
if (type == REDRAW_SPAN_PANE) {
|
||||||
|
redraw_damage_draw_pane_prompt(dctx,
|
||||||
|
span, cy, clip_x,
|
||||||
|
clip_end - clip_x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Consume a client's window's pending damage by composing exactly the
|
||||||
|
* damaged cells, after clipping each rectangle to what this client can see
|
||||||
|
* and translating it into this client's own scene coordinates.
|
||||||
|
*
|
||||||
|
* Unlike redraw_pane(), this does not redraw a whole pane's worth of cells
|
||||||
|
* for a small disturbance - only the cells within the (clipped) rectangle
|
||||||
|
* are touched, via redraw_draw_damage_rect().
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
redraw_client_damage(struct client *c)
|
||||||
|
{
|
||||||
|
struct window *w = c->session->curw->window;
|
||||||
|
struct redraw_scene *scene;
|
||||||
|
struct redraw_draw_ctx dctx;
|
||||||
|
struct redraw_damage *rd;
|
||||||
|
u_int ox, oy, sx, sy, x0, y0, x1, y1;
|
||||||
|
|
||||||
|
if (TAILQ_EMPTY(&w->damage))
|
||||||
|
return;
|
||||||
|
redraw_status_serial++;
|
||||||
|
|
||||||
|
scene = redraw_get_scene(c);
|
||||||
|
if (scene == NULL)
|
||||||
|
return;
|
||||||
|
redraw_set_draw_context(&dctx, scene);
|
||||||
|
redraw_get_window_offset(c, &ox, &oy, &sx, &sy);
|
||||||
|
|
||||||
|
tty_sync_start(&c->tty);
|
||||||
|
tty_update_mode(&c->tty, c->tty.mode & ~CURSOR_MODES, NULL);
|
||||||
|
|
||||||
|
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||||
|
x0 = (rd->x > ox) ? rd->x : ox;
|
||||||
|
y0 = (rd->y > oy) ? rd->y : oy;
|
||||||
|
x1 = (rd->x + rd->sx < ox + sx) ? rd->x + rd->sx : ox + sx;
|
||||||
|
y1 = (rd->y + rd->sy < oy + sy) ? rd->y + rd->sy : oy + sy;
|
||||||
|
if (x0 >= x1 || y0 >= y1)
|
||||||
|
continue;
|
||||||
|
log_debug("%s: %s composing damage %u,%u %ux%u", __func__,
|
||||||
|
c->name, x0 - ox, y0 - oy, x1 - x0, y1 - y0);
|
||||||
|
redraw_draw_damage_rect(&dctx, x0 - ox, y0 - oy, x1 - x0,
|
||||||
|
y1 - y0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+56
-31
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: screen-write.c,v 1.290 2026/08/24 15:05:26 nicm Exp $ */
|
/* $OpenBSD: screen-write.c,v 1.292 2026/09/21 12:14:32 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -120,14 +120,39 @@ screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy)
|
|||||||
evtimer_add(&w->offset_timer, &tv);
|
evtimer_add(&w->offset_timer, &tv);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do a full redraw. */
|
/*
|
||||||
|
* Called when a write could not be applied directly to the terminal and
|
||||||
|
* needs a redraw instead. Report damage for the requested rows. wp->yoff is
|
||||||
|
* already adjusted past any top pane-border-status row, so wp->yoff + py is
|
||||||
|
* the correct window-coordinate row. wp->xoff/wp->yoff are signed and can be
|
||||||
|
* negative for a floating pane positioned partly off the window's left or
|
||||||
|
* top edge, so clip to the window's own origin here before converting to
|
||||||
|
* the unsigned coordinates redraw_damage_window() takes - passing a
|
||||||
|
* negative offset through unclipped wraps to a huge value that its own
|
||||||
|
* bounds check then silently rejects, losing the pane's visible portion
|
||||||
|
* entirely rather than just the off-screen part.
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
screen_write_redraw_cb(const struct tty_ctx *ttyctx)
|
screen_write_redraw_cb(const struct tty_ctx *ttyctx, u_int py, u_int ny)
|
||||||
{
|
{
|
||||||
struct window_pane *wp = ttyctx->arg;
|
struct window_pane *wp = ttyctx->arg;
|
||||||
|
int x0, y0, x1, y1;
|
||||||
|
|
||||||
if (wp != NULL)
|
if (wp == NULL)
|
||||||
wp->flags |= PANE_REDRAW;
|
return;
|
||||||
|
|
||||||
|
x0 = wp->xoff;
|
||||||
|
y0 = wp->yoff + (int)py;
|
||||||
|
x1 = x0 + (int)wp->sx;
|
||||||
|
y1 = y0 + (int)ny;
|
||||||
|
if (x0 < 0)
|
||||||
|
x0 = 0;
|
||||||
|
if (y0 < 0)
|
||||||
|
y0 = 0;
|
||||||
|
if (x1 <= x0 || y1 <= y0)
|
||||||
|
return;
|
||||||
|
redraw_damage_window(wp->window, (u_int)x0, (u_int)y0,
|
||||||
|
(u_int)(x1 - x0), (u_int)(y1 - y0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update context for client. */
|
/* Update context for client. */
|
||||||
@@ -305,21 +330,16 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
|
|||||||
|
|
||||||
if (~ctx->flags & SCREEN_WRITE_SYNC) {
|
if (~ctx->flags & SCREEN_WRITE_SYNC) {
|
||||||
/*
|
/*
|
||||||
* For the active pane showing its base screen or for an
|
* For the active pane showing its base screen, only use
|
||||||
* overlay (no pane), only use synchronized updates if
|
* synchronized updates if requested (commands that move the
|
||||||
* requested (commands that move the cursor); for other panes
|
* cursor); for other panes or a pane in a mode, always use it,
|
||||||
* or a pane in a mode, always use it, since the cursor will
|
* since the cursor will have to move.
|
||||||
* 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))
|
ctx->wp->screen != &ctx->wp->base))
|
||||||
ttyctx->flags |= TTY_CTX_SYNC;
|
ttyctx->flags |= TTY_CTX_SYNC;
|
||||||
else {
|
else if (is_sync)
|
||||||
if (ctx->wp == NULL)
|
ttyctx->flags |= TTY_CTX_SYNC;
|
||||||
ttyctx->flags |= TTY_CTX_OVERLAY_SYNC;
|
|
||||||
if (is_sync)
|
|
||||||
ttyctx->flags |= TTY_CTX_SYNC;
|
|
||||||
}
|
|
||||||
tty_write(tty_cmd_syncstart, ttyctx);
|
tty_write(tty_cmd_syncstart, ttyctx);
|
||||||
ctx->flags |= SCREEN_WRITE_SYNC;
|
ctx->flags |= SCREEN_WRITE_SYNC;
|
||||||
}
|
}
|
||||||
@@ -714,7 +734,7 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src,
|
|||||||
if (!window_position_is_visible(r, xoff + s->cx))
|
if (!window_position_is_visible(r, xoff + s->cx))
|
||||||
break;
|
break;
|
||||||
ttyctx.cell = &gc;
|
ttyctx.cell = &gc;
|
||||||
ttyctx.flags &= (TTY_CTX_OVERLAY_SYNC|TTY_CTX_SYNC);
|
ttyctx.flags &= TTY_CTX_SYNC;
|
||||||
tty_write(tty_cmd_cell, &ttyctx);
|
tty_write(tty_cmd_cell, &ttyctx);
|
||||||
ttyctx.ocx++;
|
ttyctx.ocx++;
|
||||||
|
|
||||||
@@ -838,34 +858,39 @@ screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, int choice,
|
|||||||
struct screen *s = ctx->s;
|
struct screen *s = ctx->s;
|
||||||
struct grid_cell default_gc;
|
struct grid_cell default_gc;
|
||||||
const struct grid_cell *gc = &default_gc;
|
const struct grid_cell *gc = &default_gc;
|
||||||
u_int cx, cy, i, j, width = menu->width;
|
u_int border, cx = s->cx, cy = s->cy, i, j, width;
|
||||||
const char *name;
|
const char *name;
|
||||||
|
|
||||||
cx = s->cx;
|
|
||||||
cy = s->cy;
|
|
||||||
|
|
||||||
memcpy(&default_gc, menu_gc, sizeof default_gc);
|
memcpy(&default_gc, menu_gc, sizeof default_gc);
|
||||||
|
|
||||||
screen_write_box(ctx, menu->width + 4, menu->count + 2, lines,
|
if (lines == BOX_LINES_NONE) {
|
||||||
border_gc, menu->title);
|
border = 0;
|
||||||
|
width = menu->item_width;
|
||||||
|
} else {
|
||||||
|
border = 1;
|
||||||
|
width = menu->width;
|
||||||
|
screen_write_box(ctx, width + 4, menu->count + 2, lines,
|
||||||
|
border_gc, menu->title);
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < menu->count; i++) {
|
for (i = 0; i < menu->count; i++) {
|
||||||
name = menu->items[i].name;
|
name = menu->items[i].name;
|
||||||
if (name == NULL) {
|
if (name == NULL) {
|
||||||
screen_write_cursormove(ctx, cx, cy + 1 + i, 0);
|
screen_write_cursormove(ctx, cx, cy + border + i, 0);
|
||||||
screen_write_hline(ctx, width + 4, 1, 1, lines,
|
screen_write_hline(ctx, width + 2 + (2 * border), 1, 1,
|
||||||
border_gc);
|
lines, border_gc);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice >= 0 && i == (u_int)choice && *name != '-')
|
if (choice >= 0 && i == (u_int)choice && *name != '-')
|
||||||
gc = choice_gc;
|
gc = choice_gc;
|
||||||
|
|
||||||
screen_write_cursormove(ctx, cx + 1, cy + 1 + i, 0);
|
screen_write_cursormove(ctx, cx + border, cy + border + i, 0);
|
||||||
for (j = 0; j < width + 2; j++)
|
for (j = 0; j < width + 2; j++)
|
||||||
screen_write_putc(ctx, gc, ' ');
|
screen_write_putc(ctx, gc, ' ');
|
||||||
|
|
||||||
screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0);
|
screen_write_cursormove(ctx, cx + border + 1, cy + border + i,
|
||||||
|
0);
|
||||||
if (*name == '-') {
|
if (*name == '-') {
|
||||||
default_gc.attr |= GRID_ATTR_DIM;
|
default_gc.attr |= GRID_ATTR_DIM;
|
||||||
format_draw(ctx, gc, width, name + 1, NULL, 0);
|
format_draw(ctx, gc, width, name + 1, NULL, 0);
|
||||||
@@ -2155,7 +2180,7 @@ screen_write_fullredraw(struct screen_write_ctx *ctx)
|
|||||||
|
|
||||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||||
if (ttyctx.redraw_cb != NULL)
|
if (ttyctx.redraw_cb != NULL)
|
||||||
ttyctx.redraw_cb(&ttyctx);
|
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trim collected items. */
|
/* Trim collected items. */
|
||||||
@@ -3175,7 +3200,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
|||||||
|
|
||||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||||
if (ttyctx.redraw_cb != NULL)
|
if (ttyctx.redraw_cb != NULL)
|
||||||
ttyctx.redraw_cb(&ttyctx);
|
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Turn alternate screen off. */
|
/* Turn alternate screen off. */
|
||||||
@@ -3200,5 +3225,5 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
|||||||
|
|
||||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||||
if (ttyctx.redraw_cb != NULL)
|
if (ttyctx.redraw_cb != NULL)
|
||||||
ttyctx.redraw_cb(&ttyctx);
|
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||||
}
|
}
|
||||||
|
|||||||
+112
-161
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: server-client.c,v 1.511 2026/09/08 10:20:08 nicm Exp $ */
|
/* $OpenBSD: server-client.c,v 1.514 2026/09/22 06:46:50 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -67,77 +67,6 @@ server_client_how_many(void)
|
|||||||
return (n);
|
return (n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overlay timer callback. */
|
|
||||||
static void
|
|
||||||
server_client_overlay_timer(__unused int fd, __unused short events, void *data)
|
|
||||||
{
|
|
||||||
server_client_clear_overlay(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set an overlay on client. */
|
|
||||||
void
|
|
||||||
server_client_set_overlay(struct client *c, u_int delay,
|
|
||||||
overlay_check_cb checkcb, overlay_mode_cb modecb,
|
|
||||||
overlay_draw_cb drawcb, overlay_key_cb keycb, overlay_free_cb freecb,
|
|
||||||
overlay_resize_cb resizecb, void *data)
|
|
||||||
{
|
|
||||||
struct timeval tv;
|
|
||||||
|
|
||||||
if (c->overlay_draw != NULL)
|
|
||||||
server_client_clear_overlay(c);
|
|
||||||
|
|
||||||
tv.tv_sec = delay / 1000;
|
|
||||||
tv.tv_usec = (delay % 1000) * 1000L;
|
|
||||||
|
|
||||||
if (event_initialized(&c->overlay_timer))
|
|
||||||
evtimer_del(&c->overlay_timer);
|
|
||||||
evtimer_set(&c->overlay_timer, server_client_overlay_timer, c);
|
|
||||||
if (delay != 0)
|
|
||||||
evtimer_add(&c->overlay_timer, &tv);
|
|
||||||
|
|
||||||
c->overlay_check = checkcb;
|
|
||||||
c->overlay_mode = modecb;
|
|
||||||
c->overlay_draw = drawcb;
|
|
||||||
c->overlay_key = keycb;
|
|
||||||
c->overlay_free = freecb;
|
|
||||||
c->overlay_resize = resizecb;
|
|
||||||
c->overlay_data = data;
|
|
||||||
|
|
||||||
if (c->overlay_check == NULL)
|
|
||||||
c->tty.flags |= TTY_FREEZE;
|
|
||||||
if (c->overlay_mode == NULL)
|
|
||||||
c->tty.flags |= TTY_NOCURSOR;
|
|
||||||
window_update_focus(c->session->curw->window);
|
|
||||||
server_redraw_client(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Clear overlay mode on client. */
|
|
||||||
void
|
|
||||||
server_client_clear_overlay(struct client *c)
|
|
||||||
{
|
|
||||||
if (c->overlay_draw == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (event_initialized(&c->overlay_timer))
|
|
||||||
evtimer_del(&c->overlay_timer);
|
|
||||||
|
|
||||||
if (c->overlay_free != NULL)
|
|
||||||
c->overlay_free(c, c->overlay_data);
|
|
||||||
|
|
||||||
c->overlay_check = NULL;
|
|
||||||
c->overlay_mode = NULL;
|
|
||||||
c->overlay_draw = NULL;
|
|
||||||
c->overlay_key = NULL;
|
|
||||||
c->overlay_free = NULL;
|
|
||||||
c->overlay_resize = NULL;
|
|
||||||
c->overlay_data = NULL;
|
|
||||||
|
|
||||||
c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR);
|
|
||||||
if (c->session != NULL)
|
|
||||||
window_update_focus(c->session->curw->window);
|
|
||||||
server_redraw_client(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Are these ranges empty? That is, nothing is visible. */
|
/* Are these ranges empty? That is, nothing is visible. */
|
||||||
int
|
int
|
||||||
server_client_ranges_is_empty(struct visible_ranges *r)
|
server_client_ranges_is_empty(struct visible_ranges *r)
|
||||||
@@ -161,52 +90,6 @@ server_client_ensure_ranges(struct visible_ranges *r, u_int n)
|
|||||||
r->size = n;
|
r->size = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Given overlay position and dimensions, return parts of the input range which
|
|
||||||
* are visible.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px,
|
|
||||||
u_int py, u_int nx, struct visible_ranges *r)
|
|
||||||
{
|
|
||||||
u_int ox, onx;
|
|
||||||
|
|
||||||
/* Trivial case of no overlap in the y direction. */
|
|
||||||
if (py < y || py > y + sy - 1) {
|
|
||||||
server_client_ensure_ranges(r, 1);
|
|
||||||
r->ranges[0].px = px;
|
|
||||||
r->ranges[0].nx = nx;
|
|
||||||
r->used = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
server_client_ensure_ranges(r, 2);
|
|
||||||
|
|
||||||
/* Visible bit to the left of the popup. */
|
|
||||||
if (px < x) {
|
|
||||||
r->ranges[0].px = px;
|
|
||||||
r->ranges[0].nx = x - px;
|
|
||||||
if (r->ranges[0].nx > nx)
|
|
||||||
r->ranges[0].nx = nx;
|
|
||||||
} else {
|
|
||||||
r->ranges[0].px = 0;
|
|
||||||
r->ranges[0].nx = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Visible bit to the right of the popup. */
|
|
||||||
ox = x + sx;
|
|
||||||
if (px > ox)
|
|
||||||
ox = px;
|
|
||||||
onx = px + nx;
|
|
||||||
if (onx > ox) {
|
|
||||||
r->ranges[1].px = ox;
|
|
||||||
r->ranges[1].nx = onx - ox;
|
|
||||||
} else {
|
|
||||||
r->ranges[1].px = 0;
|
|
||||||
r->ranges[1].nx = 0;
|
|
||||||
}
|
|
||||||
r->used = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if this client is inside this server. */
|
/* Check if this client is inside this server. */
|
||||||
int
|
int
|
||||||
server_client_check_nested(struct client *c)
|
server_client_check_nested(struct client *c)
|
||||||
@@ -473,7 +356,33 @@ server_client_set_session(struct client *c, struct session *s)
|
|||||||
tty_update_client_offset(c);
|
tty_update_client_offset(c);
|
||||||
status_timer_start(c);
|
status_timer_start(c);
|
||||||
server_client_fire_session_changed(c, old);
|
server_client_fire_session_changed(c, old);
|
||||||
server_redraw_client(c);
|
|
||||||
|
/*
|
||||||
|
* A full redraw is only needed if the client's session or
|
||||||
|
* current window actually changed - not if this merely
|
||||||
|
* confirmed the client is still looking at the same window
|
||||||
|
* (as happens when switch-client -t targets a pane in the
|
||||||
|
* already-current window, e.g. clicking a pane name in a
|
||||||
|
* second #{P:} status line: the default MouseDown1Status
|
||||||
|
* binding resolves that click to switch-client -t=, which
|
||||||
|
* reaches here regardless of whether anything besides the
|
||||||
|
* active pane changed). Redrawing unconditionally here
|
||||||
|
* forced a full window redraw for what should have been
|
||||||
|
* just an active-pane change, already handled narrowly by
|
||||||
|
* window_set_active_pane() and window_redraw_active_switch()
|
||||||
|
* before this is reached.
|
||||||
|
*
|
||||||
|
* old and s may be the same session object, whose curw was
|
||||||
|
* already updated to the new window before this function was
|
||||||
|
* called - old->curw and s->curw would then read the same,
|
||||||
|
* already-current value, so comparing them can never detect
|
||||||
|
* a same-session window change. Compare against the client's
|
||||||
|
* own cached scene instead, which only reflects what it has
|
||||||
|
* actually drawn.
|
||||||
|
*/
|
||||||
|
if (old == NULL || old != s ||
|
||||||
|
!redraw_client_has_window(c, s->curw->window))
|
||||||
|
server_redraw_client(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
server_check_unattached();
|
server_check_unattached();
|
||||||
@@ -490,7 +399,6 @@ server_client_lost(struct client *c)
|
|||||||
cfg_client = NULL;
|
cfg_client = NULL;
|
||||||
c->flags |= CLIENT_DEAD;
|
c->flags |= CLIENT_DEAD;
|
||||||
|
|
||||||
server_client_clear_overlay(c);
|
|
||||||
status_prompt_clear(c);
|
status_prompt_clear(c);
|
||||||
status_message_clear(c);
|
status_message_clear(c);
|
||||||
|
|
||||||
@@ -502,6 +410,9 @@ server_client_lost(struct client *c)
|
|||||||
TAILQ_REMOVE(&clients, c, entry);
|
TAILQ_REMOVE(&clients, c, entry);
|
||||||
log_debug("lost client %p", c);
|
log_debug("lost client %p", c);
|
||||||
|
|
||||||
|
cmd_wait_for_client_lost(c);
|
||||||
|
cmdq_next(c);
|
||||||
|
|
||||||
if (c->flags & CLIENT_ATTACHED) {
|
if (c->flags & CLIENT_ATTACHED) {
|
||||||
server_client_attached_lost(c);
|
server_client_attached_lost(c);
|
||||||
events_fire_client("client-detached", c);
|
events_fire_client("client-detached", c);
|
||||||
@@ -1391,15 +1302,19 @@ server_client_repeat_time(struct client *c, struct key_binding *bd)
|
|||||||
return (repeat);
|
return (repeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle a key press on a dead pane waiting for a key. */
|
/* Handle a key press which closes a dead pane. */
|
||||||
static int
|
static int
|
||||||
server_client_handle_dead_key(struct window_pane *wp, key_code key)
|
server_client_handle_dead_key(struct window_pane *wp, key_code key)
|
||||||
{
|
{
|
||||||
|
int remain_on_exit;
|
||||||
|
|
||||||
if (wp == NULL ||
|
if (wp == NULL ||
|
||||||
(~wp->flags & PANE_EXITED) ||
|
(~wp->flags & PANE_EXITED) ||
|
||||||
KEYC_IS_MOUSE(key) ||
|
KEYC_IS_MOUSE(key) ||
|
||||||
KEYC_IS_PASTE(key) ||
|
KEYC_IS_PASTE(key))
|
||||||
options_get_number(wp->options, "remain-on-exit") != 3)
|
return (0);
|
||||||
|
remain_on_exit = options_get_number(wp->options, "remain-on-exit");
|
||||||
|
if (remain_on_exit != 3 && remain_on_exit != 4)
|
||||||
return (0);
|
return (0);
|
||||||
options_set_number(wp->options, "remain-on-exit", 0);
|
options_set_number(wp->options, "remain-on-exit", 0);
|
||||||
server_destroy_pane(wp, 0);
|
server_destroy_pane(wp, 0);
|
||||||
@@ -1462,8 +1377,19 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
|||||||
/*
|
/*
|
||||||
* Mouse drag is in progress, so fire the callback (now that
|
* Mouse drag is in progress, so fire the callback (now that
|
||||||
* the mouse event is valid).
|
* the mouse event is valid).
|
||||||
|
*
|
||||||
|
* Start a synchronized-output region here rather than
|
||||||
|
* leaving it to whatever redraw eventually follows: a drag
|
||||||
|
* callback may write directly via the pane's fast path
|
||||||
|
* immediately, with any correction only arriving later via
|
||||||
|
* redraw_client_damage(), which opens its own sync region.
|
||||||
|
* Since tty_sync_end() is only called once, at the very end
|
||||||
|
* of this client's pass in server_client_reset_state(),
|
||||||
|
* starting it here merges both into one atomic terminal
|
||||||
|
* update instead of two visible frames.
|
||||||
*/
|
*/
|
||||||
if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) {
|
if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) {
|
||||||
|
tty_sync_start(&c->tty);
|
||||||
c->tty.mouse_drag_update(c, m);
|
c->tty.mouse_drag_update(c, m);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@@ -1742,10 +1668,6 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
|||||||
if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
|
if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
|
||||||
return (0);
|
return (0);
|
||||||
|
|
||||||
/*
|
|
||||||
* Handle theme reporting keys before overlays so they work even when a
|
|
||||||
* popup is open.
|
|
||||||
*/
|
|
||||||
if (event->key == KEYC_REPORT_LIGHT_THEME) {
|
if (event->key == KEYC_REPORT_LIGHT_THEME) {
|
||||||
server_client_report_theme(c, THEME_LIGHT);
|
server_client_report_theme(c, THEME_LIGHT);
|
||||||
return (0);
|
return (0);
|
||||||
@@ -1756,9 +1678,9 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Key presses in overlay mode, for panes capturing all keys and in the
|
* Dead panes waiting for a key, modal cancel keys, panes capturing all keys
|
||||||
* command prompt are a special case. The queue might be blocked so they
|
* and the command prompt are special cases. The queue might be blocked so
|
||||||
* need to be processed immediately rather than queued.
|
* they need to be processed immediately rather than queued.
|
||||||
*/
|
*/
|
||||||
if (~c->flags & CLIENT_READONLY) {
|
if (~c->flags & CLIENT_READONLY) {
|
||||||
if (c->message_string != NULL) {
|
if (c->message_string != NULL) {
|
||||||
@@ -1767,25 +1689,20 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
|||||||
status_message_clear(c);
|
status_message_clear(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->overlay_key != NULL) {
|
|
||||||
switch (c->overlay_key(c, c->overlay_data, event)) {
|
|
||||||
case 0:
|
|
||||||
return (0);
|
|
||||||
case 1:
|
|
||||||
server_client_clear_overlay(c);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server_client_clear_overlay(c);
|
|
||||||
|
|
||||||
wp = s->curw->window->active;
|
wp = s->curw->window->active;
|
||||||
|
if (server_client_handle_dead_key(wp, event->key))
|
||||||
|
return (0);
|
||||||
|
if (wp != NULL &&
|
||||||
|
wp == wp->window->modal &&
|
||||||
|
(wp->flags & PANE_CLOSEONCANCEL) &&
|
||||||
|
(event->key == '\033' || event->key == ('c'|KEYC_CTRL))) {
|
||||||
|
server_kill_pane(wp);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
if (wp != NULL &&
|
if (wp != NULL &&
|
||||||
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
||||||
TAILQ_EMPTY(&wp->modes) &&
|
TAILQ_EMPTY(&wp->modes) &&
|
||||||
!KEYC_IS_MOUSE(event->key)) {
|
!KEYC_IS_MOUSE(event->key)) {
|
||||||
if (server_client_handle_dead_key(wp, event->key))
|
|
||||||
return (0);
|
|
||||||
if (~wp->flags & PANE_EXITED) {
|
if (~wp->flags & PANE_EXITED) {
|
||||||
window_pane_key(wp, c, s, s->curw, event->key,
|
window_pane_key(wp, c, s, s->curw, event->key,
|
||||||
&event->m);
|
&event->m);
|
||||||
@@ -1900,7 +1817,12 @@ server_client_loop(void)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Any windows will have been redrawn as part of clients, so clear
|
* Any windows will have been redrawn as part of clients, so clear
|
||||||
* their flags now.
|
* their flags now. A client whose redraw was deferred this pass
|
||||||
|
* (waiting for outstanding tty output to drain) has already
|
||||||
|
* escalated to CLIENT_REDRAWWINDOW or CLIENT_REDRAWSCROLLBARS in
|
||||||
|
* server_client_check_redraw() to cover whatever it is about to
|
||||||
|
* lose here, so PANE_REDRAW/PANE_REDRAWSCROLLBAR and window damage
|
||||||
|
* can simply be cleared unconditionally.
|
||||||
*/
|
*/
|
||||||
RB_FOREACH(w, windows, &windows) {
|
RB_FOREACH(w, windows, &windows) {
|
||||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||||
@@ -1911,6 +1833,8 @@ server_client_loop(void)
|
|||||||
wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR|
|
wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR|
|
||||||
PANE_ACTIVITY);
|
PANE_ACTIVITY);
|
||||||
}
|
}
|
||||||
|
redraw_free_damage(w);
|
||||||
|
|
||||||
check_window_name(w);
|
check_window_name(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2166,11 +2090,8 @@ server_client_reset_state(struct client *c)
|
|||||||
flags = (tty->flags & TTY_BLOCK);
|
flags = (tty->flags & TTY_BLOCK);
|
||||||
tty->flags &= ~TTY_BLOCK;
|
tty->flags &= ~TTY_BLOCK;
|
||||||
|
|
||||||
/* Get mode from overlay if any, else from screen. */
|
/* Get mode from the menu if any, else from the screen. */
|
||||||
if (c->overlay_draw != NULL) {
|
if (w->menu != NULL) {
|
||||||
if (c->overlay_mode != NULL)
|
|
||||||
s = c->overlay_mode(c, c->overlay_data, &cx, &cy);
|
|
||||||
} else if (w->menu != NULL) {
|
|
||||||
menu_get_cursor(w->menu, &cx, &cy);
|
menu_get_cursor(w->menu, &cx, &cy);
|
||||||
s = menu_screen(w->menu);
|
s = menu_screen(w->menu);
|
||||||
} else if (wp != NULL && c->prompt == NULL)
|
} else if (wp != NULL && c->prompt == NULL)
|
||||||
@@ -2192,7 +2113,7 @@ server_client_reset_state(struct client *c)
|
|||||||
if (c->prompt != NULL) {
|
if (c->prompt != NULL) {
|
||||||
prompt = 1;
|
prompt = 1;
|
||||||
status_prompt_cursor(c, &cx, &cy);
|
status_prompt_cursor(c, &cx, &cy);
|
||||||
} else if (wp != NULL && c->overlay_draw == NULL) {
|
} else if (wp != NULL) {
|
||||||
if (w->menu != NULL) {
|
if (w->menu != NULL) {
|
||||||
tty_window_offset(tty, &ox, &oy, &sx, &sy);
|
tty_window_offset(tty, &ox, &oy, &sx, &sy);
|
||||||
if (cx < ox || cx >= ox + sx ||
|
if (cx < ox || cx >= ox + sx ||
|
||||||
@@ -2247,7 +2168,7 @@ server_client_reset_state(struct client *c)
|
|||||||
if (!cursor)
|
if (!cursor)
|
||||||
mode &= ~MODE_CURSOR;
|
mode &= ~MODE_CURSOR;
|
||||||
}
|
}
|
||||||
} else if (c->overlay_mode == NULL || s == NULL)
|
} else if (s == NULL)
|
||||||
mode &= ~MODE_CURSOR;
|
mode &= ~MODE_CURSOR;
|
||||||
if (~pane_mode & MODE_SYNC) {
|
if (~pane_mode & MODE_SYNC) {
|
||||||
log_debug("%s: cursor to %u,%u", __func__, cx, cy);
|
log_debug("%s: cursor to %u,%u", __func__, cx, cy);
|
||||||
@@ -2264,7 +2185,7 @@ server_client_reset_state(struct client *c)
|
|||||||
* movement events.
|
* movement events.
|
||||||
*/
|
*/
|
||||||
if (options_get_number(oo, "mouse")) {
|
if (options_get_number(oo, "mouse")) {
|
||||||
if (c->overlay_draw == NULL && w->menu == NULL) {
|
if (w->menu == NULL) {
|
||||||
mode &= ~ALL_MOUSE_MODES;
|
mode &= ~ALL_MOUSE_MODES;
|
||||||
TAILQ_FOREACH(loop, &w->panes, entry) {
|
TAILQ_FOREACH(loop, &w->panes, entry) {
|
||||||
if (loop->screen->mode & MODE_MOUSE_ALL)
|
if (loop->screen->mode & MODE_MOUSE_ALL)
|
||||||
@@ -2280,7 +2201,7 @@ server_client_reset_state(struct client *c)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Clear bracketed paste mode if at the prompt. */
|
/* Clear bracketed paste mode if at the prompt. */
|
||||||
if (c->overlay_draw == NULL && prompt)
|
if (prompt)
|
||||||
mode &= ~MODE_BRACKETPASTE;
|
mode &= ~MODE_BRACKETPASTE;
|
||||||
|
|
||||||
/* Set the terminal mode and reset attributes. */
|
/* Set the terminal mode and reset attributes. */
|
||||||
@@ -2459,6 +2380,8 @@ server_client_any_pane_redraw(struct client *c)
|
|||||||
|
|
||||||
if (c->flags & CLIENT_REDRAWWINDOW)
|
if (c->flags & CLIENT_REDRAWWINDOW)
|
||||||
return (1);
|
return (1);
|
||||||
|
if (!TAILQ_EMPTY(&w->damage))
|
||||||
|
return (1);
|
||||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||||
if (wp->flags & (PANE_REDRAW|PANE_REDRAWSCROLLBAR))
|
if (wp->flags & (PANE_REDRAW|PANE_REDRAWSCROLLBAR))
|
||||||
return (1);
|
return (1);
|
||||||
@@ -2482,11 +2405,10 @@ server_client_check_redraw(struct client *c)
|
|||||||
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
|
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
|
||||||
return;
|
return;
|
||||||
if (c->flags & CLIENT_ALLREDRAWFLAGS) {
|
if (c->flags & CLIENT_ALLREDRAWFLAGS) {
|
||||||
log_debug("%s: redraw%s%s%s%s%s", c->name,
|
log_debug("%s: redraw%s%s%s%s", c->name,
|
||||||
(c->flags & CLIENT_REDRAWWINDOW) ? " window" : "",
|
(c->flags & CLIENT_REDRAWWINDOW) ? " window" : "",
|
||||||
(c->flags & CLIENT_REDRAWSTATUS) ? " status" : "",
|
(c->flags & CLIENT_REDRAWSTATUS) ? " status" : "",
|
||||||
(c->flags & CLIENT_REDRAWBORDERS) ? " borders" : "",
|
(c->flags & CLIENT_REDRAWBORDERS) ? " borders" : "",
|
||||||
(c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : "",
|
|
||||||
(c->flags & CLIENT_REDRAWMENU) ? " menu" : "");
|
(c->flags & CLIENT_REDRAWMENU) ? " menu" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2504,9 +2426,25 @@ server_client_check_redraw(struct client *c)
|
|||||||
/*
|
/*
|
||||||
* If there is outstanding data, defer the redraw until it has been
|
* If there is outstanding data, defer the redraw until it has been
|
||||||
* consumed. We can just add a timer to get out of the event loop and
|
* consumed. We can just add a timer to get out of the event loop and
|
||||||
* end up back here.
|
* end up back here. server_client_loop() clears PANE_REDRAW,
|
||||||
|
* PANE_REDRAWSCROLLBAR and window damage unconditionally every pass,
|
||||||
|
* so escalate to a coarser, persistent client flag that survives
|
||||||
|
* that clear and forces a full catch-up redraw once this client is
|
||||||
|
* unblocked, rather than trying to keep the fine-grained state
|
||||||
|
* around for a retry.
|
||||||
|
*
|
||||||
|
* If a synchronized-output frame is open, discount anything queued
|
||||||
|
* since it started (down to sync_offset, the length when it opened):
|
||||||
|
* those bytes are already part of the frame this pass is committed
|
||||||
|
* to flushing (see tty_sync_start()), not a reason to defer this
|
||||||
|
* pass's redraw - without this, a mouse-drag callback that itself
|
||||||
|
* opens the frame before writing anything would see its own
|
||||||
|
* just-queued bytes as "outstanding output" and defer against
|
||||||
|
* itself every single motion event.
|
||||||
*/
|
*/
|
||||||
n = EVBUFFER_LENGTH(tty->out);
|
n = EVBUFFER_LENGTH(tty->out);
|
||||||
|
if ((tty->flags & TTY_SYNCING) && n > tty->sync_offset)
|
||||||
|
n = tty->sync_offset;
|
||||||
if (n != 0 || (tty->flags & TTY_BLOCK)) {
|
if (n != 0 || (tty->flags & TTY_BLOCK)) {
|
||||||
if (n != 0)
|
if (n != 0)
|
||||||
log_debug("%s: redraw deferred (%zu left)", c->name, n);
|
log_debug("%s: redraw deferred (%zu left)", c->name, n);
|
||||||
@@ -2518,6 +2456,8 @@ server_client_check_redraw(struct client *c)
|
|||||||
log_debug("redraw timer started");
|
log_debug("redraw timer started");
|
||||||
evtimer_add(&ev, &tv);
|
evtimer_add(&ev, &tv);
|
||||||
}
|
}
|
||||||
|
if (!TAILQ_EMPTY(&w->damage))
|
||||||
|
c->flags |= CLIENT_REDRAWWINDOW;
|
||||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||||
if (wp->flags & PANE_REDRAW) {
|
if (wp->flags & PANE_REDRAW) {
|
||||||
c->flags |= CLIENT_REDRAWWINDOW;
|
c->flags |= CLIENT_REDRAWWINDOW;
|
||||||
@@ -2551,6 +2491,20 @@ server_client_check_redraw(struct client *c)
|
|||||||
redraw_pane_scrollbar(c, wp);
|
redraw_pane_scrollbar(c, wp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Window damage is also what makes server_client_any_pane_
|
||||||
|
* redraw() decide a redraw is needed at all, independently of
|
||||||
|
* any CLIENT_ALLREDRAWFLAGS bit. Every current damage source
|
||||||
|
* happens to set one of those flags too, so the block below
|
||||||
|
* always consumes it - but consume it here too in case that
|
||||||
|
* ever stops holding, since server_client_loop() clears
|
||||||
|
* window damage unconditionally every pass regardless of
|
||||||
|
* whether it was actually drawn.
|
||||||
|
*/
|
||||||
|
if (!TAILQ_EMPTY(&w->damage) &&
|
||||||
|
(c->flags & CLIENT_ALLREDRAWFLAGS) == 0)
|
||||||
|
redraw_client_damage(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -2564,6 +2518,7 @@ server_client_check_redraw(struct client *c)
|
|||||||
}
|
}
|
||||||
server_client_set_progress_bar(c);
|
server_client_set_progress_bar(c);
|
||||||
redraw_screen(c);
|
redraw_screen(c);
|
||||||
|
redraw_client_damage(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Put the tty back how it was. */
|
/* Put the tty back how it was. */
|
||||||
@@ -2694,10 +2649,6 @@ server_client_dispatch(struct imsg *imsg, void *arg)
|
|||||||
tty_resize(&c->tty);
|
tty_resize(&c->tty);
|
||||||
tty_repeat_requests(&c->tty, 0);
|
tty_repeat_requests(&c->tty, 0);
|
||||||
recalculate_sizes();
|
recalculate_sizes();
|
||||||
if (c->overlay_resize == NULL)
|
|
||||||
server_client_clear_overlay(c);
|
|
||||||
else
|
|
||||||
c->overlay_resize(c, c->overlay_data);
|
|
||||||
server_redraw_client(c);
|
server_redraw_client(c);
|
||||||
if (c->session != NULL)
|
if (c->session != NULL)
|
||||||
server_client_fire_resized(c, old_sx, old_sy);
|
server_client_fire_resized(c, old_sx, old_sy);
|
||||||
|
|||||||
+4
-3
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: server-fn.c,v 1.152 2026/09/03 19:12:36 nicm Exp $ */
|
/* $OpenBSD: server-fn.c,v 1.154 2026/09/21 12:43:36 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -310,8 +310,8 @@ server_link_window(struct session *src, struct winlink *srcwl,
|
|||||||
}
|
}
|
||||||
if (killflag) {
|
if (killflag) {
|
||||||
/*
|
/*
|
||||||
* Can't use session_detach as it will destroy session
|
* Can't use session_detach as it won't detach the only
|
||||||
* if this makes it empty.
|
* window.
|
||||||
*/
|
*/
|
||||||
events_fire_winlink("window-unlinked", dstwl);
|
events_fire_winlink("window-unlinked", dstwl);
|
||||||
dstwl->flags &= ~WINLINK_ALERTFLAGS;
|
dstwl->flags &= ~WINLINK_ALERTFLAGS;
|
||||||
@@ -386,6 +386,7 @@ server_destroy_pane(struct window_pane *wp, int notify)
|
|||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
|
case 4:
|
||||||
if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
|
if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
|
||||||
break;
|
break;
|
||||||
/* FALLTHROUGH */
|
/* FALLTHROUGH */
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: session.c,v 1.107 2026/08/05 07:35:35 nicm Exp $ */
|
/* $OpenBSD: session.c,v 1.108 2026/09/21 12:43:36 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -336,10 +336,17 @@ session_attach(struct session *s, struct window *w, int idx, char **cause)
|
|||||||
return (wl);
|
return (wl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Detach a window from a session. */
|
/*
|
||||||
|
* Detach a window from a session. Returns 1 if the window has not been
|
||||||
|
* detached - the caller must destroy the session.
|
||||||
|
*/
|
||||||
int
|
int
|
||||||
session_detach(struct session *s, struct winlink *wl)
|
session_detach(struct session *s, struct winlink *wl)
|
||||||
{
|
{
|
||||||
|
if (RB_MIN(winlinks, &s->windows) == wl &&
|
||||||
|
RB_MAX(winlinks, &s->windows) == wl)
|
||||||
|
return (1);
|
||||||
|
|
||||||
if (s->curw == wl &&
|
if (s->curw == wl &&
|
||||||
session_last(s) != 0 &&
|
session_last(s) != 0 &&
|
||||||
session_previous(s, 0) != 0)
|
session_previous(s, 0) != 0)
|
||||||
@@ -352,8 +359,6 @@ session_detach(struct session *s, struct winlink *wl)
|
|||||||
|
|
||||||
session_group_synchronize_from(s);
|
session_group_synchronize_from(s);
|
||||||
|
|
||||||
if (RB_EMPTY(&s->windows))
|
|
||||||
return (1);
|
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: spawn.c,v 1.52 2026/08/20 09:19:24 nicm Exp $ */
|
/* $OpenBSD: spawn.c,v 1.53 2026/09/21 12:43:36 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -166,8 +166,8 @@ spawn_window(struct spawn_context *sc, char **cause)
|
|||||||
}
|
}
|
||||||
if (wl != NULL) {
|
if (wl != NULL) {
|
||||||
/*
|
/*
|
||||||
* Can't use session_detach as it will destroy session
|
* Can't use session_detach as it won't detach the only
|
||||||
* if this makes it empty.
|
* window.
|
||||||
*/
|
*/
|
||||||
wl->flags &= ~WINLINK_ALERTFLAGS;
|
wl->flags &= ~WINLINK_ALERTFLAGS;
|
||||||
events_fire_winlink("window-unlinked", wl);
|
events_fire_winlink("window-unlinked", wl);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: status.c,v 1.273 2026/07/06 14:29:10 nicm Exp $ */
|
/* $OpenBSD: status.c,v 1.274 2026/09/21 10:22:31 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -577,8 +577,6 @@ status_prompt_set(struct client *c, struct cmd_find_state *fs,
|
|||||||
struct prompt_create_data pd;
|
struct prompt_create_data pd;
|
||||||
struct status_prompt_data *spd;
|
struct status_prompt_data *spd;
|
||||||
|
|
||||||
server_client_clear_overlay(c);
|
|
||||||
|
|
||||||
status_message_clear(c);
|
status_message_clear(c);
|
||||||
status_prompt_clear(c);
|
status_prompt_clear(c);
|
||||||
status_push_screen(c);
|
status_push_screen(c);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.\" $OpenBSD: tmux.1,v 1.1168 2026/09/09 07:03:39 nicm Exp $
|
.\" $OpenBSD: tmux.1,v 1.1172 2026/09/22 06:58:05 nicm Exp $
|
||||||
.\"
|
.\"
|
||||||
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
.\"
|
.\"
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||||
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
.\"
|
.\"
|
||||||
.Dd $Mdocdate: September 9 2026 $
|
.Dd $Mdocdate: September 22 2026 $
|
||||||
.Dt TMUX 1
|
.Dt TMUX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -3655,7 +3655,7 @@ but a different format may be specified with
|
|||||||
.Fl F .
|
.Fl F .
|
||||||
.Tg newp
|
.Tg newp
|
||||||
.It Xo Ic new\-pane
|
.It Xo Ic new\-pane
|
||||||
.Op Fl AbCdefhIkKLMOPvWZ
|
.Op Fl AbCDefhIkKLMOPvWZ
|
||||||
.Op Fl B Ar border\-lines
|
.Op Fl B Ar border\-lines
|
||||||
.Op Fl c Ar start\-directory
|
.Op Fl c Ar start\-directory
|
||||||
.Op Fl e Ar environment
|
.Op Fl e Ar environment
|
||||||
@@ -3717,6 +3717,13 @@ all keys, including the prefix key, are passed directly to the modal pane.
|
|||||||
With
|
With
|
||||||
.Fl C ,
|
.Fl C ,
|
||||||
the modal pane is closed when the mouse is clicked outside it.
|
the modal pane is closed when the mouse is clicked outside it.
|
||||||
|
With
|
||||||
|
.Fl D ,
|
||||||
|
the modal pane is closed when
|
||||||
|
.Ql Escape
|
||||||
|
or
|
||||||
|
.Ql C-c
|
||||||
|
is pressed.
|
||||||
.Pp
|
.Pp
|
||||||
The
|
The
|
||||||
.Fl L
|
.Fl L
|
||||||
@@ -5078,6 +5085,9 @@ The available features are:
|
|||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It 256
|
.It 256
|
||||||
Supports 256 colours with the SGR escape sequences.
|
Supports 256 colours with the SGR escape sequences.
|
||||||
|
.It appesc
|
||||||
|
Supports application escape key mode, where the Escape key sends a sequence
|
||||||
|
that cannot be mistaken for the start of another key.
|
||||||
.It clipboard
|
.It clipboard
|
||||||
Allows setting the system clipboard.
|
Allows setting the system clipboard.
|
||||||
.It ccolour
|
.It ccolour
|
||||||
@@ -5346,10 +5356,16 @@ section on how to specify
|
|||||||
.Ar style .
|
.Ar style .
|
||||||
.It Ic menu\-border\-lines Ar type
|
.It Ic menu\-border\-lines Ar type
|
||||||
Set the type of characters used for drawing menu borders.
|
Set the type of characters used for drawing menu borders.
|
||||||
See
|
.Ar type
|
||||||
.Ic popup\-border\-lines
|
may be one of
|
||||||
for possible values for
|
.Ic single ,
|
||||||
.Ar border\-lines .
|
.Ic rounded ,
|
||||||
|
.Ic double ,
|
||||||
|
.Ic heavy ,
|
||||||
|
.Ic simple ,
|
||||||
|
.Ic padded ,
|
||||||
|
or
|
||||||
|
.Ic none .
|
||||||
.It Ic message\-command\-style Ar style
|
.It Ic message\-command\-style Ar style
|
||||||
Set status line message command style.
|
Set status line message command style.
|
||||||
This is used for the command prompt with
|
This is used for the command prompt with
|
||||||
@@ -5973,6 +5989,8 @@ single lines using ACS or UTF\-8 characters
|
|||||||
double lines using UTF\-8 characters
|
double lines using UTF\-8 characters
|
||||||
.It heavy
|
.It heavy
|
||||||
heavy lines using UTF\-8 characters
|
heavy lines using UTF\-8 characters
|
||||||
|
.It rounded
|
||||||
|
single lines with rounded corners using UTF\-8 characters
|
||||||
.It simple
|
.It simple
|
||||||
simple ASCII characters
|
simple ASCII characters
|
||||||
.It number
|
.It number
|
||||||
@@ -6007,48 +6025,6 @@ see the
|
|||||||
section.
|
section.
|
||||||
Attributes are ignored.
|
Attributes are ignored.
|
||||||
.Pp
|
.Pp
|
||||||
.It Ic popup\-style Ar style
|
|
||||||
Set the popup style.
|
|
||||||
See the
|
|
||||||
.Sx STYLES
|
|
||||||
section on how to specify
|
|
||||||
.Ar style .
|
|
||||||
Attributes are ignored.
|
|
||||||
.Pp
|
|
||||||
.It Ic popup\-border\-style Ar style
|
|
||||||
Set the popup border style.
|
|
||||||
See the
|
|
||||||
.Sx STYLES
|
|
||||||
section on how to specify
|
|
||||||
.Ar style .
|
|
||||||
Attributes are ignored.
|
|
||||||
.Pp
|
|
||||||
.It Ic popup\-border\-lines Ar type
|
|
||||||
Set the type of characters used for drawing popup borders.
|
|
||||||
.Ar type
|
|
||||||
may be one of:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It single
|
|
||||||
single lines using ACS or UTF\-8 characters (default)
|
|
||||||
.It rounded
|
|
||||||
variation of single with rounded corners using UTF\-8 characters
|
|
||||||
.It double
|
|
||||||
double lines using UTF\-8 characters
|
|
||||||
.It heavy
|
|
||||||
heavy lines using UTF\-8 characters
|
|
||||||
.It simple
|
|
||||||
simple ASCII characters
|
|
||||||
.It padded
|
|
||||||
simple ASCII space character
|
|
||||||
.It none
|
|
||||||
no border
|
|
||||||
.El
|
|
||||||
.Pp
|
|
||||||
.Ql double
|
|
||||||
and
|
|
||||||
.Ql heavy
|
|
||||||
will fall back to standard ACS line drawing when UTF\-8 is not supported.
|
|
||||||
.Pp
|
|
||||||
.It Xo Ic pane\-scrollbars
|
.It Xo Ic pane\-scrollbars
|
||||||
.Op Ic off | modal | on | auto\-hide
|
.Op Ic off | modal | on | auto\-hide
|
||||||
.Xc
|
.Xc
|
||||||
@@ -6328,7 +6304,7 @@ uses when the colour with that index is requested.
|
|||||||
The index may be from zero to 255.
|
The index may be from zero to 255.
|
||||||
.Pp
|
.Pp
|
||||||
.It Xo Ic remain\-on\-exit
|
.It Xo Ic remain\-on\-exit
|
||||||
.Op Ic on | off | failed | key
|
.Op Ic on | off | failed | key | failed\-key
|
||||||
.Xc
|
.Xc
|
||||||
A pane with this flag set is not destroyed when the program running in it
|
A pane with this flag set is not destroyed when the program running in it
|
||||||
exits.
|
exits.
|
||||||
@@ -6338,6 +6314,10 @@ then only when the program exit status is not zero.
|
|||||||
If set to
|
If set to
|
||||||
.Ic key ,
|
.Ic key ,
|
||||||
the pane stays open and closes when a key is pressed.
|
the pane stays open and closes when a key is pressed.
|
||||||
|
If set to
|
||||||
|
.Ic failed\-key ,
|
||||||
|
the pane stays open and closes when a key is pressed only if the program exit
|
||||||
|
status is not zero.
|
||||||
The pane may be reactivated with the
|
The pane may be reactivated with the
|
||||||
.Ic respawn\-pane
|
.Ic respawn\-pane
|
||||||
command.
|
command.
|
||||||
@@ -8191,10 +8171,16 @@ command should be omitted.
|
|||||||
.Pp
|
.Pp
|
||||||
.Fl b
|
.Fl b
|
||||||
sets the type of characters used for drawing menu borders.
|
sets the type of characters used for drawing menu borders.
|
||||||
See
|
.Ar border\-lines
|
||||||
.Ic popup\-border\-lines
|
may be one of
|
||||||
for possible values for
|
.Ic single ,
|
||||||
.Ar border\-lines .
|
.Ic rounded ,
|
||||||
|
.Ic double ,
|
||||||
|
.Ic heavy ,
|
||||||
|
.Ic simple ,
|
||||||
|
.Ic padded ,
|
||||||
|
or
|
||||||
|
.Ic none .
|
||||||
.Pp
|
.Pp
|
||||||
.Fl H
|
.Fl H
|
||||||
sets the style for the selected menu item (see
|
sets the style for the selected menu item (see
|
||||||
@@ -8230,15 +8216,18 @@ Both may be a row or column number, or one of the following special values:
|
|||||||
.It Li "S" Ta Fl y Ta "The line above or below the status line"
|
.It Li "S" Ta Fl y Ta "The line above or below the status line"
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Or a format, which is expanded including the following additional variables:
|
Or a format, which is expanded with the following additional variables.
|
||||||
|
The
|
||||||
|
.Ql popup_
|
||||||
|
prefix is retained for compatibility:
|
||||||
.Bl -column "XXXXXXXXXXXXXXXXXXXXXXXXXX" -offset indent
|
.Bl -column "XXXXXXXXXXXXXXXXXXXXXXXXXX" -offset indent
|
||||||
.It Sy "Variable name" Ta Sy "Replaced with"
|
.It Sy "Variable name" Ta Sy "Replaced with"
|
||||||
.It Li "popup_centre_x" Ta "Centered in the client"
|
.It Li "popup_centre_x" Ta "Centered in the window"
|
||||||
.It Li "popup_centre_y" Ta "Centered in the client"
|
.It Li "popup_centre_y" Ta "Centered in the window"
|
||||||
.It Li "popup_height" Ta "Height of menu or popup"
|
.It Li "popup_height" Ta "Height of the menu"
|
||||||
.It Li "popup_last_x" Ta "Left of the last menu"
|
.It Li "popup_last_x" Ta "Left of the last menu"
|
||||||
.It Li "popup_last_y" Ta "Bottom of the last menu"
|
.It Li "popup_last_y" Ta "Bottom of the last menu"
|
||||||
.It Li "popup_mouse_bottom" Ta "Bottom of at the mouse"
|
.It Li "popup_mouse_bottom" Ta "Bottom at the mouse"
|
||||||
.It Li "popup_mouse_centre_x" Ta "Horizontal centre at the mouse"
|
.It Li "popup_mouse_centre_x" Ta "Horizontal centre at the mouse"
|
||||||
.It Li "popup_mouse_centre_y" Ta "Vertical centre at the mouse"
|
.It Li "popup_mouse_centre_y" Ta "Vertical centre at the mouse"
|
||||||
.It Li "popup_mouse_top" Ta "Top at the mouse"
|
.It Li "popup_mouse_top" Ta "Top at the mouse"
|
||||||
@@ -8249,7 +8238,7 @@ Or a format, which is expanded including the following additional variables:
|
|||||||
.It Li "popup_pane_right" Ta "Right of the pane"
|
.It Li "popup_pane_right" Ta "Right of the pane"
|
||||||
.It Li "popup_pane_top" Ta "Top of the pane"
|
.It Li "popup_pane_top" Ta "Top of the pane"
|
||||||
.It Li "popup_status_line_y" Ta "Above or below the status line"
|
.It Li "popup_status_line_y" Ta "Above or below the status line"
|
||||||
.It Li "popup_width" Ta "Width of menu or popup"
|
.It Li "popup_width" Ta "Width of the menu"
|
||||||
.It Li "popup_window_status_line_x" Ta "At the window position in status line"
|
.It Li "popup_window_status_line_x" Ta "At the window position in status line"
|
||||||
.It Li "popup_window_status_line_y" Ta "At the status line showing the window"
|
.It Li "popup_window_status_line_y" Ta "At the status line showing the window"
|
||||||
.El
|
.El
|
||||||
@@ -8335,116 +8324,6 @@ lists the format variables and their values.
|
|||||||
.Fl I
|
.Fl I
|
||||||
forwards any input read from stdin to the empty pane given by
|
forwards any input read from stdin to the empty pane given by
|
||||||
.Ar target\-pane .
|
.Ar target\-pane .
|
||||||
.Tg popup
|
|
||||||
.It Xo Ic display\-popup
|
|
||||||
.Op Fl BCEkN
|
|
||||||
.Op Fl b Ar border\-lines
|
|
||||||
.Op Fl c Ar target\-client
|
|
||||||
.Op Fl d Ar start\-directory
|
|
||||||
.Op Fl e Ar environment
|
|
||||||
.Op Fl h Ar height
|
|
||||||
.Op Fl s Ar style
|
|
||||||
.Op Fl S Ar border\-style
|
|
||||||
.Op Fl t Ar target\-pane
|
|
||||||
.Op Fl T Ar title
|
|
||||||
.Op Fl w Ar width
|
|
||||||
.Op Fl x Ar position
|
|
||||||
.Op Fl y Ar position
|
|
||||||
.Op Ar shell\-command Op Ar argument ...
|
|
||||||
.Xc
|
|
||||||
.D1 Pq alias: Ic popup
|
|
||||||
Display a popup running
|
|
||||||
.Ar shell\-command
|
|
||||||
(or
|
|
||||||
.Ar default\-command
|
|
||||||
when omitted) on
|
|
||||||
.Ar target\-client .
|
|
||||||
A popup is a rectangular box drawn over the top of any panes.
|
|
||||||
If the command is run inside an existing popup, that popup is modified.
|
|
||||||
Only the
|
|
||||||
.Fl b ,
|
|
||||||
.Fl B ,
|
|
||||||
.Fl C ,
|
|
||||||
.Fl E ,
|
|
||||||
.Fl EE ,
|
|
||||||
.Fl K ,
|
|
||||||
.Fl N ,
|
|
||||||
.Fl s ,
|
|
||||||
and
|
|
||||||
.Fl S
|
|
||||||
options are accepted in this case;
|
|
||||||
all others are ignored.
|
|
||||||
.Pp
|
|
||||||
.Fl E
|
|
||||||
closes the popup automatically when
|
|
||||||
.Ar shell\-command
|
|
||||||
exits.
|
|
||||||
Two
|
|
||||||
.Fl E
|
|
||||||
closes the popup only if
|
|
||||||
.Ar shell\-command
|
|
||||||
exited with success.
|
|
||||||
.Fl k
|
|
||||||
allows any key to dismiss the popup instead of only
|
|
||||||
.Ql Escape
|
|
||||||
or
|
|
||||||
.Ql C\-c .
|
|
||||||
.Pp
|
|
||||||
.Fl x
|
|
||||||
and
|
|
||||||
.Fl y
|
|
||||||
give the position of the popup, they have the same meaning as for the
|
|
||||||
.Ic display\-menu
|
|
||||||
command.
|
|
||||||
.Fl w
|
|
||||||
and
|
|
||||||
.Fl h
|
|
||||||
give the width and height - both may be a percentage (followed by
|
|
||||||
.Ql % ) .
|
|
||||||
If omitted, half of the terminal size is used.
|
|
||||||
.Pp
|
|
||||||
.Fl B
|
|
||||||
does not surround the popup by a border.
|
|
||||||
.Pp
|
|
||||||
.Fl b
|
|
||||||
sets the type of characters used for drawing popup borders.
|
|
||||||
When
|
|
||||||
.Fl B
|
|
||||||
is specified, the
|
|
||||||
.Fl b
|
|
||||||
option is ignored.
|
|
||||||
See
|
|
||||||
.Ic popup\-border\-lines
|
|
||||||
for possible values for
|
|
||||||
.Ar border\-lines .
|
|
||||||
.Pp
|
|
||||||
.Fl s
|
|
||||||
sets the style for the popup and
|
|
||||||
.Fl S
|
|
||||||
sets the style for the popup border (see
|
|
||||||
.Sx STYLES ) .
|
|
||||||
.Pp
|
|
||||||
.Fl e
|
|
||||||
takes the form
|
|
||||||
.Ql VARIABLE=value
|
|
||||||
and sets an environment variable for the popup; it may be specified multiple
|
|
||||||
times.
|
|
||||||
.Pp
|
|
||||||
.Fl T
|
|
||||||
is a format for the popup title (see
|
|
||||||
.Sx FORMATS ) .
|
|
||||||
.Pp
|
|
||||||
The
|
|
||||||
.Fl C
|
|
||||||
flag closes any popup on the client.
|
|
||||||
.Pp
|
|
||||||
.Fl N
|
|
||||||
disables any previously specified
|
|
||||||
.Fl E ,
|
|
||||||
.Fl EE ,
|
|
||||||
or
|
|
||||||
.Fl k
|
|
||||||
option.
|
|
||||||
.Tg showphist
|
.Tg showphist
|
||||||
.It Xo Ic show\-prompt\-history
|
.It Xo Ic show\-prompt\-history
|
||||||
.Op Fl T Ar prompt\-type
|
.Op Fl T Ar prompt\-type
|
||||||
@@ -8957,6 +8836,8 @@ These are set automatically if the
|
|||||||
capability is present.
|
capability is present.
|
||||||
.It Em \&Dseks , \&Eneks
|
.It Em \&Dseks , \&Eneks
|
||||||
Disable and enable extended keys.
|
Disable and enable extended keys.
|
||||||
|
.It Em \&Dsesc , \&Enesc
|
||||||
|
Disable and enable application escape key mode.
|
||||||
.It Em \&Dsfcs , \&Enfcs
|
.It Em \&Dsfcs , \&Enfcs
|
||||||
Disable and enable focus reporting.
|
Disable and enable focus reporting.
|
||||||
These are set automatically if the
|
These are set automatically if the
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: tmux.h,v 1.1438 2026/09/09 08:30:05 nicm Exp $ */
|
/* $OpenBSD: tmux.h,v 1.1447 2026/09/22 14:10:26 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -69,6 +69,7 @@ struct options_array_item;
|
|||||||
struct options_entry;
|
struct options_entry;
|
||||||
struct prompt;
|
struct prompt;
|
||||||
struct window_pane_prompt;
|
struct window_pane_prompt;
|
||||||
|
struct redraw_damage;
|
||||||
struct redraw_scene;
|
struct redraw_scene;
|
||||||
struct redraw_span;
|
struct redraw_span;
|
||||||
struct screen_write_citem;
|
struct screen_write_citem;
|
||||||
@@ -464,6 +465,7 @@ enum tty_code_code {
|
|||||||
TTYC_DL1,
|
TTYC_DL1,
|
||||||
TTYC_DSBP,
|
TTYC_DSBP,
|
||||||
TTYC_DSEKS,
|
TTYC_DSEKS,
|
||||||
|
TTYC_DSESC,
|
||||||
TTYC_DSFCS,
|
TTYC_DSFCS,
|
||||||
TTYC_DSMG,
|
TTYC_DSMG,
|
||||||
TTYC_E3,
|
TTYC_E3,
|
||||||
@@ -474,6 +476,7 @@ enum tty_code_code {
|
|||||||
TTYC_ENACS,
|
TTYC_ENACS,
|
||||||
TTYC_ENBP,
|
TTYC_ENBP,
|
||||||
TTYC_ENEKS,
|
TTYC_ENEKS,
|
||||||
|
TTYC_ENESC,
|
||||||
TTYC_ENFCS,
|
TTYC_ENFCS,
|
||||||
TTYC_ENMG,
|
TTYC_ENMG,
|
||||||
TTYC_FSL,
|
TTYC_FSL,
|
||||||
@@ -1150,7 +1153,8 @@ enum pane_lines {
|
|||||||
PANE_LINES_SIMPLE,
|
PANE_LINES_SIMPLE,
|
||||||
PANE_LINES_NUMBER,
|
PANE_LINES_NUMBER,
|
||||||
PANE_LINES_SPACES,
|
PANE_LINES_SPACES,
|
||||||
PANE_LINES_NONE
|
PANE_LINES_NONE,
|
||||||
|
PANE_LINES_ROUNDED
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Pane border indicator option. */
|
/* Pane border indicator option. */
|
||||||
@@ -1181,6 +1185,7 @@ struct menu {
|
|||||||
struct menu_item *items;
|
struct menu_item *items;
|
||||||
u_int count;
|
u_int count;
|
||||||
u_int width;
|
u_int width;
|
||||||
|
u_int item_width;
|
||||||
};
|
};
|
||||||
typedef void (*menu_choice_cb)(struct menu *, u_int, key_code, void *);
|
typedef void (*menu_choice_cb)(struct menu *, u_int, key_code, void *);
|
||||||
|
|
||||||
@@ -1339,6 +1344,7 @@ struct window_pane {
|
|||||||
#define PANE_CLOSEONCLICK 0x80000
|
#define PANE_CLOSEONCLICK 0x80000
|
||||||
#define PANE_CAPTUREALLKEYS 0x100000
|
#define PANE_CAPTUREALLKEYS 0x100000
|
||||||
#define PANE_FLOATOVERZOOM 0x200000
|
#define PANE_FLOATOVERZOOM 0x200000
|
||||||
|
#define PANE_CLOSEONCANCEL 0x400000
|
||||||
|
|
||||||
bitstr_t *sync_dirty;
|
bitstr_t *sync_dirty;
|
||||||
u_int sync_dirty_size;
|
u_int sync_dirty_size;
|
||||||
@@ -1397,6 +1403,7 @@ struct window_pane {
|
|||||||
struct screen base;
|
struct screen base;
|
||||||
|
|
||||||
struct screen status_screen;
|
struct screen status_screen;
|
||||||
|
u_int status_serial;
|
||||||
|
|
||||||
TAILQ_HEAD(, window_mode_entry) modes;
|
TAILQ_HEAD(, window_mode_entry) modes;
|
||||||
|
|
||||||
@@ -1427,6 +1434,7 @@ struct window_pane {
|
|||||||
TAILQ_HEAD(window_panes, window_pane);
|
TAILQ_HEAD(window_panes, window_pane);
|
||||||
TAILQ_HEAD(window_panes_zindex, window_pane);
|
TAILQ_HEAD(window_panes_zindex, window_pane);
|
||||||
RB_HEAD(window_pane_tree, window_pane);
|
RB_HEAD(window_pane_tree, window_pane);
|
||||||
|
TAILQ_HEAD(redraw_damages, redraw_damage);
|
||||||
|
|
||||||
/* Window structure. */
|
/* Window structure. */
|
||||||
struct window {
|
struct window {
|
||||||
@@ -1470,6 +1478,9 @@ struct window {
|
|||||||
|
|
||||||
uint64_t redraw_scene_generation;
|
uint64_t redraw_scene_generation;
|
||||||
|
|
||||||
|
struct redraw_damages damage;
|
||||||
|
u_int damage_count;
|
||||||
|
|
||||||
struct menu_data *menu;
|
struct menu_data *menu;
|
||||||
u_int menu_last_px;
|
u_int menu_last_px;
|
||||||
u_int menu_last_py;
|
u_int menu_last_py;
|
||||||
@@ -1737,6 +1748,7 @@ struct tty_term {
|
|||||||
#define TERM_VT100LIKE 0x20
|
#define TERM_VT100LIKE 0x20
|
||||||
#define TERM_SIXEL 0x40
|
#define TERM_SIXEL 0x40
|
||||||
#define TERM_INVALIDMS 0x80
|
#define TERM_INVALIDMS 0x80
|
||||||
|
#define TERM_NOREPLACE 0x100
|
||||||
int flags;
|
int flags;
|
||||||
|
|
||||||
LIST_ENTRY(tty_term) entry;
|
LIST_ENTRY(tty_term) entry;
|
||||||
@@ -1794,8 +1806,15 @@ struct tty {
|
|||||||
struct event timer;
|
struct event timer;
|
||||||
size_t discarded;
|
size_t discarded;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Buffer length at the instant a synchronized-output frame opened
|
||||||
|
* (tty_sync_start()), so server_client_check_redraw()'s "is there
|
||||||
|
* already outstanding output" check can discount whatever this
|
||||||
|
* pass itself queued into that frame - see tty_sync_start().
|
||||||
|
*/
|
||||||
|
size_t sync_offset;
|
||||||
|
|
||||||
struct termios tio;
|
struct termios tio;
|
||||||
struct visible_ranges r;
|
|
||||||
|
|
||||||
struct grid_cell cell;
|
struct grid_cell cell;
|
||||||
struct grid_cell last_cell;
|
struct grid_cell last_cell;
|
||||||
@@ -1842,7 +1861,7 @@ struct tty {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Terminal command context. */
|
/* Terminal command context. */
|
||||||
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *);
|
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *, u_int, u_int);
|
||||||
typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *);
|
typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *);
|
||||||
struct tty_ctx {
|
struct tty_ctx {
|
||||||
struct screen *s;
|
struct screen *s;
|
||||||
@@ -1857,7 +1876,6 @@ struct tty_ctx {
|
|||||||
#define TTY_CTX_INVISIBLE_PANES 0x2
|
#define TTY_CTX_INVISIBLE_PANES 0x2
|
||||||
#define TTY_CTX_WINDOW_BIGGER 0x4
|
#define TTY_CTX_WINDOW_BIGGER 0x4
|
||||||
#define TTY_CTX_SYNC 0x8
|
#define TTY_CTX_SYNC 0x8
|
||||||
#define TTY_CTX_OVERLAY_SYNC 0x10
|
|
||||||
#define TTY_CTX_CELL_INVALIDATE 0x20
|
#define TTY_CTX_CELL_INVALIDATE 0x20
|
||||||
#define TTY_CTX_PANE_OBSCURED 0x40
|
#define TTY_CTX_PANE_OBSCURED 0x40
|
||||||
|
|
||||||
@@ -2192,16 +2210,6 @@ struct prompt_draw_data {
|
|||||||
u_int prompt_line;
|
u_int prompt_line;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Overlay callbacks */
|
|
||||||
typedef struct visible_ranges *(*overlay_check_cb)(struct client *, void *,
|
|
||||||
u_int, u_int, u_int);
|
|
||||||
typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *,
|
|
||||||
u_int *);
|
|
||||||
typedef void (*overlay_draw_cb)(struct client *, void *);
|
|
||||||
typedef int (*overlay_key_cb)(struct client *, void *, struct key_event *);
|
|
||||||
typedef void (*overlay_free_cb)(struct client *, void *);
|
|
||||||
typedef void (*overlay_resize_cb)(struct client *, void *);
|
|
||||||
|
|
||||||
/* Client connection. */
|
/* Client connection. */
|
||||||
struct client {
|
struct client {
|
||||||
const char *name;
|
const char *name;
|
||||||
@@ -2287,7 +2295,7 @@ struct client {
|
|||||||
#define CLIENT_SIZECHANGED 0x400000
|
#define CLIENT_SIZECHANGED 0x400000
|
||||||
#define CLIENT_STATUSOFF 0x800000
|
#define CLIENT_STATUSOFF 0x800000
|
||||||
#define CLIENT_REDRAWSTATUSALWAYS 0x1000000
|
#define CLIENT_REDRAWSTATUSALWAYS 0x1000000
|
||||||
#define CLIENT_REDRAWOVERLAY 0x2000000
|
/* 0x2000000 unused */
|
||||||
#define CLIENT_CONTROL_NOOUTPUT 0x4000000
|
#define CLIENT_CONTROL_NOOUTPUT 0x4000000
|
||||||
#define CLIENT_DEFAULTSOCKET 0x8000000
|
#define CLIENT_DEFAULTSOCKET 0x8000000
|
||||||
#define CLIENT_STARTSERVER 0x10000000
|
#define CLIENT_STARTSERVER 0x10000000
|
||||||
@@ -2302,13 +2310,12 @@ struct client {
|
|||||||
#define CLIENT_ASSUMEPASTING 0x2000000000ULL
|
#define CLIENT_ASSUMEPASTING 0x2000000000ULL
|
||||||
#define CLIENT_WRITE_ACK 0x4000000000ULL
|
#define CLIENT_WRITE_ACK 0x4000000000ULL
|
||||||
#define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL
|
#define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL
|
||||||
#define CLIENT_CONTROL_DISCARD 0x1000000000ULL
|
#define CLIENT_CONTROL_DISCARD 0x10000000000ULL
|
||||||
#define CLIENT_ALLREDRAWFLAGS \
|
#define CLIENT_ALLREDRAWFLAGS \
|
||||||
(CLIENT_REDRAWWINDOW| \
|
(CLIENT_REDRAWWINDOW| \
|
||||||
CLIENT_REDRAWSTATUS| \
|
CLIENT_REDRAWSTATUS| \
|
||||||
CLIENT_REDRAWSTATUSALWAYS| \
|
CLIENT_REDRAWSTATUSALWAYS| \
|
||||||
CLIENT_REDRAWBORDERS| \
|
CLIENT_REDRAWBORDERS| \
|
||||||
CLIENT_REDRAWOVERLAY| \
|
|
||||||
CLIENT_REDRAWMENU)
|
CLIENT_REDRAWMENU)
|
||||||
#define CLIENT_UNATTACHEDFLAGS \
|
#define CLIENT_UNATTACHEDFLAGS \
|
||||||
(CLIENT_DEAD| \
|
(CLIENT_DEAD| \
|
||||||
@@ -2354,15 +2361,6 @@ struct client {
|
|||||||
u_int pan_ox;
|
u_int pan_ox;
|
||||||
u_int pan_oy;
|
u_int pan_oy;
|
||||||
|
|
||||||
overlay_check_cb overlay_check;
|
|
||||||
overlay_mode_cb overlay_mode;
|
|
||||||
overlay_draw_cb overlay_draw;
|
|
||||||
overlay_key_cb overlay_key;
|
|
||||||
overlay_free_cb overlay_free;
|
|
||||||
overlay_resize_cb overlay_resize;
|
|
||||||
void *overlay_data;
|
|
||||||
struct event overlay_timer;
|
|
||||||
|
|
||||||
struct client_files files;
|
struct client_files files;
|
||||||
u_int source_file_depth;
|
u_int source_file_depth;
|
||||||
|
|
||||||
@@ -2964,8 +2962,6 @@ void tty_default_attributes(struct tty *, u_int,
|
|||||||
void tty_update_mode(struct tty *, int, struct screen *);
|
void tty_update_mode(struct tty *, int, struct screen *);
|
||||||
const struct grid_cell *tty_check_codeset(struct tty *,
|
const struct grid_cell *tty_check_codeset(struct tty *,
|
||||||
const struct grid_cell *);
|
const struct grid_cell *);
|
||||||
struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int,
|
|
||||||
u_int);
|
|
||||||
void tty_sync_start(struct tty *);
|
void tty_sync_start(struct tty *);
|
||||||
void tty_sync_end(struct tty *);
|
void tty_sync_end(struct tty *);
|
||||||
int tty_open(struct tty *, char **);
|
int tty_open(struct tty *, char **);
|
||||||
@@ -3006,7 +3002,7 @@ void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *);
|
|||||||
/* tty-term.c */
|
/* tty-term.c */
|
||||||
extern struct tty_terms tty_terms;
|
extern struct tty_terms tty_terms;
|
||||||
u_int tty_term_ncodes(void);
|
u_int tty_term_ncodes(void);
|
||||||
void tty_term_apply(struct tty_term *, const char *, int);
|
void tty_term_apply(struct tty_term *, const char *, int, int);
|
||||||
void tty_term_apply_overrides(struct tty_term *);
|
void tty_term_apply_overrides(struct tty_term *);
|
||||||
struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, char **);
|
struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, char **);
|
||||||
void tty_term_free(struct tty_term *);
|
void tty_term_free(struct tty_term *);
|
||||||
@@ -3226,6 +3222,7 @@ void cmdq_print_data(struct cmdq_item *, struct evbuffer *);
|
|||||||
void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...);
|
void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...);
|
||||||
|
|
||||||
/* cmd-wait-for.c */
|
/* cmd-wait-for.c */
|
||||||
|
void cmd_wait_for_client_lost(struct client *);
|
||||||
void cmd_wait_for_flush(void);
|
void cmd_wait_for_flush(void);
|
||||||
|
|
||||||
/* client.c */
|
/* client.c */
|
||||||
@@ -3316,14 +3313,8 @@ int server_create_socket(uint64_t, char **);
|
|||||||
|
|
||||||
/* server-client.c */
|
/* server-client.c */
|
||||||
u_int server_client_how_many(void);
|
u_int server_client_how_many(void);
|
||||||
void server_client_set_overlay(struct client *, u_int, overlay_check_cb,
|
|
||||||
overlay_mode_cb, overlay_draw_cb, overlay_key_cb,
|
|
||||||
overlay_free_cb, overlay_resize_cb, void *);
|
|
||||||
void server_client_clear_overlay(struct client *);
|
|
||||||
void server_client_ensure_ranges(struct visible_ranges *, u_int);
|
void server_client_ensure_ranges(struct visible_ranges *, u_int);
|
||||||
int server_client_ranges_is_empty(struct visible_ranges *);
|
int server_client_ranges_is_empty(struct visible_ranges *);
|
||||||
void server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int,
|
|
||||||
u_int, struct visible_ranges *);
|
|
||||||
void server_client_set_key_table(struct client *, const char *);
|
void server_client_set_key_table(struct client *, const char *);
|
||||||
const char *server_client_get_key_table(struct client *);
|
const char *server_client_get_key_table(struct client *);
|
||||||
int server_client_check_nested(struct client *);
|
int server_client_check_nested(struct client *);
|
||||||
@@ -3432,7 +3423,7 @@ void recalculate_sizes_now(int);
|
|||||||
/* input.c */
|
/* input.c */
|
||||||
#define INPUT_BUF_DEFAULT_SIZE 1048576
|
#define INPUT_BUF_DEFAULT_SIZE 1048576
|
||||||
struct input_ctx *input_init(struct window_pane *, struct bufferevent *,
|
struct input_ctx *input_init(struct window_pane *, struct bufferevent *,
|
||||||
struct colour_palette *, struct client *);
|
struct colour_palette *);
|
||||||
void input_free(struct input_ctx *);
|
void input_free(struct input_ctx *);
|
||||||
void input_reset(struct input_ctx *, int);
|
void input_reset(struct input_ctx *, int);
|
||||||
struct evbuffer *input_pending(struct input_ctx *);
|
struct evbuffer *input_pending(struct input_ctx *);
|
||||||
@@ -3657,8 +3648,12 @@ void redraw_screen(struct client *);
|
|||||||
void redraw_pane(struct client *, struct window_pane *);
|
void redraw_pane(struct client *, struct window_pane *);
|
||||||
void redraw_pane_scrollbar(struct client *, struct window_pane *);
|
void redraw_pane_scrollbar(struct client *, struct window_pane *);
|
||||||
void redraw_free_scene(struct redraw_scene *);
|
void redraw_free_scene(struct redraw_scene *);
|
||||||
|
int redraw_client_has_window(struct client *, struct window *);
|
||||||
void redraw_invalidate_scene(struct window *);
|
void redraw_invalidate_scene(struct window *);
|
||||||
void redraw_invalidate_all_scenes(void);
|
void redraw_invalidate_all_scenes(void);
|
||||||
|
void redraw_damage_window(struct window *, u_int, u_int, u_int, u_int);
|
||||||
|
void redraw_free_damage(struct window *);
|
||||||
|
void redraw_client_damage(struct client *);
|
||||||
int redraw_get_status_border_cell_type(struct redraw_span **, u_int);
|
int redraw_get_status_border_cell_type(struct redraw_span **, u_int);
|
||||||
|
|
||||||
/* screen.c */
|
/* screen.c */
|
||||||
@@ -3832,6 +3827,9 @@ int window_pane_get_pane_status(struct window_pane *);
|
|||||||
struct style_range *window_pane_status_get_range(struct window_pane *, u_int,
|
struct style_range *window_pane_status_get_range(struct window_pane *, u_int,
|
||||||
u_int);
|
u_int);
|
||||||
int window_pane_is_floating(struct window_pane *);
|
int window_pane_is_floating(struct window_pane *);
|
||||||
|
void window_pane_redraw_floating(struct window *,
|
||||||
|
struct window_pane *, int, int, int, int);
|
||||||
|
int window_pane_is_floating_with_hidden(struct window_pane *);
|
||||||
|
|
||||||
/* window-border.c */
|
/* window-border.c */
|
||||||
void window_set_fill_cells(struct window *);
|
void window_set_fill_cells(struct window *);
|
||||||
@@ -4171,6 +4169,7 @@ void menu_add_item(struct menu *, const struct menu_item *,
|
|||||||
struct cmdq_item *, struct client *,
|
struct cmdq_item *, struct client *,
|
||||||
struct cmd_find_state *);
|
struct cmd_find_state *);
|
||||||
void menu_free(struct menu *);
|
void menu_free(struct menu *);
|
||||||
|
void menu_get_size(struct menu *, enum box_lines, u_int *, u_int *);
|
||||||
int menu_display(struct menu *, int, int, struct cmdq_item *,
|
int menu_display(struct menu *, int, int, struct cmdq_item *,
|
||||||
u_int, u_int, struct client *, enum box_lines, const char *,
|
u_int, u_int, struct client *, enum box_lines, const char *,
|
||||||
const char *, const char *, struct cmd_find_state *,
|
const char *, const char *, struct cmd_find_state *,
|
||||||
@@ -4187,20 +4186,6 @@ void menu_get_cursor(struct menu_data *, u_int *, u_int *);
|
|||||||
void menu_resize(struct menu_data *, struct window *);
|
void menu_resize(struct menu_data *, struct window *);
|
||||||
int menu_key(struct client *, struct menu_data *, struct key_event *);
|
int menu_key(struct client *, struct menu_data *, struct key_event *);
|
||||||
|
|
||||||
/* popup.c */
|
|
||||||
#define POPUP_CLOSEEXIT 0x1
|
|
||||||
#define POPUP_CLOSEEXITZERO 0x2
|
|
||||||
#define POPUP_CLOSEANYKEY 0x4
|
|
||||||
typedef void (*popup_close_cb)(int, void *);
|
|
||||||
int popup_display(int, enum box_lines, struct cmdq_item *, u_int,
|
|
||||||
u_int, u_int, u_int, struct environ *, const char *, int,
|
|
||||||
char **, const char *, const char *, struct client *,
|
|
||||||
struct session *, const char *, const char *,
|
|
||||||
popup_close_cb, void *);
|
|
||||||
int popup_present(struct client *);
|
|
||||||
int popup_modify(struct client *, const char *, const char *,
|
|
||||||
const char *, enum box_lines, int);
|
|
||||||
|
|
||||||
/* style.c */
|
/* style.c */
|
||||||
int style_parse(struct style *,const struct grid_cell *,
|
int style_parse(struct style *,const struct grid_cell *,
|
||||||
const char *);
|
const char *);
|
||||||
|
|||||||
+8
-4
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: tty-draw.c,v 1.15 2026/07/26 09:02:08 nicm Exp $ */
|
/* $OpenBSD: tty-draw.c,v 1.17 2026/09/22 06:49:47 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -52,8 +52,7 @@ tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx,
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* If genuine BCE is available, can try escape sequences. */
|
/* If genuine BCE is available, can try escape sequences. */
|
||||||
if (tty->client->overlay_check == NULL &&
|
if (!wrapped &&
|
||||||
!wrapped &&
|
|
||||||
nx >= 10 &&
|
nx >= 10 &&
|
||||||
!tty_fake_bce(tty, defaults, bg)) {
|
!tty_fake_bce(tty, defaults, bg)) {
|
||||||
/* Off the end of the line, use EL if available. */
|
/* Off the end of the line, use EL if available. */
|
||||||
@@ -219,7 +218,12 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Did the previous line wrap on to this one? */
|
/* Did the previous line wrap on to this one? */
|
||||||
if (py != 0 && atx == 0 && tty->cx >= tty->sx && nx == tty->sx) {
|
if (py != 0 &&
|
||||||
|
atx == 0 &&
|
||||||
|
tty->cx >= tty->sx &&
|
||||||
|
tty->cy != UINT_MAX &&
|
||||||
|
tty->cy + 1 == aty &&
|
||||||
|
nx == tty->sx) {
|
||||||
gl = grid_get_line(gd, gd->hsize + py - 1);
|
gl = grid_get_line(gd, gd->hsize + py - 1);
|
||||||
if (gl->flags & GRID_LINE_WRAPPED)
|
if (gl->flags & GRID_LINE_WRAPPED)
|
||||||
wrapped = 1;
|
wrapped = 1;
|
||||||
|
|||||||
+21
-5
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: tty-features.c,v 1.43 2026/08/31 12:41:03 kirill Exp $ */
|
/* $OpenBSD: tty-features.c,v 1.45 2026/09/22 14:10:26 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -189,6 +189,18 @@ static const struct tty_feature tty_feature_focus = {
|
|||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Terminal supports application escape key mode. */
|
||||||
|
static const char *const tty_feature_appesc_capabilities[] = {
|
||||||
|
"Enesc=\\E[?7727h",
|
||||||
|
"Dsesc=\\E[?7727l",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
static const struct tty_feature tty_feature_appesc = {
|
||||||
|
"appesc",
|
||||||
|
tty_feature_appesc_capabilities,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
/* Terminal supports cursor styles. */
|
/* Terminal supports cursor styles. */
|
||||||
static const char *const tty_feature_cstyle_capabilities[] = {
|
static const char *const tty_feature_cstyle_capabilities[] = {
|
||||||
"Ss=\\E[%p1%d q",
|
"Ss=\\E[%p1%d q",
|
||||||
@@ -198,7 +210,7 @@ static const char *const tty_feature_cstyle_capabilities[] = {
|
|||||||
static const struct tty_feature tty_feature_cstyle = {
|
static const struct tty_feature tty_feature_cstyle = {
|
||||||
"cstyle",
|
"cstyle",
|
||||||
tty_feature_cstyle_capabilities,
|
tty_feature_cstyle_capabilities,
|
||||||
0
|
TERM_NOREPLACE
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Terminal supports cursor colours. */
|
/* Terminal supports cursor colours. */
|
||||||
@@ -378,6 +390,7 @@ static const struct tty_feature tty_feature_utf8 = {
|
|||||||
/* Available terminal features. */
|
/* Available terminal features. */
|
||||||
static const struct tty_feature *const tty_features[] = {
|
static const struct tty_feature *const tty_features[] = {
|
||||||
&tty_feature_256,
|
&tty_feature_256,
|
||||||
|
&tty_feature_appesc,
|
||||||
&tty_feature_bpaste,
|
&tty_feature_bpaste,
|
||||||
&tty_feature_ccolour,
|
&tty_feature_ccolour,
|
||||||
&tty_feature_clipboard,
|
&tty_feature_clipboard,
|
||||||
@@ -541,11 +554,11 @@ tty_apply_features(struct tty_term *term)
|
|||||||
capability = tf->capabilities;
|
capability = tf->capabilities;
|
||||||
while (*capability != NULL) {
|
while (*capability != NULL) {
|
||||||
log_debug("adding capability: %s", *capability);
|
log_debug("adding capability: %s", *capability);
|
||||||
tty_term_apply(term, *capability, 1);
|
tty_term_apply(term, *capability, 1, tf->flags);
|
||||||
capability++;
|
capability++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
term->flags |= tf->flags;
|
term->flags |= (tf->flags & ~TERM_NOREPLACE);
|
||||||
if (tf == &tty_feature_utf8)
|
if (tf == &tty_feature_utf8)
|
||||||
c->flags |= CLIENT_UTF8;
|
c->flags |= CLIENT_UTF8;
|
||||||
}
|
}
|
||||||
@@ -568,6 +581,7 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
|||||||
"256,RGB,bpaste,clipboard,mouse,strikethrough,title"
|
"256,RGB,bpaste,clipboard,mouse,strikethrough,title"
|
||||||
{ .name = "mintty",
|
{ .name = "mintty",
|
||||||
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
|
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
|
||||||
|
"appesc,"
|
||||||
"ccolour,"
|
"ccolour,"
|
||||||
"cstyle,"
|
"cstyle,"
|
||||||
"extkeys,"
|
"extkeys,"
|
||||||
@@ -623,6 +637,7 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
|||||||
"extkeys,"
|
"extkeys,"
|
||||||
"focus,"
|
"focus,"
|
||||||
"hyperlinks,"
|
"hyperlinks,"
|
||||||
|
"margins,"
|
||||||
"usstyle"
|
"usstyle"
|
||||||
},
|
},
|
||||||
{ .name = "ghostty",
|
{ .name = "ghostty",
|
||||||
@@ -631,6 +646,7 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
|||||||
"cstyle,"
|
"cstyle,"
|
||||||
"extkeys,"
|
"extkeys,"
|
||||||
"focus,"
|
"focus,"
|
||||||
|
"margins,"
|
||||||
"overline,"
|
"overline,"
|
||||||
"hyperlinks,"
|
"hyperlinks,"
|
||||||
"osc7,"
|
"osc7,"
|
||||||
@@ -661,7 +677,7 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
|||||||
"cstyle,"
|
"cstyle,"
|
||||||
"extkeys,"
|
"extkeys,"
|
||||||
"focus"
|
"focus"
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
u_int i;
|
u_int i;
|
||||||
|
|
||||||
|
|||||||
+9
-3
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: tty-term.c,v 1.109 2026/08/25 08:37:08 nicm Exp $ */
|
/* $OpenBSD: tty-term.c,v 1.111 2026/09/22 14:10:26 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -90,6 +90,7 @@ static const struct tty_term_code_entry tty_term_codes[] = {
|
|||||||
[TTYC_DL1] = { TTYCODE_STRING, "dl1" },
|
[TTYC_DL1] = { TTYCODE_STRING, "dl1" },
|
||||||
[TTYC_DL] = { TTYCODE_STRING, "dl" },
|
[TTYC_DL] = { TTYCODE_STRING, "dl" },
|
||||||
[TTYC_DSEKS] = { TTYCODE_STRING, "Dseks" },
|
[TTYC_DSEKS] = { TTYCODE_STRING, "Dseks" },
|
||||||
|
[TTYC_DSESC] = { TTYCODE_STRING, "Dsesc" },
|
||||||
[TTYC_DSFCS] = { TTYCODE_STRING, "Dsfcs" },
|
[TTYC_DSFCS] = { TTYCODE_STRING, "Dsfcs" },
|
||||||
[TTYC_DSBP] = { TTYCODE_STRING, "Dsbp" },
|
[TTYC_DSBP] = { TTYCODE_STRING, "Dsbp" },
|
||||||
[TTYC_DSMG] = { TTYCODE_STRING, "Dsmg" },
|
[TTYC_DSMG] = { TTYCODE_STRING, "Dsmg" },
|
||||||
@@ -101,6 +102,7 @@ static const struct tty_term_code_entry tty_term_codes[] = {
|
|||||||
[TTYC_ENACS] = { TTYCODE_STRING, "enacs" },
|
[TTYC_ENACS] = { TTYCODE_STRING, "enacs" },
|
||||||
[TTYC_ENBP] = { TTYCODE_STRING, "Enbp" },
|
[TTYC_ENBP] = { TTYCODE_STRING, "Enbp" },
|
||||||
[TTYC_ENEKS] = { TTYCODE_STRING, "Eneks" },
|
[TTYC_ENEKS] = { TTYCODE_STRING, "Eneks" },
|
||||||
|
[TTYC_ENESC] = { TTYCODE_STRING, "Enesc" },
|
||||||
[TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" },
|
[TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" },
|
||||||
[TTYC_ENMG] = { TTYCODE_STRING, "Enmg" },
|
[TTYC_ENMG] = { TTYCODE_STRING, "Enmg" },
|
||||||
[TTYC_FSL] = { TTYCODE_STRING, "fsl" },
|
[TTYC_FSL] = { TTYCODE_STRING, "fsl" },
|
||||||
@@ -362,7 +364,8 @@ tty_term_override_next(const char *s, size_t *offset)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
tty_term_apply(struct tty_term *term, const char *capabilities, int quiet)
|
tty_term_apply(struct tty_term *term, const char *capabilities, int quiet,
|
||||||
|
int flags)
|
||||||
{
|
{
|
||||||
const struct tty_term_code_entry *ent;
|
const struct tty_term_code_entry *ent;
|
||||||
struct tty_code *code;
|
struct tty_code *code;
|
||||||
@@ -405,6 +408,9 @@ tty_term_apply(struct tty_term *term, const char *capabilities, int quiet)
|
|||||||
if (strcmp(s, ent->name) != 0)
|
if (strcmp(s, ent->name) != 0)
|
||||||
continue;
|
continue;
|
||||||
code = &term->codes[i];
|
code = &term->codes[i];
|
||||||
|
if ((flags & TERM_NOREPLACE) &&
|
||||||
|
code->type != TTYCODE_NONE)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (remove) {
|
if (remove) {
|
||||||
code->type = TTYCODE_NONE;
|
code->type = TTYCODE_NONE;
|
||||||
@@ -457,7 +463,7 @@ tty_term_apply_overrides(struct tty_term *term)
|
|||||||
offset = 0;
|
offset = 0;
|
||||||
first = tty_term_override_next(s, &offset);
|
first = tty_term_override_next(s, &offset);
|
||||||
if (first != NULL && fnmatch(first, term->name, 0) == 0)
|
if (first != NULL && fnmatch(first, term->name, 0) == 0)
|
||||||
tty_term_apply(term, s + offset, 0);
|
tty_term_apply(term, s + offset, 0, 0);
|
||||||
a = options_array_next(a);
|
a = options_array_next(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: tty.c,v 1.480 2026/08/25 08:37:08 nicm Exp $ */
|
/* $OpenBSD: tty.c,v 1.482 2026/09/22 06:58:06 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -66,7 +66,6 @@ static void tty_redraw_region(struct tty *, const struct tty_ctx *);
|
|||||||
static void tty_emulate_repeat(struct tty *, enum tty_code_code,
|
static void tty_emulate_repeat(struct tty *, enum tty_code_code,
|
||||||
enum tty_code_code, u_int);
|
enum tty_code_code, u_int);
|
||||||
static void tty_draw_pane(struct tty *, const struct tty_ctx *, 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
|
#ifdef ENABLE_SIXEL
|
||||||
static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *),
|
static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *),
|
||||||
@@ -503,8 +502,7 @@ tty_stop_tty(struct tty *tty)
|
|||||||
if (tty_term_has(tty->term, TTYC_DSBP))
|
if (tty_term_has(tty->term, TTYC_DSBP))
|
||||||
tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP));
|
tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP));
|
||||||
|
|
||||||
if (tty->term->flags & TERM_VT100LIKE)
|
tty_raw(tty, tty_term_string(tty->term, TTYC_DSESC));
|
||||||
tty_raw(tty, "\033[?7727l");
|
|
||||||
tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS));
|
tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS));
|
||||||
tty_raw(tty, tty_term_string(tty->term, TTYC_DSEKS));
|
tty_raw(tty, tty_term_string(tty->term, TTYC_DSEKS));
|
||||||
|
|
||||||
@@ -545,8 +543,6 @@ void
|
|||||||
tty_free(struct tty *tty)
|
tty_free(struct tty *tty)
|
||||||
{
|
{
|
||||||
tty_close(tty);
|
tty_close(tty);
|
||||||
|
|
||||||
free(tty->r.ranges);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -563,8 +559,7 @@ tty_update_features(struct tty *tty)
|
|||||||
tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS));
|
tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS));
|
||||||
if (options_get_number(global_options, "focus-events"))
|
if (options_get_number(global_options, "focus-events"))
|
||||||
tty_puts(tty, tty_term_string(tty->term, TTYC_ENFCS));
|
tty_puts(tty, tty_term_string(tty->term, TTYC_ENFCS));
|
||||||
if (tty->term->flags & TERM_VT100LIKE)
|
tty_puts(tty, tty_term_string(tty->term, TTYC_ENESC));
|
||||||
tty_puts(tty, "\033[?7727h");
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Features might have changed since the first draw during attach. For
|
* Features might have changed since the first draw during attach. For
|
||||||
@@ -1120,7 +1115,7 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
*/
|
*/
|
||||||
if (tty_large_region(tty, ctx) || ctx->flags & TTY_CTX_PANE_OBSCURED) {
|
if (tty_large_region(tty, ctx) || ctx->flags & TTY_CTX_PANE_OBSCURED) {
|
||||||
log_debug("%s: %s large region redraw", __func__, c->name);
|
log_debug("%s: %s large region redraw", __func__, c->name);
|
||||||
ctx->redraw_cb(ctx);
|
ctx->redraw_cb(ctx, ctx->orupper, ctx->orlower - ctx->orupper + 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1201,9 +1196,6 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py,
|
|||||||
u_int px, u_int nx, u_int bg)
|
u_int px, u_int nx, u_int bg)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
struct client *c = tty->client;
|
||||||
struct visible_ranges *r;
|
|
||||||
struct visible_range *rr;
|
|
||||||
u_int i;
|
|
||||||
|
|
||||||
log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py);
|
log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py);
|
||||||
|
|
||||||
@@ -1212,7 +1204,7 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py,
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* If genuine BCE is available, can try escape sequences. */
|
/* If genuine BCE is available, can try escape sequences. */
|
||||||
if (c->overlay_check == NULL && !tty_fake_bce(tty, defaults, bg)) {
|
if (!tty_fake_bce(tty, defaults, bg)) {
|
||||||
/* Off the end of the line, use EL if available. */
|
/* Off the end of the line, use EL if available. */
|
||||||
if (px + nx >= tty->sx && tty_term_has(tty->term, TTYC_EL)) {
|
if (px + nx >= tty->sx && tty_term_has(tty->term, TTYC_EL)) {
|
||||||
tty_cursor(tty, px, py);
|
tty_cursor(tty, px, py);
|
||||||
@@ -1235,18 +1227,9 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* Couldn't use an escape sequence, use spaces. */
|
||||||
* Couldn't use an escape sequence, use spaces. Clear only the visible
|
tty_cursor(tty, px, py);
|
||||||
* bit if there is an overlay.
|
tty_repeat_space(tty, nx);
|
||||||
*/
|
|
||||||
r = tty_check_overlay_range(tty, px, py, nx);
|
|
||||||
for (i = 0; i < r->used; i++) {
|
|
||||||
rr = &r->ranges[i];
|
|
||||||
if (rr->nx != 0) {
|
|
||||||
tty_cursor(tty, rr->px, py);
|
|
||||||
tty_repeat_space(tty, rr->nx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear a line, adjusting to visible part of pane. */
|
/* Clear a line, adjusting to visible part of pane. */
|
||||||
@@ -1255,22 +1238,12 @@ tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py,
|
|||||||
u_int px, u_int nx, u_int bg)
|
u_int px, u_int nx, u_int bg)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
struct client *c = tty->client;
|
||||||
struct visible_ranges *r;
|
u_int l, x, rx, ry;
|
||||||
struct visible_range *ri;
|
|
||||||
u_int i, l, x, rx, ry;
|
|
||||||
|
|
||||||
log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py);
|
log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py);
|
||||||
|
|
||||||
if (tty_clamp_line(tty, ctx, px, py, nx, &l, &x, &rx, &ry)) {
|
if (tty_clamp_line(tty, ctx, px, py, nx, &l, &x, &rx, &ry))
|
||||||
r = tty_check_overlay_range(tty, x, ry, rx);
|
tty_clear_line(tty, &ctx->defaults, ry, x, rx, bg);
|
||||||
for (i = 0; i < r->used; i++) {
|
|
||||||
ri = &r->ranges[i];
|
|
||||||
if (ri->nx == 0)
|
|
||||||
continue;
|
|
||||||
tty_clear_line(tty, &ctx->defaults, ry, ri->px, ri->nx,
|
|
||||||
bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clamp area position to visible part of pane. */
|
/* Clamp area position to visible part of pane. */
|
||||||
@@ -1351,11 +1324,8 @@ tty_clear_area(struct tty *tty, const struct tty_ctx *ctx, u_int py,
|
|||||||
if (nx == 0 || ny == 0)
|
if (nx == 0 || ny == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/*
|
/* If BCE is available, can try to clear as a region. */
|
||||||
* If there is an overlay or BCE is not available, cannot clear as a
|
if (!tty_fake_bce(tty, defaults, bg)) {
|
||||||
* region.
|
|
||||||
*/
|
|
||||||
if (c->overlay_check == NULL && !tty_fake_bce(tty, defaults, bg)) {
|
|
||||||
/* Use ED if clearing off the bottom of the terminal. */
|
/* Use ED if clearing off the bottom of the terminal. */
|
||||||
if (px == 0 &&
|
if (px == 0 &&
|
||||||
px + nx >= tty->sx &&
|
px + nx >= tty->sx &&
|
||||||
@@ -1427,53 +1397,28 @@ static void
|
|||||||
tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py)
|
tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py)
|
||||||
{
|
{
|
||||||
struct screen *s = ctx->s;
|
struct screen *s = ctx->s;
|
||||||
u_int nx = ctx->sx, i, x, rx, ry, j;
|
u_int nx = ctx->sx, i, x, rx, ry;
|
||||||
struct visible_ranges *r;
|
|
||||||
struct visible_range *rr;
|
|
||||||
|
|
||||||
log_debug("%s: %s %u", __func__, tty->client->name, py);
|
log_debug("%s: %s %u", __func__, tty->client->name, py);
|
||||||
|
|
||||||
if (~ctx->flags & TTY_CTX_WINDOW_BIGGER) {
|
if (~ctx->flags & TTY_CTX_WINDOW_BIGGER) {
|
||||||
r = tty_check_overlay_range(tty, ctx->xoff, ctx->yoff + py, nx);
|
tty_draw_line(tty, s, 0, py, nx, ctx->xoff, ctx->yoff + py,
|
||||||
for (j = 0; j < r->used; j++) {
|
&ctx->style_ctx);
|
||||||
rr = &r->ranges[j];
|
|
||||||
if (rr->nx == 0)
|
|
||||||
continue;
|
|
||||||
tty_draw_line(tty, s, rr->px - ctx->xoff, py, rr->nx,
|
|
||||||
rr->px, ctx->yoff + py, &ctx->style_ctx);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) {
|
if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry))
|
||||||
r = tty_check_overlay_range(tty, x, ry, rx);
|
tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->style_ctx);
|
||||||
for (j = 0; j < r->used; j++) {
|
|
||||||
rr = &r->ranges[j];
|
|
||||||
if (rr->nx == 0)
|
|
||||||
continue;
|
|
||||||
tty_draw_line(tty, s, i + rr->px - x, py, rr->nx,
|
|
||||||
rr->px, ry, &ctx->style_ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
tty_cmd_redrawline(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_redrawline(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
u_int i, x, rx, ry, j;
|
u_int i, x, rx, ry;
|
||||||
struct visible_ranges *r;
|
|
||||||
struct visible_range *rr;
|
|
||||||
|
|
||||||
if (tty_clamp_line(tty, ctx, ctx->ocx, ctx->ocy, ctx->n,
|
if (tty_clamp_line(tty, ctx, ctx->ocx, ctx->ocy, ctx->n,
|
||||||
&i, &x, &rx, &ry)) {
|
&i, &x, &rx, &ry))
|
||||||
r = tty_check_overlay_range(tty, x, ry, rx);
|
tty_draw_line(tty, ctx->s, ctx->ocx + i, ctx->ocy, rx, x, ry,
|
||||||
for (j = 0; j < r->used; j++) {
|
&ctx->style_ctx);
|
||||||
rr = &r->ranges[j];
|
|
||||||
if (rr->nx == 0)
|
|
||||||
continue;
|
|
||||||
tty_draw_line(tty, ctx->s, ctx->ocx + i + rr->px - x,
|
|
||||||
ctx->ocy, rr->nx, rr->px, ry, &ctx->style_ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if character needs to be mapped for codeset. */
|
/* Check if character needs to be mapped for codeset. */
|
||||||
@@ -1510,36 +1455,6 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc)
|
|||||||
return (&new);
|
return (&new);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if a single character is covered by the overlay. */
|
|
||||||
static int
|
|
||||||
tty_check_overlay(struct tty *tty, u_int px, u_int py)
|
|
||||||
{
|
|
||||||
struct visible_ranges *r;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* With a single character, if there is anything visible (that is, the
|
|
||||||
* range is not empty), it must be that character.
|
|
||||||
*/
|
|
||||||
r = tty_check_overlay_range(tty, px, py, 1);
|
|
||||||
return (!server_client_ranges_is_empty(r));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return parts of the input range which are visible. */
|
|
||||||
struct visible_ranges *
|
|
||||||
tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx)
|
|
||||||
{
|
|
||||||
struct client *c = tty->client;
|
|
||||||
|
|
||||||
if (c->overlay_check == NULL) {
|
|
||||||
server_client_ensure_ranges(&tty->r, 1);
|
|
||||||
tty->r.ranges[0].px = px;
|
|
||||||
tty->r.ranges[0].nx = nx;
|
|
||||||
tty->r.used = 1;
|
|
||||||
return (&tty->r);
|
|
||||||
}
|
|
||||||
return (c->overlay_check(c, c->overlay_data, px, py, nx));
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ENABLE_SIXEL
|
#ifdef ENABLE_SIXEL
|
||||||
/* Update context for client. */
|
/* Update context for client. */
|
||||||
static int
|
static int
|
||||||
@@ -1602,6 +1517,7 @@ tty_sync_start(struct tty *tty)
|
|||||||
if (tty->flags & TTY_SYNCING)
|
if (tty->flags & TTY_SYNCING)
|
||||||
return;
|
return;
|
||||||
tty->flags |= TTY_SYNCING;
|
tty->flags |= TTY_SYNCING;
|
||||||
|
tty->sync_offset = EVBUFFER_LENGTH(tty->out);
|
||||||
|
|
||||||
if (tty_term_has(tty->term, TTYC_SYNC)) {
|
if (tty_term_has(tty->term, TTYC_SYNC)) {
|
||||||
log_debug("%s sync start", tty->client->name);
|
log_debug("%s sync start", tty->client->name);
|
||||||
@@ -1683,14 +1599,11 @@ tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *),
|
|||||||
void
|
void
|
||||||
tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
|
||||||
|
|
||||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||||
!tty_full_width(tty, ctx) ||
|
!tty_full_width(tty, ctx) ||
|
||||||
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
||||||
(!tty_term_has(tty->term, TTYC_ICH) &&
|
(!tty_term_has(tty->term, TTYC_ICH) &&
|
||||||
!tty_term_has(tty->term, TTYC_ICH1)) ||
|
!tty_term_has(tty->term, TTYC_ICH1))) {
|
||||||
c->overlay_check != NULL) {
|
|
||||||
tty_draw_pane(tty, ctx, ctx->ocy);
|
tty_draw_pane(tty, ctx, ctx->ocy);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1705,14 +1618,11 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
|
||||||
|
|
||||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||||
!tty_full_width(tty, ctx) ||
|
!tty_full_width(tty, ctx) ||
|
||||||
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
||||||
(!tty_term_has(tty->term, TTYC_DCH) &&
|
(!tty_term_has(tty->term, TTYC_DCH) &&
|
||||||
!tty_term_has(tty->term, TTYC_DCH1)) ||
|
!tty_term_has(tty->term, TTYC_DCH1))) {
|
||||||
c->overlay_check != NULL) {
|
|
||||||
tty_draw_pane(tty, ctx, ctx->ocy);
|
tty_draw_pane(tty, ctx, ctx->ocy);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1735,16 +1645,13 @@ tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
|
||||||
|
|
||||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||||
!tty_full_width(tty, ctx) ||
|
!tty_full_width(tty, ctx) ||
|
||||||
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
||||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||||
!tty_term_has(tty->term, TTYC_IL1) ||
|
!tty_term_has(tty->term, TTYC_IL1) ||
|
||||||
ctx->sx == 1 ||
|
ctx->sx == 1 ||
|
||||||
ctx->sy == 1 ||
|
ctx->sy == 1) {
|
||||||
c->overlay_check != NULL) {
|
|
||||||
tty_redraw_region(tty, ctx);
|
tty_redraw_region(tty, ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1762,16 +1669,13 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
|
||||||
|
|
||||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||||
!tty_full_width(tty, ctx) ||
|
!tty_full_width(tty, ctx) ||
|
||||||
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
|
||||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||||
!tty_term_has(tty->term, TTYC_DL1) ||
|
!tty_term_has(tty->term, TTYC_DL1) ||
|
||||||
ctx->sx == 1 ||
|
ctx->sx == 1 ||
|
||||||
ctx->sy == 1 ||
|
ctx->sy == 1) {
|
||||||
c->overlay_check != NULL) {
|
|
||||||
tty_redraw_region(tty, ctx);
|
tty_redraw_region(tty, ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1815,8 +1719,6 @@ tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
|
||||||
|
|
||||||
if (ctx->ocy != ctx->orupper)
|
if (ctx->ocy != ctx->orupper)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -1827,8 +1729,7 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
(!tty_term_has(tty->term, TTYC_RI) &&
|
(!tty_term_has(tty->term, TTYC_RI) &&
|
||||||
!tty_term_has(tty->term, TTYC_RIN)) ||
|
!tty_term_has(tty->term, TTYC_RIN)) ||
|
||||||
ctx->sx == 1 ||
|
ctx->sx == 1 ||
|
||||||
ctx->sy == 1 ||
|
ctx->sy == 1) {
|
||||||
c->overlay_check != NULL) {
|
|
||||||
tty_redraw_region(tty, ctx);
|
tty_redraw_region(tty, ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1848,8 +1749,6 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
|
||||||
|
|
||||||
if (ctx->ocy != ctx->orlower)
|
if (ctx->ocy != ctx->orlower)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -1858,8 +1757,7 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
tty_fake_bce(tty, &ctx->defaults, 8) ||
|
tty_fake_bce(tty, &ctx->defaults, 8) ||
|
||||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||||
ctx->sx == 1 ||
|
ctx->sx == 1 ||
|
||||||
ctx->sy == 1 ||
|
ctx->sy == 1) {
|
||||||
c->overlay_check != NULL) {
|
|
||||||
tty_redraw_region(tty, ctx);
|
tty_redraw_region(tty, ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1890,7 +1788,6 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
|
||||||
u_int i;
|
u_int i;
|
||||||
|
|
||||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||||
@@ -1898,8 +1795,7 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
tty_fake_bce(tty, &ctx->defaults, 8) ||
|
tty_fake_bce(tty, &ctx->defaults, 8) ||
|
||||||
!tty_term_has(tty->term, TTYC_CSR) ||
|
!tty_term_has(tty->term, TTYC_CSR) ||
|
||||||
ctx->sx == 1 ||
|
ctx->sx == 1 ||
|
||||||
ctx->sy == 1 ||
|
ctx->sy == 1) {
|
||||||
c->overlay_check != NULL) {
|
|
||||||
tty_redraw_region(tty, ctx);
|
tty_redraw_region(tty, ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1929,7 +1825,6 @@ void
|
|||||||
tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
u_int i;
|
u_int i;
|
||||||
struct client *c = tty->client;
|
|
||||||
|
|
||||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||||
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
|
(!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
|
||||||
@@ -1938,8 +1833,7 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
(!tty_term_has(tty->term, TTYC_RI) &&
|
(!tty_term_has(tty->term, TTYC_RI) &&
|
||||||
!tty_term_has(tty->term, TTYC_RIN)) ||
|
!tty_term_has(tty->term, TTYC_RIN)) ||
|
||||||
ctx->sx == 1 ||
|
ctx->sx == 1 ||
|
||||||
ctx->sy == 1 ||
|
ctx->sy == 1) {
|
||||||
c->overlay_check != NULL) {
|
|
||||||
tty_redraw_region(tty, ctx);
|
tty_redraw_region(tty, ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2027,12 +1921,10 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
|
||||||
u_int i, j;
|
u_int i, j;
|
||||||
|
|
||||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
if (ctx->flags & TTY_CTX_WINDOW_BIGGER) {
|
||||||
c->overlay_check != NULL) {
|
ctx->redraw_cb(ctx, 0, ctx->sy);
|
||||||
ctx->redraw_cb(ctx);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2051,29 +1943,9 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
const struct grid_cell *gcp = ctx->cell;
|
|
||||||
struct screen *s = ctx->s;
|
|
||||||
struct visible_ranges *r;
|
|
||||||
u_int px, py, i, vis = 0;
|
|
||||||
|
|
||||||
px = ctx->xoff + ctx->ocx - ctx->wox;
|
|
||||||
py = ctx->yoff + ctx->ocy - ctx->woy;
|
|
||||||
if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, 1, 1))
|
if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, 1, 1))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (gcp->data.width == 1 && !tty_check_overlay(tty, px, py))
|
|
||||||
return;
|
|
||||||
if (gcp->data.width > 1) { /* could be partially obscured */
|
|
||||||
r = tty_check_overlay_range(tty, px, py, gcp->data.width);
|
|
||||||
for (i = 0; i < r->used; i++)
|
|
||||||
vis += r->ranges[i].nx;
|
|
||||||
if (vis < gcp->data.width) {
|
|
||||||
tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width,
|
|
||||||
px, py, &ctx->style_ctx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx->xoff + ctx->ocx - ctx->wox > tty->sx - 1 &&
|
if (ctx->xoff + ctx->ocx - ctx->wox > tty->sx - 1 &&
|
||||||
ctx->ocy == ctx->orlower &&
|
ctx->ocy == ctx->orlower &&
|
||||||
tty_full_width(tty, ctx))
|
tty_full_width(tty, ctx))
|
||||||
@@ -2093,9 +1965,6 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct visible_ranges *r;
|
|
||||||
struct visible_range *ri;
|
|
||||||
u_int i, px, py, cx;
|
|
||||||
const char *cp = ctx->data.data;
|
const char *cp = ctx->data.data;
|
||||||
size_t n = ctx->data.size;
|
size_t n = ctx->data.size;
|
||||||
|
|
||||||
@@ -2114,27 +1983,14 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
tty->cy == tty->rlower)
|
tty->cy == tty->rlower)
|
||||||
tty_draw_pane(tty, ctx, ctx->ocy);
|
tty_draw_pane(tty, ctx, ctx->ocy);
|
||||||
else
|
else
|
||||||
ctx->redraw_cb(ctx);
|
ctx->redraw_cb(ctx, ctx->ocy, 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tty_margin_off(tty);
|
tty_margin_off(tty);
|
||||||
tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy);
|
tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy);
|
||||||
tty_attributes(tty, ctx->cell, &ctx->style_ctx);
|
tty_attributes(tty, ctx->cell, &ctx->style_ctx);
|
||||||
|
tty_putn(tty, cp, n, n);
|
||||||
/* Get tty position from pane position for overlay check. */
|
|
||||||
px = ctx->xoff + ctx->ocx - ctx->wox;
|
|
||||||
py = ctx->yoff + ctx->ocy - ctx->woy;
|
|
||||||
|
|
||||||
r = tty_check_overlay_range(tty, px, py, n);
|
|
||||||
for (i = 0; i < r->used; i++) {
|
|
||||||
ri = &r->ranges[i];
|
|
||||||
if (ri->nx != 0) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -2228,23 +2084,8 @@ tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx)
|
|||||||
void
|
void
|
||||||
tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
|
tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
if (ctx->flags & TTY_CTX_SYNC)
|
||||||
|
|
||||||
if ((ctx->flags & TTY_CTX_OVERLAY_SYNC) &&
|
|
||||||
(ctx->flags & TTY_CTX_SYNC)) {
|
|
||||||
/*
|
|
||||||
* This is an overlay and a command that moves the cursor so
|
|
||||||
* start synchronized updates.
|
|
||||||
*/
|
|
||||||
tty_sync_start(tty);
|
tty_sync_start(tty);
|
||||||
} else if (~ctx->flags & TTY_CTX_OVERLAY_SYNC) {
|
|
||||||
/*
|
|
||||||
* This is a pane. If there is an overlay, always start;
|
|
||||||
* otherwise, only if requested.
|
|
||||||
*/
|
|
||||||
if ((ctx->flags & TTY_CTX_SYNC) || c->overlay_draw != NULL)
|
|
||||||
tty_sync_start(tty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -2263,10 +2104,6 @@ tty_cell(struct tty *tty, const struct grid_cell *gc,
|
|||||||
if (gc->flags & GRID_FLAG_PADDING)
|
if (gc->flags & GRID_FLAG_PADDING)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Check if character is covered by overlay or floating pane. */
|
|
||||||
if (!tty_check_overlay(tty, tty->cx, tty->cy))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Check the output codeset and apply attributes. */
|
/* Check the output codeset and apply attributes. */
|
||||||
gcp = tty_check_codeset(tty, gc);
|
gcp = tty_check_codeset(tty, gc);
|
||||||
tty_attributes(tty, gcp, style_ctx);
|
tty_attributes(tty, gcp, style_ctx);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: utf8.c,v 1.72 2026/09/01 12:49:49 nicm Exp $ */
|
/* $OpenBSD: utf8.c,v 1.73 2026/09/20 08:11:00 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -374,8 +374,13 @@ utf8_add_to_width_cache(const char *s)
|
|||||||
wc_end = wc_start;
|
wc_end = wc_start;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (wc = wc_start; wc <= wc_end; wc++)
|
wc = wc_start;
|
||||||
|
for (;;) {
|
||||||
utf8_insert_width_cache(wc, width);
|
utf8_insert_width_cache(wc, width);
|
||||||
|
if (wc == wc_end)
|
||||||
|
break;
|
||||||
|
wc++;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
utf8_no_width = 1;
|
utf8_no_width = 1;
|
||||||
ud = utf8_fromcstr(copy);
|
ud = utf8_fromcstr(copy);
|
||||||
|
|||||||
+5
-1
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: window-border.c,v 1.3 2026/07/23 09:38:27 nicm Exp $ */
|
/* $OpenBSD: window-border.c,v 1.4 2026/09/11 10:17:16 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -120,6 +120,10 @@ window_get_border_cell(struct window_pane *wp, enum pane_lines pane_lines,
|
|||||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||||
utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
|
utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
|
||||||
break;
|
break;
|
||||||
|
case PANE_LINES_ROUNDED:
|
||||||
|
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||||
|
utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type));
|
||||||
|
break;
|
||||||
case PANE_LINES_SIMPLE:
|
case PANE_LINES_SIMPLE:
|
||||||
gc->attr &= ~GRID_ATTR_CHARSET;
|
gc->attr &= ~GRID_ATTR_CHARSET;
|
||||||
utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
|
utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user