mirror of
https://github.com/tmux/tmux.git
synced 2026-09-22 12:57:43 +00:00
Compare commits
40 Commits
redraw-dam
...
image-grid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f53976a266 | ||
|
|
9c70c3351b | ||
|
|
7df1259bf3 | ||
|
|
073dc4c3dd | ||
|
|
7bd1ba9cad | ||
|
|
fce3ac131a | ||
|
|
b30f9b23fa | ||
|
|
7bd1a8572e | ||
|
|
f1d7f7766d | ||
|
|
958946e92b | ||
|
|
f0216004ee | ||
|
|
07455f928d | ||
|
|
c754b738c3 | ||
|
|
36900e144f | ||
|
|
cce975eb77 | ||
|
|
509ad56006 | ||
|
|
d5ce3136c6 | ||
|
|
9fce2352c6 | ||
|
|
81af9c402a | ||
|
|
236d28674b | ||
|
|
3e3567b343 | ||
|
|
88bd41265b | ||
|
|
867c5d3c1f | ||
|
|
3ee659c109 | ||
|
|
6eded1bc85 | ||
|
|
ca4e4c03c5 | ||
|
|
1c9fdd18d4 | ||
|
|
02518f0e3c | ||
|
|
d83ffb2f63 | ||
|
|
9c7c777fd9 | ||
|
|
718e281820 | ||
|
|
507ecd320a | ||
|
|
49be26557d | ||
|
|
00ffe744b3 | ||
|
|
6820eceee5 | ||
|
|
c336f49585 | ||
|
|
aa2dd64873 | ||
|
|
832d2beb2d | ||
|
|
08f0c0485b | ||
|
|
c4bdd8eca1 |
4
.github/workflows/lock.yml
vendored
4
.github/workflows/lock.yml
vendored
@@ -16,10 +16,8 @@ concurrency:
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'tmux/tmux'
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6
|
||||
- uses: dessant/lock-threads@v6
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '30'
|
||||
|
||||
21
.github/workflows/regress.yml
vendored
21
.github/workflows/regress.yml
vendored
@@ -16,8 +16,7 @@ jobs:
|
||||
regress:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
if: github.repository == 'tmux/tmux'
|
||||
timeout-minutes: 300
|
||||
timeout-minutes: 45
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -27,10 +26,10 @@ jobs:
|
||||
runner: ubuntu-24.04
|
||||
make: make
|
||||
configure: --enable-utf8proc --enable-asan
|
||||
- name: ubuntu-24.04-arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
make: make
|
||||
configure: --enable-utf8proc --enable-asan
|
||||
#- name: ubuntu-24.04-arm64
|
||||
# runner: ubuntu-24.04-arm
|
||||
# make: make
|
||||
# configure: --enable-utf8proc --enable-asan
|
||||
- name: macos-26-arm64
|
||||
runner: macos-26
|
||||
make: gmake
|
||||
@@ -38,9 +37,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: dependencies
|
||||
if: runner.os == 'Linux'
|
||||
@@ -72,18 +69,18 @@ jobs:
|
||||
|
||||
- name: build
|
||||
run: |
|
||||
sh autogen.sh
|
||||
sh autogen.sh
|
||||
./configure ${{ matrix.configure }}
|
||||
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
|
||||
- name: test
|
||||
run: |
|
||||
cd regress
|
||||
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
${{ matrix.make }}
|
||||
|
||||
- name: logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: regress-logs-${{ matrix.name }}
|
||||
path: regress/logs/*.log
|
||||
|
||||
78
CHANGES
78
CHANGES
@@ -1,4 +1,7 @@
|
||||
CHANGES FROM 3.7c TO 3.8
|
||||
CHANGES FROM 3.7b TO 3.8
|
||||
|
||||
* Build with jemalloc on macOS to avoid what appears to be a bug in the system
|
||||
calloc(3) (issue 5385).
|
||||
|
||||
* Many improvements to floating panes:
|
||||
|
||||
@@ -9,12 +12,7 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
- modal panes may be created with new-pane -O; a window can have one modal
|
||||
pane and it prevents interaction with the other panes while it is active.
|
||||
-C closes the pane when the mouse is clicked outside it and -K sends all
|
||||
keys, including the prefix key, to the pane. A modal pane is now used for
|
||||
the editor in buffer mode;
|
||||
|
||||
- new-pane -A creates a floating pane which remains above a zoomed pane;
|
||||
modal panes do this by default;
|
||||
A modal pane is now used for the editor in buffer mode;
|
||||
|
||||
- move-pane and resize-pane can now move and resize floating panes, including
|
||||
mouse dragging;
|
||||
@@ -29,15 +27,10 @@ CHANGES FROM 3.7c TO 3.8
|
||||
-k for choose commands kills the pane when the mode exits, which is useful
|
||||
for modes in floating panes;
|
||||
|
||||
- command-prompt -P opens a prompt inside a pane rather than on the status
|
||||
line;
|
||||
|
||||
- pane-border-status has top-floating and bottom-floating, and there are new
|
||||
default bindings under C-b g for common move and resize operations.
|
||||
default bindings under C-b g for common move and resize operations;
|
||||
|
||||
* Layout strings now use a JSON subset format which includes floating panes.
|
||||
The old format is still accepted; control mode clients receive old layouts
|
||||
unless they set the new-layouts flag.
|
||||
- floating panes are unzoomed before creation to avoid a crash.
|
||||
|
||||
* Add support for themes and improve default colours:
|
||||
|
||||
@@ -84,7 +77,7 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
- new hooks to cover areas including client create and destroy, pane
|
||||
activity, pane mode and prompt changes, pane movement and resizing, session
|
||||
group changes and window creation, close, swap and zoom;
|
||||
group changes and window creation, close and zoom;
|
||||
|
||||
- OSC 133 escape sequences now trigger events: pane-command-started,
|
||||
pane-command-finished and pane-shell-prompt.
|
||||
@@ -97,23 +90,10 @@ CHANGES FROM 3.7c TO 3.8
|
||||
while scrolling or while hovered and disappears after pane-scrollbars-timeout
|
||||
(Michael Grant).
|
||||
|
||||
* Add a clear-on-attach server option. When disabled, tmux does not enter the
|
||||
alternate screen on attach and instead scrolls the existing terminal content
|
||||
into the scrollback buffer so it remains accessible (issue 5508).
|
||||
|
||||
* Copy mode can now automatically refresh as pane content changes with the
|
||||
refresh-on, refresh-off and refresh-toggle commands. Automatic refresh is off
|
||||
by default; refresh-now refreshes once and is bound to r. These replace
|
||||
refresh-from-pane (issue 5165).
|
||||
|
||||
* Extend the fill-character option so both inside and outside the window can be
|
||||
changed, with a new default for unused areas inside the window.
|
||||
changed.
|
||||
|
||||
* Change show-options and show-hooks to use formats and add a -F flag to each.
|
||||
|
||||
* Allow individual terminal features to be disabled by adding @ to their names
|
||||
in terminal-features, and add a utf8 feature for terminals which support
|
||||
UTF-8 output.
|
||||
* Change set-option and set-hooks to use formats and add a -F flag to each.
|
||||
|
||||
* Add a #{A/count:frames} modifier to show a series of frames as an animation
|
||||
in the status line (issue 5412 from Fernando Daciuk).
|
||||
@@ -128,12 +108,6 @@ CHANGES FROM 3.7c TO 3.8
|
||||
* Add new-window -E, respawn-pane -E and respawn-window -E as more convenient
|
||||
methods to create an empty pane (rather than using '' for the command).
|
||||
|
||||
* Add capture-pane -I to include the time each line entered history.
|
||||
|
||||
* Various memory leak fixes from Heon Jeong.
|
||||
|
||||
* Add a default C-b T binding to change the current pane title.
|
||||
|
||||
* Menus now belong to the window, so appear on all clients.
|
||||
|
||||
* Dragging over an existing copy mode selection now adjusts it (Michael Grant).
|
||||
@@ -147,12 +121,7 @@ CHANGES FROM 3.7c TO 3.8
|
||||
* Copy mode no longer exits at the bottom while a selection is in progress
|
||||
(issue 5349).
|
||||
|
||||
* Add copy-mode-current-line-style to set the style of the line containing the
|
||||
cursor in copy mode (issue 5391).
|
||||
|
||||
* Copy mode word commands now recognise all Unicode whitespace characters; a
|
||||
space in word-separators matches any Unicode whitespace character (issue
|
||||
5562).
|
||||
* Fix scrollbar initial state so scrollbars appear on new windows (issue 5339).
|
||||
|
||||
* Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
|
||||
nolink) (issues 4842 and 4280 from Moritz Angermann).
|
||||
@@ -164,9 +133,7 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* Add or improve format variables including client_colours,
|
||||
pane_start_command_list, window_manual_width, window_manual_height,
|
||||
pane_last_output_time, pane_modal_flag, window_modal_pane, pane_private_modes,
|
||||
pane_output_generation, history_added, history_collected, history_generation,
|
||||
hook_fire_count and hook_fire_time.
|
||||
pane_last_output_time, pane_modal_flag and window_modal_pane.
|
||||
|
||||
* Add additional pane sort orders, and a z sort order for choose-tree so
|
||||
floating panes can be sorted by z-index.
|
||||
@@ -178,18 +145,12 @@ CHANGES FROM 3.7c TO 3.8
|
||||
|
||||
* The mouse option now defaults to on.
|
||||
|
||||
* The active-pane client flag has been removed.
|
||||
|
||||
* Fix send-keys -K so keys are inserted in the correct place in the input
|
||||
queue, like keys from key bindings (issue 3476).
|
||||
|
||||
* Fix control mode clients hanging on exit if pty data was still queued (Ben
|
||||
Maurer, issue 5356), and avoid sending notifications to clients which are
|
||||
already exiting (Ben Maurer, issue 5357). Queue notifications so they are not
|
||||
sent inside %begin/%end (issue 5458), do not let a stuck client prevent the
|
||||
server from exiting (issue 5444), and reset control mode offsets when a pane
|
||||
is respawned (issue 5498). Bound buffered command replies so a control mode
|
||||
client that stops reading cannot consume unlimited memory (issue 5565).
|
||||
already exiting (Ben Maurer, issue 5357).
|
||||
|
||||
* Fix grouped sessions sometimes being left as unusable command targets while
|
||||
they are being killed (Bryce Miller, issue 5180).
|
||||
@@ -208,18 +169,7 @@ CHANGES FROM 3.7c TO 3.8
|
||||
* Do not crash looking for the next or previous session (issue 5344), or when
|
||||
no client is available.
|
||||
|
||||
CHANGES FROM 3.7b TO 3.7c
|
||||
|
||||
* Build with jemalloc on macOS to avoid what appears to be a bug in calloc
|
||||
(issue 5385).
|
||||
|
||||
* Fix scrollbar initial state so they appear on new windows (issue 5339).
|
||||
|
||||
* Check time periodically in loops rather than every one (issue 5367).
|
||||
|
||||
* Use message-style again as default for message-format.
|
||||
|
||||
* Unzoom before creating floating panes to avoid a crash.
|
||||
* Check time periodically in loops rather than for every item (issue 5367).
|
||||
|
||||
CHANGES FROM 3.7a TO 3.7b
|
||||
|
||||
|
||||
@@ -174,7 +174,6 @@ dist_tmux_SOURCES = \
|
||||
input-keys.c \
|
||||
input.c \
|
||||
job.c \
|
||||
json.c \
|
||||
key-bindings.c \
|
||||
key-string.c \
|
||||
layout-custom.c \
|
||||
@@ -188,6 +187,7 @@ dist_tmux_SOURCES = \
|
||||
options-table.c \
|
||||
options.c \
|
||||
paste.c \
|
||||
popup.c \
|
||||
proc.c \
|
||||
prompt-history.c \
|
||||
prompt.c \
|
||||
@@ -246,8 +246,8 @@ if HAVE_UTF8PROC
|
||||
nodist_tmux_SOURCES += compat/utf8proc.c
|
||||
endif
|
||||
|
||||
# Enable sixel support.
|
||||
if ENABLE_SIXEL
|
||||
# Enable image support.
|
||||
if ENABLE_IMAGES
|
||||
dist_tmux_SOURCES += image.c image-sixel.c
|
||||
endif
|
||||
|
||||
|
||||
79
SYNCING.md
79
SYNCING.md
@@ -35,7 +35,7 @@ The usual local layout is:
|
||||
```sh
|
||||
cd /some/where/useful
|
||||
git clone https://github.com/tmux/tmux.git tmux-portable
|
||||
git clone https://github.com/tmux/tmux-openbsd-cutover.git tmux-openbsd-cutover
|
||||
git clone https://github.com/ThomasAdam/tmux-obsd.git tmux-openbsd-cutover
|
||||
```
|
||||
|
||||
The exact directory names do not matter, but the examples below use:
|
||||
@@ -56,19 +56,18 @@ The cutover repository has three important branches:
|
||||
|
||||
# Adding the OpenBSD remote to portable
|
||||
|
||||
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:
|
||||
In the portable repository, add the cutover repository as a remote:
|
||||
|
||||
```sh
|
||||
cd /path/to/tmux-portable
|
||||
git remote add tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
||||
git remote add tmux-openbsd /path/to/tmux-openbsd-cutover
|
||||
git config remote.tmux-openbsd.tagOpt --no-tags
|
||||
```
|
||||
|
||||
If the remote already exists, update it instead:
|
||||
|
||||
```sh
|
||||
git remote set-url tmux-openbsd https://github.com/tmux/tmux-openbsd-cutover.git
|
||||
git remote set-url tmux-openbsd /path/to/tmux-openbsd-cutover
|
||||
git config remote.tmux-openbsd.tagOpt --no-tags
|
||||
```
|
||||
|
||||
@@ -78,32 +77,6 @@ Fetch the cutover master branch explicitly:
|
||||
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
||||
```
|
||||
|
||||
To merge unpublished changes from a local cutover clone instead, first ensure
|
||||
it has an up-to-date local `master` branch. A normal clone may check out
|
||||
`automation` and have only `origin/master`; fetching `master` from that clone
|
||||
will then fail with `couldn't find remote ref master`.
|
||||
|
||||
With a clean cutover working tree:
|
||||
|
||||
```sh
|
||||
cd /path/to/tmux-openbsd-cutover
|
||||
git fetch --no-tags origin
|
||||
git switch master
|
||||
git merge --ff-only origin/master
|
||||
```
|
||||
|
||||
`git switch master` creates a tracking branch from `origin/master` if there is
|
||||
no local `master` yet. If the fast-forward fails, reconcile the local cutover
|
||||
changes before continuing; do not reset them away.
|
||||
|
||||
Then, in portable, point the remote at that clone and fetch its local `master`:
|
||||
|
||||
```sh
|
||||
cd /path/to/tmux-portable
|
||||
git remote set-url tmux-openbsd /path/to/tmux-openbsd-cutover
|
||||
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
||||
```
|
||||
|
||||
# Automated syncing
|
||||
|
||||
The normal sync is performed by the GitHub Actions workflow in the
|
||||
@@ -126,9 +99,7 @@ OpenBSD changes.
|
||||
If the workflow fails while merging into portable, do the merge locally and
|
||||
push the result.
|
||||
|
||||
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:
|
||||
Start from an up-to-date portable master:
|
||||
|
||||
```sh
|
||||
cd /path/to/tmux-portable
|
||||
@@ -137,15 +108,10 @@ git checkout master
|
||||
git pull --ff-only origin master
|
||||
```
|
||||
|
||||
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:
|
||||
Fetch the cutover branch:
|
||||
|
||||
```sh
|
||||
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 remote add tmux-openbsd /path/to/tmux-openbsd-cutover 2>/dev/null || true
|
||||
git config remote.tmux-openbsd.tagOpt --no-tags
|
||||
git fetch --no-tags tmux-openbsd master:refs/remotes/tmux-openbsd/master
|
||||
```
|
||||
@@ -156,22 +122,7 @@ Merge it:
|
||||
git merge --no-ff --log refs/remotes/tmux-openbsd/master
|
||||
```
|
||||
|
||||
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.
|
||||
Resolve conflicts by deciding whether portable or OpenBSD owns the file.
|
||||
|
||||
Useful commands:
|
||||
|
||||
@@ -189,25 +140,11 @@ git add path/to/file
|
||||
|
||||
This takes the OpenBSD/cutover version of a conflicted file.
|
||||
|
||||
For a modify/delete conflict, the side that deleted the file has no version
|
||||
to check out. For example, portable generates `Makefile` using autotools and
|
||||
does not track OpenBSD's `Makefile`. If Git reports that `Makefile` was deleted
|
||||
in HEAD and modified in cutover, keep the portable deletion with:
|
||||
|
||||
```sh
|
||||
git rm -- Makefile
|
||||
```
|
||||
|
||||
This removes the OpenBSD file left by the merge; regenerate the portable
|
||||
`Makefile` with your usual configure command before building. Use this only
|
||||
for the unmerged OpenBSD file, not an existing generated build file.
|
||||
|
||||
Before committing, inspect the result:
|
||||
|
||||
```sh
|
||||
git status
|
||||
git diff --check
|
||||
git diff --cached --check
|
||||
git diff --cached --stat
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: arguments.c,v 1.67 2026/08/25 06:04:33 nicm Exp $ */
|
||||
/* $OpenBSD: arguments.c,v 1.66 2026/06/26 09:54:56 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -762,6 +762,8 @@ args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
|
||||
cmdq_error(item, "%s", error);
|
||||
free(error);
|
||||
}
|
||||
else
|
||||
cmdlist->references++;
|
||||
args_make_commands_free(state);
|
||||
return (cmdlist);
|
||||
}
|
||||
@@ -825,10 +827,8 @@ args_make_commands(struct args_command_state *state, int argc, char **argv,
|
||||
int i;
|
||||
|
||||
if (state->cmdlist != NULL) {
|
||||
if (argc == 0) {
|
||||
state->cmdlist->references++;
|
||||
if (argc == 0)
|
||||
return (state->cmdlist);
|
||||
}
|
||||
return (cmd_list_copy(state->cmdlist, argc, argv));
|
||||
}
|
||||
|
||||
|
||||
16
cfg.c
16
cfg.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cfg.c,v 1.91 2026/08/03 13:38:42 nicm Exp $ */
|
||||
/* $OpenBSD: cfg.c,v 1.90 2026/07/14 17:17:17 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -223,15 +223,13 @@ cfg_print_causes(struct cmdq_item *item)
|
||||
{
|
||||
struct client *c = cmdq_get_client(item);
|
||||
u_int i;
|
||||
char *cause;
|
||||
|
||||
for (i = 0; i < cfg_ncauses; i++) {
|
||||
cause = cfg_causes[i];
|
||||
if (c != NULL && (c->flags & CLIENT_CONTROL))
|
||||
control_notify_write(c, "%%config-error %s", cause);
|
||||
control_write(c, "%%config-error %s", cfg_causes[i]);
|
||||
else
|
||||
cmdq_print(item, "%s", cause);
|
||||
free(cause);
|
||||
cmdq_print(item, "%s", cfg_causes[i]);
|
||||
free(cfg_causes[i]);
|
||||
}
|
||||
|
||||
free(cfg_causes);
|
||||
@@ -246,16 +244,14 @@ cfg_show_causes(struct session *s)
|
||||
struct window_pane *wp;
|
||||
struct window_mode_entry *wme;
|
||||
u_int i;
|
||||
char *cause;
|
||||
|
||||
if (cfg_ncauses == 0)
|
||||
return;
|
||||
|
||||
if (c != NULL && (c->flags & CLIENT_CONTROL)) {
|
||||
for (i = 0; i < cfg_ncauses; i++) {
|
||||
cause = cfg_causes[i];
|
||||
control_notify_write(c, "%%config-error %s", cause);
|
||||
free(cause);
|
||||
control_write(c, "%%config-error %s", cfg_causes[i]);
|
||||
free(cfg_causes[i]);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
10
client.c
10
client.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: client.c,v 1.168 2026/08/17 20:14:31 nicm Exp $ */
|
||||
/* $OpenBSD: client.c,v 1.166 2026/07/10 15:45:11 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -276,7 +276,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
||||
proc_set_signals(client_proc, client_signal);
|
||||
|
||||
/* Save the flags. */
|
||||
client_flags = flags|CLIENT_WRITE_ACK;
|
||||
client_flags = flags;
|
||||
log_debug("flags are %#llx", (unsigned long long)client_flags);
|
||||
|
||||
/* Initialize the client socket and start the server. */
|
||||
@@ -712,9 +712,6 @@ client_dispatch_wait(struct imsg *imsg)
|
||||
fprintf(stderr, "server version is too old for client\n");
|
||||
proc_exit(client_proc);
|
||||
break;
|
||||
default:
|
||||
log_debug("unknown message type %u", imsg->hdr.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -801,8 +798,5 @@ client_dispatch_attached(struct imsg *imsg)
|
||||
system(data);
|
||||
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
|
||||
break;
|
||||
default:
|
||||
log_debug("unknown message type %u", imsg->hdr.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +91,8 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
|
||||
wp = target.wp;
|
||||
|
||||
if (wl != NULL) {
|
||||
if (wp != NULL) {
|
||||
window_redraw_active_switch(wp->window, wp);
|
||||
if (wp != NULL)
|
||||
window_set_active_pane(wp->window, wp, 1);
|
||||
}
|
||||
session_set_current(s, wl);
|
||||
if (wp != NULL)
|
||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-break-pane.c,v 1.76 2026/08/17 07:04:45 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-break-pane.c,v 1.75 2026/07/23 09:38:27 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -174,14 +174,13 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
window_set_fill_cells(w);
|
||||
|
||||
if (idx == -1)
|
||||
idx = -1 - options_get_number(dst_s->options, "base-index");
|
||||
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
|
||||
|
||||
layout_init(w, wp);
|
||||
wp->flags |= PANE_CHANGED;
|
||||
colour_palette_from_option(&wp->palette, wp->options);
|
||||
|
||||
if (idx == -1)
|
||||
idx = -1 - options_get_number(dst_s->options, "base-index");
|
||||
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
|
||||
window_remove_ref(w, __func__);
|
||||
events_fire_window("window-created", w);
|
||||
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-capture-pane.c,v 1.69 2026/09/08 07:31:59 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-capture-pane.c,v 1.68 2026/07/20 11:16:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
|
||||
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
|
||||
.name = "capture-pane",
|
||||
.alias = "capturep",
|
||||
|
||||
.args = { "ab:CeE:FHIJLMNpPqRS:Tt:", 0, 0, NULL },
|
||||
.usage = "[-aCeFHIJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
|
||||
.args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL },
|
||||
.usage = "[-aCeFHJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
|
||||
"[-S start-line] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -200,7 +200,7 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
|
||||
tmp[0] = line[i];
|
||||
tmp[1] = '\0';
|
||||
} else
|
||||
snprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
|
||||
xsnprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
|
||||
buf = cmd_capture_pane_append(buf, len, tmp,
|
||||
strlen(tmp));
|
||||
}
|
||||
@@ -259,7 +259,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
struct grid_cell *gc = NULL;
|
||||
struct window_mode_entry *wme;
|
||||
int n, join_lines, number_lines, flags = 0;
|
||||
int show_flags, show_time, hyperlinks;
|
||||
int show_flags, hyperlinks;
|
||||
u_int *links = NULL, nlinks = 0;
|
||||
u_int i, sx, top, bottom, tmp;
|
||||
char *cause, *buf = NULL, *line, b[64], *cp;
|
||||
@@ -342,7 +342,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
flags |= GRID_STRING_TRIM_SPACES;
|
||||
number_lines = args_has(args, 'L');
|
||||
show_flags = args_has(args, 'F');
|
||||
show_time = args_has(args, 'I');
|
||||
hyperlinks = args_has(args, 'H');
|
||||
if (hyperlinks)
|
||||
links = xreallocarray(NULL, gd->sx, sizeof *links);
|
||||
@@ -359,7 +358,6 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
free(line);
|
||||
continue;
|
||||
}
|
||||
gl = grid_peek_line(gd, i);
|
||||
|
||||
if (number_lines) {
|
||||
if (i >= gd->hsize)
|
||||
@@ -370,16 +368,11 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
if (n >= 0)
|
||||
buf = cmd_capture_pane_append(buf, len, b, n);
|
||||
}
|
||||
if (show_time) {
|
||||
n = snprintf(b, sizeof b, "%llu ",
|
||||
(unsigned long long)grid_line_time(gl));
|
||||
if (n >= 0)
|
||||
buf = cmd_capture_pane_append(buf, len, b, n);
|
||||
}
|
||||
if (show_flags) {
|
||||
cp = b;
|
||||
*cp = '\0';
|
||||
|
||||
gl = grid_peek_line(gd, i);
|
||||
if (gl->flags & GRID_LINE_DEAD)
|
||||
*cp++ = 'D';
|
||||
if (gl->flags & GRID_LINE_HYPERLINK)
|
||||
@@ -400,6 +393,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
|
||||
}
|
||||
buf = cmd_capture_pane_append(buf, len, line, linelen);
|
||||
|
||||
gl = grid_peek_line(gd, i);
|
||||
if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
|
||||
buf[(*len)++] = '\n';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-command-prompt.c,v 1.76 2026/08/25 06:04:33 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-command-prompt.c,v 1.75 2026/06/25 11:39:11 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -242,11 +242,9 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
cmd_free_argv(argc, argv);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-display-menu.c,v 1.55 2026/09/21 12:14:32 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-display-menu.c,v 1.52 2026/07/14 19:07:03 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -54,8 +54,8 @@ const struct cmd_entry cmd_display_popup_entry = {
|
||||
.name = "display-popup",
|
||||
.alias = "popup",
|
||||
|
||||
.args = { "Bb:Cc:d:e:Eh:ks:S:t:T:w:x:y:", 0, -1, NULL },
|
||||
.usage = "[-BCEk] [-b border-lines] [-c target-client] "
|
||||
.args = { "Bb:Cc:d:e:Eh:kNs:S:t:T:w:x:y:", 0, -1, NULL },
|
||||
.usage = "[-BCEkN] [-b border-lines] [-c target-client] "
|
||||
"[-d start-directory] [-e environment] [-h height] "
|
||||
"[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
|
||||
" [-T title] [-w width] [-x position] [-y position] "
|
||||
@@ -91,6 +91,203 @@ cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause)
|
||||
return (type);
|
||||
}
|
||||
|
||||
static int
|
||||
cmd_display_menu_get_popup_pos(struct client *tc, struct cmdq_item *item,
|
||||
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
|
||||
{
|
||||
struct tty *tty = &tc->tty;
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct key_event *event = cmdq_get_event(item);
|
||||
struct session *s = tc->session;
|
||||
struct winlink *wl = target->wl;
|
||||
struct window_pane *wp = target->wp;
|
||||
struct style_ranges *ranges = NULL;
|
||||
struct style_range *sr = NULL;
|
||||
const char *xp, *yp;
|
||||
char *p;
|
||||
int top;
|
||||
u_int line, ox, oy, sx, sy, lines, position;
|
||||
long n;
|
||||
struct format_tree *ft;
|
||||
|
||||
/*
|
||||
* Work out the position from the -x and -y arguments. This is the
|
||||
* bottom-left position.
|
||||
*/
|
||||
|
||||
/* If the popup is too big, stop now. */
|
||||
if (w > tty->sx || h > tty->sy)
|
||||
return (0);
|
||||
|
||||
/* Create format with mouse position if any. */
|
||||
ft = format_create_from_target(item);
|
||||
if (event->m.valid) {
|
||||
format_add(ft, "popup_mouse_x", "%u", event->m.x);
|
||||
format_add(ft, "popup_mouse_y", "%u", event->m.y);
|
||||
}
|
||||
|
||||
/* Position of the previous menu, for -x/-y L. */
|
||||
format_add(ft, "popup_last_x", "%u", target->w->menu_last_px);
|
||||
format_add(ft, "popup_last_y", "%u", target->w->menu_last_py + h);
|
||||
|
||||
/*
|
||||
* If there are any status lines, add this window position and the
|
||||
* status line position.
|
||||
*/
|
||||
top = status_at_line(tc);
|
||||
if (top != -1) {
|
||||
lines = status_line_size(tc);
|
||||
if (top == 0)
|
||||
top = lines;
|
||||
else
|
||||
top = 0;
|
||||
position = options_get_number(s->options, "status-position");
|
||||
|
||||
for (line = 0; line < lines; line++) {
|
||||
ranges = &tc->status.entries[line].ranges;
|
||||
TAILQ_FOREACH(sr, ranges, entry) {
|
||||
if (sr->type != STYLE_RANGE_WINDOW)
|
||||
continue;
|
||||
if (sr->argument == (u_int)wl->idx)
|
||||
break;
|
||||
}
|
||||
if (sr != NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
if (sr != NULL) {
|
||||
format_add(ft, "popup_window_status_line_x", "%u",
|
||||
sr->start);
|
||||
if (position == 0) {
|
||||
format_add(ft, "popup_window_status_line_y",
|
||||
"%u", line + 1 + h);
|
||||
} else {
|
||||
format_add(ft, "popup_window_status_line_y",
|
||||
"%u", tty->sy - lines + line);
|
||||
}
|
||||
}
|
||||
|
||||
if (position == 0)
|
||||
format_add(ft, "popup_status_line_y", "%u", lines + h);
|
||||
else {
|
||||
format_add(ft, "popup_status_line_y", "%u",
|
||||
tty->sy - lines);
|
||||
}
|
||||
} else
|
||||
top = 0;
|
||||
|
||||
/* Popup width and height. */
|
||||
format_add(ft, "popup_width", "%u", w);
|
||||
format_add(ft, "popup_height", "%u", h);
|
||||
|
||||
/* Position so popup is in the centre. */
|
||||
n = (long)(tty->sx - 1) / 2 - w / 2;
|
||||
if (n < 0)
|
||||
format_add(ft, "popup_centre_x", "%u", 0);
|
||||
else
|
||||
format_add(ft, "popup_centre_x", "%ld", n);
|
||||
n = (tty->sy - 1) / 2 + h / 2;
|
||||
if (n >= tty->sy)
|
||||
format_add(ft, "popup_centre_y", "%u", tty->sy - h);
|
||||
else
|
||||
format_add(ft, "popup_centre_y", "%ld", n);
|
||||
|
||||
/* Position of popup relative to mouse. */
|
||||
if (event->m.valid) {
|
||||
n = (long)event->m.x - w / 2;
|
||||
if (n < 0)
|
||||
format_add(ft, "popup_mouse_centre_x", "%u", 0);
|
||||
else
|
||||
format_add(ft, "popup_mouse_centre_x", "%ld", n);
|
||||
n = event->m.y - h / 2;
|
||||
if (n + h >= tty->sy) {
|
||||
format_add(ft, "popup_mouse_centre_y", "%u",
|
||||
tty->sy - h);
|
||||
} else
|
||||
format_add(ft, "popup_mouse_centre_y", "%ld", n);
|
||||
n = (long)event->m.y + h;
|
||||
if (n >= tty->sy)
|
||||
format_add(ft, "popup_mouse_top", "%u", tty->sy - 1);
|
||||
else
|
||||
format_add(ft, "popup_mouse_top", "%ld", n);
|
||||
n = event->m.y - h;
|
||||
if (n < 0)
|
||||
format_add(ft, "popup_mouse_bottom", "%u", 0);
|
||||
else
|
||||
format_add(ft, "popup_mouse_bottom", "%ld", n);
|
||||
}
|
||||
|
||||
/* Position in pane. */
|
||||
tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy);
|
||||
n = top + wp->yoff - oy + h;
|
||||
if (n >= tty->sy)
|
||||
format_add(ft, "popup_pane_top", "%u", tty->sy - h);
|
||||
else
|
||||
format_add(ft, "popup_pane_top", "%ld", n);
|
||||
format_add(ft, "popup_pane_bottom", "%u", top + wp->yoff + wp->sy - oy);
|
||||
format_add(ft, "popup_pane_left", "%u", wp->xoff - ox);
|
||||
n = (long)wp->xoff + wp->sx - ox - w;
|
||||
if (n < 0)
|
||||
format_add(ft, "popup_pane_right", "%u", 0);
|
||||
else
|
||||
format_add(ft, "popup_pane_right", "%ld", n);
|
||||
|
||||
/* Expand horizontal position. */
|
||||
xp = args_get(args, 'x');
|
||||
if (xp == NULL || strcmp(xp, "C") == 0)
|
||||
xp = "#{popup_centre_x}";
|
||||
else if (strcmp(xp, "R") == 0)
|
||||
xp = "#{popup_pane_right}";
|
||||
else if (strcmp(xp, "P") == 0)
|
||||
xp = "#{popup_pane_left}";
|
||||
else if (strcmp(xp, "M") == 0)
|
||||
xp = "#{popup_mouse_centre_x}";
|
||||
else if (strcmp(xp, "L") == 0)
|
||||
xp = "#{popup_last_x}";
|
||||
else if (strcmp(xp, "W") == 0)
|
||||
xp = "#{popup_window_status_line_x}";
|
||||
p = format_expand(ft, xp);
|
||||
n = strtol(p, NULL, 10);
|
||||
if (n + w >= tty->sx)
|
||||
n = tty->sx - w;
|
||||
else if (n < 0)
|
||||
n = 0;
|
||||
*px = n;
|
||||
log_debug("%s: -x: %s = %s = %u (-w %u)", __func__, xp, p, *px, w);
|
||||
free(p);
|
||||
|
||||
/* Expand vertical position */
|
||||
yp = args_get(args, 'y');
|
||||
if (yp == NULL || strcmp(yp, "C") == 0)
|
||||
yp = "#{popup_centre_y}";
|
||||
else if (strcmp(yp, "P") == 0)
|
||||
yp = "#{popup_pane_bottom}";
|
||||
else if (strcmp(yp, "M") == 0)
|
||||
yp = "#{popup_mouse_top}";
|
||||
else if (strcmp(yp, "L") == 0)
|
||||
yp = "#{popup_last_y}";
|
||||
else if (strcmp(yp, "S") == 0)
|
||||
yp = "#{popup_status_line_y}";
|
||||
else if (strcmp(yp, "W") == 0)
|
||||
yp = "#{popup_window_status_line_y}";
|
||||
p = format_expand(ft, yp);
|
||||
n = strtol(p, NULL, 10);
|
||||
if (n < h)
|
||||
n = 0;
|
||||
else
|
||||
n -= h;
|
||||
if (n + h >= tty->sy)
|
||||
n = tty->sy - h;
|
||||
else if (n < 0)
|
||||
n = 0;
|
||||
*py = n;
|
||||
log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h);
|
||||
free(p);
|
||||
|
||||
format_free(ft);
|
||||
return (1);
|
||||
}
|
||||
|
||||
static int
|
||||
cmd_display_menu_get_menu_pos(struct client *tc, struct cmdq_item *item,
|
||||
struct args *args, u_int *px, u_int *py, u_int w, u_int h)
|
||||
@@ -285,8 +482,8 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
||||
enum box_lines lines = BOX_LINES_DEFAULT;
|
||||
char *title, *cause = NULL;
|
||||
int flags = 0, starting_choice = 0;
|
||||
u_int px, py, sx, sy, i, count = args_count(args);
|
||||
struct options *o = target->w->options;
|
||||
u_int px, py, i, count = args_count(args);
|
||||
struct options *o = target->s->curw->window->options;
|
||||
struct options_entry *oe;
|
||||
|
||||
if (args_has(args, 'C')) {
|
||||
@@ -334,6 +531,9 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
if (menu->count == 0)
|
||||
goto out;
|
||||
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py,
|
||||
menu->width + 4, menu->count + 2))
|
||||
goto out;
|
||||
|
||||
value = args_get(args, 'b');
|
||||
if (value != NULL) {
|
||||
@@ -344,11 +544,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cmdq_error(item, "menu-border-lines %s", cause);
|
||||
goto fail;
|
||||
}
|
||||
} else
|
||||
lines = options_get_number(o, "menu-border-lines");
|
||||
menu_get_size(menu, lines, &sx, &sy);
|
||||
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py, sx, sy))
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (args_has(args, 'O'))
|
||||
flags |= MENU_STAYOPEN;
|
||||
@@ -369,22 +565,6 @@ fail:
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
static enum pane_lines
|
||||
cmd_display_popup_get_lines(const char *value, char **cause)
|
||||
{
|
||||
const struct options_table_entry *oe;
|
||||
|
||||
if (value == NULL)
|
||||
value = "single";
|
||||
else if (strcmp(value, "rounded") == 0)
|
||||
value = "single";
|
||||
else if (strcmp(value, "padded") == 0)
|
||||
value = "spaces";
|
||||
|
||||
oe = options_search("pane-border-lines");
|
||||
return (options_find_choice(oe, value, cause));
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
@@ -392,199 +572,146 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct session *s = target->s;
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct winlink *wl = target->wl;
|
||||
struct window *w = wl->window;
|
||||
struct window_pane *wp = target->wp, *new_wp = NULL;
|
||||
struct spawn_context sc = { 0 };
|
||||
struct layout_cell *lc = NULL;
|
||||
struct layout_geometry lg;
|
||||
struct event_payload *ep;
|
||||
struct cmd_find_state fs;
|
||||
const char *value, *style = args_get(args, 's');
|
||||
struct tty *tty = &tc->tty;
|
||||
const char *value, *shell, *shellcmd = NULL;
|
||||
const char *style = args_get(args, 's');
|
||||
const char *border_style = args_get(args, 'S');
|
||||
char *cause = NULL, *title = NULL;
|
||||
enum pane_lines lines = PANE_LINES_SINGLE;
|
||||
u_int px, py, sx, sy, count = args_count(args);
|
||||
char *cwd = NULL, *cause = NULL, **argv = NULL;
|
||||
char *title = NULL;
|
||||
int modify = popup_present(tc);
|
||||
int flags = -1, argc = 0;
|
||||
enum box_lines lines = BOX_LINES_DEFAULT;
|
||||
u_int px, py, w, h, count = args_count(args);
|
||||
struct args_value *av;
|
||||
long long ll;
|
||||
struct environ *env = NULL;
|
||||
struct options *o = s->curw->window->options;
|
||||
struct options_entry *oe;
|
||||
|
||||
if (args_has(args, 'C')) {
|
||||
if (w->modal != NULL)
|
||||
server_kill_pane(w->modal);
|
||||
server_client_clear_overlay(tc);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (tc->flags & CLIENT_CONTROL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (w->modal != NULL)
|
||||
if (!modify && tc->overlay_draw != NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
if (!modify) {
|
||||
h = tty->sy / 2;
|
||||
if (args_has(args, 'h')) {
|
||||
h = args_percentage(args, 'h', 1, tty->sy, tty->sy,
|
||||
&cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "height %s", cause);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
w = tty->sx / 2;
|
||||
if (args_has(args, 'w')) {
|
||||
w = args_percentage(args, 'w', 1, tty->sx, tty->sx,
|
||||
&cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "width %s", cause);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (w > tty->sx)
|
||||
w = tty->sx;
|
||||
if (h > tty->sy)
|
||||
h = tty->sy;
|
||||
if (!cmd_display_menu_get_popup_pos(tc, item, args, &px, &py,
|
||||
w, h))
|
||||
goto out;
|
||||
|
||||
value = args_get(args, 'd');
|
||||
if (value != NULL)
|
||||
cwd = format_single_from_target(item, value);
|
||||
else
|
||||
cwd = xstrdup(server_client_get_cwd(tc, s));
|
||||
if (count == 0) {
|
||||
shellcmd = options_get_string(s->options,
|
||||
"default-command");
|
||||
} else if (count == 1)
|
||||
shellcmd = args_string(args, 0);
|
||||
if (count <= 1 && (shellcmd == NULL || *shellcmd == '\0')) {
|
||||
shellcmd = NULL;
|
||||
shell = options_get_string(s->options, "default-shell");
|
||||
if (!checkshell(shell))
|
||||
shell = _PATH_BSHELL;
|
||||
cmd_append_argv(&argc, &argv, shell);
|
||||
} else
|
||||
args_to_vector(args, &argc, &argv);
|
||||
|
||||
if (args_has(args, 'e') >= 1) {
|
||||
env = environ_create();
|
||||
av = args_first_value(args, 'e');
|
||||
while (av != NULL) {
|
||||
environ_put(env, av->string, 0);
|
||||
av = args_next_value(av);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = args_get(args, 'b');
|
||||
if (args_has(args, 'B'))
|
||||
lines = PANE_LINES_NONE;
|
||||
lines = BOX_LINES_NONE;
|
||||
else if (value != NULL) {
|
||||
lines = cmd_display_popup_get_lines(value, &cause);
|
||||
oe = options_get(o, "popup-border-lines");
|
||||
lines = options_find_choice(options_table_entry(oe), value,
|
||||
&cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "pane-border-lines %s", cause);
|
||||
cmdq_error(item, "popup-border-lines %s", cause);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
sy = w->sy / 2;
|
||||
if (args_has(args, 'h')) {
|
||||
ll = args_percentage(args, 'h', 1, w->sy, w->sy, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "height %s", cause);
|
||||
goto fail;
|
||||
}
|
||||
sy = ll;
|
||||
}
|
||||
|
||||
sx = w->sx / 2;
|
||||
if (args_has(args, 'w')) {
|
||||
ll = args_percentage(args, 'w', 1, w->sx, w->sx, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "width %s", cause);
|
||||
goto fail;
|
||||
}
|
||||
sx = ll;
|
||||
}
|
||||
|
||||
if (sx > w->sx)
|
||||
sx = w->sx;
|
||||
if (sy > w->sy)
|
||||
sy = w->sy;
|
||||
if ((lines == PANE_LINES_NONE && (sx < 1 || sy < 1)) ||
|
||||
(lines != PANE_LINES_NONE && (sx < 3 || sy < 3)))
|
||||
goto out;
|
||||
if (!cmd_display_menu_get_menu_pos(tc, item, args, &px, &py, sx, sy))
|
||||
goto out;
|
||||
|
||||
lg.sx = sx;
|
||||
lg.sy = sy;
|
||||
lg.xoff = px;
|
||||
lg.yoff = py;
|
||||
if (lines != PANE_LINES_NONE) {
|
||||
lg.sx -= 2;
|
||||
lg.sy -= 2;
|
||||
lg.xoff++;
|
||||
lg.yoff++;
|
||||
}
|
||||
|
||||
window_push_zoom(w, 0, 1);
|
||||
lc = layout_floating_pane(w, wp, &lg);
|
||||
if (lc == NULL) {
|
||||
window_pop_zoom(w);
|
||||
goto out;
|
||||
}
|
||||
|
||||
sc.item = item;
|
||||
sc.s = s;
|
||||
sc.wl = wl;
|
||||
sc.tc = tc;
|
||||
sc.wp0 = wp;
|
||||
sc.lc = lc;
|
||||
sc.idx = -1;
|
||||
sc.cwd = args_get(args, 'd');
|
||||
sc.flags = (SPAWN_FLOATING|SPAWN_MODAL|SPAWN_FLOATOVERZOOM);
|
||||
|
||||
if (count != 1 || *args_string(args, 0) != '\0')
|
||||
args_to_vector(args, &sc.argc, &sc.argv);
|
||||
sc.environ = environ_create();
|
||||
av = args_first_value(args, 'e');
|
||||
while (av != NULL) {
|
||||
environ_put(sc.environ, av->string, 0);
|
||||
av = args_next_value(av);
|
||||
}
|
||||
|
||||
new_wp = spawn_pane(&sc, &cause);
|
||||
if (new_wp == NULL) {
|
||||
cmdq_error(item, "create pane failed: %s", cause);
|
||||
free(cause);
|
||||
cause = NULL;
|
||||
window_pop_zoom(w);
|
||||
goto fail;
|
||||
}
|
||||
window_pop_zoom(w);
|
||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||
if (!args_has(args, 'E'))
|
||||
new_wp->flags |= PANE_CLOSEONCANCEL;
|
||||
|
||||
options_set_number(new_wp->options, "pane-border-lines", lines);
|
||||
if (args_has(args, 'E') > 1) {
|
||||
if (args_has(args, 'k'))
|
||||
options_set_number(new_wp->options, "remain-on-exit", 4);
|
||||
else
|
||||
options_set_number(new_wp->options, "remain-on-exit", 2);
|
||||
} else if (args_has(args, 'E'))
|
||||
options_set_number(new_wp->options, "remain-on-exit", 0);
|
||||
else if (args_has(args, 'k'))
|
||||
options_set_number(new_wp->options, "remain-on-exit", 3);
|
||||
else
|
||||
options_set_number(new_wp->options, "remain-on-exit", 1);
|
||||
options_set_string(new_wp->options, "remain-on-exit-format", 0, "%s", "");
|
||||
|
||||
if (style != NULL) {
|
||||
if (options_set_string(new_wp->options, "window-style", 0,
|
||||
"%s", style) == NULL) {
|
||||
cmdq_error(item, "bad style: %s", style);
|
||||
goto fail;
|
||||
}
|
||||
options_set_string(new_wp->options, "window-active-style", 0,
|
||||
"%s", style);
|
||||
new_wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED|
|
||||
PANE_THEMECHANGED);
|
||||
}
|
||||
if (border_style != NULL) {
|
||||
if (options_set_string(new_wp->options, "pane-border-style", 0,
|
||||
"%s", border_style) == NULL) {
|
||||
cmdq_error(item, "bad border style: %s", border_style);
|
||||
goto fail;
|
||||
}
|
||||
options_set_string(new_wp->options, "pane-active-border-style",
|
||||
0, "%s", border_style);
|
||||
}
|
||||
if (args_has(args, 'T'))
|
||||
title = format_single_from_target(item, args_get(args, 'T'));
|
||||
if (title != NULL) {
|
||||
options_set_number(new_wp->options, "pane-border-status",
|
||||
PANE_STATUS_TOP);
|
||||
options_set_string(new_wp->options, "pane-border-format", 0,
|
||||
"%s", "#{pane_title}");
|
||||
screen_set_title(&new_wp->base, title, 0);
|
||||
ep = event_payload_create();
|
||||
cmd_find_from_pane(&fs, new_wp, 0);
|
||||
event_payload_set_target(ep, &fs);
|
||||
event_payload_set_pane(ep, "pane", new_wp);
|
||||
event_payload_set_window(ep, "window", new_wp->window);
|
||||
event_payload_set_string(ep, "new_title", "%s", title);
|
||||
events_fire("pane-title-changed", ep);
|
||||
else
|
||||
title = xstrdup("");
|
||||
|
||||
if (args_has(args, 'N') || !modify)
|
||||
flags = 0;
|
||||
if (args_has(args, 'E') > 1) {
|
||||
if (flags == -1)
|
||||
flags = 0;
|
||||
flags |= POPUP_CLOSEEXITZERO;
|
||||
} else if (args_has(args, 'E')) {
|
||||
if (flags == -1)
|
||||
flags = 0;
|
||||
flags |= POPUP_CLOSEEXIT;
|
||||
}
|
||||
if (args_has(args, 'k')) {
|
||||
if (flags == -1)
|
||||
flags = 0;
|
||||
flags |= POPUP_CLOSEANYKEY;
|
||||
}
|
||||
|
||||
new_wp->wait_item = item;
|
||||
server_redraw_session(s);
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
if (modify) {
|
||||
popup_modify(tc, title, style, border_style, lines, flags);
|
||||
goto out;
|
||||
}
|
||||
if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
|
||||
argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0)
|
||||
goto out;
|
||||
environ_free(env);
|
||||
free(cwd);
|
||||
free(title);
|
||||
cmd_free_argv(argc, argv);
|
||||
return (CMD_RETURN_WAIT);
|
||||
|
||||
out:
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
cmd_free_argv(argc, argv);
|
||||
environ_free(env);
|
||||
free(cwd);
|
||||
free(title);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
fail:
|
||||
free(cause);
|
||||
if (new_wp != NULL) {
|
||||
server_client_remove_pane(new_wp);
|
||||
layout_close_pane(new_wp);
|
||||
window_remove_pane(new_wp->window, new_wp);
|
||||
}
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
cmd_free_argv(argc, argv);
|
||||
environ_free(env);
|
||||
free(cwd);
|
||||
free(title);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-display-message.c,v 1.66 2026/09/08 08:33:10 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-display-message.c,v 1.65 2026/02/23 08:46:57 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = {
|
||||
.name = "display-message",
|
||||
.alias = "display",
|
||||
|
||||
.args = { "aCc:d:jlINpt:F:v", 0, 1, NULL },
|
||||
.usage = "[-aCIjlNpv] [-c target-client] [-d delay] [-F format] "
|
||||
.args = { "aCc:d:lINpt:F:v", 0, 1, NULL },
|
||||
.usage = "[-aCIlNpv] [-c target-client] [-d delay] [-F format] "
|
||||
CMD_TARGET_PANE_USAGE " [message]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
|
||||
@@ -67,15 +67,14 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct winlink *wl = target->wl;
|
||||
struct window_pane *wp = target->wp;
|
||||
const char *template;
|
||||
char *msg, *cause = NULL;
|
||||
char *msg, *cause;
|
||||
int delay = -1, flags, Nflag = args_has(args, 'N');
|
||||
int Cflag = args_has(args, 'C');
|
||||
struct format_tree *ft;
|
||||
u_int count = args_count(args);
|
||||
struct evbuffer *evb;
|
||||
struct json_node *jn;
|
||||
|
||||
if (args_has(args, 'I') && !args_has(args, 'j')) {
|
||||
if (args_has(args, 'I')) {
|
||||
if (wp == NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
switch (window_pane_start_input(wp, item, &cause)) {
|
||||
@@ -108,9 +107,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
||||
template = args_string(args, 0);
|
||||
else
|
||||
template = args_get(args, 'F');
|
||||
if (args_has(args, 'j') && template == NULL)
|
||||
template = "";
|
||||
else if (template == NULL)
|
||||
if (template == NULL)
|
||||
template = DISPLAY_MESSAGE_TEMPLATE;
|
||||
|
||||
/*
|
||||
@@ -132,7 +129,7 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
|
||||
format_defaults(ft, c, s, wl, wp);
|
||||
|
||||
if (args_has(args, 'a') && !args_has(args, 'j')) {
|
||||
if (args_has(args, 'a')) {
|
||||
format_each(ft, cmd_display_message_each, item);
|
||||
format_free(ft);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
@@ -142,19 +139,6 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
|
||||
msg = xstrdup(template);
|
||||
else
|
||||
msg = format_expand_time(ft, template);
|
||||
if (args_has(args, 'j')) {
|
||||
jn = json_parse(msg, &cause);
|
||||
if (jn == NULL) {
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
free(msg);
|
||||
format_free(ft);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
free(msg);
|
||||
msg = json_to_string(jn);
|
||||
json_destroy_node(jn);
|
||||
}
|
||||
|
||||
if (cmdq_get_client(item) == NULL)
|
||||
cmdq_error(item, "%s", msg);
|
||||
|
||||
18
cmd-find.c
18
cmd-find.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-find.c,v 1.88 2026/09/07 12:05:12 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-find.c,v 1.87 2026/07/17 12:42:51 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -388,11 +388,9 @@ cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window)
|
||||
|
||||
/* Try as an offset. */
|
||||
if (!exact && (window[0] == '+' || window[0] == '-')) {
|
||||
if (window[1] != '\0') {
|
||||
n = strtonum(window + 1, 1, INT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
} else
|
||||
if (window[1] != '\0')
|
||||
n = strtonum(window + 1, 1, INT_MAX, NULL);
|
||||
else
|
||||
n = 1;
|
||||
s = fs->s;
|
||||
if (fs->flags & CMD_FIND_WINDOW_INDEX) {
|
||||
@@ -628,11 +626,9 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane)
|
||||
|
||||
/* Try as an offset. */
|
||||
if (pane[0] == '+' || pane[0] == '-') {
|
||||
if (pane[1] != '\0') {
|
||||
n = strtonum(pane + 1, 1, INT_MAX, &errstr);
|
||||
if (errstr != NULL)
|
||||
return (-1);
|
||||
} else
|
||||
if (pane[1] != '\0')
|
||||
n = strtonum(pane + 1, 1, INT_MAX, NULL);
|
||||
else
|
||||
n = 1;
|
||||
wp = fs->w->active;
|
||||
if (pane[0] == '+')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-if-shell.c,v 1.87 2026/08/25 06:04:33 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-if-shell.c,v 1.86 2025/08/01 09:05:51 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -98,7 +98,6 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_ERROR);
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
@@ -165,11 +164,9 @@ cmd_if_shell_callback(struct job *job)
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-join-pane.c,v 1.75 2026/09/21 10:33:16 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-join-pane.c,v 1.73 2026/07/17 15:24:30 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 George Nachman <tmux@georgester.com>
|
||||
@@ -133,7 +133,7 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
||||
} else if (strcmp(position, "back") == 0) {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||
if (!window_pane_is_floating_with_hidden(owp))
|
||||
if (!window_pane_is_floating(owp))
|
||||
break;
|
||||
}
|
||||
if (owp != NULL)
|
||||
@@ -142,30 +142,24 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
||||
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
||||
} else if (strcmp(position, "forward") == 0) {
|
||||
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
||||
while (owp != NULL && owp->layout_cell == NULL)
|
||||
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
|
||||
if (owp != NULL) {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||
}
|
||||
} else if (strcmp(position, "backward") == 0) {
|
||||
owp = TAILQ_NEXT(wp, zentry);
|
||||
while (owp != NULL && owp->layout_cell == NULL)
|
||||
owp = TAILQ_NEXT(owp, zentry);
|
||||
if (owp != NULL && window_pane_is_floating(owp)) {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
||||
}
|
||||
} else if (strcmp(position, "forward-loop") == 0) {
|
||||
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
||||
while (owp != NULL && owp->layout_cell == NULL)
|
||||
owp = TAILQ_PREV(owp, window_panes_zindex, zentry);
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
if (owp != NULL)
|
||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||
else {
|
||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||
if (!window_pane_is_floating_with_hidden(owp))
|
||||
if (!window_pane_is_floating(owp))
|
||||
break;
|
||||
}
|
||||
if (owp != NULL)
|
||||
@@ -175,8 +169,6 @@ cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
||||
}
|
||||
} else if (strcmp(position, "backward-loop") == 0) {
|
||||
owp = TAILQ_NEXT(wp, zentry);
|
||||
while (owp != NULL && owp->layout_cell == NULL)
|
||||
owp = TAILQ_NEXT(owp, zentry);
|
||||
if (owp != NULL && window_pane_is_floating(owp)) {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
||||
@@ -308,7 +300,6 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc;
|
||||
int y, ly, x, lx;
|
||||
int old_xoff, old_yoff, old_sx, old_sy;
|
||||
|
||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||
if (wp == NULL) {
|
||||
@@ -330,17 +321,10 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
||||
ly = m->statusat - 1;
|
||||
|
||||
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.yoff += y - ly;
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
}
|
||||
@@ -363,10 +347,8 @@ cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl,
|
||||
|
||||
n = 0;
|
||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||
if (!window_pane_is_floating_with_hidden(owp))
|
||||
if (!window_pane_is_floating(owp))
|
||||
break;
|
||||
if (owp->layout_cell == NULL)
|
||||
continue;
|
||||
if (n >= z)
|
||||
break;
|
||||
n++;
|
||||
@@ -449,22 +431,25 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (cmd_get_entry(self) == &cmd_move_pane_entry) {
|
||||
if (args_has(args, 'M'))
|
||||
return (cmd_join_pane_mouse_update(item));
|
||||
if (args_has(args, 'P') ||
|
||||
args_has(args, 'z') ||
|
||||
args_has(args, 'X') ||
|
||||
if (!window_pane_is_floating(dst_wp)) {
|
||||
cmdq_error(item, "pane is not floating");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if ((s = args_get(args, 'P')) != NULL) {
|
||||
server_unzoom_window(dst_w);
|
||||
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
|
||||
}
|
||||
if ((s = args_get(args, 'z')) != NULL) {
|
||||
server_unzoom_window(dst_w);
|
||||
return (cmd_join_pane_zindex(item, dst_wl, dst_wp, s));
|
||||
}
|
||||
if (args_has(args, 'X') ||
|
||||
args_has(args, 'Y') ||
|
||||
args_has(args, 'U') ||
|
||||
args_has(args, 'D') ||
|
||||
args_has(args, 'L') ||
|
||||
args_has(args, 'R')) {
|
||||
if (!window_pane_is_floating(dst_wp)) {
|
||||
cmdq_error(item, "pane is not floating");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if ((s = args_get(args, 'P')) != NULL)
|
||||
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
|
||||
if ((s = args_get(args, 'z')) != NULL)
|
||||
return (cmd_join_pane_zindex(item, dst_wl, dst_wp, s));
|
||||
server_unzoom_window(dst_w);
|
||||
return (cmd_join_pane_move(item, args, dst_wl, dst_wp));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-list-panes.c,v 1.41 2026/09/08 10:20:08 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-list-panes.c,v 1.40 2026/06/01 14:01:09 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -97,7 +97,6 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
|
||||
struct cmdq_item *item, int type)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct window_pane *wp, **l;
|
||||
u_int i, n;
|
||||
struct format_tree *ft;
|
||||
@@ -148,7 +147,7 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
|
||||
wp = l[i];
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_add(ft, "line", "%u", n);
|
||||
format_defaults(ft, c, s, wl, wp);
|
||||
format_defaults(ft, NULL, s, wl, wp);
|
||||
|
||||
if (filter != NULL) {
|
||||
expanded = format_expand(ft, filter);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-list-sessions.c,v 1.37 2026/09/08 10:20:08 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-list-sessions.c,v 1.36 2026/02/27 08:25:12 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -53,7 +53,6 @@ static enum cmd_retval
|
||||
cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct session **l;
|
||||
u_int n, i;
|
||||
struct format_tree *ft;
|
||||
@@ -77,7 +76,7 @@ cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
|
||||
for (i = 0; i < n; i++) {
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_add(ft, "line", "%u", i);
|
||||
format_defaults(ft, c, l[i], NULL, NULL);
|
||||
format_defaults(ft, NULL, l[i], NULL, NULL);
|
||||
|
||||
if (filter != NULL) {
|
||||
expanded = format_expand(ft, filter);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-list-windows.c,v 1.51 2026/09/08 10:20:08 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-list-windows.c,v 1.50 2026/02/27 08:25:12 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -60,7 +60,6 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct winlink *wl, **l;
|
||||
struct session *s;
|
||||
u_int i, n;
|
||||
@@ -95,7 +94,7 @@ cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
|
||||
s = wl->session;
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_add(ft, "line", "%u", n);
|
||||
format_defaults(ft, c, s, wl, NULL);
|
||||
format_defaults(ft, NULL, s, wl, NULL);
|
||||
|
||||
if (filter != NULL) {
|
||||
expanded = format_expand(ft, filter);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-new-window.c,v 1.104 2026/09/03 21:04:11 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-new-window.c,v 1.103 2026/07/08 08:07:42 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -60,8 +60,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct session *s = target->s;
|
||||
struct winlink *wl = target->wl, *new_wl = NULL;
|
||||
int idx = target->idx, before;
|
||||
int count = args_count(args);
|
||||
int idx = target->idx, before, count = args_count(args);
|
||||
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
||||
const char *template, *name;
|
||||
struct cmd_find_state fs;
|
||||
@@ -75,10 +74,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
|
||||
/*
|
||||
* If -S is given, select an existing window instead of creating a
|
||||
* new one: preferring -t if it already points at a window, or
|
||||
* otherwise -n if one exists with that name. If neither matches,
|
||||
* fall through and create a window as normal.
|
||||
* If -S and -n are given and -t is not and a single window with this
|
||||
* name already exists, select it.
|
||||
*/
|
||||
name = args_get(args, 'n');
|
||||
if (name != NULL) {
|
||||
@@ -91,39 +88,32 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
wname = clean_name(expanded, 0);
|
||||
free(expanded);
|
||||
}
|
||||
if (args_has(args, 'S')) {
|
||||
if (idx != -1)
|
||||
new_wl = winlink_find_by_index(&s->windows, idx);
|
||||
else if (wname != NULL) {
|
||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
if (strcmp(wl->window->name, expanded) != 0)
|
||||
continue;
|
||||
if (new_wl == NULL) {
|
||||
new_wl = wl;
|
||||
continue;
|
||||
}
|
||||
cmdq_error(item, "multiple windows named %s",
|
||||
wname);
|
||||
free(wname);
|
||||
free(expanded);
|
||||
return (CMD_RETURN_ERROR);
|
||||
if (args_has(args, 'S') && wname != NULL && target->idx == -1) {
|
||||
expanded = format_single(item, wname, c, s, NULL, NULL);
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
if (strcmp(wl->window->name, expanded) != 0)
|
||||
continue;
|
||||
if (new_wl == NULL) {
|
||||
new_wl = wl;
|
||||
continue;
|
||||
}
|
||||
cmdq_error(item, "multiple windows named %s", wname);
|
||||
free(wname);
|
||||
free(expanded);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/* Found an existing window to select instead of creating a new one. */
|
||||
if (new_wl != NULL) {
|
||||
free(wname);
|
||||
if (args_has(args, 'd'))
|
||||
free(expanded);
|
||||
if (new_wl != NULL) {
|
||||
free(wname);
|
||||
if (args_has(args, 'd'))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (session_set_current(s, new_wl) == 0)
|
||||
server_redraw_session(s);
|
||||
if (c != NULL && c->session != NULL)
|
||||
s->curw->window->latest = c;
|
||||
recalculate_sizes();
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (session_set_current(s, new_wl) == 0)
|
||||
server_redraw_session(s);
|
||||
if (c != NULL && c->session != NULL)
|
||||
s->curw->window->latest = c;
|
||||
recalculate_sizes();
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
before = args_has(args, 'b');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-parse.y,v 1.60 2026/09/08 10:20:08 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-parse.y,v 1.58 2026/04/27 12:31:11 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -210,7 +210,7 @@ expanded : format
|
||||
cmd_find_from_client(&fs, c, 0);
|
||||
fsp = &fs;
|
||||
}
|
||||
ft = format_create(c, pi->item, FORMAT_NONE, flags);
|
||||
ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
|
||||
format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
|
||||
|
||||
$$ = format_expand(ft, $1);
|
||||
@@ -802,7 +802,6 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
|
||||
if (last == NULL) {
|
||||
pr->status = CMD_PARSE_SUCCESS;
|
||||
pr->cmdlist = cmd_list_new();
|
||||
cmd_parse_free_commands(cmds);
|
||||
return (1);
|
||||
}
|
||||
|
||||
@@ -815,7 +814,6 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
|
||||
pi->flags |= CMD_PARSE_NOALIAS;
|
||||
cmd_parse_build_commands(cmds, pi, pr);
|
||||
pi->flags &= ~CMD_PARSE_NOALIAS;
|
||||
cmd_parse_free_commands(cmds);
|
||||
return (1);
|
||||
}
|
||||
|
||||
|
||||
17
cmd-queue.c
17
cmd-queue.c
@@ -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.121 2026/07/14 17:17:17 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -802,7 +802,7 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
|
||||
u_int number = item->number;
|
||||
|
||||
if (c != NULL && (c->flags & CLIENT_CONTROL))
|
||||
control_write_guard(c, guard, t, number, flags);
|
||||
control_write(c, "%%%s %ld %u %d", guard, t, number, flags);
|
||||
}
|
||||
|
||||
/* Show message from command. */
|
||||
@@ -850,18 +850,7 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...)
|
||||
|
||||
if (c == NULL) {
|
||||
cmd_get_source(cmd, &file, &line);
|
||||
if (!cfg_finished) {
|
||||
if (file != NULL)
|
||||
cfg_add_cause("%s:%u: %s", file, line, msg);
|
||||
else
|
||||
cfg_add_cause("%s", msg);
|
||||
} else {
|
||||
if (file != NULL) {
|
||||
server_add_message("message: %s:%u: %s", file,
|
||||
line, msg);
|
||||
} else
|
||||
server_add_message("message: %s", msg);
|
||||
}
|
||||
cfg_add_cause("%s:%u: %s", file, line, msg);
|
||||
} else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
|
||||
server_add_message("%s message: %s", c->name, msg);
|
||||
if (~c->flags & CLIENT_UTF8) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.69 2026/09/21 10:33:16 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.67 2026/07/10 13:38:45 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -91,9 +91,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_redraw_window(w);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (!window_pane_is_floating(wp))
|
||||
server_unzoom_window(w);
|
||||
lc = wp->layout_cell; /* may have been replaced by unzoom */
|
||||
server_unzoom_window(w);
|
||||
|
||||
if (args_has(args, 'x')) {
|
||||
x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);
|
||||
@@ -239,7 +237,6 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
||||
int left, right;
|
||||
int new_xoff, new_yoff, resizes = 0;
|
||||
int old_xoff, old_yoff, old_sx, old_sy;
|
||||
|
||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||
if (wp == NULL) {
|
||||
@@ -250,10 +247,6 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
lc = wp->layout_cell;
|
||||
sx = wp->sx;
|
||||
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;
|
||||
right = wp->xoff + sx;
|
||||
if (window_pane_scrollbar_reserve(wp) &&
|
||||
@@ -353,8 +346,7 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
}
|
||||
if (resizes != 0) {
|
||||
layout_fix_panes(w, NULL);
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.95 2026/09/20 07:59:55 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.93 2026/07/17 12:42:51 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -164,7 +164,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (cdata->client != NULL)
|
||||
cdata->client->references++;
|
||||
if (args_has(args, 'c'))
|
||||
cdata->cwd = format_single_from_target(item, args_get(args, 'c'));
|
||||
cdata->cwd = xstrdup(args_get(args, 'c'));
|
||||
else
|
||||
cdata->cwd = xstrdup(server_client_get_cwd(c, s));
|
||||
|
||||
@@ -233,11 +233,9 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
|
||||
} else if (item == NULL) {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
|
||||
cmdq_insert_after(item, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
|
||||
if (cdata->item != NULL)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-select-layout.c,v 1.45 2026/09/09 07:03:39 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-select-layout.c,v 1.43 2026/07/10 13:38:45 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -73,13 +73,12 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct client *c = cmdq_get_target_client(item);
|
||||
struct winlink *wl = target->wl;
|
||||
struct window *w = wl->window;
|
||||
struct window_pane *wp = target->wp;
|
||||
const char *layoutname;
|
||||
char *oldlayout, *cause = NULL;
|
||||
int next, previous, layout, flags = 0;
|
||||
char *oldlayout, *cause;
|
||||
int next, previous, layout;
|
||||
|
||||
server_unzoom_window(w);
|
||||
|
||||
@@ -90,12 +89,8 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'p'))
|
||||
previous = 1;
|
||||
|
||||
if (c != NULL &&
|
||||
(c->flags & CLIENT_CONTROL) &&
|
||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
||||
oldlayout = w->old_layout;
|
||||
w->old_layout = layout_dump(w, w->layout_root, flags);
|
||||
w->old_layout = layout_dump(w, w->layout_root);
|
||||
|
||||
if (next || previous) {
|
||||
if (next)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-select-pane.c,v 1.78 2026/08/20 09:19:24 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-select-pane.c,v 1.77 2026/07/17 12:42:51 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -160,7 +160,6 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
char *title;
|
||||
const char *style;
|
||||
struct options_entry *o;
|
||||
int visible, Zflag = args_has(args, 'Z');
|
||||
|
||||
if (entry == &cmd_last_pane_entry || args_has(args, 'l')) {
|
||||
/*
|
||||
@@ -186,18 +185,14 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_redraw_window_borders(lastwp->window);
|
||||
server_status_window(lastwp->window);
|
||||
} else {
|
||||
if (w->modal != NULL && lastwp != w->modal)
|
||||
visible = 1;
|
||||
else
|
||||
visible = window_pane_is_visible(lastwp);
|
||||
if (!visible && window_push_zoom(w, 0, Zflag))
|
||||
if (window_push_zoom(w, 0, args_has(args, 'Z')))
|
||||
server_redraw_window(w);
|
||||
window_redraw_active_switch(w, lastwp);
|
||||
if (window_set_active_pane(w, lastwp, 1)) {
|
||||
cmd_find_from_winlink(current, wl, 0);
|
||||
cmd_select_pane_redraw(w);
|
||||
}
|
||||
if (!visible && window_pop_zoom(w))
|
||||
if (window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
@@ -273,18 +268,14 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
if (wp == w->active)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (w->modal != NULL && wp != w->modal)
|
||||
visible = 1;
|
||||
else
|
||||
visible = window_pane_is_visible(wp);
|
||||
if (!visible && window_push_zoom(w, 0, Zflag))
|
||||
if (window_push_zoom(w, 0, args_has(args, 'Z')))
|
||||
server_redraw_window(w);
|
||||
window_redraw_active_switch(w, wp);
|
||||
if (window_set_active_pane(w, wp, 1))
|
||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||
cmdq_insert_hook(s, item, current, "after-select-pane");
|
||||
cmd_select_pane_redraw(w);
|
||||
if (!visible && window_pop_zoom(w))
|
||||
if (window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-set-buffer.c,v 1.38 2026/08/24 07:05:23 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-set-buffer.c,v 1.37 2026/02/15 17:43:26 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -100,7 +100,6 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cmdq_error(item, "%s", cause);
|
||||
goto fail;
|
||||
}
|
||||
free(bufname);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
@@ -108,10 +107,8 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cmdq_error(item, "no data specified");
|
||||
goto fail;
|
||||
}
|
||||
if ((newsize = strlen(args_string(args, 0))) == 0) {
|
||||
free(bufname);
|
||||
if ((newsize = strlen(args_string(args, 0))) == 0)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
if (args_has(args, 'a') && pb != NULL) {
|
||||
olddata = paste_buffer_data(pb, &bufsize);
|
||||
@@ -130,7 +127,6 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'w') && tc != NULL)
|
||||
tty_set_selection(&tc->tty, "", bufdata, bufsize);
|
||||
|
||||
free(bufname);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
fail:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-source-file.c,v 1.63 2026/08/18 08:05:05 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-source-file.c,v 1.62 2025/11/18 08:42:09 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -50,7 +50,6 @@ const struct cmd_entry cmd_source_file_entry = {
|
||||
|
||||
struct cmd_source_file_data {
|
||||
struct cmdq_item *item;
|
||||
struct client *client;
|
||||
int flags;
|
||||
|
||||
struct cmdq_item *after;
|
||||
@@ -61,24 +60,10 @@ struct cmd_source_file_data {
|
||||
u_int nfiles;
|
||||
};
|
||||
|
||||
static void
|
||||
cmd_source_file_free_data(struct cmd_source_file_data *cdata)
|
||||
{
|
||||
u_int i;
|
||||
|
||||
for (i = 0; i < cdata->nfiles; i++)
|
||||
free(cdata->files[i]);
|
||||
free(cdata->files);
|
||||
if (cdata->client != NULL)
|
||||
server_client_unref(cdata->client);
|
||||
free(cdata);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_source_file_complete_cb(struct cmdq_item *item, void *data)
|
||||
cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
|
||||
{
|
||||
struct cmd_source_file_data *cdata = data;
|
||||
struct client *c = cdata->client;
|
||||
struct client *c = cmdq_get_client(item);
|
||||
|
||||
if (c == NULL) {
|
||||
cmd_source_file_depth--;
|
||||
@@ -89,36 +74,36 @@ cmd_source_file_complete_cb(struct cmdq_item *item, void *data)
|
||||
}
|
||||
|
||||
cfg_print_causes(item);
|
||||
cmd_source_file_free_data(cdata);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_source_file_complete(struct cmd_source_file_data *cdata)
|
||||
cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata)
|
||||
{
|
||||
struct client *c = cdata->client;
|
||||
struct cmdq_item *new_item;
|
||||
u_int i;
|
||||
|
||||
if (!cfg_finished) {
|
||||
cmd_source_file_free_data(cdata);
|
||||
return;
|
||||
if (cfg_finished) {
|
||||
if (cdata->retval == CMD_RETURN_ERROR &&
|
||||
c != NULL &&
|
||||
c->session == NULL)
|
||||
c->retval = 1;
|
||||
new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL);
|
||||
cmdq_insert_after(cdata->after, new_item);
|
||||
}
|
||||
|
||||
if (cdata->retval == CMD_RETURN_ERROR &&
|
||||
c != NULL &&
|
||||
c->session == NULL)
|
||||
c->retval = 1;
|
||||
new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
|
||||
cmdq_insert_after(cdata->after, new_item);
|
||||
for (i = 0; i < cdata->nfiles; i++)
|
||||
free(cdata->files[i]);
|
||||
free(cdata->files);
|
||||
free(cdata);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_source_file_done(__unused struct client *oc, const char *path,
|
||||
int error, int closed, struct evbuffer *buffer, void *data)
|
||||
cmd_source_file_done(struct client *c, const char *path, int error,
|
||||
int closed, struct evbuffer *buffer, void *data)
|
||||
{
|
||||
struct cmd_source_file_data *cdata = data;
|
||||
struct cmdq_item *item = cdata->item;
|
||||
struct client *c = cdata->client;
|
||||
void *bdata = EVBUFFER_DATA(buffer);
|
||||
size_t bsize = EVBUFFER_LENGTH(buffer);
|
||||
u_int n;
|
||||
@@ -142,7 +127,7 @@ cmd_source_file_done(__unused struct client *oc, const char *path,
|
||||
if (n < cdata->nfiles)
|
||||
file_read(c, cdata->files[n], cmd_source_file_done, cdata);
|
||||
else {
|
||||
cmd_source_file_complete(cdata);
|
||||
cmd_source_file_complete(c, cdata);
|
||||
cmdq_continue(item);
|
||||
}
|
||||
}
|
||||
@@ -202,9 +187,6 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
cdata = xcalloc(1, sizeof *cdata);
|
||||
cdata->item = item;
|
||||
cdata->client = c;
|
||||
if (c != NULL)
|
||||
c->references++;
|
||||
|
||||
if (args_has(args, 'q'))
|
||||
cdata->flags |= CMD_PARSE_QUIET;
|
||||
@@ -267,7 +249,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
|
||||
file_read(c, cdata->files[0], cmd_source_file_done, cdata);
|
||||
retval = CMD_RETURN_WAIT;
|
||||
} else
|
||||
cmd_source_file_complete(cdata);
|
||||
cmd_source_file_complete(c, cdata);
|
||||
|
||||
free(cwd);
|
||||
return (retval);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-split-window.c,v 1.146 2026/07/21 12:28:43 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
|
||||
.name = "new-pane",
|
||||
.alias = "newp",
|
||||
|
||||
.args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] "
|
||||
.args = { "bB:Cc:de:EfF:hIkl:LMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-bCdefhIklMOPvWZ] [-B border-lines] "
|
||||
"[-c start-directory] [-e environment] "
|
||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||
"[-s style] [-S active-border-style] "
|
||||
@@ -89,7 +89,6 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state fs;
|
||||
struct key_event *event = cmdq_get_event(item);
|
||||
int input, empty, is_floating, flags = 0;
|
||||
int restore_zoom = 0;
|
||||
const char *template, *style, *value;
|
||||
char *cause = NULL, *cp, *title;
|
||||
const struct options_table_entry *oe;
|
||||
@@ -97,16 +96,9 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
enum pane_lines lines;
|
||||
u_int count = args_count(args);
|
||||
|
||||
if (window_active_pane_is_over_zoom(w))
|
||||
restore_zoom = 1;
|
||||
|
||||
if (cmd_get_entry(self) == &cmd_new_pane_entry)
|
||||
is_floating = !args_has(args, 'L');
|
||||
else {
|
||||
if (!window_pane_is_visible(wp))
|
||||
restore_zoom = 0;
|
||||
if (!restore_zoom)
|
||||
window_unzoom(w, 1);
|
||||
is_floating = window_pane_is_floating(wp);
|
||||
flags |= SPAWN_SPLIT;
|
||||
}
|
||||
@@ -140,11 +132,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'Z'))
|
||||
flags |= SPAWN_ZOOM;
|
||||
if (args_has(args, 'O'))
|
||||
flags |= SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
|
||||
if (is_floating && args_has(args, 'A'))
|
||||
flags |= SPAWN_FLOATOVERZOOM;
|
||||
if ((w->flags & WINDOW_ZOOMED) && (flags & SPAWN_FLOATOVERZOOM))
|
||||
restore_zoom = 1;
|
||||
flags |= SPAWN_MODAL;
|
||||
|
||||
input = args_has(args, 'I');
|
||||
if (input || (count == 1 && *args_string(args, 0) == '\0'))
|
||||
@@ -180,8 +168,6 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
if (restore_zoom)
|
||||
window_pop_zoom(w);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
@@ -215,12 +201,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
*/
|
||||
goto fail;
|
||||
}
|
||||
if (args_has(args, 'K') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||
if (args_has(args, 'C') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CLOSEONCLICK;
|
||||
if (args_has(args, 'D') && args_has(args, 'O'))
|
||||
new_wp->flags |= PANE_CLOSEONCANCEL;
|
||||
|
||||
style = args_get(args, 's');
|
||||
if (style != NULL) {
|
||||
@@ -288,10 +270,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (~flags & SPAWN_DETACHED)
|
||||
cmd_find_from_winlink_pane(current, wl, new_wp, 0);
|
||||
|
||||
if (restore_zoom) {
|
||||
window_pop_zoom(wp->window);
|
||||
server_redraw_window(wp->window);
|
||||
} else if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) {
|
||||
if ((~flags & SPAWN_FLOATING) && !args_has(args, 'O')) {
|
||||
window_pop_zoom(wp->window);
|
||||
server_redraw_window(wp->window);
|
||||
}
|
||||
@@ -342,9 +321,8 @@ fail:
|
||||
if (!is_floating)
|
||||
layout_close_pane(new_wp);
|
||||
window_remove_pane(wp->window, new_wp);
|
||||
}
|
||||
if (restore_zoom || (~flags & SPAWN_FLOATING))
|
||||
window_pop_zoom(wp->window);
|
||||
} else if (args_has(args, 'O'))
|
||||
window_pop_modal_zoom(wp->window);
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
@@ -362,7 +340,6 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
||||
enum pane_lines lines;
|
||||
u_int sx, sy;
|
||||
int x, y, xoff, yoff, border;
|
||||
int old_xoff, old_yoff, old_sx, old_sy;
|
||||
|
||||
if (c->tty.mouse_last_pane == -1)
|
||||
return;
|
||||
@@ -418,15 +395,8 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m)
|
||||
if (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_fix_panes(w, NULL);
|
||||
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-switch-client.c,v 1.75 2026/08/20 09:19:24 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-switch-client.c,v 1.74 2026/05/22 15:22:43 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -53,7 +53,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state target;
|
||||
const char *tflag = args_get(args, 't');
|
||||
enum cmd_find_type type;
|
||||
int flags, visible, Zflag = args_has(args, 'Z');
|
||||
int flags;
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct session *s;
|
||||
@@ -139,15 +139,11 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (wl != NULL && wp != NULL && wp != wl->window->active) {
|
||||
w = wl->window;
|
||||
if (w->modal != NULL && wp != w->modal)
|
||||
visible = 1;
|
||||
else
|
||||
visible = window_pane_is_visible(wp);
|
||||
if (!visible && window_push_zoom(w, 0, Zflag))
|
||||
if (window_push_zoom(w, 0, args_has(args, 'Z')))
|
||||
server_redraw_window(w);
|
||||
window_redraw_active_switch(w, wp);
|
||||
window_set_active_pane(w, wp, 1);
|
||||
if (!visible && window_pop_zoom(w))
|
||||
if (window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
}
|
||||
if (wl != NULL) {
|
||||
|
||||
@@ -31,7 +31,7 @@ getpeereid(int s, uid_t *uid, gid_t *gid)
|
||||
{
|
||||
#ifdef HAVE_SO_PEERCRED
|
||||
struct ucred uc;
|
||||
socklen_t len = sizeof uc;
|
||||
int len = sizeof uc;
|
||||
|
||||
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1)
|
||||
return (-1);
|
||||
|
||||
25
configure.ac
25
configure.ac
@@ -1,6 +1,6 @@
|
||||
# configure.ac
|
||||
|
||||
AC_INIT([tmux], next-3.9)
|
||||
AC_INIT([tmux], next-3.8)
|
||||
AC_PREREQ([2.60])
|
||||
|
||||
AC_CONFIG_AUX_DIR(etc)
|
||||
@@ -530,15 +530,24 @@ if test "x$enable_cgroups" = xyes; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Enable sixel support.
|
||||
# Enable image support.
|
||||
AC_ARG_ENABLE(
|
||||
images,
|
||||
AS_HELP_STRING(--enable-images, enable SIXEL and ASCII images)
|
||||
)
|
||||
|
||||
# Keep the old option as an alias for one compatibility cycle.
|
||||
AC_ARG_ENABLE(
|
||||
sixel,
|
||||
AS_HELP_STRING(--enable-sixel, enable sixel images)
|
||||
AS_HELP_STRING(--enable-sixel, deprecated alias for --enable-images)
|
||||
)
|
||||
if test "x$enable_sixel" = xyes; then
|
||||
AC_DEFINE(ENABLE_SIXEL)
|
||||
enable_images=yes
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes])
|
||||
if test "x$enable_images" = xyes; then
|
||||
AC_DEFINE(ENABLE_IMAGES)
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_IMAGES, [test "x$enable_images" = xyes])
|
||||
|
||||
# Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
|
||||
AC_MSG_CHECKING(for b64_ntop)
|
||||
@@ -1137,10 +1146,10 @@ if test "x$enable_asan" = xyes; then
|
||||
else
|
||||
AC_MSG_NOTICE([ASAN: off])
|
||||
fi
|
||||
if test "x$enable_sixel" = xyes; then
|
||||
AC_MSG_NOTICE([SIXEL: on])
|
||||
if test "x$enable_images" = xyes; then
|
||||
AC_MSG_NOTICE([images: on])
|
||||
else
|
||||
AC_MSG_NOTICE([SIXEL: off])
|
||||
AC_MSG_NOTICE([images: off])
|
||||
fi
|
||||
if test "x$enable_debug" = xyes; then
|
||||
AC_MSG_NOTICE([debug: on])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: control-notify.c,v 1.39 2026/09/08 10:20:08 nicm Exp $ */
|
||||
/* $OpenBSD: control-notify.c,v 1.37 2026/07/10 13:38:45 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -44,8 +44,7 @@ control_pane_mode_changed_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%pane-mode-changed %%%u",
|
||||
wp->id);
|
||||
control_write(c, "%%pane-mode-changed %%%u", wp->id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -54,9 +53,8 @@ control_pane_mode_changed_cb(__unused const char *name,
|
||||
if (value == NULL)
|
||||
return;
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%pane-mode-changed %s", value);
|
||||
if (CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
control_write(c, "%%pane-mode-changed %s", value);
|
||||
}
|
||||
free(value);
|
||||
}
|
||||
@@ -70,7 +68,6 @@ control_window_layout_changed_cb(__unused const char *name,
|
||||
struct session *s;
|
||||
struct winlink *wl;
|
||||
struct window *w = event_payload_get_window(ep, "window");
|
||||
struct format_tree *ft;
|
||||
const char *template;
|
||||
char *cp;
|
||||
|
||||
@@ -85,25 +82,19 @@ control_window_layout_changed_cb(__unused const char *name,
|
||||
* and we don't need to inform the client about the layout change
|
||||
* because the whole window will go away soon.
|
||||
*/
|
||||
if (TAILQ_FIRST(&w->winlinks) == NULL || w->layout_root == NULL)
|
||||
wl = TAILQ_FIRST(&w->winlinks);
|
||||
if (wl == NULL || w->layout_root == NULL)
|
||||
return;
|
||||
cp = format_single(NULL, template, NULL, NULL, wl, NULL);
|
||||
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
|
||||
continue;
|
||||
s = c->session;
|
||||
wl = winlink_find_by_window_id(&s->windows, w->id);
|
||||
if (wl == NULL)
|
||||
continue;
|
||||
|
||||
ft = format_create(c, NULL, FORMAT_NONE, 0);
|
||||
format_defaults(ft, c, s, wl, NULL);
|
||||
cp = format_expand(ft, template);
|
||||
format_free(ft);
|
||||
|
||||
control_notify_write(c, "%s", cp);
|
||||
free(cp);
|
||||
if (winlink_find_by_window_id(&s->windows, w->id) != NULL)
|
||||
control_write(c, "%s", cp);
|
||||
}
|
||||
free(cp);
|
||||
}
|
||||
|
||||
/* Notify control clients that window pane changed. */
|
||||
@@ -120,7 +111,7 @@ control_window_pane_changed_cb(__unused const char *name,
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
control_notify_write(c, "%%window-pane-changed @%u %%%u", w->id,
|
||||
control_write(c, "%%window-pane-changed @%u %%%u", w->id,
|
||||
w->active->id);
|
||||
}
|
||||
}
|
||||
@@ -142,11 +133,9 @@ control_window_unlinked_cb(__unused const char *name, struct event_payload *ep,
|
||||
cs = c->session;
|
||||
|
||||
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
|
||||
control_notify_write(c, "%%window-close @%u", w->id);
|
||||
else {
|
||||
control_notify_write(c, "%%unlinked-window-close @%u",
|
||||
w->id);
|
||||
}
|
||||
control_write(c, "%%window-close @%u", w->id);
|
||||
else
|
||||
control_write(c, "%%unlinked-window-close @%u", w->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,11 +156,9 @@ control_window_linked_cb(__unused const char *name, struct event_payload *ep,
|
||||
cs = c->session;
|
||||
|
||||
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
|
||||
control_notify_write(c, "%%window-add @%u", w->id);
|
||||
else {
|
||||
control_notify_write(c, "%%unlinked-window-add @%u",
|
||||
w->id);
|
||||
}
|
||||
control_write(c, "%%window-add @%u", w->id);
|
||||
else
|
||||
control_write(c, "%%unlinked-window-add @%u", w->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,11 +179,11 @@ control_window_renamed_cb(__unused const char *name, struct event_payload *ep,
|
||||
cs = c->session;
|
||||
|
||||
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) {
|
||||
control_notify_write(c, "%%window-renamed @%u %s",
|
||||
w->id, w->name);
|
||||
control_write(c, "%%window-renamed @%u %s", w->id,
|
||||
w->name);
|
||||
} else {
|
||||
control_notify_write(c,
|
||||
"%%unlinked-window-renamed @%u %s", w->id, w->name);
|
||||
control_write(c, "%%unlinked-window-renamed @%u %s",
|
||||
w->id, w->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,12 +206,11 @@ control_client_session_changed_cb(__unused const char *name,
|
||||
continue;
|
||||
|
||||
if (cc == c) {
|
||||
control_notify_write(c, "%%session-changed $%u %s",
|
||||
s->id, s->name);
|
||||
control_write(c, "%%session-changed $%u %s", s->id,
|
||||
s->name);
|
||||
} else {
|
||||
control_notify_write(c,
|
||||
"%%client-session-changed %s $%u %s", cc->name,
|
||||
s->id, s->name);
|
||||
control_write(c, "%%client-session-changed %s $%u %s",
|
||||
cc->name, s->id, s->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,9 +226,8 @@ control_client_detached_cb(__unused const char *name, struct event_payload *ep,
|
||||
if (cc == NULL)
|
||||
return;
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%client-detached %s", cc->name);
|
||||
if (CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
control_write(c, "%%client-detached %s", cc->name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,8 +244,8 @@ control_session_renamed_cb(__unused const char *name, struct event_payload *ep,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%session-renamed $%u %s", s->id,
|
||||
s->name);
|
||||
|
||||
control_write(c, "%%session-renamed $%u %s", s->id, s->name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +259,8 @@ control_session_created_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%sessions-changed");
|
||||
|
||||
control_write(c, "%%sessions-changed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +274,8 @@ control_session_closed_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%sessions-changed");
|
||||
|
||||
control_write(c, "%%sessions-changed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,8 +298,9 @@ control_session_window_changed_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%session-window-changed $%u @%u",
|
||||
s->id, s->curw->window->id);
|
||||
|
||||
control_write(c, "%%session-window-changed $%u @%u", s->id,
|
||||
s->curw->window->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +317,8 @@ control_paste_buffer_changed_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%paste-buffer-changed %s", pbname);
|
||||
|
||||
control_write(c, "%%paste-buffer-changed %s", pbname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +335,8 @@ control_paste_buffer_deleted_cb(__unused const char *name,
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
control_notify_write(c, "%%paste-buffer-deleted %s", pbname);
|
||||
|
||||
control_write(c, "%%paste-buffer-deleted %s", pbname);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
250
control.c
250
control.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: control.c,v 1.68 2026/09/09 08:30:05 nicm Exp $ */
|
||||
/* $OpenBSD: control.c,v 1.62 2026/07/17 08:37:29 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -53,17 +53,6 @@ struct control_block {
|
||||
TAILQ_ENTRY(control_block) all_entry;
|
||||
};
|
||||
|
||||
/*
|
||||
* A notification line deferred because it was generated while a command's
|
||||
* %begin/%end guard block was open. Notifications must never appear inside a
|
||||
* guard block, so they are held here and flushed once the block closes.
|
||||
*/
|
||||
struct control_line {
|
||||
char *line;
|
||||
|
||||
TAILQ_ENTRY(control_line) entry;
|
||||
};
|
||||
|
||||
/* Control client pane. */
|
||||
struct control_pane {
|
||||
u_int pane;
|
||||
@@ -108,19 +97,11 @@ struct control_state {
|
||||
u_int pending_count;
|
||||
|
||||
TAILQ_HEAD(, control_block) all_blocks;
|
||||
size_t queued_reply_bytes;
|
||||
|
||||
struct bufferevent *read_event;
|
||||
struct bufferevent *write_event;
|
||||
|
||||
struct monitor_set *subs;
|
||||
|
||||
/*
|
||||
* Depth of open %begin/%end guard blocks and notifications deferred
|
||||
* until the outermost block closes.
|
||||
*/
|
||||
int guard_depth;
|
||||
TAILQ_HEAD(, control_line) deferred;
|
||||
};
|
||||
|
||||
/* Low and high watermarks. */
|
||||
@@ -133,9 +114,6 @@ struct control_state {
|
||||
/* Maximum age for clients that are not using pause mode. */
|
||||
#define CONTROL_MAXIMUM_AGE 300000
|
||||
|
||||
/* Maximum buffered command replies for a client that is not reading. */
|
||||
#define CONTROL_MAXIMUM_REPLY_BUFFER (64 * 1024 * 1024)
|
||||
|
||||
/* Flags to ignore client. */
|
||||
#define CONTROL_IGNORE_FLAGS \
|
||||
(CLIENT_CONTROL_NOOUTPUT| \
|
||||
@@ -169,15 +147,6 @@ RB_GENERATE_STATIC(control_windows, control_window, entry, control_window_cmp);
|
||||
static void
|
||||
control_free_block(struct control_state *cs, struct control_block *cb)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
if (cb->size == 0 && cb->line != NULL) {
|
||||
size = strlen(cb->line) + 1;
|
||||
if (cs->queued_reply_bytes > size)
|
||||
cs->queued_reply_bytes -= size;
|
||||
else
|
||||
cs->queued_reply_bytes = 0;
|
||||
}
|
||||
free(cb->line);
|
||||
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
|
||||
free(cb);
|
||||
@@ -383,7 +352,7 @@ control_continue_pane(struct client *c, struct window_pane *wp)
|
||||
cp->flags &= ~CONTROL_PANE_PAUSED;
|
||||
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
||||
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
||||
control_notify_write(c, "%%continue %%%u", wp->id);
|
||||
control_write(c, "%%continue %%%u", wp->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,186 +366,52 @@ control_pause_pane(struct client *c, struct window_pane *wp)
|
||||
if (~cp->flags & CONTROL_PANE_PAUSED) {
|
||||
cp->flags |= CONTROL_PANE_PAUSED;
|
||||
control_discard_pane(c, cp);
|
||||
control_notify_write(c, "%%pause %%%u", wp->id);
|
||||
control_write(c, "%%pause %%%u", wp->id);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset a pane after its buffer has been replaced: drop any output still
|
||||
* queued from the old buffer and start again from the pane's own offset.
|
||||
*/
|
||||
void
|
||||
control_reset_pane(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
struct control_pane *cp;
|
||||
|
||||
if (c->control_state == NULL)
|
||||
return;
|
||||
cp = control_get_pane(c, wp);
|
||||
if (cp == NULL)
|
||||
return;
|
||||
control_discard_pane(c, cp);
|
||||
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
||||
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the replies buffered for a client, including one about to be
|
||||
* added, have grown too large and kill it if so. Returns 1 if further output
|
||||
* for the client should be dropped.
|
||||
*/
|
||||
static int
|
||||
control_check_reply_buffer(struct client *c, size_t added)
|
||||
/* Write a line. */
|
||||
static void printflike(2, 0)
|
||||
control_vwrite(struct client *c, const char *fmt, va_list ap)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
size_t size;
|
||||
char *s;
|
||||
|
||||
if (c->flags & CLIENT_CONTROL_DISCARD)
|
||||
return (1);
|
||||
size = EVBUFFER_LENGTH(cs->write_event->output);
|
||||
size += cs->queued_reply_bytes;
|
||||
size += added;
|
||||
if (size < CONTROL_MAXIMUM_REPLY_BUFFER)
|
||||
return (0);
|
||||
xvasprintf(&s, fmt, ap);
|
||||
log_debug("%s: %s: writing line: %s", __func__, c->name, s);
|
||||
|
||||
log_debug("%s: %s: %zu bytes of replies buffered", __func__, c->name,
|
||||
size);
|
||||
if (~c->flags & CLIENT_EXIT) {
|
||||
c->exit_message = xstrdup("too far behind");
|
||||
c->flags |= CLIENT_EXIT;
|
||||
control_discard(c);
|
||||
}
|
||||
c->flags |= CLIENT_CONTROL_DISCARD;
|
||||
return (1);
|
||||
}
|
||||
bufferevent_write(cs->write_event, s, strlen(s));
|
||||
bufferevent_write(cs->write_event, "\n", 1);
|
||||
|
||||
/* Write an already-formatted line, queueing it behind %output if needed. */
|
||||
static void
|
||||
control_write_line(struct client *c, char *line)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_block *cb;
|
||||
size_t size = strlen(line) + 1;
|
||||
|
||||
if (control_check_reply_buffer(c, size)) {
|
||||
free(line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TAILQ_EMPTY(&cs->all_blocks)) {
|
||||
log_debug("%s: %s: writing line: %s", __func__, c->name, line);
|
||||
bufferevent_write(cs->write_event, line, size - 1);
|
||||
bufferevent_write(cs->write_event, "\n", 1);
|
||||
bufferevent_enable(cs->write_event, EV_WRITE);
|
||||
free(line);
|
||||
return;
|
||||
}
|
||||
|
||||
cb = xcalloc(1, sizeof *cb);
|
||||
cb->line = line;
|
||||
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
|
||||
cs->queued_reply_bytes += size;
|
||||
cb->t = get_timer();
|
||||
|
||||
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
|
||||
bufferevent_enable(cs->write_event, EV_WRITE);
|
||||
free(s);
|
||||
}
|
||||
|
||||
/* Flush notifications that were deferred while a guard block was open. */
|
||||
static void
|
||||
control_flush_deferred(struct client *c)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_line *cl, *cl1;
|
||||
|
||||
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
|
||||
TAILQ_REMOVE(&cs->deferred, cl, entry);
|
||||
control_write_line(c, cl->line);
|
||||
free(cl);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a line of command output or error text. This is a sink for arbitrary
|
||||
* user-controlled text (command output, capture-pane, error messages), so it
|
||||
* must never try to interpret the content: guard tracking is done only in
|
||||
* control_write_guard.
|
||||
*/
|
||||
/* Write a line. */
|
||||
void
|
||||
control_write(struct client *c, const char *fmt, ...)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
char *line;
|
||||
struct control_block *cb;
|
||||
va_list ap;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
xvasprintf(&line, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
control_write_line(c, line);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a %begin, %end or %error guard around a command's output. This is the
|
||||
* only place guard lines are produced, so the block depth is maintained here;
|
||||
* when the outermost block closes any deferred notifications are flushed after
|
||||
* it. "guard" is always one of the fixed strings from cmdq_guard, never user
|
||||
* text.
|
||||
*/
|
||||
void
|
||||
control_write_guard(struct client *c, const char *guard, long t, u_int number,
|
||||
int flags)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
char *line;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
if (strcmp(guard, "begin") == 0)
|
||||
cs->guard_depth++;
|
||||
|
||||
xasprintf(&line, "%%%s %ld %u %d", guard, t, number, flags);
|
||||
control_write_line(c, line);
|
||||
|
||||
if (strcmp(guard, "begin") != 0 && cs->guard_depth > 0 &&
|
||||
--cs->guard_depth == 0)
|
||||
control_flush_deferred(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a notification line. Notifications must never appear inside a command's
|
||||
* %begin/%end guard block, so if one is open the line is deferred until it
|
||||
* closes.
|
||||
*/
|
||||
void
|
||||
control_notify_write(struct client *c, const char *fmt, ...)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_line *cl;
|
||||
va_list ap;
|
||||
char *line;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
xvasprintf(&line, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (cs->guard_depth == 0) {
|
||||
control_write_line(c, line);
|
||||
if (TAILQ_EMPTY(&cs->all_blocks)) {
|
||||
control_vwrite(c, fmt, ap);
|
||||
va_end(ap);
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("%s: %s: deferring notification: %s", __func__, c->name,
|
||||
line);
|
||||
cl = xcalloc(1, sizeof *cl);
|
||||
cl->line = line;
|
||||
TAILQ_INSERT_TAIL(&cs->deferred, cl, entry);
|
||||
cb = xcalloc(1, sizeof *cb);
|
||||
xvasprintf(&cb->line, fmt, ap);
|
||||
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
|
||||
cb->t = get_timer();
|
||||
|
||||
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
|
||||
bufferevent_enable(cs->write_event, EV_WRITE);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Check age for this pane. */
|
||||
@@ -603,7 +438,7 @@ control_check_age(struct client *c, struct window_pane *wp,
|
||||
return (0);
|
||||
cp->flags |= CONTROL_PANE_PAUSED;
|
||||
control_discard_pane(c, cp);
|
||||
control_notify_write(c, "%%pause %%%u", wp->id);
|
||||
control_write(c, "%%pause %%%u", wp->id);
|
||||
} else {
|
||||
if (age < CONTROL_MAXIMUM_AGE)
|
||||
return (0);
|
||||
@@ -970,17 +805,14 @@ control_sub_change(struct monitor_change *change, __unused void *data)
|
||||
|
||||
if (wp != NULL) {
|
||||
w = wp->window;
|
||||
control_notify_write(c,
|
||||
"%%subscription-changed %s $%u @%u %u %%%u : %s",
|
||||
control_write(c, "%%subscription-changed %s $%u @%u %u %%%u : %s",
|
||||
change->name, s->id, w->id, wl->idx, wp->id, change->value);
|
||||
} else if (wl != NULL) {
|
||||
w = wl->window;
|
||||
control_notify_write(c,
|
||||
"%%subscription-changed %s $%u @%u %u - : %s",
|
||||
control_write(c, "%%subscription-changed %s $%u @%u %u - : %s",
|
||||
change->name, s->id, w->id, wl->idx, change->value);
|
||||
} else {
|
||||
control_notify_write(c,
|
||||
"%%subscription-changed %s $%u - - - : %s",
|
||||
control_write(c, "%%subscription-changed %s $%u - - - : %s",
|
||||
change->name, s->id, change->value);
|
||||
}
|
||||
}
|
||||
@@ -1003,7 +835,6 @@ control_start(struct client *c)
|
||||
RB_INIT(&cs->windows);
|
||||
TAILQ_INIT(&cs->pending_list);
|
||||
TAILQ_INIT(&cs->all_blocks);
|
||||
TAILQ_INIT(&cs->deferred);
|
||||
cs->subs = monitor_create_client(c, control_sub_change, NULL);
|
||||
|
||||
cs->read_event = bufferevent_new(c->fd, control_read_callback,
|
||||
@@ -1047,20 +878,6 @@ control_discard(struct client *c)
|
||||
bufferevent_disable(cs->read_event, EV_READ);
|
||||
}
|
||||
|
||||
/* Discard all tmux-owned queued control blocks and stop writing. */
|
||||
void
|
||||
control_discard_all(struct client *c)
|
||||
{
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_block *cb, *cb1;
|
||||
|
||||
control_discard(c);
|
||||
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
||||
control_free_block(cs, cb);
|
||||
cs->queued_reply_bytes = 0;
|
||||
bufferevent_disable(cs->write_event, EV_WRITE);
|
||||
}
|
||||
|
||||
/* Stop control mode. */
|
||||
void
|
||||
control_stop(struct client *c)
|
||||
@@ -1068,19 +885,12 @@ control_stop(struct client *c)
|
||||
struct control_state *cs = c->control_state;
|
||||
struct control_block *cb, *cb1;
|
||||
struct control_window *cw, *cw1;
|
||||
struct control_line *cl, *cl1;
|
||||
|
||||
if (cs == NULL)
|
||||
return;
|
||||
|
||||
monitor_destroy(cs->subs);
|
||||
|
||||
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
|
||||
TAILQ_REMOVE(&cs->deferred, cl, entry);
|
||||
free(cl->line);
|
||||
free(cl);
|
||||
}
|
||||
|
||||
if (~c->flags & CLIENT_CONTROLCONTROL)
|
||||
bufferevent_free(cs->write_event);
|
||||
bufferevent_free(cs->read_event);
|
||||
|
||||
86
file.c
86
file.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: file.c,v 1.22 2026/08/17 07:56:56 nicm Exp $ */
|
||||
/* $OpenBSD: file.c,v 1.21 2026/07/26 15:08:15 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -502,8 +502,7 @@ file_push(struct client_file *cf)
|
||||
} else if (cf->stream > 2) {
|
||||
close.stream = cf->stream;
|
||||
proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
|
||||
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
|
||||
file_fire_done(cf);
|
||||
file_fire_done(cf);
|
||||
}
|
||||
free(msg);
|
||||
}
|
||||
@@ -528,48 +527,14 @@ file_write_left(struct client_files *files)
|
||||
return (waiting != 0);
|
||||
}
|
||||
|
||||
/* Finish writing a client file. */
|
||||
static void
|
||||
file_write_finished(struct client_file *cf)
|
||||
{
|
||||
struct msg_write_done msg;
|
||||
|
||||
if (cf->event != NULL) {
|
||||
bufferevent_free(cf->event);
|
||||
cf->event = NULL;
|
||||
}
|
||||
if (cf->fd != -1) {
|
||||
if (close(cf->fd) != 0 && cf->error == 0)
|
||||
cf->error = errno;
|
||||
cf->fd = -1;
|
||||
}
|
||||
|
||||
msg.stream = cf->stream;
|
||||
msg.error = cf->error;
|
||||
proc_send(cf->peer, MSG_WRITE_DONE, -1, &msg, sizeof msg);
|
||||
|
||||
if (cf->cb != NULL)
|
||||
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
|
||||
file_free(cf);
|
||||
}
|
||||
|
||||
/* Client file write error callback. */
|
||||
static void
|
||||
file_write_error_callback(__unused struct bufferevent *bev, short what,
|
||||
file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
|
||||
void *arg)
|
||||
{
|
||||
struct client_file *cf = arg;
|
||||
int error;
|
||||
|
||||
if (what & EVBUFFER_ERROR)
|
||||
error = errno;
|
||||
else
|
||||
error = EIO;
|
||||
if (error == 0)
|
||||
error = EIO;
|
||||
|
||||
log_debug("write error file %d", cf->stream);
|
||||
cf->error = error;
|
||||
|
||||
bufferevent_free(cf->event);
|
||||
cf->event = NULL;
|
||||
@@ -577,9 +542,7 @@ file_write_error_callback(__unused struct bufferevent *bev, short what,
|
||||
close(cf->fd);
|
||||
cf->fd = -1;
|
||||
|
||||
if (cf->closed)
|
||||
file_write_finished(cf);
|
||||
else if (cf->cb != NULL)
|
||||
if (cf->cb != NULL)
|
||||
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
|
||||
}
|
||||
|
||||
@@ -591,10 +554,15 @@ file_write_callback(__unused struct bufferevent *bev, void *arg)
|
||||
|
||||
log_debug("write check file %d", cf->stream);
|
||||
|
||||
if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0)
|
||||
file_write_finished(cf);
|
||||
else if (cf->cb != NULL)
|
||||
if (cf->cb != NULL)
|
||||
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
|
||||
|
||||
if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) {
|
||||
bufferevent_free(cf->event);
|
||||
close(cf->fd);
|
||||
RB_REMOVE(client_files, cf->tree, cf);
|
||||
file_free(cf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle a file write open message (client). */
|
||||
@@ -695,10 +663,14 @@ file_write_close(struct client_files *files, struct imsg *imsg)
|
||||
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
|
||||
fatalx("unknown stream number");
|
||||
log_debug("close file %d", cf->stream);
|
||||
cf->closed = 1;
|
||||
|
||||
if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) {
|
||||
file_write_finished(cf);
|
||||
if (cf->event != NULL)
|
||||
bufferevent_free(cf->event);
|
||||
if (cf->fd != -1)
|
||||
close(cf->fd);
|
||||
RB_REMOVE(client_files, files, cf);
|
||||
file_free(cf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,28 +829,6 @@ file_write_ready(struct client_files *files, struct imsg *imsg)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Handle a write done message (server). */
|
||||
int
|
||||
file_write_done(struct client_files *files, struct imsg *imsg)
|
||||
{
|
||||
struct msg_write_done *msg = imsg->data;
|
||||
size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
|
||||
struct client_file find, *cf;
|
||||
|
||||
if (msglen != sizeof *msg)
|
||||
return (-1);
|
||||
find.stream = msg->stream;
|
||||
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
|
||||
return (0);
|
||||
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
|
||||
return (0);
|
||||
|
||||
log_debug("file %d write done", cf->stream);
|
||||
cf->error = msg->error;
|
||||
file_fire_done(cf);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Handle read data message (server). */
|
||||
int
|
||||
file_read_data(struct client_files *files, struct imsg *imsg)
|
||||
|
||||
168
format.c
168
format.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: format.c,v 1.418 2026/09/20 08:19:31 nicm Exp $ */
|
||||
/* $OpenBSD: format.c,v 1.410 2026/07/29 20:43:20 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -855,42 +855,26 @@ format_cb_window_active_clients_list(struct format_tree *ft)
|
||||
static void *
|
||||
format_cb_window_layout(struct format_tree *ft)
|
||||
{
|
||||
struct client *c = ft->client;
|
||||
struct window *w = ft->w;
|
||||
struct layout_cell *lcroot;
|
||||
int flags = 0;
|
||||
struct window *w = ft->w;
|
||||
|
||||
if (w == NULL)
|
||||
return (NULL);
|
||||
|
||||
if (w->saved_layout_root != NULL)
|
||||
lcroot = w->saved_layout_root;
|
||||
else
|
||||
lcroot = w->layout_root;
|
||||
|
||||
if (c != NULL &&
|
||||
(c->flags & CLIENT_CONTROL) &&
|
||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
||||
return (layout_dump(w, lcroot, flags));
|
||||
return (layout_dump(w, w->saved_layout_root));
|
||||
return (layout_dump(w, w->layout_root));
|
||||
}
|
||||
|
||||
/* Callback for window_visible_layout. */
|
||||
static void *
|
||||
format_cb_window_visible_layout(struct format_tree *ft)
|
||||
{
|
||||
struct client *c = ft->client;
|
||||
struct window *w = ft->w;
|
||||
int flags = 0;
|
||||
|
||||
if (w == NULL)
|
||||
return (NULL);
|
||||
|
||||
if (c != NULL &&
|
||||
(c->flags & CLIENT_CONTROL) &&
|
||||
(~c->flags & CLIENT_CONTROL_NEWLAYOUTS))
|
||||
flags |= LAYOUT_CUSTOM_OLD_FORMAT;
|
||||
return (layout_dump(w, w->layout_root, flags));
|
||||
return (layout_dump(w, w->layout_root));
|
||||
}
|
||||
|
||||
/* Callback for pane_start_command. */
|
||||
@@ -1903,37 +1887,6 @@ format_cb_cursor_blinking(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_added. */
|
||||
static void *
|
||||
format_cb_history_added(struct format_tree *ft)
|
||||
{
|
||||
if (ft->wp != NULL)
|
||||
return (format_printf("%u", ft->wp->base.grid->scroll_added));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_collected. */
|
||||
static void *
|
||||
format_cb_history_collected(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
|
||||
if (wp != NULL)
|
||||
return (format_printf("%u", wp->base.grid->scroll_collected));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_generation. */
|
||||
static void *
|
||||
format_cb_history_generation(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
|
||||
if (wp != NULL)
|
||||
return (format_printf("%u", wp->base.grid->scroll_generation));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for history_limit. */
|
||||
static void *
|
||||
format_cb_history_limit(struct format_tree *ft)
|
||||
@@ -2148,60 +2101,6 @@ format_cb_synchronized_output_flag(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_private_modes. */
|
||||
static void *
|
||||
format_cb_pane_private_modes(struct format_tree *ft)
|
||||
{
|
||||
static const struct {
|
||||
int mode;
|
||||
int number;
|
||||
} table[] = {
|
||||
{ MODE_KCURSOR, 1 }, /* DECCKM */
|
||||
{ MODE_ORIGIN, 6 }, /* DECOM */
|
||||
{ MODE_WRAP, 7 }, /* DECAWM */
|
||||
{ MODE_CURSOR_BLINKING, 12 }, /* cursor blinking */
|
||||
{ MODE_CURSOR, 25 }, /* DECTCEM */
|
||||
{ MODE_MOUSE_STANDARD, 1000 }, /* mouse normal tracking */
|
||||
{ MODE_MOUSE_BUTTON, 1002 }, /* mouse button tracking */
|
||||
{ MODE_MOUSE_ALL, 1003 }, /* mouse any tracking */
|
||||
{ MODE_FOCUSON, 1004 }, /* focus reporting */
|
||||
{ MODE_MOUSE_UTF8, 1005 }, /* mouse: UTF-8 */
|
||||
{ MODE_MOUSE_SGR, 1006 }, /* mouse: SGR */
|
||||
{ MODE_BRACKETPASTE, 2004 }, /* bracketed paste */
|
||||
{ MODE_SYNC, 2026 }, /* synchronized output */
|
||||
{ MODE_THEME_UPDATES, 2031 }, /* theme update notifications */
|
||||
};
|
||||
int mode;
|
||||
char *value = NULL, *tmp;
|
||||
u_int i;
|
||||
|
||||
if (ft->wp == NULL)
|
||||
return (NULL);
|
||||
mode = ft->wp->base.mode;
|
||||
|
||||
for (i = 0; i < nitems(table); i++) {
|
||||
if (~mode & table[i].mode)
|
||||
continue;
|
||||
/*
|
||||
* Only report cursor blinking when set by the application, not
|
||||
* when it comes from the cursor-style option.
|
||||
*/
|
||||
if (table[i].mode == MODE_CURSOR_BLINKING &&
|
||||
(~mode & MODE_CURSOR_BLINKING_SET))
|
||||
continue;
|
||||
if (value == NULL)
|
||||
xasprintf(&value, "%d", table[i].number);
|
||||
else {
|
||||
xasprintf(&tmp, "%s,%d", value, table[i].number);
|
||||
free(value);
|
||||
value = tmp;
|
||||
}
|
||||
}
|
||||
if (value == NULL)
|
||||
return (xstrdup(""));
|
||||
return (value);
|
||||
}
|
||||
|
||||
/* Callback for pane_active. */
|
||||
static void *
|
||||
format_cb_pane_active(struct format_tree *ft)
|
||||
@@ -2323,19 +2222,6 @@ format_cb_pane_last_output_time(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_output_generation. */
|
||||
static void *
|
||||
format_cb_pane_output_generation(struct format_tree *ft)
|
||||
{
|
||||
unsigned long long value;
|
||||
|
||||
if (ft->wp != NULL) {
|
||||
value = ft->wp->output_generation;
|
||||
return (format_printf("%llu", value));
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_last_prompt_time. */
|
||||
static void *
|
||||
format_cb_pane_last_prompt_time(struct format_tree *ft)
|
||||
@@ -2876,10 +2762,10 @@ format_cb_session_activity_flag(struct format_tree *ft)
|
||||
|
||||
if (ft->s != NULL) {
|
||||
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
||||
if (wl->flags & WINLINK_ACTIVITY)
|
||||
if (ft->wl->flags & WINLINK_ACTIVITY)
|
||||
return (xstrdup("1"));
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
@@ -2894,8 +2780,8 @@ format_cb_session_bell_flag(struct format_tree *ft)
|
||||
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
||||
if (wl->flags & WINLINK_BELL)
|
||||
return (xstrdup("1"));
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
@@ -2908,10 +2794,10 @@ format_cb_session_silence_flag(struct format_tree *ft)
|
||||
|
||||
if (ft->s != NULL) {
|
||||
RB_FOREACH(wl, winlinks, &ft->s->windows) {
|
||||
if (wl->flags & WINLINK_SILENCE)
|
||||
if (ft->wl->flags & WINLINK_SILENCE)
|
||||
return (xstrdup("1"));
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
@@ -3078,6 +2964,17 @@ format_cb_sixel_support(__unused struct format_tree *ft)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for image_support. */
|
||||
static void *
|
||||
format_cb_image_support(__unused struct format_tree *ft)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
return (xstrdup("1"));
|
||||
#else
|
||||
return (xstrdup("0"));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for active_window_index. */
|
||||
static void *
|
||||
format_cb_active_window_index(struct format_tree *ft)
|
||||
@@ -3723,21 +3620,12 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "cursor_y", FORMAT_TABLE_STRING,
|
||||
format_cb_cursor_y
|
||||
},
|
||||
{ "history_added", FORMAT_TABLE_STRING,
|
||||
format_cb_history_added
|
||||
},
|
||||
{ "history_all_bytes", FORMAT_TABLE_STRING,
|
||||
format_cb_history_all_bytes
|
||||
},
|
||||
{ "history_bytes", FORMAT_TABLE_STRING,
|
||||
format_cb_history_bytes
|
||||
},
|
||||
{ "history_collected", FORMAT_TABLE_STRING,
|
||||
format_cb_history_collected
|
||||
},
|
||||
{ "history_generation", FORMAT_TABLE_STRING,
|
||||
format_cb_history_generation
|
||||
},
|
||||
{ "history_limit", FORMAT_TABLE_STRING,
|
||||
format_cb_history_limit
|
||||
},
|
||||
@@ -3750,6 +3638,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "host_short", FORMAT_TABLE_STRING,
|
||||
format_cb_host_short
|
||||
},
|
||||
{ "image_support", FORMAT_TABLE_STRING,
|
||||
format_cb_image_support
|
||||
},
|
||||
{ "insert_flag", FORMAT_TABLE_STRING,
|
||||
format_cb_insert_flag
|
||||
},
|
||||
@@ -3918,9 +3809,6 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "pane_mode", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_mode
|
||||
},
|
||||
{ "pane_output_generation", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_output_generation
|
||||
},
|
||||
{ "pane_path", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_path
|
||||
},
|
||||
@@ -3939,9 +3827,6 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "pane_pipe_pid", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_pipe_pid
|
||||
},
|
||||
{ "pane_private_modes", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_private_modes
|
||||
},
|
||||
{ "pane_right", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_right
|
||||
},
|
||||
@@ -4329,8 +4214,6 @@ format_log_debug_cb(const char *key, const char *value, void *arg)
|
||||
void
|
||||
format_log_debug(struct format_tree *ft, const char *prefix)
|
||||
{
|
||||
if (log_get_level() == 0)
|
||||
return;
|
||||
format_each(ft, format_log_debug_cb, (void *)prefix);
|
||||
}
|
||||
|
||||
@@ -4459,7 +4342,7 @@ format_quote_shell(const char *s)
|
||||
|
||||
at = out = xmalloc(strlen(s) * 2 + 1);
|
||||
for (cp = s; *cp != '\0'; cp++) {
|
||||
if (strchr("|&;<>(){}$`\\\"'*?[# =%\n\t", *cp) != NULL)
|
||||
if (strchr("|&;<>()$`\\\"'*?[# =%\n\t", *cp) != NULL)
|
||||
*at++ = '\\';
|
||||
*at++ = *cp;
|
||||
}
|
||||
@@ -5250,7 +5133,6 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
|
||||
format_log(es, "session loop: $%u", s->id);
|
||||
if (active != NULL &&
|
||||
ft->c != NULL &&
|
||||
ft->c->session != NULL &&
|
||||
s->id == ft->c->session->id)
|
||||
use = active;
|
||||
else
|
||||
|
||||
@@ -44,7 +44,7 @@ LLVMFuzzerTestOneInput(const u_char *data, size_t size)
|
||||
w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0);
|
||||
wp = window_add_pane(w, NULL, 0, 0);
|
||||
bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty);
|
||||
wp->ictx = input_init(wp, vpty[0], NULL);
|
||||
wp->ictx = input_init(wp, vpty[0], NULL, NULL);
|
||||
window_add_ref(w, __func__);
|
||||
|
||||
wp->fd = open("/dev/null", O_WRONLY);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: grid-view.c,v 1.39 2026/08/03 12:58:53 nicm Exp $ */
|
||||
/* $OpenBSD: grid-view.c,v 1.38 2026/01/22 08:55:01 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -47,9 +47,9 @@ grid_view_set_cell(struct grid *gd, u_int px, u_int py,
|
||||
|
||||
/* Set padding. */
|
||||
void
|
||||
grid_view_set_padding(struct grid *gd, u_int px, u_int py, int bg)
|
||||
grid_view_set_padding(struct grid *gd, u_int px, u_int py)
|
||||
{
|
||||
grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py), bg);
|
||||
grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py));
|
||||
}
|
||||
|
||||
/* Set cells. */
|
||||
|
||||
243
grid.c
243
grid.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: grid.c,v 1.158 2026/09/01 12:49:49 nicm Exp $ */
|
||||
/* $OpenBSD: grid.c,v 1.155 2026/07/29 17:42:56 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -41,6 +41,9 @@
|
||||
/* Default grid cell data. */
|
||||
const struct grid_cell grid_default_cell = {
|
||||
{ { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 8, 0
|
||||
#ifdef ENABLE_IMAGES
|
||||
, 0, 0, 0
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -49,16 +52,38 @@ const struct grid_cell grid_default_cell = {
|
||||
*/
|
||||
static const struct grid_cell grid_padding_cell = {
|
||||
{ { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 8, 0
|
||||
#ifdef ENABLE_IMAGES
|
||||
, 0, 0, 0
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Cleared grid cell data. */
|
||||
static const struct grid_cell grid_cleared_cell = {
|
||||
{ { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 8, 0
|
||||
#ifdef ENABLE_IMAGES
|
||||
, 0, 0, 0
|
||||
#endif
|
||||
};
|
||||
static const struct grid_cell_entry grid_cleared_entry = {
|
||||
{ .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Return the image referenced by a stored cell, or zero. */
|
||||
static u_int
|
||||
grid_entry_image(struct grid_line *gl, struct grid_cell_entry *gce)
|
||||
{
|
||||
struct grid_extd_entry *gee;
|
||||
|
||||
if (~gce->flags & GRID_FLAG_EXTENDED || gce->offset >= gl->extdsize)
|
||||
return (0);
|
||||
gee = &gl->extddata[gce->offset];
|
||||
if (~gee->flags & GRID_FLAG_IMAGE)
|
||||
return (0);
|
||||
return (image_get_id_by_grid_id(gee->image_id));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
void
|
||||
grid_check_is_clear(struct grid *gd)
|
||||
@@ -134,6 +159,10 @@ grid_need_extended_cell(const struct grid_cell_entry *gce,
|
||||
return (1);
|
||||
if (gc->flags & GRID_FLAG_TAB)
|
||||
return (1);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (gc->flags & GRID_FLAG_IMAGE)
|
||||
return (1);
|
||||
#endif
|
||||
return (0);
|
||||
}
|
||||
|
||||
@@ -181,6 +210,11 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce,
|
||||
gee->bg = gc->bg;
|
||||
gee->us = gc->us;
|
||||
gee->link = gc->link;
|
||||
#ifdef ENABLE_IMAGES
|
||||
gee->image_id = image_get_grid_id(gc->image_id);
|
||||
gee->image_x = gc->image_x;
|
||||
gee->image_y = gc->image_y;
|
||||
#endif
|
||||
return (gee);
|
||||
}
|
||||
|
||||
@@ -268,6 +302,12 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved)
|
||||
struct grid_extd_entry *gee;
|
||||
u_int old_offset = gce->offset;
|
||||
int had_extd = (gce->flags & GRID_FLAG_EXTENDED);
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int image_id = grid_entry_image(gl, gce);
|
||||
|
||||
if (!moved && image_id != 0)
|
||||
image_free(image_id);
|
||||
#endif
|
||||
|
||||
memcpy(gce, &grid_cleared_entry, sizeof *gce);
|
||||
if (!moved && had_extd && old_offset < gl->extdsize) {
|
||||
@@ -314,6 +354,13 @@ grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2)
|
||||
return (0);
|
||||
if (gc1->link != gc2->link)
|
||||
return (0);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if ((gc1->flags & GRID_FLAG_IMAGE) &&
|
||||
(gc1->image_id != gc2->image_id ||
|
||||
gc1->image_x != gc2->image_x ||
|
||||
gc1->image_y != gc2->image_y))
|
||||
return (0);
|
||||
#endif
|
||||
return (1);
|
||||
}
|
||||
|
||||
@@ -346,6 +393,10 @@ static void
|
||||
grid_free_line(struct grid *gd, u_int py)
|
||||
{
|
||||
struct grid_line *gl = &gd->linedata[py];
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell_entry *gce;
|
||||
u_int image_id, px;
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
assert(gl->cellused <= gl->cellsize);
|
||||
@@ -353,6 +404,14 @@ grid_free_line(struct grid *gd, u_int py)
|
||||
assert(gl->cellsize == 0 || gl->celldata != NULL);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
for (px = 0; px < gl->cellsize; px++) {
|
||||
gce = &gl->celldata[px];
|
||||
image_id = grid_entry_image(gl, gce);
|
||||
if (image_id != 0)
|
||||
image_free(image_id);
|
||||
}
|
||||
#endif
|
||||
free(gl->celldata);
|
||||
free(gl->extddata);
|
||||
memset(gl, 0, sizeof *gl);
|
||||
@@ -624,6 +683,11 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc)
|
||||
gc->bg = gee->bg;
|
||||
gc->us = gee->us;
|
||||
gc->link = gee->link;
|
||||
#ifdef ENABLE_IMAGES
|
||||
gc->image_id = image_get_id_by_grid_id(gee->image_id);
|
||||
gc->image_x = gee->image_x;
|
||||
gc->image_y = gee->image_y;
|
||||
#endif
|
||||
|
||||
if (gc->flags & GRID_FLAG_TAB)
|
||||
grid_set_tab(gc, gee->data);
|
||||
@@ -644,6 +708,11 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc)
|
||||
gc->us = 8;
|
||||
utf8_set(&gc->data, gce->data.data);
|
||||
gc->link = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
gc->image_id = 0;
|
||||
gc->image_x = 0;
|
||||
gc->image_y = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Get cell for reading. */
|
||||
@@ -663,6 +732,9 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
|
||||
{
|
||||
struct grid_line *gl;
|
||||
struct grid_cell_entry *gce;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int old_id, new_id = 0;
|
||||
#endif
|
||||
|
||||
if (grid_check_y(gd, __func__, py) != 0)
|
||||
return;
|
||||
@@ -674,6 +746,17 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
|
||||
gl->cellused = px + 1;
|
||||
|
||||
gce = &gl->celldata[px];
|
||||
#ifdef ENABLE_IMAGES
|
||||
old_id = grid_entry_image(gl, gce);
|
||||
if (gc->flags & GRID_FLAG_IMAGE)
|
||||
new_id = gc->image_id;
|
||||
if (old_id != new_id) {
|
||||
if (old_id != 0)
|
||||
image_free(old_id);
|
||||
if (new_id != 0)
|
||||
image_ref(new_id);
|
||||
}
|
||||
#endif
|
||||
if (grid_need_extended_cell(gce, gc))
|
||||
grid_extended_cell(gl, gce, gc);
|
||||
else
|
||||
@@ -682,13 +765,9 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
|
||||
|
||||
/* Set padding at position. */
|
||||
void
|
||||
grid_set_padding(struct grid *gd, u_int px, u_int py, int bg)
|
||||
grid_set_padding(struct grid *gd, u_int px, u_int py)
|
||||
{
|
||||
struct grid_cell gc;
|
||||
|
||||
memcpy(&gc, &grid_padding_cell, sizeof gc);
|
||||
gc.bg = bg;
|
||||
grid_set_cell(gd, px, py, &gc);
|
||||
grid_set_cell(gd, px, py, &grid_padding_cell);
|
||||
}
|
||||
|
||||
/* Set cells at position. */
|
||||
@@ -699,6 +778,10 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc,
|
||||
struct grid_line *gl;
|
||||
struct grid_cell_entry *gce;
|
||||
struct grid_extd_entry *gee;
|
||||
const struct grid_cell *new_gc;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell old_gc, image_gc;
|
||||
#endif
|
||||
u_int i;
|
||||
|
||||
if (grid_check_y(gd, __func__, py) != 0)
|
||||
@@ -712,11 +795,39 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc,
|
||||
|
||||
for (i = 0; i < slen; i++) {
|
||||
gce = &gl->celldata[px + i];
|
||||
if (grid_need_extended_cell(gce, gc)) {
|
||||
gee = grid_extended_cell(gl, gce, gc);
|
||||
new_gc = gc;
|
||||
#ifdef ENABLE_IMAGES
|
||||
{
|
||||
u_int old_id = grid_entry_image(gl, gce);
|
||||
u_int new_id;
|
||||
|
||||
if (old_id != 0 && (~gc->flags & GRID_FLAG_IMAGE)) {
|
||||
grid_get_cell1(gl, px + i, &old_gc);
|
||||
memcpy(&image_gc, gc, sizeof image_gc);
|
||||
image_gc.flags |= GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED;
|
||||
image_gc.image_id = old_gc.image_id;
|
||||
image_gc.image_x = old_gc.image_x;
|
||||
image_gc.image_y = old_gc.image_y;
|
||||
new_gc = &image_gc;
|
||||
}
|
||||
if (new_gc->flags & GRID_FLAG_IMAGE)
|
||||
new_id = new_gc->image_id;
|
||||
else
|
||||
new_id = 0;
|
||||
|
||||
if (old_id != new_id) {
|
||||
if (old_id != 0)
|
||||
image_free(old_id);
|
||||
if (new_id != 0)
|
||||
image_ref(new_id);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (grid_need_extended_cell(gce, new_gc)) {
|
||||
gee = grid_extended_cell(gl, gce, new_gc);
|
||||
gee->data = utf8_build_one(s[i]);
|
||||
} else
|
||||
grid_store_cell(gce, gc, s[i]);
|
||||
grid_store_cell(gce, new_gc, s[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -831,6 +942,9 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
|
||||
{
|
||||
struct grid_line *gl;
|
||||
u_int xx;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int image_id;
|
||||
#endif
|
||||
|
||||
if (nx == 0 || px == dx)
|
||||
return;
|
||||
@@ -841,6 +955,16 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
|
||||
|
||||
grid_expand_line(gd, py, px + nx, 8);
|
||||
grid_expand_line(gd, py, dx + nx, 8);
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* References in the source range are transferred, not duplicated. */
|
||||
for (xx = dx; xx < dx + nx; xx++) {
|
||||
if (xx >= px && xx < px + nx)
|
||||
continue;
|
||||
image_id = grid_entry_image(gl, &gl->celldata[xx]);
|
||||
if (image_id != 0)
|
||||
image_free(image_id);
|
||||
}
|
||||
#endif
|
||||
memmove(&gl->celldata[dx], &gl->celldata[px],
|
||||
nx * sizeof *gl->celldata);
|
||||
if (dx + nx > gl->cellused)
|
||||
@@ -1213,6 +1337,13 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx,
|
||||
grid_get_cell(gd, xx, py, &gc);
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
continue;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (gc.flags & GRID_FLAG_IMAGE) {
|
||||
utf8_set(&gc.data, ' ');
|
||||
gc.flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED);
|
||||
gc.image_id = gc.image_x = gc.image_y = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (lastgc != NULL && (flags & GRID_STRING_WITH_SEQUENCES)) {
|
||||
grid_string_cells_code(*lastgc, &gc, code, sizeof code,
|
||||
@@ -1280,6 +1411,9 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
{
|
||||
struct grid_line *dstl, *srcl;
|
||||
u_int yy;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int px, image_id;
|
||||
#endif
|
||||
|
||||
if (dy + ny > dst->hsize + dst->sy)
|
||||
ny = dst->hsize + dst->sy - dy;
|
||||
@@ -1308,6 +1442,13 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
} else
|
||||
dstl->extddata = NULL;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
for (px = 0; px < dstl->cellsize; px++) {
|
||||
image_id = grid_entry_image(dstl, &dstl->celldata[px]);
|
||||
if (image_id != 0)
|
||||
image_ref(image_id);
|
||||
}
|
||||
#endif
|
||||
sy++;
|
||||
dy++;
|
||||
}
|
||||
@@ -1321,6 +1462,30 @@ grid_reflow_dead(struct grid_line *gl)
|
||||
gl->flags = GRID_LINE_DEAD;
|
||||
}
|
||||
|
||||
/* Image rows are cell-aligned and must not be reflowed like text. */
|
||||
static int
|
||||
grid_reflow_has_image(struct grid_line *gl)
|
||||
{
|
||||
struct grid_cell gc;
|
||||
u_int i;
|
||||
|
||||
if (~gl->flags & GRID_LINE_EXTENDED)
|
||||
return (0);
|
||||
for (i = 0; i < gl->cellused; i++) {
|
||||
grid_get_cell1(gl, i, &gc);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (gc.flags & GRID_FLAG_IMAGE)
|
||||
return (1);
|
||||
#endif
|
||||
/* Kitty Unicode placeholder base character (U+10EEEE). */
|
||||
if (gc.data.size >= 4 && gc.data.data[0] == 0xf4 &&
|
||||
gc.data.data[1] == 0x8e && gc.data.data[2] == 0xbb &&
|
||||
gc.data.data[3] == 0xae)
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Add lines, return the first new one. */
|
||||
static struct grid_line *
|
||||
grid_reflow_add(struct grid *gd, u_int n)
|
||||
@@ -1383,6 +1548,10 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
break;
|
||||
line = yy + 1 + lines;
|
||||
|
||||
/* Do not join wrapped text to a cell-aligned image row. */
|
||||
if (grid_reflow_has_image(&gd->linedata[line]))
|
||||
break;
|
||||
|
||||
/* If the next line is empty, skip it. */
|
||||
if (~gd->linedata[line].flags & GRID_LINE_WRAPPED)
|
||||
wrapped = 0;
|
||||
@@ -1443,8 +1612,7 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
|
||||
/* Remove the lines that were completely consumed. */
|
||||
for (i = yy + 1; i < yy + 1 + lines; i++) {
|
||||
free(gd->linedata[i].celldata);
|
||||
free(gd->linedata[i].extddata);
|
||||
grid_free_line(gd, i);
|
||||
grid_reflow_dead(&gd->linedata[i]);
|
||||
}
|
||||
|
||||
@@ -1500,6 +1668,15 @@ grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
}
|
||||
width += gc.data.width;
|
||||
grid_set_cell(target, xx, line, &gc);
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* The tail of the original line is discarded below. Transfer
|
||||
* its image reference to the new cell rather than duplicating
|
||||
* it.
|
||||
*/
|
||||
if (gc.flags & GRID_FLAG_IMAGE)
|
||||
image_free(gc.image_id);
|
||||
#endif
|
||||
xx++;
|
||||
}
|
||||
if (flags & GRID_LINE_WRAPPED)
|
||||
@@ -1546,6 +1723,12 @@ grid_reflow(struct grid *gd, u_int sx)
|
||||
if (gl->flags & GRID_LINE_DEAD)
|
||||
continue;
|
||||
|
||||
/* Keep image markers at their original cell coordinates. */
|
||||
if (grid_reflow_has_image(gl)) {
|
||||
grid_reflow_move(target, gl);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Work out the width of this line. at is the point at which
|
||||
* the available width is hit, and width is the full line
|
||||
@@ -1635,7 +1818,7 @@ grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy)
|
||||
void
|
||||
grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
|
||||
{
|
||||
u_int yy, ay = 0, ey = gd->hsize + gd->sy - 1;
|
||||
u_int yy, ay = 0;
|
||||
|
||||
for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) {
|
||||
if (ay == wy)
|
||||
@@ -1649,7 +1832,7 @@ grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
|
||||
* until we find the end or the line now containing wx.
|
||||
*/
|
||||
if (wx == UINT_MAX) {
|
||||
while (yy < ey && gd->linedata[yy].flags & GRID_LINE_WRAPPED)
|
||||
while (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
|
||||
yy++;
|
||||
wx = gd->linedata[yy].cellused;
|
||||
} else {
|
||||
@@ -1711,29 +1894,21 @@ grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
|
||||
{
|
||||
struct grid_cell gc, tmp_gc;
|
||||
u_int pxx;
|
||||
int has_tab, has_space;
|
||||
|
||||
has_tab = (strchr(set, '\t') != NULL);
|
||||
has_space = (strchr(set, ' ') != NULL);
|
||||
|
||||
grid_get_cell(gd, px, py, &gc);
|
||||
if (gc.flags & GRID_FLAG_PADDING) {
|
||||
if (!has_tab && !has_space)
|
||||
return (0);
|
||||
pxx = px;
|
||||
do
|
||||
grid_get_cell(gd, --pxx, py, &tmp_gc);
|
||||
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
|
||||
if (((has_tab || has_space) &&
|
||||
(tmp_gc.flags & GRID_FLAG_TAB)) ||
|
||||
(has_space && utf8_has_whitespace(&tmp_gc.data)))
|
||||
return (tmp_gc.data.width - (px - pxx));
|
||||
return (0);
|
||||
if (strchr(set, '\t')) {
|
||||
if (gc.flags & GRID_FLAG_PADDING) {
|
||||
pxx = px;
|
||||
do
|
||||
grid_get_cell(gd, --pxx, py, &tmp_gc);
|
||||
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
|
||||
if (tmp_gc.flags & GRID_FLAG_TAB)
|
||||
return (tmp_gc.data.width - (px - pxx));
|
||||
} else if (gc.flags & GRID_FLAG_TAB)
|
||||
return (gc.data.width);
|
||||
}
|
||||
if ((has_tab || has_space) && (gc.flags & GRID_FLAG_TAB))
|
||||
return (gc.data.width);
|
||||
if (has_space && utf8_has_whitespace(&gc.data))
|
||||
return (gc.data.width == 0 ? 1 : gc.data.width);
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
return (0);
|
||||
return (utf8_cstrhas(set, &gc.data));
|
||||
}
|
||||
|
||||
|
||||
1063
image-sixel.c
1063
image-sixel.c
File diff suppressed because it is too large
Load Diff
815
image.c
815
image.c
@@ -2,6 +2,7 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -18,210 +19,708 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
static struct images all_images = TAILQ_HEAD_INITIALIZER(all_images);
|
||||
static u_int all_images_count;
|
||||
#define MAX_IMAGE_COUNT 20
|
||||
/* An average of part of an image cell. RGB is premultiplied. */
|
||||
struct image_sample {
|
||||
u_char red;
|
||||
u_char green;
|
||||
u_char blue;
|
||||
u_char alpha;
|
||||
u_char brightness;
|
||||
};
|
||||
|
||||
static void printflike(3, 4)
|
||||
image_log(struct image *im, const char* from, const char* fmt, ...)
|
||||
/* Half blocks, quadrants and sextants all divide evenly into a 2 by 6 grid. */
|
||||
#define IMAGE_SAMPLE_COLUMNS 2
|
||||
#define IMAGE_SAMPLE_ROWS 6
|
||||
struct image_cell {
|
||||
struct image_sample whole;
|
||||
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
|
||||
};
|
||||
|
||||
/* Immutable image data and cell geometry. */
|
||||
struct image {
|
||||
u_int id;
|
||||
u_short grid_id;
|
||||
u_int references;
|
||||
u_int width;
|
||||
u_int height;
|
||||
u_int canvas_width;
|
||||
u_int canvas_height;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
size_t stride;
|
||||
size_t size;
|
||||
u_char *pixels;
|
||||
/* Original indexed SIXEL data, if this image arrived as SIXEL. */
|
||||
struct sixel_image *sixel;
|
||||
struct image_cell *cells; /* lazily generated text samples */
|
||||
|
||||
RB_ENTRY(image) entry;
|
||||
};
|
||||
RB_HEAD(images, image);
|
||||
|
||||
/* A cell-aligned part of an image to draw at a terminal position. */
|
||||
struct image_rect {
|
||||
struct image *image;
|
||||
struct grid_cell cell;
|
||||
u_int source_x;
|
||||
u_int source_y;
|
||||
u_int width;
|
||||
u_int height;
|
||||
u_int destination_x;
|
||||
u_int destination_y;
|
||||
};
|
||||
|
||||
static struct images images = RB_INITIALIZER(&images);
|
||||
static u_int image_next_id;
|
||||
static u_short image_next_grid_id;
|
||||
static struct image *image_grid_ids[USHRT_MAX + 1];
|
||||
|
||||
struct image_backend {
|
||||
const char *name;
|
||||
int flags;
|
||||
void (*draw_rect)(struct tty *,
|
||||
const struct image_rect *, const struct tty_style_ctx *);
|
||||
void (*free)(struct tty *, int);
|
||||
};
|
||||
|
||||
static const struct image_backend image_backend_ascii = {
|
||||
"ascii", IMAGE_BACKEND_SCROLLS, NULL, NULL
|
||||
};
|
||||
static const struct image_backend image_backend_sixel = {
|
||||
"sixel", IMAGE_BACKEND_GRAPHICAL|IMAGE_BACKEND_TEMPORAL,
|
||||
sixel_draw_rect, sixel_free_output
|
||||
};
|
||||
|
||||
/* Compare images by server ID. */
|
||||
static int
|
||||
image_cmp(struct image *a, struct image *b)
|
||||
{
|
||||
va_list ap;
|
||||
char s[128];
|
||||
if (a->id < b->id)
|
||||
return (-1);
|
||||
if (a->id > b->id)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
RB_GENERATE_STATIC(images, image, entry, image_cmp);
|
||||
|
||||
if (log_get_level() == 0)
|
||||
return;
|
||||
|
||||
if (fmt == NULL) {
|
||||
log_debug("%s: %p (%ux%u %u,%u)", from, im, im->sx, im->sy,
|
||||
im->px, im->py);
|
||||
return;
|
||||
}
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(s, sizeof s, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
log_debug("%s: %p (%ux%u %u,%u): %s", from, im, im->sx, im->sy,
|
||||
im->px, im->py, s);
|
||||
/* Find the image backend supported by a terminal. */
|
||||
static const struct image_backend *
|
||||
image_tty_find_backend(struct tty *tty)
|
||||
{
|
||||
if (tty->term != NULL && tty->term->flags & TERM_SIXEL &&
|
||||
tty->xpixel != 0 && tty->ypixel != 0)
|
||||
return (&image_backend_sixel);
|
||||
return (&image_backend_ascii);
|
||||
}
|
||||
|
||||
static void
|
||||
image_free(struct image *im)
|
||||
/* Update a terminal's image backend after its capabilities change. */
|
||||
void
|
||||
image_tty_update(struct tty *tty)
|
||||
{
|
||||
image_log(im, __func__, NULL);
|
||||
const struct image_backend *backend;
|
||||
|
||||
TAILQ_REMOVE(&all_images, im, all_entry);
|
||||
all_images_count--;
|
||||
backend = image_tty_find_backend(tty);
|
||||
if (tty->image_backend == backend)
|
||||
return;
|
||||
|
||||
TAILQ_REMOVE(im->list, im, entry);
|
||||
sixel_free(im->data);
|
||||
free(im->fallback);
|
||||
free(im);
|
||||
if (tty->image_backend != NULL && tty->image_backend->free != NULL)
|
||||
tty->image_backend->free(tty, !!(tty->flags & TTY_OPENED));
|
||||
tty->image_data = NULL;
|
||||
tty->image_backend = backend;
|
||||
log_debug("%s: %s image backend is %s", __func__,
|
||||
tty->client->name, backend->name);
|
||||
}
|
||||
|
||||
/* Return the flags for a terminal's image backend. */
|
||||
int
|
||||
image_free_all(struct screen *s)
|
||||
image_backend_flags(struct tty *tty)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = !TAILQ_EMPTY(&s->images);
|
||||
|
||||
if (redraw)
|
||||
log_debug ("%s", __func__);
|
||||
TAILQ_FOREACH_SAFE(im, &s->images, entry, im1)
|
||||
image_free(im);
|
||||
return (redraw);
|
||||
image_tty_update(tty);
|
||||
return (tty->image_backend->flags);
|
||||
}
|
||||
|
||||
/* Create text placeholder for an image. */
|
||||
/* Discard image backend state after a terminal geometry change. */
|
||||
void
|
||||
image_tty_geometry_changed(struct tty *tty)
|
||||
{
|
||||
image_tty_update(tty);
|
||||
if (tty->image_backend->free != NULL)
|
||||
tty->image_backend->free(tty, !!(tty->flags & TTY_OPENED));
|
||||
}
|
||||
|
||||
/* Free image backend state for a terminal. */
|
||||
void
|
||||
image_tty_free(struct tty *tty, int send)
|
||||
{
|
||||
if (tty->image_backend != NULL && tty->image_backend->free != NULL)
|
||||
tty->image_backend->free(tty, send);
|
||||
tty->image_backend = NULL;
|
||||
tty->image_data = NULL;
|
||||
}
|
||||
|
||||
/* Average a rectangle of image pixels into one fallback sample. */
|
||||
static void
|
||||
image_fallback(char **ret, u_int sx, u_int sy)
|
||||
image_sample(struct image *im, uint64_t sample_x, uint64_t sample_y,
|
||||
uint64_t sample_columns, uint64_t sample_rows, struct image_sample *sample)
|
||||
{
|
||||
char *buf, *label;
|
||||
u_int py, size, lsize;
|
||||
const u_char *pixel;
|
||||
uint64_t red = 0, green = 0, blue = 0, alpha = 0;
|
||||
uint64_t brightness = 0, count = 0;
|
||||
u_int x, y, x0, x1, y0, y1;
|
||||
|
||||
if (sy == 0) {
|
||||
*ret = xstrdup("");
|
||||
x0 = sample_x * im->canvas_width / sample_columns;
|
||||
x1 = ((sample_x + 1) * im->canvas_width + sample_columns - 1) /
|
||||
sample_columns;
|
||||
y0 = sample_y * im->canvas_height / sample_rows;
|
||||
y1 = ((sample_y + 1) * im->canvas_height + sample_rows - 1) /
|
||||
sample_rows;
|
||||
if (x1 <= x0)
|
||||
x1 = x0 + 1;
|
||||
if (y1 <= y0)
|
||||
y1 = y0 + 1;
|
||||
count = (uint64_t)(x1 - x0) * (y1 - y0);
|
||||
if (x0 >= im->width || y0 >= im->height)
|
||||
return;
|
||||
if (x1 > im->width)
|
||||
x1 = im->width;
|
||||
if (y1 > im->height)
|
||||
y1 = im->height;
|
||||
|
||||
for (y = y0; y < y1; y++) {
|
||||
for (x = x0; x < x1; x++) {
|
||||
pixel = im->pixels + y * im->stride + x * 4;
|
||||
red += pixel[0] * pixel[3] / 255;
|
||||
green += pixel[1] * pixel[3] / 255;
|
||||
blue += pixel[2] * pixel[3] / 255;
|
||||
alpha += pixel[3];
|
||||
brightness += ((2126ULL * pixel[0] +
|
||||
7152ULL * pixel[1] + 722ULL * pixel[2]) / 10000) *
|
||||
pixel[3] / 255;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate first line. */
|
||||
lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1;
|
||||
if (sx < lsize - 3)
|
||||
size = lsize - 1;
|
||||
else
|
||||
size = sx + 2;
|
||||
|
||||
/* Remaining lines. Every placeholder line has \r\n at the end. */
|
||||
size += (sx + 2) * (sy - 1) + 1;
|
||||
*ret = buf = xmalloc(size);
|
||||
|
||||
/* Render first line. */
|
||||
if (sx < lsize - 3) {
|
||||
memcpy(buf, label, lsize);
|
||||
buf += lsize - 1;
|
||||
} else {
|
||||
memcpy(buf, label, lsize - 3);
|
||||
buf += lsize - 3;
|
||||
memset(buf, '+', sx - lsize + 3);
|
||||
buf += sx - lsize + 3;
|
||||
snprintf(buf, 3, "\r\n");
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
/* Remaining lines. */
|
||||
for (py = 1; py < sy; py++) {
|
||||
memset(buf, '+', sx);
|
||||
buf += sx;
|
||||
snprintf(buf, 3, "\r\n");
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
free(label);
|
||||
if (count == 0)
|
||||
return;
|
||||
sample->red = red / count;
|
||||
sample->green = green / count;
|
||||
sample->blue = blue / count;
|
||||
sample->alpha = alpha / count;
|
||||
sample->brightness = brightness / count;
|
||||
}
|
||||
|
||||
struct image*
|
||||
image_store(struct screen *s, struct sixel_image *si)
|
||||
/* Build fallback samples for every image cell. */
|
||||
static void
|
||||
image_make_cells(struct image *im)
|
||||
{
|
||||
struct image_cell *cell;
|
||||
uint64_t columns, rows;
|
||||
u_int x, y, sample_x, sample_y;
|
||||
|
||||
columns = (uint64_t)im->sx * IMAGE_SAMPLE_COLUMNS;
|
||||
rows = (uint64_t)im->sy * IMAGE_SAMPLE_ROWS;
|
||||
im->cells = xcalloc((size_t)im->sx * im->sy, sizeof *im->cells);
|
||||
for (y = 0; y < im->sy; y++) {
|
||||
for (x = 0; x < im->sx; x++) {
|
||||
cell = &im->cells[(size_t)y * im->sx + x];
|
||||
image_sample(im, x, y, im->sx, im->sy, &cell->whole);
|
||||
for (sample_y = 0; sample_y < IMAGE_SAMPLE_ROWS;
|
||||
sample_y++) {
|
||||
for (sample_x = 0;
|
||||
sample_x < IMAGE_SAMPLE_COLUMNS; sample_x++) {
|
||||
image_sample(im,
|
||||
(uint64_t)x * IMAGE_SAMPLE_COLUMNS + sample_x,
|
||||
(uint64_t)y * IMAGE_SAMPLE_ROWS + sample_y,
|
||||
columns, rows,
|
||||
&cell->samples[sample_y][sample_x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Find an image by server ID. */
|
||||
struct image *
|
||||
image_find(u_int id)
|
||||
{
|
||||
struct image find;
|
||||
|
||||
find.id = id;
|
||||
return (RB_FIND(images, &images, &find));
|
||||
}
|
||||
|
||||
/* Return an image's server ID. */
|
||||
u_int
|
||||
image_get_id(const struct image *im)
|
||||
{
|
||||
return (im->id);
|
||||
}
|
||||
|
||||
/* Return an image's pixel dimensions. */
|
||||
void
|
||||
image_get_size(const struct image *im, u_int *width, u_int *height)
|
||||
{
|
||||
if (width != NULL)
|
||||
*width = im->width;
|
||||
if (height != NULL)
|
||||
*height = im->height;
|
||||
}
|
||||
|
||||
/* Return an image canvas's pixel dimensions. */
|
||||
void
|
||||
image_get_canvas_size(const struct image *im, u_int *width,
|
||||
u_int *height)
|
||||
{
|
||||
if (width != NULL)
|
||||
*width = im->canvas_width;
|
||||
if (height != NULL)
|
||||
*height = im->canvas_height;
|
||||
}
|
||||
|
||||
/* Return an image's cell dimensions. */
|
||||
void
|
||||
image_get_size_in_cells(const struct image *im, u_int *sx, u_int *sy)
|
||||
{
|
||||
if (sx != NULL)
|
||||
*sx = im->sx;
|
||||
if (sy != NULL)
|
||||
*sy = im->sy;
|
||||
}
|
||||
|
||||
/* Return an image's RGBA pixels and layout. */
|
||||
const u_char *
|
||||
image_get_pixels(const struct image *im, size_t *stride, size_t *size)
|
||||
{
|
||||
if (stride != NULL)
|
||||
*stride = im->stride;
|
||||
if (size != NULL)
|
||||
*size = im->size;
|
||||
return (im->pixels);
|
||||
}
|
||||
|
||||
/* Return an image's original SIXEL data. */
|
||||
struct sixel_image *
|
||||
image_get_sixel(const struct image *im)
|
||||
{
|
||||
return (im->sixel);
|
||||
}
|
||||
|
||||
/* Associate original SIXEL data with an image. */
|
||||
void
|
||||
image_set_sixel(struct image *im, struct sixel_image *si)
|
||||
{
|
||||
im->sixel = si;
|
||||
}
|
||||
|
||||
/* Return the image for a drawing rectangle. */
|
||||
struct image *
|
||||
image_rect_get_image(const struct image_rect *rectangle)
|
||||
{
|
||||
return (rectangle->image);
|
||||
}
|
||||
|
||||
/* Return the source and destination coordinates of a drawing rectangle. */
|
||||
void
|
||||
image_rect_get_coords(const struct image_rect *rectangle,
|
||||
u_int *source_x, u_int *source_y, u_int *width, u_int *height,
|
||||
u_int *destination_x, u_int *destination_y)
|
||||
{
|
||||
*source_x = rectangle->source_x;
|
||||
*source_y = rectangle->source_y;
|
||||
*width = rectangle->width;
|
||||
*height = rectangle->height;
|
||||
*destination_x = rectangle->destination_x;
|
||||
*destination_y = rectangle->destination_y;
|
||||
}
|
||||
|
||||
/* Create and register an immutable image. */
|
||||
struct image *
|
||||
image_create(u_int width, u_int height, u_int canvas_width,
|
||||
u_int canvas_height, u_int sx, u_int sy, u_char *pixels)
|
||||
{
|
||||
struct image *im;
|
||||
u_int i;
|
||||
|
||||
if (width == 0 || height == 0 || canvas_width < width ||
|
||||
canvas_height < height || sx == 0 || sy == 0 || pixels == NULL)
|
||||
return (NULL);
|
||||
if ((uint64_t)width * height * 4 > SIZE_MAX)
|
||||
return (NULL);
|
||||
if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells)
|
||||
return (NULL);
|
||||
if (sx > USHRT_MAX || sy > USHRT_MAX)
|
||||
return (NULL);
|
||||
for (i = 0; i < USHRT_MAX; i++) {
|
||||
if (++image_next_grid_id == 0)
|
||||
image_next_grid_id++;
|
||||
if (image_grid_ids[image_next_grid_id] == NULL)
|
||||
break;
|
||||
}
|
||||
if (i == USHRT_MAX)
|
||||
return (NULL);
|
||||
|
||||
im = xcalloc(1, sizeof *im);
|
||||
im->s = s;
|
||||
im->data = si;
|
||||
do {
|
||||
if (++image_next_id == 0)
|
||||
image_next_id++;
|
||||
im->id = image_next_id;
|
||||
} while (image_find(im->id) != NULL);
|
||||
|
||||
im->px = s->cx;
|
||||
im->py = s->cy;
|
||||
sixel_size_in_cells(si, &im->sx, &im->sy);
|
||||
|
||||
image_fallback(&im->fallback, im->sx, im->sy);
|
||||
|
||||
image_log(im, __func__, NULL);
|
||||
im->list = &s->images;
|
||||
TAILQ_INSERT_TAIL(im->list, im, entry);
|
||||
|
||||
TAILQ_INSERT_TAIL(&all_images, im, all_entry);
|
||||
if (++all_images_count == MAX_IMAGE_COUNT)
|
||||
image_free(TAILQ_FIRST(&all_images));
|
||||
im->references = 1;
|
||||
im->grid_id = image_next_grid_id;
|
||||
im->width = width;
|
||||
im->height = height;
|
||||
im->canvas_width = canvas_width;
|
||||
im->canvas_height = canvas_height;
|
||||
im->sx = sx;
|
||||
im->sy = sy;
|
||||
im->stride = (size_t)width * 4;
|
||||
im->size = im->stride * height;
|
||||
im->pixels = pixels;
|
||||
|
||||
RB_INSERT(images, &images, im);
|
||||
image_grid_ids[im->grid_id] = im;
|
||||
log_debug("%s: image %u is %ux%u pixels on %ux%u canvas, "
|
||||
"%ux%u cells", __func__, im->id, width, height, canvas_width,
|
||||
canvas_height, sx, sy);
|
||||
return (im);
|
||||
}
|
||||
|
||||
int
|
||||
image_check_line(struct screen *s, u_int py, u_int ny)
|
||||
/* Return the compact grid ID for an image. */
|
||||
u_short
|
||||
image_get_grid_id(u_int id)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = 0, in;
|
||||
struct image *im = image_find(id);
|
||||
|
||||
TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) {
|
||||
in = (py + ny > im->py && py < im->py + im->sy);
|
||||
image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in);
|
||||
if (in) {
|
||||
image_free(im);
|
||||
redraw = 1;
|
||||
}
|
||||
}
|
||||
return (redraw);
|
||||
if (im == NULL)
|
||||
return (0);
|
||||
return (im->grid_id);
|
||||
}
|
||||
|
||||
/* Return the server image ID for a compact grid ID. */
|
||||
u_int
|
||||
image_get_id_by_grid_id(u_short grid_id)
|
||||
{
|
||||
struct image *im;
|
||||
|
||||
if (grid_id == 0 || (im = image_grid_ids[grid_id]) == NULL)
|
||||
return (0);
|
||||
return (im->id);
|
||||
}
|
||||
|
||||
/* Add a reference to an image. */
|
||||
void
|
||||
image_ref(u_int id)
|
||||
{
|
||||
struct image *im = image_find(id);
|
||||
|
||||
if (im == NULL)
|
||||
fatalx("reference to missing image %u", id);
|
||||
if (im->references == UINT_MAX)
|
||||
fatalx("too many references to image %u", id);
|
||||
im->references++;
|
||||
}
|
||||
|
||||
/* Drop a reference to an image. */
|
||||
void
|
||||
image_free(u_int id)
|
||||
{
|
||||
struct image *im = image_find(id);
|
||||
|
||||
if (im == NULL)
|
||||
fatalx("free of missing image %u", id);
|
||||
if (--im->references != 0)
|
||||
return;
|
||||
|
||||
log_debug("%s: freeing image %u", __func__, id);
|
||||
RB_REMOVE(images, &images, im);
|
||||
image_grid_ids[im->grid_id] = NULL;
|
||||
free(im->pixels);
|
||||
if (im->sixel != NULL)
|
||||
sixel_free(im->sixel);
|
||||
free(im->cells);
|
||||
free(im);
|
||||
}
|
||||
|
||||
/* Return a precomputed fallback cell sample. */
|
||||
static const struct image_cell *
|
||||
image_get_cell(struct image *im, u_int x, u_int y)
|
||||
{
|
||||
if (im == NULL || x >= im->sx || y >= im->sy)
|
||||
return (NULL);
|
||||
if (im->cells == NULL)
|
||||
image_make_cells(im);
|
||||
return (&im->cells[(size_t)y * im->sx + x]);
|
||||
}
|
||||
|
||||
/* Return the brightness of an image cell. */
|
||||
u_char
|
||||
image_get_brightness(struct image *im, u_int x, u_int y)
|
||||
{
|
||||
const struct image_cell *cell;
|
||||
|
||||
cell = image_get_cell(im, x, y);
|
||||
if (cell == NULL)
|
||||
return (0);
|
||||
return (cell->whole.brightness);
|
||||
}
|
||||
|
||||
/* Fill a grid cell with an ASCII brightness glyph. */
|
||||
static void
|
||||
image_get_text_cell(struct image *im, u_int x, u_int y,
|
||||
const struct grid_cell *gc, struct grid_cell *out)
|
||||
{
|
||||
static const char ramp[] = " .:-=+*#%@";
|
||||
u_int level;
|
||||
|
||||
memcpy(out, gc, sizeof *out);
|
||||
level = image_get_brightness(im, x, y) * (sizeof ramp - 2) / 255;
|
||||
utf8_set(&out->data, ramp[level]);
|
||||
out->flags &= ~GRID_FLAG_IMAGE;
|
||||
}
|
||||
|
||||
/* Get the terminal cell used to draw an image marker. */
|
||||
int
|
||||
image_get_draw_cell(struct tty *tty, const struct grid_cell *gc,
|
||||
struct grid_cell *out)
|
||||
{
|
||||
struct image *im = image_find(gc->image_id);
|
||||
|
||||
if (image_backend_flags(tty) & IMAGE_BACKEND_GRAPHICAL) {
|
||||
memcpy(out, gc, sizeof *out);
|
||||
out->flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED|
|
||||
GRID_FLAG_SELECTED);
|
||||
return (1);
|
||||
}
|
||||
if (gc->flags & GRID_FLAG_IMAGE_DAMAGED) {
|
||||
memcpy(out, gc, sizeof *out);
|
||||
out->flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED);
|
||||
return (0);
|
||||
}
|
||||
image_get_text_cell(im, gc->image_x, gc->image_y, gc, out);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Store an image marker in a grid cell. */
|
||||
void
|
||||
image_set_cell(struct grid_cell *gc, struct image *im, u_int x, u_int y)
|
||||
{
|
||||
/* Keep the cell contents as the underlay for transparent pixels. */
|
||||
gc->flags &= ~GRID_FLAG_IMAGE_DAMAGED;
|
||||
gc->flags |= GRID_FLAG_IMAGE;
|
||||
gc->image_id = im->id;
|
||||
gc->image_x = x;
|
||||
gc->image_y = y;
|
||||
}
|
||||
|
||||
/* Convert a cell-aligned image rectangle into source pixel coordinates. */
|
||||
void
|
||||
image_get_pixel_rect(const struct image *im, u_int x, u_int y,
|
||||
u_int width, u_int height, u_int *px, u_int *py, u_int *pwidth,
|
||||
u_int *pheight)
|
||||
{
|
||||
u_int x1, y1;
|
||||
|
||||
*px = *py = *pwidth = *pheight = 0;
|
||||
if (im == NULL || x >= im->sx || y >= im->sy || width == 0 ||
|
||||
height == 0)
|
||||
return;
|
||||
if (width > im->sx - x)
|
||||
width = im->sx - x;
|
||||
if (height > im->sy - y)
|
||||
height = im->sy - y;
|
||||
|
||||
*px = (uint64_t)x * im->canvas_width / im->sx;
|
||||
*py = (uint64_t)y * im->canvas_height / im->sy;
|
||||
x1 = ((uint64_t)(x + width) * im->canvas_width + im->sx - 1) /
|
||||
im->sx;
|
||||
y1 = ((uint64_t)(y + height) * im->canvas_height + im->sy - 1) /
|
||||
im->sy;
|
||||
if (*px >= im->width || *py >= im->height) {
|
||||
*px = *py = 0;
|
||||
return;
|
||||
}
|
||||
if (x1 <= *px)
|
||||
x1 = *px + 1;
|
||||
if (y1 <= *py)
|
||||
y1 = *py + 1;
|
||||
if (x1 > im->width)
|
||||
x1 = im->width;
|
||||
if (y1 > im->height)
|
||||
y1 = im->height;
|
||||
*pwidth = x1 - *px;
|
||||
*pheight = y1 - *py;
|
||||
}
|
||||
|
||||
/* Calculate the cell dimensions required for pixel dimensions. */
|
||||
void
|
||||
image_size_in_cells(u_int width, u_int height, u_int xpixel, u_int ypixel,
|
||||
u_int *sx, u_int *sy)
|
||||
{
|
||||
if (xpixel == 0)
|
||||
xpixel = 8;
|
||||
if (ypixel == 0)
|
||||
ypixel = 16;
|
||||
*sx = width / xpixel + (width % xpixel != 0);
|
||||
*sy = height / ypixel + (height % ypixel != 0);
|
||||
}
|
||||
|
||||
/* Return if a screen area contains image markers. */
|
||||
static int
|
||||
image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = 0, in;
|
||||
struct grid_cell gc;
|
||||
u_int x, y, ex, ey;
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
|
||||
TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) {
|
||||
in = (py < im->py + im->sy &&
|
||||
py + ny > im->py &&
|
||||
px < im->px + im->sx &&
|
||||
px + nx > im->px);
|
||||
image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in);
|
||||
if (in) {
|
||||
image_free(im);
|
||||
redraw = 1;
|
||||
if (px >= sx || py >= sy || nx == 0 || ny == 0)
|
||||
return (0);
|
||||
if (nx > sx - px)
|
||||
ex = sx;
|
||||
else
|
||||
ex = px + nx;
|
||||
if (ny > sy - py)
|
||||
ey = sy;
|
||||
else
|
||||
ey = py + ny;
|
||||
for (y = py; y < ey; y++) {
|
||||
for (x = px; x < ex; x++) {
|
||||
grid_view_get_cell(s->grid, x, y, &gc);
|
||||
if (gc.flags & GRID_FLAG_IMAGE)
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
return (redraw);
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
image_scroll_up(struct screen *s, u_int lines)
|
||||
/* Redraw image markers in a screen area. */
|
||||
void
|
||||
image_redraw_area(struct screen_write_ctx *ctx, u_int px, u_int py, u_int nx,
|
||||
u_int ny)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = 0;
|
||||
u_int sx, sy;
|
||||
struct sixel_image *new;
|
||||
|
||||
TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) {
|
||||
if (im->py >= lines) {
|
||||
image_log(im, __func__, "1, lines=%u", lines);
|
||||
im->py -= lines;
|
||||
redraw = 1;
|
||||
continue;
|
||||
}
|
||||
if (im->py + im->sy <= lines) {
|
||||
image_log(im, __func__, "2, lines=%u", lines);
|
||||
image_free(im);
|
||||
redraw = 1;
|
||||
continue;
|
||||
}
|
||||
sx = im->sx;
|
||||
sy = (im->py + im->sy) - lines;
|
||||
image_log(im, __func__, "3, lines=%u, sy=%u", lines, sy);
|
||||
|
||||
new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1);
|
||||
sixel_free(im->data);
|
||||
im->data = new;
|
||||
|
||||
im->py = 0;
|
||||
sixel_size_in_cells(im->data, &im->sx, &im->sy);
|
||||
|
||||
free(im->fallback);
|
||||
image_fallback(&im->fallback, im->sx, im->sy);
|
||||
redraw = 1;
|
||||
}
|
||||
return (redraw);
|
||||
if (ctx->wp != NULL && image_check_area(ctx->s, px, py, nx, ny))
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
}
|
||||
|
||||
/* Redraw all image markers on a screen. */
|
||||
void
|
||||
image_redraw_all(struct screen_write_ctx *ctx)
|
||||
{
|
||||
image_redraw_area(ctx, 0, 0, screen_size_x(ctx->s),
|
||||
screen_size_y(ctx->s));
|
||||
}
|
||||
|
||||
/* Redraw images after a scrolling operation. */
|
||||
void
|
||||
image_redraw_scroll(struct screen_write_ctx *ctx, __unused u_int lines)
|
||||
{
|
||||
image_redraw_all(ctx);
|
||||
}
|
||||
|
||||
/* Draw a span's graphical image markers before or after its text. */
|
||||
void
|
||||
image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
|
||||
u_int nx, u_int atx, u_int aty, int before,
|
||||
const struct tty_style_ctx *style_ctx)
|
||||
{
|
||||
const struct image_backend *backend;
|
||||
struct image_rect rectangle;
|
||||
struct grid_cell gc, next;
|
||||
struct image *im;
|
||||
u_int i, run;
|
||||
|
||||
image_tty_update(tty);
|
||||
backend = tty->image_backend;
|
||||
if (~backend->flags & IMAGE_BACKEND_GRAPHICAL)
|
||||
return;
|
||||
if (before && (~backend->flags & IMAGE_BACKEND_TEMPORAL))
|
||||
return;
|
||||
|
||||
for (i = 0; i < nx; i += run) {
|
||||
grid_view_get_cell(s->grid, px + i, py, &gc);
|
||||
if (~gc.flags & GRID_FLAG_IMAGE) {
|
||||
run = 1;
|
||||
continue;
|
||||
}
|
||||
if (backend->flags & IMAGE_BACKEND_TEMPORAL) {
|
||||
if ((before && (~gc.flags & GRID_FLAG_IMAGE_DAMAGED)) ||
|
||||
(!before && (gc.flags & GRID_FLAG_IMAGE_DAMAGED))) {
|
||||
run = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
im = image_find(gc.image_id);
|
||||
if (im == NULL) {
|
||||
run = 1;
|
||||
continue;
|
||||
}
|
||||
for (run = 1; i + run < nx; run++) {
|
||||
grid_view_get_cell(s->grid, px + i + run, py, &next);
|
||||
if (~next.flags & GRID_FLAG_IMAGE)
|
||||
break;
|
||||
if ((backend->flags & IMAGE_BACKEND_TEMPORAL) &&
|
||||
((before && (~next.flags & GRID_FLAG_IMAGE_DAMAGED)) ||
|
||||
(!before && (next.flags & GRID_FLAG_IMAGE_DAMAGED))))
|
||||
break;
|
||||
if (next.image_id != gc.image_id ||
|
||||
next.image_y != gc.image_y ||
|
||||
next.image_x != gc.image_x + run)
|
||||
break;
|
||||
}
|
||||
rectangle.image = im;
|
||||
memcpy(&rectangle.cell, &gc, sizeof rectangle.cell);
|
||||
rectangle.source_x = gc.image_x;
|
||||
rectangle.source_y = gc.image_y;
|
||||
rectangle.width = run;
|
||||
rectangle.height = 1;
|
||||
rectangle.destination_x = atx + i;
|
||||
rectangle.destination_y = aty;
|
||||
backend->draw_rect(tty, &rectangle, style_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Put image marker cells at the cursor. The pixel object is immutable; only
|
||||
* the marker rectangle is clipped to the available pane cells.
|
||||
*/
|
||||
void
|
||||
image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg)
|
||||
{
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_cell gc;
|
||||
u_int cx = s->cx, cy = s->cy;
|
||||
u_int x, y, sx, sy, lines;
|
||||
|
||||
image_get_size_in_cells(im, &sx, &sy);
|
||||
if (sx > screen_size_x(s) - cx)
|
||||
sx = screen_size_x(s) - cx;
|
||||
if (sy > screen_size_y(s) - 1)
|
||||
sy = screen_size_y(s) - 1;
|
||||
if (sx == 0 || sy == 0)
|
||||
return;
|
||||
|
||||
if (screen_size_y(s) - cy <= sy) {
|
||||
lines = sy - (screen_size_y(s) - cy) + 1;
|
||||
screen_write_scrollup(ctx, lines, bg);
|
||||
if (lines > cy)
|
||||
screen_write_cursormove(ctx, -1, 0, 0);
|
||||
else
|
||||
screen_write_cursormove(ctx, -1, cy - lines, 0);
|
||||
cy = s->cy;
|
||||
}
|
||||
|
||||
for (y = 0; y < sy; y++) {
|
||||
for (x = 0; x < sx; x++) {
|
||||
grid_view_get_cell(gd, cx + x, cy + y, &gc);
|
||||
image_set_cell(&gc, im, x, y);
|
||||
grid_view_set_cell(gd, cx + x, cy + y, &gc);
|
||||
}
|
||||
}
|
||||
image_redraw_area(ctx, cx, cy, sx, sy);
|
||||
screen_write_cursormove(ctx, 0, cy + sy, 0);
|
||||
}
|
||||
|
||||
34
input.c
34
input.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: input.c,v 1.272 2026/09/21 10:22:31 nicm Exp $ */
|
||||
/* $OpenBSD: input.c,v 1.269 2026/07/20 11:16:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -101,6 +101,7 @@ struct input_ctx {
|
||||
struct bufferevent *event;
|
||||
struct screen_write_ctx ctx;
|
||||
struct colour_palette *palette;
|
||||
struct client *c;
|
||||
|
||||
struct input_cell cell;
|
||||
struct input_cell old_cell;
|
||||
@@ -873,7 +874,7 @@ input_restore_state(struct input_ctx *ictx)
|
||||
/* Initialise input parser. */
|
||||
struct input_ctx *
|
||||
input_init(struct window_pane *wp, struct bufferevent *bev,
|
||||
struct colour_palette *palette)
|
||||
struct colour_palette *palette, struct client *c)
|
||||
{
|
||||
struct input_ctx *ictx;
|
||||
|
||||
@@ -881,6 +882,7 @@ input_init(struct window_pane *wp, struct bufferevent *bev,
|
||||
ictx->wp = wp;
|
||||
ictx->event = bev;
|
||||
ictx->palette = palette;
|
||||
ictx->c = c;
|
||||
|
||||
ictx->input_space = INPUT_BUF_START;
|
||||
ictx->input_buf = xmalloc(INPUT_BUF_START);
|
||||
@@ -1047,7 +1049,6 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
wp->output_generation++;
|
||||
window_update_activity(wp->window);
|
||||
if (~wp->flags & PANE_ACTIVITY) {
|
||||
wp->flags |= PANE_ACTIVITY;
|
||||
@@ -1582,7 +1583,7 @@ input_csi_dispatch(struct input_ctx *ictx)
|
||||
case -1:
|
||||
break;
|
||||
case 0:
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
input_reply(ictx, 1, "\033[?1;2;4c");
|
||||
#else
|
||||
input_reply(ictx, 1, "\033[?1;2c");
|
||||
@@ -1977,7 +1978,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx)
|
||||
screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
|
||||
break;
|
||||
case 2026:
|
||||
screen_write_end_sync(sctx);
|
||||
screen_write_stop_sync(ictx->wp);
|
||||
break;
|
||||
case 2031:
|
||||
screen_write_mode_clear(sctx, MODE_THEME_UPDATES);
|
||||
@@ -2100,7 +2101,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
|
||||
static void
|
||||
input_csi_dispatch_sm_graphics(__unused struct input_ctx *ictx)
|
||||
{
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
int n, m, o;
|
||||
|
||||
if (ictx->param_list_len > 3)
|
||||
@@ -2625,10 +2626,10 @@ input_dcs_dispatch(struct input_ctx *ictx)
|
||||
const char prefix[] = "tmux;";
|
||||
const u_int prefixlen = (sizeof prefix) - 1;
|
||||
long long allow_passthrough = 0;
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct window *w;
|
||||
struct sixel_image *si;
|
||||
int p2;
|
||||
int p1, p2;
|
||||
#endif
|
||||
|
||||
if (wp == NULL)
|
||||
@@ -2641,15 +2642,18 @@ input_dcs_dispatch(struct input_ctx *ictx)
|
||||
return (0);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (wp != NULL && buf[0] == 'q' && ictx->interm_len == 0) {
|
||||
w = wp->window;
|
||||
if (input_split(ictx) != 0)
|
||||
return (0);
|
||||
p1 = input_get(ictx, 0, 0, 0);
|
||||
if (p1 == -1)
|
||||
p1 = 0;
|
||||
p2 = input_get(ictx, 1, 0, 0);
|
||||
if (p2 == -1)
|
||||
p2 = 0;
|
||||
si = sixel_parse(buf, len, p2, w->xpixel, w->ypixel);
|
||||
si = sixel_parse(buf, len, p1, p2, w->xpixel, w->ypixel);
|
||||
if (si != NULL)
|
||||
screen_write_sixelimage(sctx, si, ictx->cell.cell.bg);
|
||||
}
|
||||
@@ -3423,9 +3427,15 @@ input_osc_52(struct input_ctx *ictx, const char *p)
|
||||
return;
|
||||
|
||||
if (wp == NULL) {
|
||||
free(out);
|
||||
return;
|
||||
/* Popup window. */
|
||||
if (ictx->c == NULL) {
|
||||
free(out);
|
||||
return;
|
||||
}
|
||||
tty_set_selection(&ictx->c->tty, clip, out, outlen);
|
||||
paste_add(NULL, out, outlen);
|
||||
} else {
|
||||
/* Normal window. */
|
||||
screen_write_start_pane(&ctx, wp, NULL);
|
||||
screen_write_setselection(&ctx, clip, out, outlen);
|
||||
screen_write_stop(&ctx);
|
||||
|
||||
@@ -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.190 2026/07/22 19:23:59 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -421,7 +421,6 @@ key_bindings_init(void)
|
||||
"bind -N 'Spread panes out evenly' E { select-layout -E }",
|
||||
"bind -N 'Switch to the last client' L { switch-client -l }",
|
||||
"bind -N 'Clear the marked pane' M { select-pane -M }",
|
||||
"bind -N 'Change the pane title' T { command-prompt -I'#T' { select-pane -T '%%' } }",
|
||||
"bind -N 'Enter copy mode' [ { copy-mode }",
|
||||
"bind -N 'Paste the most recent paste buffer' ] { paste-buffer -p }",
|
||||
"bind -N 'Create a new window' c { new-window }",
|
||||
@@ -596,7 +595,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }",
|
||||
"bind -Tcopy-mode n { send -X search-again }",
|
||||
"bind -Tcopy-mode q { send -X cancel }",
|
||||
"bind -Tcopy-mode r { send -X refresh-now }",
|
||||
"bind -Tcopy-mode r { send -X refresh-toggle }",
|
||||
"bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode Home { send -X start-of-line }",
|
||||
"bind -Tcopy-mode End { send -X end-of-line }",
|
||||
@@ -706,7 +705,7 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi n { send -X search-again }",
|
||||
"bind -Tcopy-mode-vi o { send -X other-end }",
|
||||
"bind -Tcopy-mode-vi q { send -X cancel }",
|
||||
"bind -Tcopy-mode-vi r { send -X refresh-now }",
|
||||
"bind -Tcopy-mode-vi r { send -X refresh-toggle }",
|
||||
"bind -Tcopy-mode-vi t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi v { send -X rectangle-toggle }",
|
||||
"bind -Tcopy-mode-vi w { send -X next-word }",
|
||||
|
||||
1074
layout-custom.c
1074
layout-custom.c
File diff suppressed because it is too large
Load Diff
158
layout.c
158
layout.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: layout.c,v 1.101 2026/09/20 08:42:46 nicm Exp $ */
|
||||
/* $OpenBSD: layout.c,v 1.96 2026/07/15 13:02:33 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -106,7 +106,7 @@ layout_free_cell(struct layout_cell *lc, int only_nodes)
|
||||
}
|
||||
break;
|
||||
case LAYOUT_WINDOWPANE:
|
||||
if (lc->wp != NULL && lc->wp->layout_cell != NULL) {
|
||||
if (lc->wp != NULL) {
|
||||
lc->wp->layout_cell->parent = NULL;
|
||||
lc->wp->layout_cell = NULL;
|
||||
}
|
||||
@@ -233,6 +233,29 @@ layout_make_node(struct layout_cell *lc, enum layout_type type)
|
||||
lc->wp = NULL;
|
||||
}
|
||||
|
||||
/* Fix z-indexes. */
|
||||
void
|
||||
layout_fix_zindexes(struct window *w, struct layout_cell *lc)
|
||||
{
|
||||
struct layout_cell *lcchild;
|
||||
|
||||
if (lc == NULL)
|
||||
return;
|
||||
|
||||
switch (lc->type) {
|
||||
case LAYOUT_WINDOWPANE:
|
||||
TAILQ_INSERT_TAIL(&w->z_index, lc->wp, zentry);
|
||||
break;
|
||||
case LAYOUT_LEFTRIGHT:
|
||||
case LAYOUT_TOPBOTTOM:
|
||||
TAILQ_FOREACH(lcchild, &lc->cells, entry)
|
||||
layout_fix_zindexes(w, lcchild);
|
||||
return;
|
||||
default:
|
||||
fatalx("bad layout type");
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
layout_cell_is_tiled(struct layout_cell *lc)
|
||||
{
|
||||
@@ -242,7 +265,7 @@ layout_cell_is_tiled(struct layout_cell *lc)
|
||||
return is_leaf && !is_floating;
|
||||
}
|
||||
|
||||
int
|
||||
static int
|
||||
layout_cell_has_tiled_child(struct layout_cell *lc)
|
||||
{
|
||||
struct layout_cell *lcchild;
|
||||
@@ -469,6 +492,7 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
||||
sx = PANE_MINIMUM;
|
||||
else
|
||||
sx = sx - sb_w - sb_pad;
|
||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||
}
|
||||
|
||||
window_pane_resize(wp, sx, sy);
|
||||
@@ -476,11 +500,8 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
||||
if (wp->xoff != old_xoff ||
|
||||
wp->yoff != old_yoff ||
|
||||
wp->sx != old_sx ||
|
||||
wp->sy != old_sy) {
|
||||
wp->sy != old_sy)
|
||||
changed = 1;
|
||||
if (window_pane_scrollbar_reserve(wp))
|
||||
wp->flags |= PANE_REDRAWSCROLLBAR;
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
redraw_invalidate_scene(w);
|
||||
@@ -488,20 +509,18 @@ layout_fix_panes(struct window *w, struct window_pane *skip)
|
||||
|
||||
/* Count the number of available cells in a layout. */
|
||||
u_int
|
||||
layout_count_cells(struct layout_cell *lc, int with_floating)
|
||||
layout_count_cells(struct layout_cell *lc)
|
||||
{
|
||||
struct layout_cell *lcchild;
|
||||
u_int count = 0;
|
||||
|
||||
switch (lc->type) {
|
||||
case LAYOUT_WINDOWPANE:
|
||||
if (lc->flags & LAYOUT_CELL_FLOATING && !with_floating)
|
||||
return 0;
|
||||
return (1);
|
||||
case LAYOUT_LEFTRIGHT:
|
||||
case LAYOUT_TOPBOTTOM:
|
||||
TAILQ_FOREACH(lcchild, &lc->cells, entry)
|
||||
count += layout_count_cells(lcchild, with_floating);
|
||||
count += layout_count_cells(lcchild);
|
||||
return (count);
|
||||
default:
|
||||
fatalx("bad layout type");
|
||||
@@ -520,10 +539,6 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
|
||||
|
||||
status = window_get_pane_status(w);
|
||||
|
||||
/* Floating cells do not take space from the tiled layout. */
|
||||
if (!layout_cell_is_tiled(lc) && !layout_cell_has_tiled_child(lc))
|
||||
return (0);
|
||||
|
||||
if (lc->type == LAYOUT_WINDOWPANE) {
|
||||
/* Space available in this cell only. */
|
||||
if (type == LAYOUT_LEFTRIGHT) {
|
||||
@@ -553,9 +568,6 @@ layout_resize_check(struct window *w, struct layout_cell *lc,
|
||||
/* Different type: minimum of available space in child cells. */
|
||||
minimum = UINT_MAX;
|
||||
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
|
||||
if (!layout_cell_is_tiled(lcchild) &&
|
||||
!layout_cell_has_tiled_child(lcchild))
|
||||
continue;
|
||||
available = layout_resize_check(w, lcchild, type);
|
||||
if (available < minimum)
|
||||
minimum = available;
|
||||
@@ -649,7 +661,7 @@ layout_resize_set_size(struct window *w, struct layout_cell *lc,
|
||||
|
||||
/* Find and return the nearest neighbour to a cell in a specific direction. */
|
||||
static struct layout_cell *
|
||||
layout_cell_get_neighbour_dir(struct layout_cell *lc, int direction)
|
||||
layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction)
|
||||
{
|
||||
struct layout_cell *lcn = lc;
|
||||
|
||||
@@ -683,9 +695,9 @@ layout_cell_get_neighbour(struct layout_cell *lc)
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
|
||||
direction = !direction;
|
||||
|
||||
lcother = layout_cell_get_neighbour_dir(lc, direction);
|
||||
lcother = layout_cell_get_neighbour_direction(lc, direction);
|
||||
if (lcother == NULL)
|
||||
lcother = layout_cell_get_neighbour_dir(lc, !direction);
|
||||
lcother = layout_cell_get_neighbour_direction(lc, !direction);
|
||||
|
||||
return (lcother);
|
||||
}
|
||||
@@ -702,7 +714,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
|
||||
/* If no parent, this is the last pane in a window. */
|
||||
lcparent = lc->parent;
|
||||
if (lcparent == NULL) {
|
||||
if (*lcroot == lc)
|
||||
if (lc->wp != NULL)
|
||||
*lcroot = NULL;
|
||||
layout_free_cell(lc, 0);
|
||||
return;
|
||||
@@ -770,49 +782,6 @@ layout_free(struct window *w, int only_nodes)
|
||||
layout_free_cell(w->layout_root, only_nodes);
|
||||
}
|
||||
|
||||
/* Move and resize floating panes so they stay inside the window. */
|
||||
static void
|
||||
layout_clamp_floating_panes(struct window *w, u_int sx, u_int sy)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc;
|
||||
u_int pad, avail, csx, csy;
|
||||
|
||||
TAILQ_FOREACH(wp, &w->z_index, zentry) {
|
||||
lc = wp->layout_cell;
|
||||
if (lc == NULL || (~lc->flags & LAYOUT_CELL_FLOATING))
|
||||
continue;
|
||||
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
|
||||
pad = 0;
|
||||
else
|
||||
pad = 1;
|
||||
|
||||
csx = lc->g.sx;
|
||||
avail = (sx > 2 * pad) ? sx - 2 * pad : 0;
|
||||
if (csx > avail)
|
||||
csx = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
||||
csy = lc->g.sy;
|
||||
avail = (sy > 2 * pad) ? sy - 2 * pad : 0;
|
||||
if (csy > avail)
|
||||
csy = (avail > PANE_MINIMUM) ? avail : PANE_MINIMUM;
|
||||
if (csx != lc->g.sx || csy != lc->g.sy)
|
||||
layout_set_size(lc, csx, csy, lc->g.xoff, lc->g.yoff);
|
||||
|
||||
if (lc->g.xoff + lc->g.sx + pad > sx) {
|
||||
if (lc->g.sx + 2 * pad >= sx)
|
||||
lc->g.xoff = pad;
|
||||
else
|
||||
lc->g.xoff = sx - lc->g.sx - pad;
|
||||
}
|
||||
if (lc->g.yoff + lc->g.sy + pad > sy) {
|
||||
if (lc->g.sy + 2 * pad >= sy)
|
||||
lc->g.yoff = pad;
|
||||
else
|
||||
lc->g.yoff = sy - lc->g.sy - pad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Resize the entire layout after window resize. */
|
||||
void
|
||||
layout_resize(struct window *w, u_int sx, u_int sy)
|
||||
@@ -833,11 +802,8 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
||||
* out proportionately - this should leave the layout fitting the new
|
||||
* window size.
|
||||
*/
|
||||
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) {
|
||||
layout_clamp_floating_panes(w, sx, sy);
|
||||
layout_fix_panes(w, NULL);
|
||||
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING))
|
||||
return;
|
||||
}
|
||||
xchange = sx - lc->g.sx;
|
||||
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
|
||||
if (xchange < 0 && xchange < -xlimit)
|
||||
@@ -867,7 +833,6 @@ layout_resize(struct window *w, u_int sx, u_int sy)
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_clamp_floating_panes(w, sx, sy);
|
||||
layout_fix_panes(w, NULL);
|
||||
}
|
||||
|
||||
@@ -895,7 +860,7 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
|
||||
size = lc->g.sx;
|
||||
else
|
||||
size = lc->g.sy;
|
||||
if (layout_cell_is_last_tiled(lc))
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
|
||||
change = size - new_size;
|
||||
else
|
||||
change = new_size - size;
|
||||
@@ -1020,11 +985,11 @@ layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
|
||||
if (lcparent == NULL)
|
||||
return;
|
||||
|
||||
/* If this is the last tiled cell, move back one. */
|
||||
if (layout_cell_is_last_tiled(lc)) {
|
||||
lc = layout_cell_get_neighbour_dir(lc, 0);
|
||||
if (lc == NULL)
|
||||
return;
|
||||
/* If this is the last cell, move back one. */
|
||||
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) {
|
||||
do
|
||||
lc = TAILQ_PREV(lc, layout_cells, entry);
|
||||
while (lc->flags & LAYOUT_CELL_FLOATING);
|
||||
}
|
||||
|
||||
layout_resize_layout(wp->window, lc, type, change, opposite);
|
||||
@@ -1042,22 +1007,22 @@ layout_resize_pane_grow(struct window *w, struct layout_cell *lc,
|
||||
lcadd = lc;
|
||||
|
||||
/* Look towards the tail for a suitable cell for reduction. */
|
||||
lcremove = layout_cell_get_neighbour_dir(lc, 1);
|
||||
lcremove = TAILQ_NEXT(lc, entry);
|
||||
while (lcremove != NULL) {
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size > 0)
|
||||
break;
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 1);
|
||||
lcremove = TAILQ_NEXT(lcremove, entry);
|
||||
}
|
||||
|
||||
/* If none found, look towards the head. */
|
||||
if (opposite && lcremove == NULL) {
|
||||
lcremove = layout_cell_get_neighbour_dir(lc, 0);
|
||||
lcremove = TAILQ_PREV(lc, layout_cells, entry);
|
||||
while (lcremove != NULL) {
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size > 0)
|
||||
break;
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
|
||||
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
|
||||
}
|
||||
}
|
||||
if (lcremove == NULL)
|
||||
@@ -1085,13 +1050,13 @@ layout_resize_pane_shrink(struct window *w, struct layout_cell *lc,
|
||||
size = layout_resize_check(w, lcremove, type);
|
||||
if (size != 0)
|
||||
break;
|
||||
lcremove = layout_cell_get_neighbour_dir(lcremove, 0);
|
||||
lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
|
||||
} while (lcremove != NULL);
|
||||
if (lcremove == NULL)
|
||||
return (0);
|
||||
|
||||
/* And add onto the next cell (from the original cell). */
|
||||
lcadd = layout_cell_get_neighbour_dir(lc, 1);
|
||||
lcadd = TAILQ_NEXT(lc, entry);
|
||||
if (lcadd == NULL)
|
||||
return (0);
|
||||
|
||||
@@ -1685,10 +1650,7 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args,
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (window_active_pane_is_over_zoom(w))
|
||||
window_push_zoom(w, 0, 1);
|
||||
else
|
||||
window_push_zoom(w, 1, (flags & SPAWN_ZOOM));
|
||||
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM));
|
||||
lc = layout_split_pane(wp, type, size, flags);
|
||||
if (lc == NULL)
|
||||
*cause = xstrdup("no space for a new pane");
|
||||
@@ -1715,10 +1677,8 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args,
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (flags & SPAWN_FLOATOVERZOOM)
|
||||
window_push_zoom(wp->window, 0, 1);
|
||||
else if (window_active_pane_is_over_zoom(w))
|
||||
window_push_zoom(wp->window, 0, 1);
|
||||
if (flags & SPAWN_MODAL)
|
||||
window_push_modal_zoom(w);
|
||||
else
|
||||
window_push_zoom(wp->window, 1, (flags & SPAWN_ZOOM));
|
||||
lcnew = layout_floating_pane(w, wp, &fg);
|
||||
@@ -1730,7 +1690,7 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
||||
enum pane_lines lines, struct window *w, struct layout_geometry *lg,
|
||||
char **cause)
|
||||
{
|
||||
int sx, sy, ox, oy, pad;
|
||||
int sx, sy, ox, oy;
|
||||
char *error = NULL;
|
||||
|
||||
sx = lg->sx == UINT_MAX ? w->sx / 2 : lg->sx;
|
||||
@@ -1779,20 +1739,12 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
||||
}
|
||||
}
|
||||
|
||||
if (!window_has_floating_panes(w)) {
|
||||
w->last_new_pane_x = 0;
|
||||
w->last_new_pane_y = 0;
|
||||
}
|
||||
if (ox == INT_MAX) {
|
||||
if (w->last_new_pane_x == 0)
|
||||
ox = 4;
|
||||
else {
|
||||
if (lines != PANE_LINES_NONE)
|
||||
pad = 1;
|
||||
else
|
||||
pad = 0;
|
||||
ox = w->last_new_pane_x + 4;
|
||||
if (ox + sx + pad > (int)w->sx)
|
||||
if (w->last_new_pane_x > w->sx)
|
||||
ox = 4;
|
||||
}
|
||||
w->last_new_pane_x = ox;
|
||||
@@ -1803,12 +1755,8 @@ layout_floating_args_parse(struct cmdq_item *item, struct args *args,
|
||||
if (w->last_new_pane_y == 0)
|
||||
oy = 2;
|
||||
else {
|
||||
if (lines != PANE_LINES_NONE)
|
||||
pad = 1;
|
||||
else
|
||||
pad = 0;
|
||||
oy = w->last_new_pane_y + 2;
|
||||
if (oy + sy + pad > (int)w->sy)
|
||||
if (w->last_new_pane_y > w->sy)
|
||||
oy = 2;
|
||||
}
|
||||
w->last_new_pane_y = oy;
|
||||
|
||||
63
menu.c
63
menu.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: menu.c,v 1.71 2026/09/21 12:14:32 nicm Exp $ */
|
||||
/* $OpenBSD: menu.c,v 1.69 2026/07/14 19:07:03 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -51,18 +51,6 @@ struct menu_data {
|
||||
void *data;
|
||||
};
|
||||
|
||||
void
|
||||
menu_get_size(struct menu *menu, enum box_lines lines, u_int *sx, u_int *sy)
|
||||
{
|
||||
if (lines == BOX_LINES_NONE) {
|
||||
*sx = menu->item_width + 2;
|
||||
*sy = menu->count;
|
||||
} else {
|
||||
*sx = menu->width + 4;
|
||||
*sy = menu->count + 2;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
menu_add_items(struct menu *menu, const struct menu_item *items,
|
||||
struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
|
||||
@@ -154,8 +142,6 @@ menu_add_item(struct menu *menu, const struct menu_item *item,
|
||||
width = format_width(new_item->name);
|
||||
if (*new_item->name == '-')
|
||||
width--;
|
||||
if (width > menu->item_width)
|
||||
menu->item_width = width;
|
||||
if (width > menu->width)
|
||||
menu->width = width;
|
||||
}
|
||||
@@ -252,7 +238,7 @@ menu_update(struct menu_data *md)
|
||||
screen_write_clearscreen(&ctx, 8);
|
||||
|
||||
if (md->border_lines != BOX_LINES_NONE) {
|
||||
screen_write_box(&ctx, menu_width(md), menu_height(md),
|
||||
screen_write_box(&ctx, menu->width + 4, menu->count + 2,
|
||||
md->border_lines, &md->border_style_gc, menu->title);
|
||||
}
|
||||
|
||||
@@ -300,13 +286,11 @@ menu_destroy(struct window *w)
|
||||
void
|
||||
menu_get_cursor(struct menu_data *md, u_int *cx, u_int *cy)
|
||||
{
|
||||
u_int border = (md->border_lines != BOX_LINES_NONE);
|
||||
|
||||
*cx = md->px + 1 + border;
|
||||
*cx = md->px + 2;
|
||||
if (md->choice == -1)
|
||||
*cy = md->py;
|
||||
else
|
||||
*cy = md->py + border + md->choice;
|
||||
*cy = md->py + 1 + md->choice;
|
||||
}
|
||||
|
||||
struct screen *
|
||||
@@ -318,19 +302,13 @@ menu_screen(struct menu_data *md)
|
||||
u_int
|
||||
menu_width(struct menu_data *md)
|
||||
{
|
||||
u_int sx, sy;
|
||||
|
||||
menu_get_size(md->menu, md->border_lines, &sx, &sy);
|
||||
return (sx);
|
||||
return (md->menu->width + 4);
|
||||
}
|
||||
|
||||
u_int
|
||||
menu_height(struct menu_data *md)
|
||||
{
|
||||
u_int sx, sy;
|
||||
|
||||
menu_get_size(md->menu, md->border_lines, &sx, &sy);
|
||||
return (sy);
|
||||
return (md->menu->count + 2);
|
||||
}
|
||||
|
||||
u_int
|
||||
@@ -359,8 +337,6 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
||||
struct cmdq_state *state;
|
||||
enum cmd_parse_status status;
|
||||
char *error;
|
||||
key_code key;
|
||||
u_int border;
|
||||
|
||||
if (KEYC_IS_MOUSE(event->key)) {
|
||||
/*
|
||||
@@ -376,10 +352,10 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
border = (md->border_lines != BOX_LINES_NONE);
|
||||
if (m->x < md->px || m->x >= md->px + menu_width(md) ||
|
||||
m->y < md->py + border ||
|
||||
m->y >= md->py + border + n) {
|
||||
if (m->x < md->px ||
|
||||
m->x > md->px + 4 + menu->width ||
|
||||
m->y < md->py + 1 ||
|
||||
m->y > md->py + 1 + n - 1) {
|
||||
if (~md->flags & MENU_STAYOPEN) {
|
||||
if (!move && MOUSE_RELEASE(m->b))
|
||||
return (1);
|
||||
@@ -402,7 +378,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
||||
if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
|
||||
goto chosen;
|
||||
}
|
||||
md->choice = m->y - (md->py + border);
|
||||
md->choice = m->y - (md->py + 1);
|
||||
if (md->choice != old)
|
||||
server_redraw_window_menu(md->w);
|
||||
return (0);
|
||||
@@ -411,8 +387,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
||||
name = menu->items[i].name;
|
||||
if (name == NULL || *name == '-')
|
||||
continue;
|
||||
key = (event->key & ~KEYC_MASK_FLAGS);
|
||||
if (key == (menu->items[i].key & ~KEYC_MASK_FLAGS)) {
|
||||
if ((event->key & ~KEYC_MASK_FLAGS) == menu->items[i].key) {
|
||||
md->choice = i;
|
||||
goto chosen;
|
||||
}
|
||||
@@ -569,16 +544,20 @@ menu_resize(struct menu_data *md, struct window *w)
|
||||
|
||||
if (md == NULL)
|
||||
return;
|
||||
|
||||
nx = md->px;
|
||||
ny = md->py;
|
||||
|
||||
menu_get_size(md->menu, md->border_lines, &sx, &sy);
|
||||
sx = md->menu->width + 4;
|
||||
sy = md->menu->count + 2;
|
||||
|
||||
if (nx + sx > w->sx) {
|
||||
if (w->sx <= sx)
|
||||
nx = 0;
|
||||
else
|
||||
nx = w->sx - sx;
|
||||
}
|
||||
|
||||
if (ny + sy > w->sy) {
|
||||
if (w->sy <= sy)
|
||||
ny = 0;
|
||||
@@ -610,9 +589,8 @@ menu_display(struct menu *menu, int flags, int starting_choice,
|
||||
w = fs->w;
|
||||
o = w->options;
|
||||
|
||||
if (lines == BOX_LINES_DEFAULT)
|
||||
lines = options_get_number(o, "menu-border-lines");
|
||||
menu_get_size(menu, lines, &sx, &sy);
|
||||
sx = menu->width + 4;
|
||||
sy = menu->count + 2;
|
||||
if (sx >= w->sx)
|
||||
px = 0;
|
||||
else if (px + sx > w->sx)
|
||||
@@ -624,6 +602,9 @@ menu_display(struct menu *menu, int flags, int starting_choice,
|
||||
w->menu_last_px = px;
|
||||
w->menu_last_py = py;
|
||||
|
||||
if (lines == BOX_LINES_DEFAULT)
|
||||
lines = options_get_number(o, "menu-border-lines");
|
||||
|
||||
md = xcalloc(1, sizeof *md);
|
||||
md->w = w;
|
||||
md->flags = flags;
|
||||
|
||||
19
mode-tree.c
19
mode-tree.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: mode-tree.c,v 1.102 2026/09/21 12:14:32 nicm Exp $ */
|
||||
/* $OpenBSD: mode-tree.c,v 1.100 2026/07/14 19:07:03 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -47,7 +47,7 @@ enum mode_tree_preview {
|
||||
"#[acs]x" MODE_TREE_PREFIX_STYLE " }," \
|
||||
"#{mode_tree_repeat}}" \
|
||||
"#{?mode_tree_branch," \
|
||||
"#[acs]#{?mode_tree_last,mq,tq}+" MODE_TREE_PREFIX_STYLE " ,}" \
|
||||
"#[acs]#{?mode_tree_last,mq,tq}" MODE_TREE_PREFIX_STYLE "> ,}" \
|
||||
"#{?mode_tree_has_children," \
|
||||
"#{?mode_tree_expanded,#[fg=themered]-" MODE_TREE_PREFIX_STYLE " ," \
|
||||
"#[fg=themegreen]+" MODE_TREE_PREFIX_STYLE " }," \
|
||||
@@ -1375,14 +1375,12 @@ static void
|
||||
mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
|
||||
u_int y, int outside)
|
||||
{
|
||||
struct window *w = mtd->wp->window;
|
||||
struct mode_tree_item *mti;
|
||||
struct menu *menu;
|
||||
const struct menu_item *items;
|
||||
struct mode_tree_menu *mtm;
|
||||
char *title;
|
||||
enum box_lines lines;
|
||||
u_int line, sx, sy;
|
||||
u_int line;
|
||||
|
||||
if (mtd->offset + y > mtd->line_size - 1)
|
||||
line = mtd->current;
|
||||
@@ -1407,17 +1405,14 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
|
||||
mtm->line = line;
|
||||
mtd->references++;
|
||||
|
||||
lines = options_get_number(w->options, "menu-border-lines");
|
||||
menu_get_size(menu, lines, &sx, &sy);
|
||||
if (x >= sx / 2)
|
||||
x -= sx / 2;
|
||||
if (x >= (menu->width + 4) / 2)
|
||||
x -= (menu->width + 4) / 2;
|
||||
else
|
||||
x = 0;
|
||||
x += mtd->wp->xoff;
|
||||
y += mtd->wp->yoff;
|
||||
|
||||
if (menu_display(menu, 0, 0, NULL, x, y, c, lines, NULL, NULL, NULL,
|
||||
NULL, mode_tree_menu_callback, mtm) != 0) {
|
||||
if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL,
|
||||
NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0) {
|
||||
mode_tree_remove_ref(mtd);
|
||||
free(mtm);
|
||||
menu_free(menu);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: monitor.c,v 1.8 2026/09/07 10:15:25 nicm Exp $ */
|
||||
/* $OpenBSD: monitor.c,v 1.7 2026/07/27 19:15:58 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -614,10 +614,8 @@ monitor_parse(const char *value, char **name, enum monitor_type *type, int *id,
|
||||
*type = MONITOR_ALL_WINDOWS;
|
||||
else if (sscanf(what, "@%d", id) == 1 && *id >= 0)
|
||||
*type = MONITOR_WINDOW;
|
||||
else if (*what == '\0')
|
||||
*type = MONITOR_SESSION;
|
||||
else
|
||||
goto fail;
|
||||
*type = MONITOR_SESSION;
|
||||
*name = xstrdup(copy);
|
||||
*format = xstrdup(split);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: options-table.c,v 1.247 2026/09/21 10:22:31 nicm Exp $ */
|
||||
/* $OpenBSD: options-table.c,v 1.242 2026/07/27 08:03:01 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -76,10 +76,9 @@ static const char *options_table_pane_border_indicators_list[] = {
|
||||
"off", "colour", "arrows", "both", NULL
|
||||
};
|
||||
static const char *options_table_pane_border_lines_list[] = {
|
||||
"single", "double", "heavy", "simple", "number", "spaces", "none",
|
||||
"rounded", NULL
|
||||
"single", "double", "heavy", "simple", "number", "spaces", "none", NULL
|
||||
};
|
||||
static const char *options_table_menu_border_lines_list[] = {
|
||||
static const char *options_table_popup_border_lines_list[] = {
|
||||
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
|
||||
};
|
||||
static const char *options_table_set_clipboard_list[] = {
|
||||
@@ -92,7 +91,7 @@ static const char *options_table_window_size_list[] = {
|
||||
"largest", "smallest", "manual", "latest", NULL
|
||||
};
|
||||
static const char *options_table_remain_on_exit_list[] = {
|
||||
"off", "on", "failed", "key", "failed-key", NULL
|
||||
"off", "on", "failed", "key", NULL
|
||||
};
|
||||
static const char *options_table_destroy_unattached_list[] = {
|
||||
"off", "on", "keep-last", "keep-group", NULL
|
||||
@@ -306,16 +305,6 @@ const struct options_table_entry options_table[] = {
|
||||
"When this is reached, the oldest buffer is deleted."
|
||||
},
|
||||
|
||||
{ .name = "clear-on-attach",
|
||||
.type = OPTIONS_TABLE_FLAG,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.default_num = 1,
|
||||
.text = "Whether to use the alternate screen and clear it when "
|
||||
"a client is attached. When disabled, tmux does not "
|
||||
"enter the alternate screen on attach so terminal "
|
||||
"content before tmux remains in scrollback."
|
||||
},
|
||||
|
||||
{ .name = "command-alias",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
@@ -493,7 +482,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "menu-border-lines",
|
||||
.type = OPTIONS_TABLE_CHOICE,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.choices = options_table_menu_border_lines_list,
|
||||
.choices = options_table_popup_border_lines_list,
|
||||
.default_num = BOX_LINES_SINGLE,
|
||||
.text = "Type of characters used to draw menu border lines. Some of "
|
||||
"these are only supported on terminals with UTF-8 support."
|
||||
@@ -1251,8 +1240,7 @@ const struct options_table_entry options_table[] = {
|
||||
* underscore.
|
||||
*/
|
||||
.default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~",
|
||||
.text = "Characters considered to separate words; a space matches "
|
||||
"any character with the Unicode White_Space property."
|
||||
.text = "Characters considered to separate words."
|
||||
},
|
||||
|
||||
/* Window options. */
|
||||
@@ -1666,14 +1654,40 @@ const struct options_table_entry options_table[] = {
|
||||
.text = "Pane scrollbar position."
|
||||
},
|
||||
|
||||
{ .name = "popup-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "bg=themedarkgrey,fg=themewhite",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Default style of popups."
|
||||
},
|
||||
|
||||
{ .name = "popup-border-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "bg=themedarkgrey,fg=themelightgrey",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Default style of popup borders."
|
||||
},
|
||||
|
||||
{ .name = "popup-border-lines",
|
||||
.type = OPTIONS_TABLE_CHOICE,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.choices = options_table_popup_border_lines_list,
|
||||
.default_num = BOX_LINES_SINGLE,
|
||||
.text = "Type of characters used to draw popup border lines. Some of "
|
||||
"these are only supported on terminals with UTF-8 support."
|
||||
},
|
||||
|
||||
{ .name = "remain-on-exit",
|
||||
.type = OPTIONS_TABLE_CHOICE,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.choices = options_table_remain_on_exit_list,
|
||||
.default_num = 0,
|
||||
.text = "Whether panes should remain ('on'), remain until a key is "
|
||||
"pressed after any exit ('key') or after a failure "
|
||||
"('failed-key'), or be automatically killed ('off' or "
|
||||
"pressed ('key') or be automatically killed ('off' or "
|
||||
"'failed') when the program inside exits."
|
||||
},
|
||||
|
||||
|
||||
660
popup.c
Normal file
660
popup.c
Normal file
@@ -0,0 +1,660 @@
|
||||
/* $OpenBSD: popup.c,v 1.76 2026/07/14 19:07:03 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);
|
||||
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);
|
||||
}
|
||||
6
proc.c
6
proc.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: proc.c,v 1.32 2026/08/04 13:16:03 claudio Exp $ */
|
||||
/* $OpenBSD: proc.c,v 1.31 2026/06/08 21:38:19 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -79,7 +79,7 @@ static void
|
||||
proc_event_cb(__unused int fd, short events, void *arg)
|
||||
{
|
||||
struct tmuxpeer *peer = arg;
|
||||
int n;
|
||||
ssize_t n;
|
||||
struct imsg imsg;
|
||||
|
||||
if (!(peer->flags & PEER_BAD) && (events & EV_READ)) {
|
||||
@@ -88,7 +88,7 @@ proc_event_cb(__unused int fd, short events, void *arg)
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
if ((n = imsgbuf_get(&peer->ibuf, &imsg)) == -1) {
|
||||
if ((n = imsg_get(&peer->ibuf, &imsg)) == -1) {
|
||||
peer->dispatchcb(NULL, peer->arg);
|
||||
return;
|
||||
}
|
||||
|
||||
10
prompt.c
10
prompt.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: prompt.c,v 1.7 2026/09/09 07:53:03 nicm Exp $ */
|
||||
/* $OpenBSD: prompt.c,v 1.5 2026/07/13 10:29:17 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -1334,7 +1334,7 @@ process_key:
|
||||
if (pr->index != size) {
|
||||
memmove(pr->buffer + pr->index,
|
||||
pr->buffer + pr->index + 1,
|
||||
(size - pr->index) *
|
||||
(size + 1 - pr->index) *
|
||||
sizeof *pr->buffer);
|
||||
goto changed;
|
||||
}
|
||||
@@ -1493,11 +1493,9 @@ append_key:
|
||||
utf8_set(&tmp, key);
|
||||
if (key <= 0x1f || key == 0x7f)
|
||||
tmp.width = 2;
|
||||
} else if (KEYC_IS_UNICODE(key)) {
|
||||
} else if (KEYC_IS_UNICODE(key))
|
||||
utf8_to_data(key, &tmp);
|
||||
if (tmp.size == 0)
|
||||
return (PROMPT_KEY_HANDLED);
|
||||
} else
|
||||
else
|
||||
return (PROMPT_KEY_HANDLED);
|
||||
|
||||
pr->buffer = xreallocarray(pr->buffer, size + 2,
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
TESTS!= echo *.sh
|
||||
LOGDIR=logs
|
||||
|
||||
.PHONY: all prepare $(TESTS)
|
||||
.PHONY: all
|
||||
.NOTPARALLEL: all
|
||||
|
||||
all: prepare $(TESTS)
|
||||
@failed=0; failures=; \
|
||||
all:
|
||||
@mkdir -p "$(LOGDIR)"; \
|
||||
rm -f "$(LOGDIR)"/*.log; \
|
||||
failed=0; failures=; \
|
||||
for test in $(TESTS); do \
|
||||
base=$${test##*/}; \
|
||||
log="$(LOGDIR)/$${base%.sh}.log"; \
|
||||
if [ -f "$$log" ]; then \
|
||||
rm -f "$$log"; \
|
||||
printf '%-40s ' "$$test"; \
|
||||
start=$$(date +%s); \
|
||||
env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
|
||||
sh -x "$$test" >"$$log" 2>&1; \
|
||||
if [ $$? -eq 0 ]; then \
|
||||
end=$$(date +%s); \
|
||||
rm -f "$$log"; \
|
||||
echo "PASS ($$((end - start))s)"; \
|
||||
else \
|
||||
end=$$(date +%s); \
|
||||
echo "FAIL ($$((end - start))s)"; \
|
||||
echo " log: $$log"; \
|
||||
failed=1; \
|
||||
failures="$$failures $$test"; \
|
||||
fi; \
|
||||
sleep 1; \
|
||||
done; \
|
||||
if [ "$$failed" -ne 0 ]; then \
|
||||
echo; \
|
||||
@@ -23,22 +39,3 @@ all: prepare $(TESTS)
|
||||
rmdir "$(LOGDIR)" 2>/dev/null || true; \
|
||||
fi; \
|
||||
exit $$failed
|
||||
|
||||
prepare:
|
||||
@mkdir -p "$(LOGDIR)"
|
||||
@rm -f "$(LOGDIR)"/*.log
|
||||
|
||||
$(TESTS): prepare
|
||||
@base="$@"; base=$${base##*/}; \
|
||||
log="$(LOGDIR)/$${base%.sh}.log"; \
|
||||
start=$$(date +%s); \
|
||||
if env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
|
||||
sh -x "$@" >"$$log" 2>&1; then \
|
||||
end=$$(date +%s); \
|
||||
rm -f "$$log"; \
|
||||
printf '%-40s PASS (%ss)\n' "$@" "$$((end - start))"; \
|
||||
else \
|
||||
end=$$(date +%s); \
|
||||
printf '%-40s FAIL (%ss)\n log: %s\n' \
|
||||
"$@" "$$((end - start))" "$$log"; \
|
||||
fi
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
# monitor-activity, monitor-bell and monitor-silence: both the
|
||||
# alert-activity, alert-bell and alert-silence hooks and the winlink alert
|
||||
# flags (window_activity_flag, window_bell_flag, window_silence_flag,
|
||||
# their session equivalents and the #, !, ~ characters in window_flags).
|
||||
# The sessions are detached so
|
||||
# flags (window_activity_flag, window_bell_flag, window_silence_flag and
|
||||
# the #, !, ~ characters in window_flags). The sessions are detached so
|
||||
# alert flags are set even on the current window; the *-action options
|
||||
# still decide whether the hooks fire. Panes run cat: activity is
|
||||
# generated by the tty echo of send-keys and a bell by sending a BEL and
|
||||
@@ -112,24 +111,6 @@ flags_have()
|
||||
esac
|
||||
}
|
||||
|
||||
assert_session_flag()
|
||||
{
|
||||
flag="#{session_${1}_flag}"
|
||||
expected=$2
|
||||
|
||||
# Only mon exists here. Check both window contexts and list-sessions.
|
||||
for target in mon:w0 "mon:$3"; do
|
||||
value=$($TMUX display -pt "$target" "$flag") ||
|
||||
fail "display $flag failed"
|
||||
[ "$value" = "$expected" ] ||
|
||||
fail "expected $flag for $target to be '$expected' but got '$value'"
|
||||
done
|
||||
value=$($TMUX list-sessions -F "$flag") ||
|
||||
fail "list-sessions $flag failed"
|
||||
[ "$value" = "$expected" ] ||
|
||||
fail "expected list-sessions $flag to be '$expected' but got '$value'"
|
||||
}
|
||||
|
||||
flags_lack()
|
||||
{
|
||||
target=$1
|
||||
@@ -169,12 +150,10 @@ $TMUX set-hook -g alert-silence \
|
||||
# alert-bell and sets the bell flag, shown as ! in window_flags.
|
||||
$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed"
|
||||
assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0
|
||||
assert_session_flag bell 0 bellw
|
||||
flags_lack mon:bellw '!'
|
||||
bell mon:bellw
|
||||
wait_for @log '|alert-bell:mon:bellw'
|
||||
wait_for_fmt mon:bellw '#{window_bell_flag}' 1
|
||||
assert_session_flag bell 1 bellw
|
||||
flags_have mon:bellw '!'
|
||||
|
||||
# Bells are not deduplicated: a second bell fires the hook again even
|
||||
@@ -185,7 +164,6 @@ wait_for @log '|alert-bell:mon:bellw|alert-bell:mon:bellw'
|
||||
# Selecting the window clears the alert flags.
|
||||
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
||||
wait_for_fmt mon:bellw '#{window_bell_flag}' 0
|
||||
assert_session_flag bell 0 bellw
|
||||
flags_lack mon:bellw '!'
|
||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
|
||||
@@ -195,7 +173,6 @@ $TMUX set -g @log '' || fail "reset @log failed"
|
||||
bell mon:w0
|
||||
wait_for @log '|alert-bell:mon:w0'
|
||||
wait_for_fmt mon:w0 '#{window_bell_flag}' 1
|
||||
assert_session_flag bell 1 bellw
|
||||
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
wait_for_fmt mon:w0 '#{window_bell_flag}' 0
|
||||
@@ -222,7 +199,6 @@ $TMUX neww -d -t mon: -n actw cat || fail "new-window actw failed"
|
||||
$TMUX set -g @log '' || fail "reset @log failed"
|
||||
activity mon:actw
|
||||
assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0
|
||||
assert_session_flag activity 0 actw
|
||||
assert_unchanged @log ''
|
||||
|
||||
# With monitor-activity on, output fires alert-activity and sets the
|
||||
@@ -232,7 +208,6 @@ $TMUX set -wt mon:actw monitor-activity on ||
|
||||
activity mon:actw
|
||||
wait_for @log '|alert-activity:mon:actw'
|
||||
wait_for_fmt mon:actw '#{window_activity_flag}' 1
|
||||
assert_session_flag activity 1 actw
|
||||
flags_have mon:actw '#'
|
||||
|
||||
# While the flag is set further activity does not fire the hook again.
|
||||
@@ -259,7 +234,6 @@ exec 3>"$OUT/fifo"
|
||||
wait_for_fmt mon: '#{session_attached}' 1
|
||||
$TMUX selectw -t mon:actw || fail "select-window actw failed"
|
||||
wait_for_fmt mon:actw '#{window_activity_flag}' 0
|
||||
assert_session_flag activity 0 actw
|
||||
flags_lack mon:actw '#'
|
||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
exec 3>&-
|
||||
@@ -276,7 +250,6 @@ $TMUX set -wt mon:w0 monitor-activity on ||
|
||||
$TMUX set -g @log '' || fail "reset @log failed"
|
||||
activity mon:w0
|
||||
wait_for_fmt mon:w0 '#{window_activity_flag}' 1
|
||||
assert_session_flag activity 1 actw
|
||||
assert_unchanged @log ''
|
||||
$TMUX set -wut mon:w0 monitor-activity ||
|
||||
fail "unset monitor-activity w0 failed"
|
||||
@@ -300,19 +273,16 @@ $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
# running.
|
||||
$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed"
|
||||
assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0
|
||||
assert_session_flag silence 0 silw
|
||||
$TMUX set -g @log '' || fail "reset @log failed"
|
||||
$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed"
|
||||
wait_for @log '|alert-silence:mon:silw'
|
||||
wait_for_fmt mon:silw '#{window_silence_flag}' 1
|
||||
assert_session_flag silence 1 silw
|
||||
flags_have mon:silw '~'
|
||||
assert_unchanged @log '|alert-silence:mon:silw'
|
||||
$TMUX set -wt mon:silw monitor-silence 0 ||
|
||||
fail "reset monitor-silence failed"
|
||||
$TMUX selectw -t mon:silw || fail "select-window silw failed"
|
||||
wait_for_fmt mon:silw '#{window_silence_flag}' 0
|
||||
assert_session_flag silence 0 silw
|
||||
flags_lack mon:silw '~'
|
||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/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
|
||||
@@ -28,7 +28,7 @@ $TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
TMP2=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$TMP2" "$TMP".*; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
trap 'rm -f "$TMP" "$TMP2"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
@@ -270,27 +270,6 @@ if [ $? -eq 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A write error after a successful open must be reported to the server.
|
||||
dd if=/dev/zero of="$TMP.big" bs=100000 count=1 2>/dev/null
|
||||
check_ok load-buffer -b write-error "$TMP.big"
|
||||
mkfifo "$TMP.fifo" || exit 1
|
||||
dd if="$TMP.fifo" of="$TMP.got" bs=1 count=8 2>/dev/null &
|
||||
reader=$!
|
||||
exec 3>"$TMP.fifo"
|
||||
out=$($TMUX save-buffer -b write-error "$TMP.fifo" 3>&- 2>&1)
|
||||
status=$?
|
||||
exec 3>&-
|
||||
wait "$reader"
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "save-buffer after write error succeeded."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(wc -c <"$TMP.got")" -ne 8 ]; then
|
||||
echo "save-buffer write error test wrote wrong amount."
|
||||
exit 1
|
||||
fi
|
||||
check_ok delete-buffer -b write-error
|
||||
|
||||
# save-buffer - writes to stdout and load-buffer - reads from stdin.
|
||||
out=$($TMUX save-buffer -b sb -)
|
||||
if [ "$out" != "data" ]; then
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# capture-pane -I line timestamps
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMP=$(mktemp)
|
||||
trap '$TMUX kill-server 2>/dev/null; rm -f "$TMP"' 0 1 15
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
before=$(date +%s)
|
||||
$TMUX new-session -d -x 40 -y 5 'seq 1 12; sleep 10' || exit 1
|
||||
sleep 1
|
||||
after=$(date +%s)
|
||||
|
||||
$TMUX capture-pane -pILF -S - -E - >"$TMP" || exit 1
|
||||
awk -v before="$before" -v after="$after" '
|
||||
$1 !~ /^-?[0-9]+$/ || $2 !~ /^[0-9]+$/ { exit 1 }
|
||||
$4 == "1" {
|
||||
if ($2 < before || $2 > after)
|
||||
exit 1
|
||||
history = 1
|
||||
}
|
||||
$4 == "9" {
|
||||
if ($2 != 0)
|
||||
exit 1
|
||||
visible = 1
|
||||
}
|
||||
END { if (!history || !visible) exit 1 }
|
||||
' "$TMP" || {
|
||||
cat "$TMP"
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
0
regress/cfg-client-lost.sh
Normal file → Executable file
0
regress/cfg-client-lost.sh
Normal file → Executable file
@@ -5,15 +5,6 @@
|
||||
PATH=/bin:/usr/bin
|
||||
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)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
@@ -39,7 +30,7 @@ must_equal()
|
||||
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
|
||||
}
|
||||
|
||||
$TMUX new-session -d -x 80 -y 24 -- $shell || exit 1
|
||||
$TMUX new-session -d -x 80 -y 24 || 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 automatic-rename off || exit 1
|
||||
|
||||
@@ -290,92 +290,4 @@ wait_count ': G4' 7
|
||||
fail "window 1 of zzz not killed"
|
||||
exit_mode q
|
||||
|
||||
# --- help and information preview --------------------------------------------
|
||||
# The complete tree help is taller than the 24-line test terminal, where the
|
||||
# help renderer deliberately leaves the tree unchanged.
|
||||
$TMUX2 resize-window -t out:0 -x 80 -y 50 || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
# The inner session has a one-line status, leaving a 49-line window.
|
||||
[ "$($TMUX display-message -p -t aaa:0 '#{window_height}')" -ge 49 ] &&
|
||||
break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "attached client did not resize for tree help"
|
||||
$TMUX choose-tree -t aaa:0 -F 'G5' -O index || exit 1
|
||||
wait_count ': G5' 7
|
||||
$TMUX2 send-keys -t out:0 F1 || fail "send-keys F1 failed"
|
||||
wait_for 'Swap current and previous window'
|
||||
# The first key closes help; the second exits the tree.
|
||||
$TMUX send-keys -t aaa:0 q q || fail "send-keys help exit failed"
|
||||
wait_mode aaa:0 0
|
||||
|
||||
$TMUX choose-tree -t aaa:0 -F 'G6' -O index || exit 1
|
||||
wait_count ': G6' 7
|
||||
$TMUX send-keys -t aaa:0 i || fail "send-keys info failed"
|
||||
wait_for 'Session'
|
||||
$TMUX send-keys -t aaa:0 i || fail "send-keys preview failed"
|
||||
exit_mode q
|
||||
|
||||
# --- window swapping and command prompt for current/tagged items -------------
|
||||
# Restrict a window-only tree to two disposable windows in aaa. J (the
|
||||
# Shift-Down equivalent) swaps their window objects while leaving their indexes
|
||||
# in place.
|
||||
$TMUX new-window -d -t aaa: -n swap-a 'cat' || exit 1
|
||||
$TMUX new-window -d -t aaa: -n swap-b 'cat' || exit 1
|
||||
aidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \
|
||||
awk -F: '$2 == "swap-a" {print $1}')
|
||||
bidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \
|
||||
awk -F: '$2 == "swap-b" {print $1}')
|
||||
$TMUX choose-tree -w -t aaa:0 -O index -F 'WT #{window_name}' \
|
||||
-f '#{m:swap-*,#{window_name}}' || exit 1
|
||||
wait_count 'WT swap-' 2
|
||||
$TMUX send-keys -t aaa:0 g j J || fail "window tree swap failed"
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
aname=$($TMUX display-message -p -t "aaa:$aidx" '#{window_name}')
|
||||
bname=$($TMUX display-message -p -t "aaa:$bidx" '#{window_name}')
|
||||
[ "$aname:$bname" = 'swap-b:swap-a' ] && break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "window tree did not swap the two windows"
|
||||
|
||||
# ':' runs an entered command for the current item and rebuilds the tree when
|
||||
# its queued command completes.
|
||||
$TMUX set-option -g @tree-command '' || exit 1
|
||||
$TMUX send-keys -t aaa:0 : || fail "window tree command prompt failed"
|
||||
wait_for '(current)'
|
||||
$TMUX send-keys -t aaa:0 -l "set-option -g @tree-command '%%'" || exit 1
|
||||
$TMUX send-keys -t aaa:0 Enter || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
value=$($TMUX show-option -gqv @tree-command)
|
||||
[ -n "$value" ] && break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "window tree entered command did not run"
|
||||
case "$value" in
|
||||
=aaa:*.) ;;
|
||||
*) fail "window tree command target was '$value'" ;;
|
||||
esac
|
||||
|
||||
# Tag both disposable windows and kill them through the tagged confirmation
|
||||
# callback. The mode-hosting window is outside the filter and survives.
|
||||
$TMUX send-keys -t aaa:0 g j t j t X || fail "tagged kill keys failed"
|
||||
wait_for 'Kill 2 tagged?'
|
||||
$TMUX send-keys -t aaa:0 y || fail "tagged kill confirmation failed"
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
left=$($TMUX list-windows -t aaa -F '#{window_name}' | \
|
||||
grep -c '^swap-' || true)
|
||||
[ "$left" -eq 0 ] && break
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "tagged windows were not killed"
|
||||
exit_mode q
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# With clear-on-attach disabled tmux must preserve the terminal contents by
|
||||
# scrolling them away, rather than entering and clearing the alternate screen.
|
||||
# Capture the bytes written by a real attached client for both settings.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
INNER_ON="$TEST_TMUX -LtestIon$$ -f/dev/null"
|
||||
OUTER_ON="$TEST_TMUX -LtestOon$$ -f/dev/null"
|
||||
INNER_OFF="$TEST_TMUX -LtestIoff$$ -f/dev/null"
|
||||
OUTER_OFF="$TEST_TMUX -LtestOoff$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER_ON kill-server 2>/dev/null
|
||||
$INNER_ON kill-server 2>/dev/null
|
||||
$OUTER_OFF kill-server 2>/dev/null
|
||||
$INNER_OFF kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$current_inner list-clients 2>/dev/null | grep -q . && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
capture_attach()
|
||||
{
|
||||
setting=$1
|
||||
output=$2
|
||||
go=$DIR/go-$setting
|
||||
if [ "$setting" = on ]; then
|
||||
current_inner=$INNER_ON
|
||||
current_outer=$OUTER_ON
|
||||
else
|
||||
current_inner=$INNER_OFF
|
||||
current_outer=$OUTER_OFF
|
||||
fi
|
||||
|
||||
$current_inner new-session -d -s inner -x40 -y10 \
|
||||
'exec sleep 100' || exit 1
|
||||
$current_inner set-option -g status off || exit 1
|
||||
$current_inner set-option -g clear-on-attach "$setting" || exit 1
|
||||
$current_outer new-session -d -s outer -x40 -y10 \
|
||||
"while [ ! -e '$go' ]; do sleep 0.01; done; exec $current_inner attach-session -t inner" || exit 1
|
||||
$current_outer set-option -g status off || exit 1
|
||||
$current_outer pipe-pane -O "cat >'$output'" || exit 1
|
||||
touch "$go"
|
||||
wait_for_client
|
||||
sleep 0.5
|
||||
$current_outer pipe-pane -O || exit 1
|
||||
}
|
||||
|
||||
on=$DIR/on
|
||||
off=$DIR/off
|
||||
capture_attach on "$on"
|
||||
capture_attach off "$off"
|
||||
|
||||
smcup=$(printf '\033[?1049h')
|
||||
grep -Fq "$smcup" "$on" || fail "clear-on-attach on did not use smcup"
|
||||
if grep -Fq "$smcup" "$off"; then
|
||||
fail "clear-on-attach off used smcup"
|
||||
fi
|
||||
|
||||
# Some screen terminfo entries have indn (CSI Ps S), while older ones only
|
||||
# have ind (newline). The preserving path uses indn once or repeats ind once
|
||||
# for every line in the terminal plus one.
|
||||
escape=$(printf '\033')
|
||||
if ! LC_ALL=C grep -Eq "${escape}\\[[0-9]+S" "$off"; then
|
||||
hex=$(od -An -tx1 -v "$off" | tr -d ' \n')
|
||||
case "$hex" in
|
||||
*0a0a0a0a0a0a0a0a0a0a0a*) ;;
|
||||
*) fail "clear-on-attach off did not scroll the old contents away" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# send-keys -K injects a key into a client. Commands produced by that key must
|
||||
# be inserted immediately after send-keys, before later commands in the same
|
||||
# command list. Focus events arriving between a prefix and its following key
|
||||
# must be forwarded without cancelling the prefix table.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
INNER="$TEST_TMUX -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
}
|
||||
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_for_option()
|
||||
{
|
||||
option=$1
|
||||
expected=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
actual=$($INNER show-option -gv "$option" 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "$option is '$actual', expected '$expected'"
|
||||
}
|
||||
|
||||
cleanup
|
||||
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g focus-events on || exit 1
|
||||
$OUTER new-session -d -s outer -x40 -y10 "$INNER attach -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
wait_for_client
|
||||
|
||||
# The binding generated by send-keys -K must run before the command which
|
||||
# follows send-keys in this command list. Before the queue-ordering fix this
|
||||
# produces AB rather than BA.
|
||||
$INNER set-option -g @order '' || exit 1
|
||||
$INNER bind-key -n a set-option -gF @order '#{@order}B' || exit 1
|
||||
$INNER send-keys -K -c "$client" a \; \
|
||||
set-option -gF @order '#{@order}A' || exit 1
|
||||
wait_for_option @order BA
|
||||
|
||||
# Put the real attached client in the prefix table, deliver FocusIn, then the
|
||||
# bound key. FocusIn is not bindable and must not reset the prefix table.
|
||||
$INNER bind-key x set-option -g @focus-prefix yes || exit 1
|
||||
$OUTER send-keys C-b || exit 1
|
||||
focus_in=$(printf '\033[I')
|
||||
$OUTER send-keys -l "$focus_in" || exit 1
|
||||
$OUTER send-keys x || exit 1
|
||||
wait_for_option @focus-prefix yes
|
||||
|
||||
exit 0
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise clock-mode initialization, all four styles, its one-second timer,
|
||||
# resize into the compact renderer, arbitrary-key exit, and cleanup.
|
||||
|
||||
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 -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
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
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER display-message -p -t clock:0 '#{pane_mode}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane mode is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
wait_hashes()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -q '#' && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "large clock did not render"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s clock -x80 -y24 '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 -w clock-mode-colour red || exit 1
|
||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t clock" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
for style in 12 24 12-with-seconds 24-with-seconds; do
|
||||
$INNER set-option -w -t clock:0 clock-mode-style "$style" || exit 1
|
||||
$INNER clock-mode -t clock:0 || exit 1
|
||||
wait_mode clock-mode
|
||||
wait_hashes
|
||||
# Let at least one timer callback observe a new second.
|
||||
[ "$style" != 12-with-seconds ] || sleep 1.2
|
||||
$INNER send-keys -t clock:0 x || exit 1
|
||||
wait_mode ''
|
||||
done
|
||||
|
||||
# Resizing an active mode below the large-glyph threshold selects the compact
|
||||
# renderer. A visible time contains a colon and no block-clock hash.
|
||||
$INNER clock-mode -t clock:0 || exit 1
|
||||
wait_mode clock-mode
|
||||
$INNER resize-window -t clock:0 -x20 -y5 || exit 1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Eq '[0-9][0-9]?:[0-9][0-9]' && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "compact clock did not render after resize"
|
||||
printf '%s\n' "$captured" | grep -q '#' &&
|
||||
fail "compact clock still used large glyphs"
|
||||
$INNER send-keys -t clock:0 Enter || exit 1
|
||||
wait_mode ''
|
||||
|
||||
$INNER has-session -t clock || fail "server died during clock-mode tests"
|
||||
exit 0
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,150 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise confirm-before's waiting and background callbacks, custom keys and
|
||||
# prompts, default-yes handling and errors. Also cover all three lock command
|
||||
# entry points with a harmless lock-command which leaves a marker.
|
||||
|
||||
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 -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
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
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_capture()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Fq "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$marker'"
|
||||
}
|
||||
|
||||
wait_option()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER show-option -gqv @confirmed 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "@confirmed is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
wait_file()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
[ -f "$marker" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "lock command did not create $marker"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s test -x80 -y24 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status on || exit 1
|
||||
$INNER set-option -g status-position bottom || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t test" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
client=$($INNER list-clients -F '#{client_name}')
|
||||
[ -n "$client" ] || fail "inner client did not attach"
|
||||
|
||||
$INNER bind-key -n M-a confirm-before \
|
||||
'set-option -g @confirmed accepted' || exit 1
|
||||
$INNER bind-key -n M-b confirm-before -b -p 'Background?' \
|
||||
'set-option -g @confirmed background' || exit 1
|
||||
$INNER bind-key -n M-c confirm-before -c x -p 'Custom?' \
|
||||
'set-option -g @confirmed custom' || exit 1
|
||||
$INNER bind-key -n M-y confirm-before -y -p 'Default?' \
|
||||
'set-option -g @confirmed default' || exit 1
|
||||
|
||||
# A non-confirming response closes the waiting prompt without running its
|
||||
# command; reopening and accepting inserts the command after the waiting item.
|
||||
$INNER set-option -g @confirmed sentinel || exit 1
|
||||
$OUTER send-keys M-a || exit 1
|
||||
wait_capture "Confirm 'set-option'? (y/n)"
|
||||
$OUTER send-keys n || exit 1
|
||||
sleep 0.2
|
||||
wait_option sentinel
|
||||
$OUTER send-keys M-a || exit 1
|
||||
wait_capture "Confirm 'set-option'? (y/n)"
|
||||
$OUTER send-keys y || exit 1
|
||||
wait_option accepted
|
||||
|
||||
# -b has no waiting queue item and appends its command to the client queue.
|
||||
$OUTER send-keys M-b || exit 1
|
||||
wait_capture 'Background?'
|
||||
$OUTER send-keys y || exit 1
|
||||
wait_option background
|
||||
|
||||
# A custom printable key is accepted, and -y makes Enter affirmative.
|
||||
$OUTER send-keys M-c || exit 1
|
||||
wait_capture 'Custom?'
|
||||
$OUTER send-keys x || exit 1
|
||||
wait_option custom
|
||||
$OUTER send-keys M-y || exit 1
|
||||
wait_capture 'Default?'
|
||||
$OUTER send-keys Enter || exit 1
|
||||
wait_option default
|
||||
|
||||
# Reject invalid multi-character and control confirmation keys.
|
||||
if $INNER confirm-before -b -t "$client" -c xx 'display-message x' \
|
||||
>/dev/null 2>&1; then
|
||||
fail "multi-character confirm key was accepted"
|
||||
fi
|
||||
if $INNER confirm-before -b -t "$client" -c "$(printf '\001')" \
|
||||
'display-message x' >/dev/null 2>&1; then
|
||||
fail "control confirm key was accepted"
|
||||
fi
|
||||
|
||||
# lock-client, lock-session and lock-server all send lock-command to the real
|
||||
# attached client. The command returns immediately and unlocks the client.
|
||||
for kind in client session server; do
|
||||
marker="$DIR/locked-$kind"
|
||||
$INNER set-option -t test lock-command "printf locked >$marker" || exit 1
|
||||
case "$kind" in
|
||||
client) $INNER lock-client -t "$client" || exit 1 ;;
|
||||
session) $INNER lock-session -t test || exit 1 ;;
|
||||
server) $INNER lock-server || exit 1 ;;
|
||||
esac
|
||||
wait_file "$marker"
|
||||
done
|
||||
|
||||
$INNER has-session -t test || fail "server died during prompt or lock tests"
|
||||
exit 0
|
||||
@@ -1,77 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A control client whose terminal has stopped accepting output (for example
|
||||
# its ssh connection or terminal emulator died) never drains the output that
|
||||
# server_client_check_exit waits on before sending MSG_EXIT, and may then
|
||||
# never close its socket either. This must not prevent the server from
|
||||
# exiting: after kill-server the server previously stayed alive forever with
|
||||
# server_exit set, closing every new connection immediately, so every new
|
||||
# client failed with "server exited unexpectedly" until the server was killed
|
||||
# by hand. The server now bounds the exit handshake: it discards output it
|
||||
# can never deliver and drops the client if it still does not close.
|
||||
#
|
||||
# The stalled terminal is simulated by a fifo whose read end is held open but
|
||||
# never read.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest$$"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
DIR=$(mktemp -d)
|
||||
FIFO=$DIR/fifo
|
||||
SERVER=
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
|
||||
cleanup() {
|
||||
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exec 8<&- 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
# A detached session whose pane floods printable output forever.
|
||||
$TMUX -f/dev/null new -d -x 80 -y 24 -s rt 'cat /dev/zero | tr "\000" x' || exit 1
|
||||
SERVER=$($TMUX display -pt rt '#{pid}')
|
||||
|
||||
# Attach a control client with output down the fifo; stdin held open by sleep
|
||||
# so it stays attached. The fifo fills and is never read, so a backlog forms
|
||||
# in the server that can never be delivered.
|
||||
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >"$FIFO" 2>&1 &
|
||||
exec 8<"$FIFO"
|
||||
|
||||
n=0
|
||||
while [ $n -lt 50 ]; do
|
||||
$TMUX lsc -F '#{client_name}' 2>/dev/null | grep -q . && break
|
||||
sleep 0.1
|
||||
n=$((n + 1))
|
||||
done
|
||||
$TMUX lsc -F '#{client_name}' 2>/dev/null | grep -q . ||
|
||||
{ echo "control client did not attach"; exit 1; }
|
||||
|
||||
# Let the pane flood fill the fifo so the client's output is stuck.
|
||||
sleep 2
|
||||
|
||||
# Ask the server to exit. This sets CLIENT_EXIT on the stalled client, whose
|
||||
# output can never drain.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# The server must exit within the exit timeout (10 seconds) plus slack,
|
||||
# rather than staying in limbo forever rejecting new clients.
|
||||
n=0
|
||||
while [ $n -lt 100 ]; do
|
||||
if ! kill -0 "$SERVER" 2>/dev/null; then
|
||||
SERVER=
|
||||
exit 0
|
||||
fi
|
||||
sleep 0.2
|
||||
n=$((n + 1))
|
||||
done
|
||||
|
||||
echo "server did not exit after kill-server; new clients see:"
|
||||
$TMUX ls 2>&1
|
||||
exit 1
|
||||
@@ -19,7 +19,7 @@ PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest$$"
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
DIR=$(mktemp -d)
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check that control client flags are available in every format expansion path
|
||||
# used to produce layouts. The layout strings themselves are tested separately.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
LANG=C.UTF-8
|
||||
export PATH TERM LC_ALL LANG
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
FIFO=$DIR/input
|
||||
OUT=$DIR/output
|
||||
CFG=$DIR/flags.conf
|
||||
PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&-
|
||||
[ -n "$PID" ] && kill "$PID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
wait_for()
|
||||
{
|
||||
pattern=$1
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt 50 ]; do
|
||||
grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0
|
||||
if [ -n "$PID" ] && ! kill -0 "$PID" 2>/dev/null; then
|
||||
fail "control client exited waiting for: $pattern"
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "missing: $pattern"
|
||||
}
|
||||
|
||||
send()
|
||||
{
|
||||
printf '%s\n' "$*" >&3
|
||||
}
|
||||
|
||||
check_commands()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
match='#{m:*new-layouts*,#{client_flags}}'
|
||||
|
||||
send "display-message -p 'DISPLAY-$name $match'"
|
||||
wait_for "DISPLAY-$name $expected"
|
||||
send "list-panes -F 'PANES-$name $match'"
|
||||
wait_for "PANES-$name $expected"
|
||||
send "list-windows -F 'WINDOWS-$name $match'"
|
||||
wait_for "WINDOWS-$name $expected"
|
||||
send "list-sessions -F 'SESSIONS-$name $match'"
|
||||
wait_for "SESSIONS-$name $expected"
|
||||
send "list-clients -F 'CLIENTS-$name $match'"
|
||||
wait_for "CLIENTS-$name $expected"
|
||||
}
|
||||
|
||||
check_config()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
send "source-file '$CFG'"
|
||||
send "display-message -p 'CONFIG-$name #{@config-new-layouts}'"
|
||||
wait_for "CONFIG-$name $expected"
|
||||
}
|
||||
|
||||
cat >"$CFG" <<'EOF'
|
||||
%if #{m:*new-layouts*,#{client_flags}}
|
||||
set-option -g @config-new-layouts 1
|
||||
%else
|
||||
set-option -g @config-new-layouts 0
|
||||
%endif
|
||||
EOF
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -s layouts -x 80 -y 24 || exit 1
|
||||
$TMUX split-window -h -t layouts: || exit 1
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
: >"$OUT"
|
||||
$TMUX -C attach-session -t layouts <"$FIFO" >"$OUT" 2>&1 &
|
||||
PID=$!
|
||||
exec 3>"$FIFO"
|
||||
|
||||
send 'display-message -p READY'
|
||||
wait_for READY
|
||||
|
||||
check_commands OFF 0
|
||||
check_config OFF 0
|
||||
|
||||
send 'refresh-client -f new-layouts'
|
||||
check_commands ON 1
|
||||
check_config ON 1
|
||||
|
||||
send 'refresh-client -f !new-layouts'
|
||||
check_commands OFF-AGAIN 0
|
||||
check_config OFF-AGAIN 0
|
||||
|
||||
# The list commands also run for unattached command clients. Supplying that
|
||||
# client to formats must not make the session loop dereference a NULL session.
|
||||
$TMUX list-sessions -F '#{S:all,active}' >/dev/null ||
|
||||
fail "session loop failed for unattached command client"
|
||||
$TMUX has-session -t layouts || fail "server exited"
|
||||
|
||||
exit 0
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Popups require an attached terminal and cannot be displayed by a control
|
||||
# client.
|
||||
# A popup command from control mode must be ignored cleanly, leaving the
|
||||
# client command queue and server usable.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
FIFO=$DIR/input
|
||||
OUT=$DIR/output
|
||||
RAN=$DIR/popup-ran
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&-
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
$TMUX new-session -d -s control -x40 -y10 'exec sleep 100' || exit 1
|
||||
$TMUX -C attach-session -t control <"$FIFO" >"$OUT" 2>&1 &
|
||||
control_pid=$!
|
||||
exec 3>"$FIFO"
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$TMUX list-clients -F '#{client_flags}' 2>/dev/null |
|
||||
grep -q 'control-mode' && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "control client did not attach"
|
||||
|
||||
printf '%s\n' "display-popup -E 'touch $RAN'" >&3
|
||||
printf '%s\n' "display-message -p CONTROL-POPUP-ALIVE" >&3
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
grep -q 'CONTROL-POPUP-ALIVE' "$OUT" 2>/dev/null && break
|
||||
if ! kill -0 "$control_pid" 2>/dev/null; then
|
||||
fail "control client exited after display-popup"
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "command after control-client popup did not run"
|
||||
$TMUX has-session -t control || fail "server exited after control-client popup"
|
||||
[ ! -e "$RAN" ] || fail "control client started a popup command"
|
||||
|
||||
exit 0
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A waited run-shell command can complete after a control client has closed its
|
||||
# input and exited. The command queue still references the client until the job
|
||||
# callback runs, but control_stop has already destroyed c->control_state. Late
|
||||
# command output must be discarded instead of dereferencing the stopped control
|
||||
# state and killing the server.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest$$"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
OUT=$(mktemp)
|
||||
trap "$TMUX kill-server 2>/dev/null; rm -f $OUT" 0 1 15
|
||||
|
||||
$TMUX -f/dev/null new-session -d -s main || exit 1
|
||||
|
||||
printf '%s\n' 'run-shell "sleep 0.2; echo TEST"' |
|
||||
$TMUX -f/dev/null -C >"$OUT" 2>&1
|
||||
|
||||
sleep 1
|
||||
|
||||
$TMUX has-session -t main 2>/dev/null || {
|
||||
echo "server exited after late run-shell output"
|
||||
cat "$OUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
grep -q '^%exit' "$OUT" || {
|
||||
echo "control client did not exit"
|
||||
cat "$OUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -30,11 +30,8 @@ killw
|
||||
EOF
|
||||
sleep 1
|
||||
$TMUX has || exit 1
|
||||
# Use a control client to request legacy layouts, keeping only pane lines
|
||||
# from the control protocol output.
|
||||
$TMUX -C lsp -aF '#{pane_id} #{window_layout}' |
|
||||
grep '^%[0-9]' >$TMP || exit 1
|
||||
cat <<EOF|cmp $TMP - || exit 1
|
||||
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1
|
||||
cat <<EOF|cmp -s $TMP - || exit 1
|
||||
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
||||
|
||||
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest$$"
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
FIFO=$(mktemp -u)
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
LANG=C.UTF-8
|
||||
export TERM LC_ALL LANG
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
IN="$TMPDIR/in"
|
||||
OUT="$TMPDIR/out"
|
||||
BADCFG="$TMPDIR/bad.conf"
|
||||
PID=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&- 2>/dev/null
|
||||
[ -n "$PID" ] && kill "$PID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" >&2
|
||||
[ -s "$OUT" ] && cat "$OUT" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
wait_for()
|
||||
{
|
||||
pattern=$1
|
||||
timeout=${2:-60}
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt "$timeout" ]; do
|
||||
grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "missing: $pattern"
|
||||
}
|
||||
|
||||
wait_for_count()
|
||||
{
|
||||
pattern=$1
|
||||
expected=$2
|
||||
timeout=${3:-60}
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt "$timeout" ]; do
|
||||
count=$(grep -F -c -- "$pattern" "$OUT" 2>/dev/null)
|
||||
[ "$count" -ge "$expected" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "missing count $expected for: $pattern"
|
||||
}
|
||||
|
||||
send()
|
||||
{
|
||||
printf '%s\n' "$1" >&3 || fail "failed to send: $1"
|
||||
}
|
||||
|
||||
# A notification is a standalone protocol message. It must never be part of
|
||||
# the response between the real %begin and matching %end or %error for a
|
||||
# command. Ignore guard-looking command output while a real guard is open.
|
||||
check_guards()
|
||||
{
|
||||
awk '
|
||||
function notification(line) {
|
||||
return line ~ /^%(sessions-changed|session-changed |unlinked-window-add )/ ||
|
||||
line ~ /^%(pause |continue |config-error )/
|
||||
}
|
||||
!open && $0 ~ /^%begin [0-9]+ [0-9]+ [0-9]+$/ {
|
||||
open = 1
|
||||
time = $2
|
||||
number = $3
|
||||
flags = $4
|
||||
next
|
||||
}
|
||||
open && ($0 == "%end " time " " number " " flags ||
|
||||
$0 == "%error " time " " number " " flags) {
|
||||
open = 0
|
||||
next
|
||||
}
|
||||
notification($0) {
|
||||
seen[$1]++
|
||||
if (open) {
|
||||
print "notification inside guard: " $0 > "/dev/stderr"
|
||||
bad = 1
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (open) {
|
||||
print "unterminated command guard" > "/dev/stderr"
|
||||
bad = 1
|
||||
}
|
||||
if (seen["%sessions-changed"] < 2 ||
|
||||
seen["%session-changed"] < 1 ||
|
||||
seen["%unlinked-window-add"] < 2 ||
|
||||
seen["%pause"] < 1 || seen["%continue"] < 1 ||
|
||||
seen["%config-error"] < 1) {
|
||||
print "missing expected notification" > "/dev/stderr"
|
||||
bad = 1
|
||||
}
|
||||
exit bad
|
||||
}' "$OUT" || fail "bad control protocol notification ordering"
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -s guard -x 80 -y 24 || exit 1
|
||||
pane=$($TMUX display-message -p -t guard:0.0 '#{pane_id}') || exit 1
|
||||
|
||||
mkfifo "$IN" || exit 1
|
||||
: >"$OUT"
|
||||
$TMUX -C attach-session -t guard <"$IN" >"$OUT" 2>&1 &
|
||||
PID=$!
|
||||
exec 3>"$IN"
|
||||
|
||||
# Attaching generates a session notification synchronously from inside the
|
||||
# attach command. It must follow the closing guard.
|
||||
wait_for '%session-changed '
|
||||
|
||||
# Guard-looking output is arbitrary command output, not protocol state. Make
|
||||
# new-session both generate notifications and print an unmatched fake %begin;
|
||||
# it must not leave those notifications permanently deferred.
|
||||
send "set-option -g @fake-begin '%begin 1 2 3'"
|
||||
send "new-session -d -P -F '#{@fake-begin}' -s fake-begin"
|
||||
wait_for_count '%sessions-changed' 1
|
||||
|
||||
# Likewise, fake %end output from a command that generates multiple
|
||||
# notifications must not flush them before the command actually ends.
|
||||
send "set-option -g @fake-end '%end 1 2 3'"
|
||||
send "new-session -d -P -F '#{@fake-end}' -s fake-end"
|
||||
wait_for_count '%sessions-changed' 2
|
||||
|
||||
# Exercise notification paths outside control-notify.c.
|
||||
send "refresh-client -A '$pane:pause'"
|
||||
wait_for "%pause $pane"
|
||||
send "refresh-client -A '$pane:continue'"
|
||||
wait_for "%continue $pane"
|
||||
|
||||
printf '%s\n' 'not-a-command' >"$BADCFG"
|
||||
send "source-file '$BADCFG'"
|
||||
wait_for '%config-error '
|
||||
|
||||
# A final command guarantees all earlier command guards have completed before
|
||||
# the transcript is checked.
|
||||
send "display-message -p 'guard-check-done'"
|
||||
wait_for 'guard-check-done'
|
||||
check_guards
|
||||
|
||||
exit 0
|
||||
@@ -1,253 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise the broad non-mouse copy-mode command surface: navigation, search,
|
||||
# jumps, state toggles, line/end-of-line copies, append, and pipe variants.
|
||||
|
||||
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
|
||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
x()
|
||||
{
|
||||
$TMUX send-keys -t copy:0 -X "$@" || fail "copy command failed: $*"
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($TMUX display-message -p -t copy:0 '#{pane_in_mode}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane_in_mode is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
fresh()
|
||||
{
|
||||
$TMUX copy-mode -t copy:0 || exit 1
|
||||
wait_mode 1
|
||||
x history-top
|
||||
x start-of-line
|
||||
}
|
||||
|
||||
select_text()
|
||||
{
|
||||
fresh
|
||||
x begin-selection
|
||||
x cursor-right
|
||||
x cursor-right
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s copy -x40 -y8 \
|
||||
"sh -c 'i=0; while [ \$i -lt 40 ]; do printf \" line-%02d needle (a[b]c) end\\n\" \$i; i=\$((i + 1)); done; printf \"\\nparagraph two needle\\n\\nparagraph three\\n\"; printf \"\\033]133;A\\007prompt-one\\n\\033]133;C\\007output-one\\n\\033]133;A\\007prompt-two\\n\\033]133;C\\007output-two\\n\"; exec sleep 100'" ||
|
||||
exit 1
|
||||
$TMUX set-option -g status off || exit 1
|
||||
$TMUX set-option -g history-limit 200 || exit 1
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
history=$($TMUX display-message -p -t copy:0 '#{history_size}')
|
||||
[ "$history" -ge 30 ] && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "history did not fill"
|
||||
|
||||
fresh
|
||||
|
||||
# Copy-mode-specific format callbacks expose the word, line, hyperlink and
|
||||
# active search match at the cursor.
|
||||
formats=$($TMUX display-message -p -t copy:0 \
|
||||
'#{copy_cursor_word}|#{copy_cursor_line}|#{copy_cursor_hyperlink}|#{search_match}') ||
|
||||
exit 1
|
||||
[ -n "$formats" ] || fail "copy-mode cursor formats were empty"
|
||||
|
||||
# Cursor, viewport and paragraph commands. Each is asserted by successful
|
||||
# dispatch and the final mode-liveness check; detailed word/selection geometry
|
||||
# is covered by the existing copy-mode tests.
|
||||
for command in back-to-indentation bottom-line top-line middle-line \
|
||||
cursor-centre-vertical cursor-centre-horizontal end-of-line history-bottom \
|
||||
history-top halfpage-down halfpage-up page-down page-up next-paragraph \
|
||||
previous-paragraph next-matching-bracket previous-matching-bracket; do
|
||||
x "$command"
|
||||
done
|
||||
x goto-line 15
|
||||
x set-mark
|
||||
x cursor-down
|
||||
x jump-to-mark
|
||||
|
||||
# OSC 133 prompt and output markers support shell-integration navigation.
|
||||
x history-top
|
||||
x next-prompt
|
||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
||||
*prompt-one*) ;;
|
||||
*) fail "next-prompt did not find prompt-one" ;;
|
||||
esac
|
||||
x next-prompt
|
||||
x previous-prompt
|
||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
||||
*prompt-one*) ;;
|
||||
*) fail "previous-prompt did not return to prompt-one" ;;
|
||||
esac
|
||||
x next-prompt -o
|
||||
case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in
|
||||
*output-one*) ;;
|
||||
*) fail "next-prompt -o did not find output-one" ;;
|
||||
esac
|
||||
|
||||
# Character jumps remember their direction and character for repeat/reverse.
|
||||
x jump-forward e
|
||||
x jump-again
|
||||
x jump-reverse
|
||||
x jump-backward e
|
||||
x jump-to-forward e
|
||||
x jump-to-backward e
|
||||
|
||||
# Direct, text and incremental searches cover both directions and the saved
|
||||
# search used by again/reverse.
|
||||
x search-forward needle
|
||||
x search-again
|
||||
x search-reverse
|
||||
x search-backward needle
|
||||
x search-forward-text needle
|
||||
x search-backward-text needle
|
||||
x search-forward-incremental '=needle'
|
||||
x search-forward-incremental '+needle'
|
||||
x search-backward-incremental '=needle'
|
||||
x search-backward-incremental '-needle'
|
||||
|
||||
# Stateful display and behaviour toggles.
|
||||
for command in rectangle-on rectangle-off rectangle-toggle \
|
||||
line-numbers-on line-numbers-off line-numbers-toggle \
|
||||
refresh-on refresh-now refresh-off refresh-toggle \
|
||||
scroll-exit-on scroll-exit-off scroll-exit-toggle toggle-position; do
|
||||
x "$command"
|
||||
done
|
||||
wait_mode 1
|
||||
x cancel
|
||||
wait_mode 0
|
||||
|
||||
# Line and end-of-line copy commands work without an explicit selection.
|
||||
fresh
|
||||
x select-line
|
||||
x copy-selection-and-cancel
|
||||
case "$($TMUX show-buffer)" in
|
||||
*line-00*) ;;
|
||||
*) fail "select-line did not select the current line" ;;
|
||||
esac
|
||||
|
||||
fresh
|
||||
x copy-line
|
||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-line produced an empty buffer"
|
||||
x cancel
|
||||
|
||||
fresh
|
||||
x copy-end-of-line
|
||||
[ -n "$($TMUX show-buffer)" ] || fail "copy-end-of-line produced an empty buffer"
|
||||
x cancel
|
||||
|
||||
# Append variants retain or close the mode as advertised.
|
||||
$TMUX set-buffer -b append-buffer prefix || exit 1
|
||||
select_text
|
||||
x append-selection
|
||||
appended=$($TMUX show-buffer -b append-buffer)
|
||||
case "$appended" in
|
||||
prefix*) ;;
|
||||
*) fail "append-selection produced '$appended'" ;;
|
||||
esac
|
||||
x cancel
|
||||
|
||||
$TMUX set-buffer -b append-cancel-buffer prefix || exit 1
|
||||
select_text
|
||||
x append-selection-and-cancel
|
||||
wait_mode 0
|
||||
appended=$($TMUX show-buffer -b append-cancel-buffer)
|
||||
case "$appended" in
|
||||
prefix*) ;;
|
||||
*) fail "append-selection-and-cancel produced '$appended'" ;;
|
||||
esac
|
||||
|
||||
# The explicit cancel variants close copy mode.
|
||||
fresh
|
||||
x copy-line-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x copy-end-of-line-and-cancel
|
||||
wait_mode 0
|
||||
select_text
|
||||
x copy-selection-and-cancel
|
||||
wait_mode 0
|
||||
|
||||
# Pipe wrappers have distinct command handlers. /dev/null keeps the test
|
||||
# deterministic while exercising asynchronous pipe job creation and cleanup.
|
||||
fresh
|
||||
x copy-pipe-line 'cat >/dev/null'
|
||||
x cancel
|
||||
fresh
|
||||
x copy-pipe-line-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
fresh
|
||||
x copy-pipe-end-of-line 'cat >/dev/null'
|
||||
x cancel
|
||||
fresh
|
||||
x copy-pipe-end-of-line-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
|
||||
select_text
|
||||
x copy-pipe-no-clear 'cat >/dev/null'
|
||||
x pipe-no-clear 'cat >/dev/null'
|
||||
x copy-pipe 'cat >/dev/null'
|
||||
x pipe 'cat >/dev/null'
|
||||
x cancel
|
||||
select_text
|
||||
x copy-pipe-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
select_text
|
||||
x pipe-and-cancel 'cat >/dev/null'
|
||||
wait_mode 0
|
||||
|
||||
# Commands which cancel on downward movement have separate handlers.
|
||||
fresh
|
||||
x history-bottom
|
||||
x bottom-line
|
||||
x cursor-down-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x history-bottom
|
||||
x halfpage-down-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x history-bottom
|
||||
x page-down-and-cancel
|
||||
wait_mode 0
|
||||
fresh
|
||||
x history-bottom
|
||||
x bottom-line
|
||||
x scroll-down-and-cancel
|
||||
wait_mode 0
|
||||
|
||||
$TMUX has-session -t copy || fail "server died during copy-mode commands"
|
||||
exit 0
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Changing an existing character selection to line mode must reset both ends
|
||||
# to complete lines, including when the cursor subsequently crosses the fixed
|
||||
# end. An invalid regular-expression search must also discard marks left by a
|
||||
# previous successful search.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -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 1 15
|
||||
|
||||
$TMUX new-session -d -x30 -y6 \
|
||||
"printf 'alpha\nbeta\ngamma\ndelta\n'; exec sleep 100" || exit 1
|
||||
$TMUX set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
# A valid regular-expression search creates marks. Replacing it with an
|
||||
# invalid expression, growing the mode, and scrolling must not leave an old
|
||||
# mark array indexed with the new geometry (the original bug was an ASan heap
|
||||
# overflow in this sequence).
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -X search-forward beta || exit 1
|
||||
[ "$($TMUX display-message -p '#{search_present}')" = 1 ] ||
|
||||
fail "successful search did not create marks"
|
||||
$TMUX send-keys -X search-forward '[' || exit 1
|
||||
$TMUX resize-window -x40 -y8 || exit 1
|
||||
$TMUX send-keys -X scroll-down || exit 1
|
||||
[ "$($TMUX display-message -p '#{search_present}')" = 0 ] ||
|
||||
fail "invalid regular expression left stale marks after resize"
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Select part of alpha through part of beta, then change the existing
|
||||
# selection to line mode. Both lines, and no partial columns, must be copied.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X selection-mode line || exit 1
|
||||
[ "$($TMUX display-message -p '#{selection_mode}')" = line ] ||
|
||||
fail "selection did not change to line mode"
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
expected=$(printf 'alpha\nbeta')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "line-mode selection did not expand to complete lines"
|
||||
|
||||
# Start with the cursor above the fixed end so selection-mode itself takes the
|
||||
# reverse-direction path, then extend it through another line.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X selection-mode line || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
expected=$(printf 'alpha\nbeta\ngamma')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "reversed line-mode selection did not keep complete lines"
|
||||
|
||||
exit 0
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/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
|
||||
@@ -117,7 +117,6 @@ $TMUX send-keys -X copy-selection
|
||||
# Test that vi cursor movement does not stop on the padding cell of a wide
|
||||
# character at the end of a line.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 1
|
||||
$TMUX new -d -x20 -y5 \
|
||||
"printf 'abc中\nxyz\n'; exec cat" || exit 1
|
||||
$TMUX set-window-option -g mode-keys vi
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
[ -z "$TMUX" ] || $TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
goto_cell()
|
||||
{
|
||||
row=$1
|
||||
column=$2
|
||||
|
||||
$TMUX send-keys -X history-top || fail "$mode: history-top failed"
|
||||
$TMUX send-keys -X start-of-line || fail "$mode: start-of-line failed"
|
||||
[ "$row" -eq 0 ] ||
|
||||
$TMUX send-keys -X -N "$row" cursor-down ||
|
||||
fail "$mode: cursor-down failed"
|
||||
[ "$column" -eq 0 ] ||
|
||||
$TMUX send-keys -X -N "$column" cursor-right ||
|
||||
fail "$mode: cursor-right failed"
|
||||
}
|
||||
|
||||
check_cursor()
|
||||
{
|
||||
expected=$1
|
||||
actual=$($TMUX display-message -p '#{copy_cursor_x},#{copy_cursor_y}')
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected cursor $expected, got $actual"
|
||||
}
|
||||
|
||||
check_word()
|
||||
{
|
||||
row=$1
|
||||
column=$2
|
||||
expected=$3
|
||||
|
||||
goto_cell "$row" "$column"
|
||||
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
|
||||
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
|
||||
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
|
||||
actual=$($TMUX show-buffer 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected word '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
check_next_word()
|
||||
{
|
||||
row=$1
|
||||
expected=$2
|
||||
|
||||
goto_cell "$row" 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: next-word failed"
|
||||
$TMUX set-buffer sentinel || fail "$mode: set-buffer failed"
|
||||
$TMUX send-keys -X select-word || fail "$mode: select-word failed"
|
||||
$TMUX send-keys -X copy-selection || fail "$mode: copy-selection failed"
|
||||
actual=$($TMUX show-buffer 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "$mode: expected next word '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
for mode in emacs vi; do
|
||||
TMUX="$TEST_TMUX -Lunicode-whitespace-$mode-$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x16 -y20 \
|
||||
"printf 'aa bb\naa\tbb\naa\302\240bb\naa\343\200\200bb\naa:bb\naaaaaaaaaaaaaaa\302\240b\nabcdefghijklmnopq\nhardone\nhardtwo\naa\341\232\200bb\naa\342\200\200bb\naa\342\200\250bb\naa\342\200\251bb\naa\342\200\257bb\naa\342\201\237bb\nxa\302\205b c\n'; exec cat" ||
|
||||
fail "$mode: new-session failed"
|
||||
$TMUX set-option -g window-size manual || fail "$mode: set size failed"
|
||||
$TMUX set-window-option -g mode-keys "$mode" ||
|
||||
fail "$mode: set mode-keys failed"
|
||||
$TMUX set-window-option -g word-separators "" ||
|
||||
fail "$mode: clear word-separators failed"
|
||||
$TMUX copy-mode || fail "$mode: copy-mode failed"
|
||||
|
||||
# ASCII space and TAB retain their existing behaviour.
|
||||
goto_cell 0 0
|
||||
$TMUX send-keys -X next-space || fail "$mode: ASCII next-space failed"
|
||||
check_cursor 3,0
|
||||
goto_cell 1 0
|
||||
$TMUX send-keys -X next-space || fail "$mode: TAB next-space failed"
|
||||
check_cursor 8,1
|
||||
$TMUX send-keys -X previous-space ||
|
||||
fail "$mode: TAB previous-space failed"
|
||||
check_cursor 0,1
|
||||
|
||||
# NBSP is whitespace for selection and word movement.
|
||||
goto_cell 2 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: NBSP next-word failed"
|
||||
check_cursor 3,2
|
||||
$TMUX send-keys -X previous-word ||
|
||||
fail "$mode: NBSP previous-word failed"
|
||||
check_cursor 0,2
|
||||
check_word 2 0 aa
|
||||
goto_cell 2 0
|
||||
$TMUX send-keys -X next-word-end ||
|
||||
fail "$mode: NBSP next-word-end failed"
|
||||
if [ "$mode" = emacs ]; then
|
||||
check_cursor 2,2
|
||||
else
|
||||
check_cursor 1,2
|
||||
fi
|
||||
|
||||
# U+3000 is two columns wide; both its leading and padding cells must be
|
||||
# skipped as one whitespace character in either direction.
|
||||
goto_cell 3 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: U+3000 next-word failed"
|
||||
check_cursor 4,3
|
||||
$TMUX send-keys -X previous-word ||
|
||||
fail "$mode: U+3000 previous-word failed"
|
||||
check_cursor 0,3
|
||||
goto_cell 3 0
|
||||
$TMUX send-keys -X next-space-end ||
|
||||
fail "$mode: U+3000 next-space-end failed"
|
||||
if [ "$mode" = emacs ]; then
|
||||
check_cursor 2,3
|
||||
else
|
||||
check_cursor 1,3
|
||||
fi
|
||||
|
||||
# Other characters in word-separators remain literal when space enables
|
||||
# Unicode whitespace matching.
|
||||
$TMUX set-window-option -g word-separators ': ' ||
|
||||
fail "$mode: set custom word-separators failed"
|
||||
goto_cell 4 0
|
||||
$TMUX send-keys -X next-word || fail "$mode: literal next-word failed"
|
||||
check_cursor 2,4
|
||||
$TMUX set-window-option -g word-separators "" ||
|
||||
fail "$mode: restore word-separators failed"
|
||||
|
||||
# Whitespace in the last column remains a boundary across a soft wrap.
|
||||
goto_cell 5 0
|
||||
$TMUX send-keys -X next-space ||
|
||||
fail "$mode: wrapped NBSP next-space failed"
|
||||
check_cursor 0,6
|
||||
|
||||
# A word split only by a soft wrap remains one word, while a hard line
|
||||
# boundary still terminates selection.
|
||||
check_word 7 0 abcdefghijklmnopq
|
||||
check_word 9 0 hardone
|
||||
|
||||
# Check the remaining representative White_Space ranges and U+0085
|
||||
# combined into a cell with another character.
|
||||
check_next_word 11 bb
|
||||
check_next_word 12 bb
|
||||
check_next_word 13 bb
|
||||
check_next_word 14 bb
|
||||
check_next_word 15 bb
|
||||
check_next_word 16 bb
|
||||
goto_cell 17 0
|
||||
$TMUX send-keys -X next-word ||
|
||||
fail "$mode: combined U+0085 next-word failed"
|
||||
check_cursor 2,17
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -1,118 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise find-window's content, name and title filters in every combination,
|
||||
# glob/regex and case-insensitive suffixes, plus zoom restoration.
|
||||
|
||||
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 -LtestI$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -LtestO$$ -f/dev/null"
|
||||
|
||||
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
|
||||
|
||||
capture()
|
||||
{
|
||||
$OUTER capture-pane -p -t outer:0.0 2>/dev/null
|
||||
}
|
||||
|
||||
wait_mode()
|
||||
{
|
||||
want=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
got=$($INNER display-message -p -t find:base.0 '#{pane_mode}' 2>/dev/null)
|
||||
[ "$got" = "$want" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "pane mode is '$got', expected '$want'"
|
||||
}
|
||||
|
||||
run_find()
|
||||
{
|
||||
label=$1
|
||||
expected=$2
|
||||
shift 2
|
||||
$INNER find-window -t find:base.0 "$@" || fail "$label failed"
|
||||
wait_mode tree-mode
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
captured=$(capture)
|
||||
printf '%s\n' "$captured" | grep -Fq "$expected" && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "$label did not show '$expected'"
|
||||
$INNER send-keys -t find:base.0 q || exit 1
|
||||
wait_mode ''
|
||||
}
|
||||
|
||||
$INNER new-session -d -s find -n base -x80 -y24 \
|
||||
"sh -c 'printf \"BaseBody\\n\"; exec sleep 100'" || exit 1
|
||||
$INNER split-window -d -h -t find:base \
|
||||
"sh -c 'printf \"OtherBody\\n\"; exec sleep 100'" || exit 1
|
||||
$INNER new-window -d -t find: -n NameNeedle 'exec sleep 100' || exit 1
|
||||
$INNER new-window -d -t find: -n body \
|
||||
"sh -c 'printf \"BodyNeedle\\n\"; exec sleep 100'" || exit 1
|
||||
$INNER new-window -d -t find: -n titled 'exec sleep 100' || exit 1
|
||||
$INNER select-pane -t find:titled.0 -T TitleNeedle || 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 -x80 -y24 "$INNER attach -t find:base" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
# Wait until the body text is in the pane history before searching it.
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t find:body.0 | grep -Fq BodyNeedle && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "pane content was not ready"
|
||||
|
||||
# No flags means C+N+T. Then cover all three pairs and all three singletons.
|
||||
run_find default NameNeedle NameNeedle
|
||||
run_find content-name NameNeedle -C -N NameNeedle
|
||||
run_find content-title titled -C -T TitleNeedle
|
||||
run_find name-title titled -N -T TitleNeedle
|
||||
run_find content-only body -C BodyNeedle
|
||||
run_find name-only NameNeedle -N NameNeedle
|
||||
run_find title-only titled -T TitleNeedle
|
||||
|
||||
# Glob case folding and all regular-expression suffix variants.
|
||||
run_find insensitive NameNeedle -N -i nameneedle
|
||||
run_find regex NameNeedle -N -r '^NameNeedle$'
|
||||
run_find regex-insensitive NameNeedle -N -r -i '^nameneedle$'
|
||||
|
||||
# -Z temporarily zooms the target window and restores its prior state on exit.
|
||||
$INNER find-window -Z -t find:base.0 -N NameNeedle || exit 1
|
||||
wait_mode tree-mode
|
||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 1 ] ||
|
||||
fail "find-window -Z did not zoom"
|
||||
$INNER send-keys -t find:base.0 q || exit 1
|
||||
wait_mode ''
|
||||
[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 0 ] ||
|
||||
fail "find-window -Z did not restore zoom"
|
||||
|
||||
exit 0
|
||||
@@ -1,108 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,206 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,200 +0,0 @@
|
||||
#!/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
|
||||
@@ -199,218 +199,5 @@ must_fail $TMUX resize-pane -t "$id" -x 0
|
||||
must_fail $TMUX resize-pane -t "$id" -y 0
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# --- Tiled pane resize with floating cells in the layout ---
|
||||
|
||||
$TMUX set-option -w -u pane-border-lines || exit 1
|
||||
base=$($TMUX display-message -p '#{pane_id}')
|
||||
|
||||
# A floating cell after the last tiled cell must not become the recipient of a
|
||||
# relative or absolute resize. This is the sequence from GitHub issue 5135.
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 50% -y 50% -X 50% -Y 50% 'sleep 100') ||
|
||||
fail "new-pane for tiled resize test failed"
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for tiled resize test failed"
|
||||
|
||||
$TMUX resize-pane -t "$lower" -U 5 || fail "relative tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 13
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
|
||||
|
||||
$TMUX resize-pane -t "$lower" -y 11 || fail "absolute tiled reset failed"
|
||||
$TMUX resize-pane -t "$lower" -y 16 || fail "absolute tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 7
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 10
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# A floating cell between tiled siblings must be skipped when finding the cell
|
||||
# which donates space to a resize.
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for middle floating cell test failed"
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$base" \
|
||||
-x 20 -y 8 -X 30 -Y 8 'sleep 100') ||
|
||||
fail "new-pane for middle floating cell test failed"
|
||||
|
||||
$TMUX resize-pane -t "$base" -D 3 || fail "resize past floating cell failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 6
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# A small floating child must not limit the available tiled space in a nested
|
||||
# layout with a different split direction.
|
||||
lower=$($TMUX split-window -dPF '#{pane_id}' -t "$base" 'sleep 100') ||
|
||||
fail "split-window for nested floating cell test failed"
|
||||
right=$($TMUX split-window -dhPF '#{pane_id}' -t "$lower" 'sleep 100') ||
|
||||
fail "horizontal split for nested floating cell test failed"
|
||||
floating=$($TMUX new-pane -dPF '#{pane_id}' -t "$lower" \
|
||||
-x 20 -y 3 -X 30 -Y 10 'sleep 100') ||
|
||||
fail "new-pane for nested floating cell test failed"
|
||||
|
||||
$TMUX resize-pane -t "$base" -D 3 || fail "nested tiled resize failed"
|
||||
must_equal "$($TMUX display-message -p -t "$base" '#{pane_height}')" 15
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$lower" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$right" '#{pane_top}')" 16
|
||||
must_equal "$($TMUX display-message -p -t "$right" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 1
|
||||
|
||||
$TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$right" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# --- Floating panes clamped when the window shrinks (issue #5581, PR #5582) ---
|
||||
#
|
||||
# layout_resize clamps floating panes back inside the window when it shrinks:
|
||||
# move them and, only if they cannot fit, shrink them (never below
|
||||
# PANE_MINIMUM). A floating cell is the pane's content, so with the default
|
||||
# single-line border the clamp counts 1 cell of border per side, exactly as
|
||||
# layout_floating_args_parse does on creation; with no border it counts 0.
|
||||
# Each case below gets its own window, since resize-window fixes a window at
|
||||
# a manual size for the rest of its life.
|
||||
|
||||
# Case 1: a lone floating pane -- break-pane -W on a window's only pane --
|
||||
# takes the early-return path in layout_resize, added during PR #5582's
|
||||
# review round, since there is no tiled tree to walk.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for lone float failed"
|
||||
|
||||
# -x 20 -y 6 -> pane 18x4; -X 60 -Y 18 -> pane_left 61, pane_top 19: with the
|
||||
# border, the footprint is columns 60-79, rows 18-23, flush with the 80x24
|
||||
# window's right and bottom edges, so the float fits before it is shrunk.
|
||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||
fail "break-pane -W for lone float failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
||||
|
||||
# Shrink to 40x16. The float still fits at its own size (18 <= 40-2, 4 <=
|
||||
# 16-2) so only its position moves, flush to the new right/bottom edges:
|
||||
# xoff = 40 - 18 - 1 = 21; yoff = 16 - 4 - 1 = 11.
|
||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||
fail "resize-window (lone float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||
|
||||
# Case 5: grow the window back. The clamp must not chase the window back
|
||||
# outward -- it only ever pulls a float in, never restores where it was.
|
||||
$TMUX resize-window -t "$win" -x 80 -y 24 ||
|
||||
fail "resize-window grow (lone float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 2: the same float, but with a tiled sibling surviving alongside it, so
|
||||
# the window's root cell stays tiled and layout_resize takes the normal path
|
||||
# (the clamp call after layout_fix_offsets) instead of the early return
|
||||
# above. Same geometry as case 1, so the same numbers should come out.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for tiled sibling failed"
|
||||
$TMUX split-window -t "$win" 'sleep 100' ||
|
||||
fail "split-window for tiled sibling failed"
|
||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||
fail "break-pane -W for tiled sibling failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
||||
|
||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||
fail "resize-window (tiled sibling) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 3: new-pane float, the original repro from issue #5581 and the PR
|
||||
# body -- also the normal path, via the tiled base pane new-window creates.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for new-pane repro failed"
|
||||
$TMUX resize-window -t "$win" -x 120 -y 40 ||
|
||||
fail "resize-window to 120x40 failed"
|
||||
|
||||
# -x 30 -y 10 -> pane 28x8; -X 85 -Y 25 -> pane_left 86, pane_top 26.
|
||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
||||
-x 30 -y 10 -X 85 -Y 25 'sleep 100') ||
|
||||
fail "new-pane for new-pane repro failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 86
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 26
|
||||
|
||||
# Shrink to 60x20: xoff = 60 - 28 - 1 = 31; yoff = 20 - 8 - 1 = 11.
|
||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
||||
fail "resize-window (new-pane repro) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 31
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 11
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 4: a float that already fits inside the shrunk window is left alone --
|
||||
# assert position and size are both unchanged, not just one of them.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for untouched float failed"
|
||||
|
||||
# -x 20 -y 6 -> pane 18x4; -X 8 -Y 3 -> pane_left 9, pane_top 4 (as at the top
|
||||
# of this file). Footprint columns 8-27, rows 3-8: well inside 60x20 too.
|
||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
||||
-x 20 -y 6 -X 8 -Y 3 'sleep 100') ||
|
||||
fail "new-pane for untouched float failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
||||
|
||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
||||
fail "resize-window (untouched float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 6: pad = 0, via the pane-border-lines window option set to none. The
|
||||
# border arithmetic differs here (no -2/+1 adjustment either on creation or
|
||||
# in the clamp).
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for pad=0 float failed"
|
||||
$TMUX set-option -w -t "$win" pane-border-lines none ||
|
||||
fail "set pane-border-lines none failed"
|
||||
|
||||
# No border: -x 20 -y 6 -> pane 20x6 directly; -X 60 -Y 18 -> pane_left 60,
|
||||
# pane_top 18 directly. Footprint (== content, no border) is columns 60-79,
|
||||
# rows 18-23: flush right/bottom of the 80x24 window, same as case 1.
|
||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||
fail "break-pane -W for pad=0 float failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 60
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 18
|
||||
|
||||
# Shrink to 40x16 with pad=0: xoff = 40 - 20 - 0 = 20; yoff = 16 - 6 - 0 = 10.
|
||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||
fail "resize-window (pad=0 float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 10
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,102 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,158 +0,0 @@
|
||||
#!/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,8 +7,6 @@ TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
trap '$TMUX kill-server 2>/dev/null' 0
|
||||
trap 'exit 1' 1 2 15
|
||||
|
||||
# test_format $format $expected_result
|
||||
test_format()
|
||||
|
||||
@@ -18,24 +18,6 @@ export TZ LANG LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
|
||||
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
|
||||
HOLD1=
|
||||
HOLD2=
|
||||
CC1=
|
||||
CC2=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
for pid in $HOLD1 $HOLD2 $CC1 $CC2; do
|
||||
kill "$pid" 2>/dev/null
|
||||
wait "$pid" 2>/dev/null
|
||||
done
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
}
|
||||
trap cleanup 0
|
||||
trap 'exit 1' 1 2 15
|
||||
|
||||
ESC=$(printf '\033')
|
||||
|
||||
@@ -597,6 +579,8 @@ assert_alive "verbose loop expansion"
|
||||
|
||||
# L loops over attached clients. Attach two control-mode clients, each held
|
||||
# open by a background process keeping a FIFO's write end open.
|
||||
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
|
||||
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
mkfifo "$FIFO1" "$FIFO2" || exit 1
|
||||
# Hold the write ends open so the control clients stay attached.
|
||||
@@ -621,9 +605,9 @@ test_format "#{L/nr:x}" "xx"
|
||||
test_format "#{L/r:x}" "xx"
|
||||
# Now detach one and confirm the count drops to one.
|
||||
kill $HOLD2 2>/dev/null
|
||||
wait $HOLD2 2>/dev/null
|
||||
HOLD2=
|
||||
sleep 1
|
||||
test_format "#{L:x}" "x"
|
||||
kill $HOLD1 $CC1 $CC2 2>/dev/null
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -7,8 +7,6 @@ TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
trap '$TMUX kill-server 2>/dev/null' 0
|
||||
trap 'exit 1' 1 2 15
|
||||
|
||||
# test_format $format $expected_result
|
||||
test_format()
|
||||
|
||||
@@ -71,10 +71,7 @@ cursor_very_visible
|
||||
cursor_x
|
||||
cursor_y
|
||||
history_all_bytes
|
||||
history_added
|
||||
history_bytes
|
||||
history_collected
|
||||
history_generation
|
||||
history_limit
|
||||
history_size
|
||||
host
|
||||
@@ -106,11 +103,6 @@ pane_at_right
|
||||
pane_at_top
|
||||
pane_bg
|
||||
pane_bottom
|
||||
pane_command_duration
|
||||
pane_command_end_time
|
||||
pane_command_running
|
||||
pane_command_start_time
|
||||
pane_command_status
|
||||
pane_current_command
|
||||
pane_current_path
|
||||
pane_dead
|
||||
@@ -128,21 +120,16 @@ pane_index
|
||||
pane_input_off
|
||||
pane_key_mode
|
||||
pane_last
|
||||
pane_last_output_time
|
||||
pane_last_prompt_time
|
||||
pane_left
|
||||
pane_marked
|
||||
pane_marked_set
|
||||
pane_modal_flag
|
||||
pane_mode
|
||||
pane_output_generation
|
||||
pane_path
|
||||
pane_pb_progress
|
||||
pane_pb_state
|
||||
pane_pid
|
||||
pane_pipe
|
||||
pane_pipe_pid
|
||||
pane_private_modes
|
||||
pane_right
|
||||
pane_search_string
|
||||
pane_start_command
|
||||
@@ -154,8 +141,6 @@ pane_title
|
||||
pane_top
|
||||
pane_tty
|
||||
pane_unseen_changes
|
||||
pane_unzoomed_height
|
||||
pane_unzoomed_width
|
||||
pane_width
|
||||
pane_x
|
||||
pane_y
|
||||
@@ -221,10 +206,7 @@ window_layout
|
||||
window_linked
|
||||
window_linked_sessions
|
||||
window_linked_sessions_list
|
||||
window_manual_height
|
||||
window_manual_width
|
||||
window_marked_flag
|
||||
window_modal_pane
|
||||
window_name
|
||||
window_offset_x
|
||||
window_offset_y
|
||||
|
||||
@@ -76,14 +76,14 @@ $TMUX set-hook -g session-closed \
|
||||
fail "set-hook session-closed failed"
|
||||
|
||||
# The only pane of the only window of a session exits: pane-exited, then
|
||||
# session-closed, then window-unlinked, each seeing the dead object in the
|
||||
# window-unlinked, then session-closed, each seeing the dead object in the
|
||||
# hook formats.
|
||||
pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') ||
|
||||
fail "new-session doomed failed"
|
||||
wait_for @log \
|
||||
"|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
|
||||
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed"
|
||||
assert_unchanged @log \
|
||||
"|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
|
||||
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed"
|
||||
|
||||
# The dead pane, window and session cannot be used as targets but the
|
||||
# server survives.
|
||||
@@ -110,14 +110,14 @@ $TMUX set-hook -g pane-exited \
|
||||
'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' ||
|
||||
fail "restore pane-exited hook failed"
|
||||
|
||||
# kill-window on the last window: session-closed then window-unlinked but
|
||||
# kill-window on the last window: window-unlinked then session-closed but
|
||||
# no pane-exited for the panes in the killed window.
|
||||
$TMUX set -g @log '' || fail "reset @log failed"
|
||||
$TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed"
|
||||
$TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed"
|
||||
$TMUX kill-window -t doomed2:0 || fail "kill-window failed"
|
||||
wait_for @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
|
||||
assert_unchanged @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
|
||||
wait_for @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2'
|
||||
assert_unchanged @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2'
|
||||
$TMUX has -t main || fail "server died after kill-window chain"
|
||||
|
||||
# kill-session: session-closed fires first, then window-unlinked for its
|
||||
|
||||
@@ -218,13 +218,11 @@ $TMUX set-hook -gu pane-mode-changed || fail "unset pane-mode-changed failed"
|
||||
|
||||
# pane-exited when a pane's command exits with remain-on-exit off.
|
||||
$TMUX set -g @x 0 || fail "set @x failed"
|
||||
$TMUX set-hook -g pane-exited \
|
||||
'set -gF @x "#{hook}:#{hook_pane}:#{hook_exit_status}:#{hook_exit_success}"' ||
|
||||
$TMUX set-hook -g pane-exited 'set -gF @x "#{hook}:#{hook_pane}"' ||
|
||||
fail "set-hook pane-exited failed"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \
|
||||
'trap "" HUP; exec </dev/null >/dev/null 2>&1; sleep 1; exit 42') ||
|
||||
fail "split-window exit 42 failed"
|
||||
wait_for @x "pane-exited:$pane:42:0"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'true') ||
|
||||
fail "split-window true failed"
|
||||
wait_for @x "pane-exited:$pane"
|
||||
$TMUX set-hook -gu pane-exited || fail "unset pane-exited failed"
|
||||
|
||||
# pane-died when a pane's command exits with remain-on-exit on.
|
||||
|
||||
@@ -12,8 +12,7 @@ $TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
OUT=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$OUT"; $TMUX kill-server 2>/dev/null' 0
|
||||
trap 'exit 1' 1 2 15
|
||||
trap "rm -f $TMP $OUT" 0 1 15
|
||||
|
||||
cat <<EOF >$TMP
|
||||
if 'true' 'wibble wobble'
|
||||
|
||||
99
regress/image-support.sh
Executable file
99
regress/image-support.sh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Grid-resident SIXEL image markers: history, copy, overwrite and fallback.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Limage$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -x 20 -y 8 "
|
||||
printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\'
|
||||
i=0
|
||||
while [ \$i -lt 10 ]; do printf '\nline-%d' \$i; i=\$((i + 1)); done
|
||||
sleep 10"
|
||||
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
|
||||
# Images scroll as grid cells, while capture output contains ordinary spaces.
|
||||
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
|
||||
$TMUX capture-pane -pS- >$TMP || exit 1
|
||||
grep 'Pq' $TMP >/dev/null && exit 1
|
||||
|
||||
# Copy mode duplicates the backing grid, including image marker references.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Ordinary text overwrites marker cells through the normal grid write path.
|
||||
$TMUX new-window -d "
|
||||
printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\'
|
||||
printf '\033[HXY'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# A 26-pixel raster occupies two default 16-pixel cells, but the final cell
|
||||
# remains only partially filled instead of stretching the raster to 32 pixels.
|
||||
# A client without an image feature exposes this in the sampled text backend.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# Re-emitting the image preserves the 26-pixel raster rather than expanding
|
||||
# it to the two complete 16-pixel grid cells occupied by the image.
|
||||
$TMUX pipe-pane || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX2 refresh-client -R || exit 1
|
||||
sleep 1
|
||||
grep -a '"1;1;26;26' $TMP >/dev/null || exit 1
|
||||
|
||||
# Selection redraws must leave text image cells visible.
|
||||
$TMUX2 copy-mode || exit 1
|
||||
$TMUX2 send-keys -X history-top || exit 1
|
||||
$TMUX2 send-keys -X start-of-line || exit 1
|
||||
$TMUX2 send-keys -X begin-selection || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# Image marker rows remain cell-aligned when a narrower terminal causes text
|
||||
# reflow. The second image cell is clipped, not moved to the following row.
|
||||
$TMUX2 send-keys -X cancel || exit 1
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:1 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 1 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -n "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP | wc -c)" = 61 ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -4,9 +4,6 @@ TERM=screen
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
# Keep panes alive until the next case or cleanup, without reading replies.
|
||||
INPUT_HOLD='while :; do sleep 3600; done'
|
||||
|
||||
TMP=$(mktemp)
|
||||
EXP=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
@@ -40,10 +37,10 @@ start_pane_hlimit()
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 1 -y 1 -s test-setup "$INPUT_HOLD" || exit 1
|
||||
$TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1
|
||||
$TMUX set-option -g history-limit "$hlimit" || exit 1
|
||||
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \
|
||||
"printf '$seq'; $INPUT_HOLD" || exit 1
|
||||
"printf '$seq'; sleep 2" || exit 1
|
||||
$TMUX kill-session -t test-setup
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user