mirror of
https://github.com/tmux/tmux.git
synced 2026-09-19 03:24:46 +00:00
Compare commits
90 Commits
local_wind
...
4902-image
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7188c20c71 | ||
|
|
710640d8fa | ||
|
|
aa6b52eff9 | ||
|
|
84a2ccdbcb | ||
|
|
87400d67b0 | ||
|
|
2a535f7d82 | ||
|
|
4e2abff2d6 | ||
|
|
e40ef98b1e | ||
|
|
ebb44d4715 | ||
|
|
e63e4fe0d0 | ||
|
|
f42cc5d956 | ||
|
|
56e9ca2dbe | ||
|
|
a80e2d1af7 | ||
|
|
b094564203 | ||
|
|
c0e38f3748 | ||
|
|
45f32d71ef | ||
|
|
40cd869d77 | ||
|
|
4c3983e286 | ||
|
|
c2426fcf08 | ||
|
|
c97ceaffe8 | ||
|
|
047308b156 | ||
|
|
cf80845be7 | ||
|
|
b6dbf10057 | ||
|
|
54d3f77c2c | ||
|
|
ed63826b15 | ||
|
|
b89f8b0624 | ||
|
|
c1cab6ea34 | ||
|
|
89a7dcc8ab | ||
|
|
48b6641d97 | ||
|
|
37b71800b5 | ||
|
|
5c9ae058bf | ||
|
|
db7ec915ea | ||
|
|
04644c7267 | ||
|
|
d53f8eff9e | ||
|
|
03efb5e293 | ||
|
|
2603f84b0a | ||
|
|
e5bcf59768 | ||
|
|
69ee9cf7f4 | ||
|
|
ae0fa8068c | ||
|
|
3442c1f26f | ||
|
|
b6c709307e | ||
|
|
002d38cb6b | ||
|
|
fb931f1676 | ||
|
|
ba2dc2cfb6 | ||
|
|
628acfd802 | ||
|
|
95b6adbd61 | ||
|
|
0b2739c62c | ||
|
|
596de59d11 | ||
|
|
7d2d187db4 | ||
|
|
674cdd94f5 | ||
|
|
75b606c3df | ||
|
|
d674f34f25 | ||
|
|
8b44703b41 | ||
|
|
c0ab9b7b3f | ||
|
|
f81c41929b | ||
|
|
cc4a76d0be | ||
|
|
52e2e2b0c0 | ||
|
|
e602dfa771 | ||
|
|
5fc24e373a | ||
|
|
a4d6004d40 | ||
|
|
260cde9f48 | ||
|
|
c8fe307682 | ||
|
|
2c7a5f73c4 | ||
|
|
643bb662a0 | ||
|
|
9731adb300 | ||
|
|
52863f0fe9 | ||
|
|
4e7f3d5520 | ||
|
|
4e379f38d6 | ||
|
|
d8da527882 | ||
|
|
5432f2ef0a | ||
|
|
8fc9c3a5f9 | ||
|
|
0852a39e31 | ||
|
|
825b2c39b8 | ||
|
|
80f2ab7237 | ||
|
|
3e6e0a2860 | ||
|
|
d601d0d8c5 | ||
|
|
f72e789240 | ||
|
|
f41b983e04 | ||
|
|
48e33179d4 | ||
|
|
aed1209c02 | ||
|
|
6739bd03f5 | ||
|
|
f66eeef8a5 | ||
|
|
2d220d9a7f | ||
|
|
da4895d559 | ||
|
|
b254f557e4 | ||
|
|
b26eeb5229 | ||
|
|
48cdd85886 | ||
|
|
f255f089fc | ||
|
|
d6074895bb | ||
|
|
faba411289 |
3
.github/workflows/lock.yml
vendored
3
.github/workflows/lock.yml
vendored
@@ -17,9 +17,8 @@ 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'
|
||||
|
||||
6
.github/workflows/regress.yml
vendored
6
.github/workflows/regress.yml
vendored
@@ -38,9 +38,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: dependencies
|
||||
if: runner.os == 'Linux'
|
||||
@@ -83,7 +81,7 @@ jobs:
|
||||
|
||||
- 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
|
||||
|
||||
10
Makefile.am
10
Makefile.am
@@ -5,7 +5,8 @@ CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c
|
||||
# Distribution tarball options.
|
||||
EXTRA_DIST = \
|
||||
CHANGES README COPYING example_tmux.conf \
|
||||
osdep-*.c mdoc2man.awk tmux.1
|
||||
osdep-*.c mdoc2man.awk tmux.1 \
|
||||
tools/generate-image-diacritics.sh
|
||||
dist_EXTRA_tmux_SOURCES = compat/*.[ch]
|
||||
|
||||
# Preprocessor flags.
|
||||
@@ -85,7 +86,6 @@ endif
|
||||
|
||||
# List of sources.
|
||||
dist_tmux_SOURCES = \
|
||||
active.c \
|
||||
alerts.c \
|
||||
arguments.c \
|
||||
attributes.c \
|
||||
@@ -247,9 +247,9 @@ if HAVE_UTF8PROC
|
||||
nodist_tmux_SOURCES += compat/utf8proc.c
|
||||
endif
|
||||
|
||||
# Enable sixel support.
|
||||
if ENABLE_SIXEL
|
||||
dist_tmux_SOURCES += image.c image-sixel.c
|
||||
# Enable image support.
|
||||
if ENABLE_IMAGES
|
||||
dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c image-fallback.c
|
||||
endif
|
||||
|
||||
# Add bits for fuzzing if enabled.
|
||||
|
||||
449
active.c
449
active.c
@@ -1,449 +0,0 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/* Local last-window stack entry. */
|
||||
struct active_window_entry {
|
||||
u_int window;
|
||||
TAILQ_ENTRY(active_window_entry) entry;
|
||||
};
|
||||
|
||||
/* Local last-window stack. */
|
||||
TAILQ_HEAD(active_window_stack, active_window_entry);
|
||||
|
||||
/* Per-client and per-session local active window state. */
|
||||
struct active_window {
|
||||
struct client *client;
|
||||
struct session *session;
|
||||
|
||||
u_int window;
|
||||
struct active_window_stack lastw;
|
||||
|
||||
RB_ENTRY(active_window) entry;
|
||||
};
|
||||
RB_HEAD(active_windows, active_window);
|
||||
|
||||
static int
|
||||
active_window_cmp(struct active_window *aw1, struct active_window *aw2)
|
||||
{
|
||||
uintptr_t c1 = (uintptr_t)aw1->client;
|
||||
uintptr_t c2 = (uintptr_t)aw2->client;
|
||||
uintptr_t s1 = (uintptr_t)aw1->session;
|
||||
uintptr_t s2 = (uintptr_t)aw2->session;
|
||||
|
||||
if (c1 < c2)
|
||||
return (-1);
|
||||
if (c1 > c2)
|
||||
return (1);
|
||||
if (s1 < s2)
|
||||
return (-1);
|
||||
if (s1 > s2)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
RB_GENERATE_STATIC(active_windows, active_window, entry, active_window_cmp);
|
||||
|
||||
static struct active_window *active_get(struct client *, struct session *);
|
||||
static struct active_window *active_add(struct client *, struct session *,
|
||||
struct winlink *);
|
||||
static void active_free(struct active_window *);
|
||||
static struct winlink *active_resolve(struct active_window *);
|
||||
static void active_stack_remove(struct active_window_stack *, u_int);
|
||||
static void active_stack_push(struct active_window_stack *, u_int);
|
||||
static void active_changed(struct client *, struct session *,
|
||||
struct winlink *, struct winlink *);
|
||||
|
||||
/* All local active window states. */
|
||||
static struct active_windows active_windows = RB_INITIALIZER(&active_windows);
|
||||
|
||||
/* Find local active window state for a client and session. */
|
||||
static struct active_window *
|
||||
active_get(struct client *c, struct session *s)
|
||||
{
|
||||
struct active_window aw;
|
||||
|
||||
if (c == NULL || s == NULL)
|
||||
return (NULL);
|
||||
aw.client = c;
|
||||
aw.session = s;
|
||||
return (RB_FIND(active_windows, &active_windows, &aw));
|
||||
}
|
||||
|
||||
/* Add local active window state for a client and session. */
|
||||
static struct active_window *
|
||||
active_add(struct client *c, struct session *s, struct winlink *wl)
|
||||
{
|
||||
struct active_window *aw;
|
||||
|
||||
aw = active_get(c, s);
|
||||
if (aw == NULL) {
|
||||
aw = xcalloc(1, sizeof *aw);
|
||||
aw->client = c;
|
||||
aw->session = s;
|
||||
TAILQ_INIT(&aw->lastw);
|
||||
RB_INSERT(active_windows, &active_windows, aw);
|
||||
}
|
||||
if (wl != NULL)
|
||||
aw->window = wl->window->id;
|
||||
return (aw);
|
||||
}
|
||||
|
||||
/* Free local active window state. */
|
||||
static void
|
||||
active_free(struct active_window *aw)
|
||||
{
|
||||
struct active_window_entry *awe;
|
||||
|
||||
while (!TAILQ_EMPTY(&aw->lastw)) {
|
||||
awe = TAILQ_FIRST(&aw->lastw);
|
||||
TAILQ_REMOVE(&aw->lastw, awe, entry);
|
||||
free(awe);
|
||||
}
|
||||
RB_REMOVE(active_windows, &active_windows, aw);
|
||||
free(aw);
|
||||
}
|
||||
|
||||
/* Resolve local active window state to a valid winlink. */
|
||||
static struct winlink *
|
||||
active_resolve(struct active_window *aw)
|
||||
{
|
||||
struct winlink *wl, *next;
|
||||
u_int window;
|
||||
|
||||
if (aw == NULL || aw->session->curw == NULL)
|
||||
return (NULL);
|
||||
|
||||
wl = winlink_find_by_window_id(&aw->session->windows, aw->window);
|
||||
if (wl != NULL)
|
||||
return (wl);
|
||||
|
||||
while (!TAILQ_EMPTY(&aw->lastw)) {
|
||||
window = TAILQ_FIRST(&aw->lastw)->window;
|
||||
|
||||
next = winlink_find_by_window_id(&aw->session->windows, window);
|
||||
active_stack_remove(&aw->lastw, window);
|
||||
if (next != NULL) {
|
||||
aw->window = next->window->id;
|
||||
return (next);
|
||||
}
|
||||
}
|
||||
|
||||
aw->window = aw->session->curw->window->id;
|
||||
return (aw->session->curw);
|
||||
}
|
||||
|
||||
/* Remove a window from a local last-window stack. */
|
||||
static void
|
||||
active_stack_remove(struct active_window_stack *stack, u_int window)
|
||||
{
|
||||
struct active_window_entry *awe, *awe1;
|
||||
|
||||
TAILQ_FOREACH_SAFE(awe, stack, entry, awe1) {
|
||||
if (awe->window == window) {
|
||||
TAILQ_REMOVE(stack, awe, entry);
|
||||
free(awe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Push a window onto a local last-window stack. */
|
||||
static void
|
||||
active_stack_push(struct active_window_stack *stack, u_int window)
|
||||
{
|
||||
struct active_window_entry *awe;
|
||||
|
||||
active_stack_remove(stack, window);
|
||||
awe = xcalloc(1, sizeof *awe);
|
||||
awe->window = window;
|
||||
TAILQ_INSERT_HEAD(stack, awe, entry);
|
||||
}
|
||||
|
||||
/* Notify the server that a client's effective window changed. */
|
||||
static void
|
||||
active_changed(struct client *c, struct session *s, struct winlink *wl,
|
||||
struct winlink *old)
|
||||
{
|
||||
struct event_payload *ep;
|
||||
struct cmd_find_state fs;
|
||||
|
||||
if (wl == NULL || wl == old)
|
||||
return;
|
||||
|
||||
if (options_get_number(global_options, "focus-events")) {
|
||||
if (old != NULL)
|
||||
window_update_focus(old->window);
|
||||
window_update_focus(wl->window);
|
||||
}
|
||||
winlink_clear_flags(wl);
|
||||
window_update_activity(wl->window);
|
||||
tty_update_window_offset(wl->window);
|
||||
|
||||
if (c != NULL && c->session == s)
|
||||
wl->window->latest = c;
|
||||
|
||||
ep = event_payload_create();
|
||||
cmd_find_from_winlink(&fs, wl, 0);
|
||||
event_payload_set_target(ep, &fs);
|
||||
event_payload_set_client(ep, "client", c);
|
||||
event_payload_set_session(ep, "session", s);
|
||||
event_payload_set_window(ep, "window", wl->window);
|
||||
event_payload_set_window(ep, "new_window", wl->window);
|
||||
event_payload_set_int(ep, "window_index", wl->idx);
|
||||
event_payload_set_int(ep, "new_window_index", wl->idx);
|
||||
if (old != NULL) {
|
||||
event_payload_set_window(ep, "old_window", old->window);
|
||||
event_payload_set_int(ep, "old_window_index", old->idx);
|
||||
}
|
||||
events_fire("client-window-changed", ep);
|
||||
|
||||
if (c != NULL) {
|
||||
c->flags |= CLIENT_REDRAWWINDOW|CLIENT_REDRAWSTATUS|
|
||||
CLIENT_REDRAWBORDERS;
|
||||
tty_update_client_offset(c);
|
||||
status_update_cache(s);
|
||||
}
|
||||
recalculate_sizes();
|
||||
}
|
||||
|
||||
/* Return if a client has local window selection for a session. */
|
||||
int
|
||||
active_is_local_window(struct client *c, struct session *s)
|
||||
{
|
||||
return (active_get(c, s) != NULL);
|
||||
}
|
||||
|
||||
/* Make a client use local or shared window selection for a session. */
|
||||
void
|
||||
active_set_local_window(struct client *c, struct session *s,
|
||||
enum active_window_mode mode)
|
||||
{
|
||||
struct active_window *aw;
|
||||
struct winlink *wl, *old;
|
||||
|
||||
if (c == NULL || s == NULL || s->curw == NULL)
|
||||
return;
|
||||
|
||||
old = active_get_effective_winlink(c, s);
|
||||
if (mode == ACTIVE_LOCAL) {
|
||||
active_add(c, s, old);
|
||||
if (c != NULL && c->session == s)
|
||||
c->flags |= CLIENT_REDRAWSTATUS;
|
||||
return;
|
||||
}
|
||||
|
||||
aw = active_get(c, s);
|
||||
if (aw != NULL) {
|
||||
active_free(aw);
|
||||
wl = active_get_effective_winlink(c, s);
|
||||
active_changed(c, s, wl, old);
|
||||
if (c != NULL && c->session == s)
|
||||
c->flags |= CLIENT_REDRAWSTATUS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the effective winlink for a client and session. */
|
||||
struct winlink *
|
||||
active_get_effective_winlink(struct client *c, struct session *s)
|
||||
{
|
||||
struct active_window *aw;
|
||||
|
||||
if (s == NULL)
|
||||
return (NULL);
|
||||
aw = active_get(c, s);
|
||||
if (aw != NULL)
|
||||
return (active_resolve(aw));
|
||||
return (s->curw);
|
||||
}
|
||||
|
||||
/* Return the effective window for a client and session. */
|
||||
struct window *
|
||||
active_get_effective_window(struct client *c, struct session *s)
|
||||
{
|
||||
struct winlink *wl;
|
||||
|
||||
wl = active_get_effective_winlink(c, s);
|
||||
if (wl == NULL)
|
||||
return (NULL);
|
||||
return (wl->window);
|
||||
}
|
||||
|
||||
/* Select a winlink for a client and session. */
|
||||
int
|
||||
active_select_window(struct client *c, struct session *s, struct winlink *wl)
|
||||
{
|
||||
struct active_window *aw;
|
||||
struct winlink *old;
|
||||
int changed;
|
||||
|
||||
if (wl == NULL)
|
||||
return (-1);
|
||||
aw = active_get(c, s);
|
||||
if (aw == NULL)
|
||||
return (session_set_current(s, wl));
|
||||
|
||||
old = active_resolve(aw);
|
||||
if (old == wl)
|
||||
return (1);
|
||||
if (old != NULL)
|
||||
active_stack_push(&aw->lastw, old->window->id);
|
||||
active_stack_remove(&aw->lastw, wl->window->id);
|
||||
aw->window = wl->window->id;
|
||||
changed = (old != wl);
|
||||
active_changed(c, s, wl, old);
|
||||
return (changed ? 0 : 1);
|
||||
}
|
||||
|
||||
/* Select a window by index. */
|
||||
int
|
||||
active_select_window_index(struct client *c, struct session *s, int idx)
|
||||
{
|
||||
struct winlink *wl;
|
||||
|
||||
wl = winlink_find_by_index(&s->windows, idx);
|
||||
return (active_select_window(c, s, wl));
|
||||
}
|
||||
|
||||
/* Select the next window. */
|
||||
int
|
||||
active_next_window(struct client *c, struct session *s, int alert)
|
||||
{
|
||||
struct winlink *wl;
|
||||
|
||||
wl = active_get_effective_winlink(c, s);
|
||||
if (wl == NULL)
|
||||
return (-1);
|
||||
do {
|
||||
wl = winlink_next(wl);
|
||||
if (wl == NULL)
|
||||
wl = RB_MIN(winlinks, &s->windows);
|
||||
if (!alert || (wl->flags & WINLINK_ALERTFLAGS))
|
||||
return (active_select_window(c, s, wl));
|
||||
} while (wl != active_get_effective_winlink(c, s));
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* Select the previous window. */
|
||||
int
|
||||
active_previous_window(struct client *c, struct session *s, int alert)
|
||||
{
|
||||
struct winlink *wl;
|
||||
|
||||
wl = active_get_effective_winlink(c, s);
|
||||
if (wl == NULL)
|
||||
return (-1);
|
||||
do {
|
||||
wl = winlink_previous(wl);
|
||||
if (wl == NULL)
|
||||
wl = RB_MAX(winlinks, &s->windows);
|
||||
if (!alert || (wl->flags & WINLINK_ALERTFLAGS))
|
||||
return (active_select_window(c, s, wl));
|
||||
} while (wl != active_get_effective_winlink(c, s));
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* Select the last window. */
|
||||
int
|
||||
active_last_window(struct client *c, struct session *s)
|
||||
{
|
||||
struct active_window *aw;
|
||||
struct winlink *wl;
|
||||
u_int window;
|
||||
|
||||
aw = active_get(c, s);
|
||||
if (aw == NULL)
|
||||
return (session_last(s));
|
||||
while (!TAILQ_EMPTY(&aw->lastw)) {
|
||||
window = TAILQ_FIRST(&aw->lastw)->window;
|
||||
|
||||
wl = winlink_find_by_window_id(&s->windows, window);
|
||||
if (wl != NULL)
|
||||
return (active_select_window(c, s, wl));
|
||||
active_stack_remove(&aw->lastw, window);
|
||||
}
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* Remove all state for a client. */
|
||||
void
|
||||
active_remove_client(struct client *c)
|
||||
{
|
||||
struct active_window *aw, *aw1;
|
||||
|
||||
RB_FOREACH_SAFE(aw, active_windows, &active_windows, aw1) {
|
||||
if (aw->client == c)
|
||||
active_free(aw);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove all state for a session. */
|
||||
void
|
||||
active_remove_session(struct session *s)
|
||||
{
|
||||
struct active_window *aw, *aw1;
|
||||
|
||||
RB_FOREACH_SAFE(aw, active_windows, &active_windows, aw1) {
|
||||
if (aw->session == s)
|
||||
active_free(aw);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove a window from all local state. */
|
||||
void
|
||||
active_remove_window(struct window *w)
|
||||
{
|
||||
struct active_window *aw;
|
||||
|
||||
RB_FOREACH(aw, active_windows, &active_windows) {
|
||||
active_stack_remove(&aw->lastw, w->id);
|
||||
if (aw->window == w->id && aw->session->curw != NULL)
|
||||
aw->window = aw->session->curw->window->id;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return if this winlink is the effective winlink. */
|
||||
int
|
||||
active_is_effective_window(struct client *c, struct session *s,
|
||||
struct winlink *wl)
|
||||
{
|
||||
return (wl != NULL && wl == active_get_effective_winlink(c, s));
|
||||
}
|
||||
|
||||
/* Return if this winlink is the last effective winlink. */
|
||||
int
|
||||
active_is_last_window(struct client *c, struct session *s, struct winlink *wl)
|
||||
{
|
||||
struct active_window *aw;
|
||||
struct active_window_entry *awe;
|
||||
|
||||
if (wl == NULL)
|
||||
return (0);
|
||||
aw = active_get(c, s);
|
||||
if (aw == NULL)
|
||||
return (wl == TAILQ_FIRST(&s->lastw));
|
||||
TAILQ_FOREACH(awe, &aw->lastw, entry) {
|
||||
if (winlink_find_by_window_id(&s->windows, awe->window) == NULL)
|
||||
continue;
|
||||
return (wl->window->id == awe->window);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
2
alerts.c
2
alerts.c
@@ -314,7 +314,7 @@ alerts_set_message(struct winlink *wl, const char *type, const char *option)
|
||||
tty_putcode(&c->tty, TTYC_BEL);
|
||||
if (visual == VISUAL_OFF)
|
||||
continue;
|
||||
if (active_get_effective_winlink(c, c->session) == wl) {
|
||||
if (c->session->curw == wl) {
|
||||
status_message_set(c, -1, 1, 0, 0,
|
||||
"%s in current window", type);
|
||||
} else {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
|
||||
if (wl != NULL) {
|
||||
if (wp != NULL)
|
||||
window_set_active_pane(wp->window, wp, 1);
|
||||
active_select_window(c, s, wl);
|
||||
session_set_current(s, wl);
|
||||
if (wp != NULL)
|
||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||
else
|
||||
|
||||
@@ -93,18 +93,14 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state *current = cmdq_get_current(item);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct cmd_find_state *source = cmdq_get_source(item);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct client *ac;
|
||||
struct winlink *wl = source->wl;
|
||||
struct winlink *current_wl;
|
||||
struct session *src_s = source->s;
|
||||
struct session *dst_s = target->s;
|
||||
struct window_pane *wp = source->wp;
|
||||
struct window *w = wl->window, *old_w = w;
|
||||
char *cause, *cp;
|
||||
int idx = target->idx, before, old_idx = wl->idx;
|
||||
int detached, local;
|
||||
const char *template, *name = args_get(args, 'n');
|
||||
|
||||
if (wp == w->modal) {
|
||||
@@ -121,18 +117,11 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
|
||||
before = args_has(args, 'b');
|
||||
ac = tc;
|
||||
if (ac == NULL || ac->session != dst_s)
|
||||
ac = c;
|
||||
detached = args_has(args, 'd');
|
||||
local = (!detached && active_is_local_window(ac, dst_s));
|
||||
if (args_has(args, 'a') || before) {
|
||||
if (target->wl != NULL)
|
||||
idx = winlink_shuffle_up(dst_s, target->wl, before);
|
||||
else {
|
||||
current_wl = active_get_effective_winlink(ac, dst_s);
|
||||
idx = winlink_shuffle_up(dst_s, current_wl, before);
|
||||
}
|
||||
else
|
||||
idx = winlink_shuffle_up(dst_s, dst_s->curw, before);
|
||||
if (idx == -1)
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
@@ -140,7 +129,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
if (window_count_panes(w, 1) == 1) {
|
||||
if (server_link_window(src_s, wl, dst_s, idx, 0,
|
||||
!detached && !local, &cause) != 0) {
|
||||
!args_has(args, 'd'), &cause) != 0) {
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
@@ -153,13 +142,6 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
wl = winlink_find_by_window(&dst_s->windows, w);
|
||||
if (wl == NULL)
|
||||
return (CMD_RETURN_ERROR);
|
||||
if (local) {
|
||||
active_select_window(ac, dst_s, wl);
|
||||
if (ac != NULL && ac->session == dst_s)
|
||||
cmd_find_from_client(current, ac, 0);
|
||||
if (ac != NULL && ac->session == dst_s)
|
||||
server_redraw_client(ac);
|
||||
}
|
||||
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
|
||||
goto out;
|
||||
}
|
||||
@@ -203,17 +185,9 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
window_remove_ref(w, __func__);
|
||||
events_fire_window("window-created", w);
|
||||
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
|
||||
if (!detached) {
|
||||
if (local)
|
||||
active_select_window(ac, dst_s, wl);
|
||||
else
|
||||
session_select(dst_s, wl->idx);
|
||||
if (local && ac != NULL && ac->session == dst_s)
|
||||
cmd_find_from_client(current, ac, 0);
|
||||
else
|
||||
cmd_find_from_session(current, dst_s, 0);
|
||||
if (local && ac != NULL && ac->session == dst_s)
|
||||
server_redraw_client(ac);
|
||||
if (!args_has(args, 'd')) {
|
||||
session_select(dst_s, wl->idx);
|
||||
cmd_find_from_session(current, dst_s, 0);
|
||||
}
|
||||
|
||||
server_redraw_session(src_s);
|
||||
|
||||
@@ -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.53 2026/08/31 07:46:55 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>
|
||||
@@ -483,7 +483,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
|
||||
char *title, *cause = NULL;
|
||||
int flags = 0, starting_choice = 0;
|
||||
u_int px, py, i, count = args_count(args);
|
||||
struct options *o = target->w->options;
|
||||
struct options *o = target->s->curw->window->options;
|
||||
struct options_entry *oe;
|
||||
|
||||
if (args_has(args, 'C')) {
|
||||
@@ -584,15 +584,13 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
|
||||
u_int px, py, w, h, count = args_count(args);
|
||||
struct args_value *av;
|
||||
struct environ *env = NULL;
|
||||
struct options *o = target->w->options;
|
||||
struct options *o = s->curw->window->options;
|
||||
struct options_entry *oe;
|
||||
|
||||
if (args_has(args, 'C')) {
|
||||
server_client_clear_overlay(tc);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (tc->flags & CLIENT_CONTROL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (!modify && tc->overlay_draw != NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
|
||||
84
cmd-find.c
84
cmd-find.c
@@ -884,13 +884,13 @@ cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags)
|
||||
if (c->session != NULL) {
|
||||
cmd_find_clear_state(fs, flags);
|
||||
|
||||
fs->wl = active_get_effective_winlink(c, c->session);
|
||||
fs->wp = fs->wl->window->active;
|
||||
fs->wp = c->session->curw->window->active;
|
||||
if (fs->wp == NULL) {
|
||||
cmd_find_from_session(fs, c->session, flags);
|
||||
return (0);
|
||||
}
|
||||
fs->s = c->session;
|
||||
fs->wl = fs->s->curw;
|
||||
fs->w = fs->wl->window;
|
||||
|
||||
cmd_find_log_state(__func__, fs);
|
||||
@@ -931,57 +931,6 @@ unknown_pane:
|
||||
return (cmd_find_from_nothing(fs, flags));
|
||||
}
|
||||
|
||||
/* Find state from mouse target. */
|
||||
static int
|
||||
cmd_find_target_from_mouse(struct cmd_find_state *fs, struct cmdq_item *item,
|
||||
enum cmd_find_type type)
|
||||
{
|
||||
struct mouse_event *m = &cmdq_get_event(item)->m;
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
|
||||
switch (type) {
|
||||
case CMD_FIND_PANE:
|
||||
if (m->s != -1 && m->w == -1 && m->wp == -1 &&
|
||||
(fs->s = session_find_by_id(m->s)) != NULL) {
|
||||
if (tc != NULL && tc->session == fs->s)
|
||||
fs->wl = active_get_effective_winlink(tc, fs->s);
|
||||
else if (c != NULL && c->session == fs->s)
|
||||
fs->wl = active_get_effective_winlink(c, fs->s);
|
||||
else
|
||||
fs->wl = fs->s->curw;
|
||||
if (fs->wl != NULL) {
|
||||
fs->w = fs->wl->window;
|
||||
fs->wp = fs->w->active;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
|
||||
if (fs->wp != NULL) {
|
||||
fs->w = fs->wl->window;
|
||||
break;
|
||||
}
|
||||
/* FALLTHROUGH */
|
||||
case CMD_FIND_WINDOW:
|
||||
case CMD_FIND_SESSION:
|
||||
fs->wl = cmd_mouse_window(m, &fs->s);
|
||||
if (fs->s != NULL && m->w == -1 &&
|
||||
tc != NULL && tc->session == fs->s)
|
||||
fs->wl = active_get_effective_winlink(tc, fs->s);
|
||||
else if (fs->s != NULL && m->w == -1 &&
|
||||
c != NULL && c->session == fs->s)
|
||||
fs->wl = active_get_effective_winlink(c, fs->s);
|
||||
if (fs->wl == NULL && fs->s != NULL)
|
||||
fs->wl = fs->s->curw;
|
||||
if (fs->wl != NULL) {
|
||||
fs->w = fs->wl->window;
|
||||
fs->wp = fs->w->active;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return (fs->wp == NULL ? -1 : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Split target into pieces and resolve for the given type. Fills in the given
|
||||
* state. Returns 0 on success or -1 on error.
|
||||
@@ -990,6 +939,7 @@ int
|
||||
cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
|
||||
const char *target, enum cmd_find_type type, int flags)
|
||||
{
|
||||
struct mouse_event *m;
|
||||
struct client *c;
|
||||
struct cmd_find_state current;
|
||||
char *colon, *period, *copy = NULL, tmp[256];
|
||||
@@ -1065,15 +1015,35 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
|
||||
cmdq_error(item, "no current client");
|
||||
goto error;
|
||||
}
|
||||
fs->wl = active_get_effective_winlink(c, c->session);
|
||||
fs->wp = fs->wl->window->active;
|
||||
fs->w = fs->wl->window;
|
||||
fs->wl = c->session->curw;
|
||||
fs->wp = c->session->curw->window->active;
|
||||
fs->w = c->session->curw->window;
|
||||
goto found;
|
||||
}
|
||||
|
||||
/* Mouse target is a plain = or {mouse}. */
|
||||
if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) {
|
||||
if (cmd_find_target_from_mouse(fs, item, type) != 0) {
|
||||
m = &cmdq_get_event(item)->m;
|
||||
switch (type) {
|
||||
case CMD_FIND_PANE:
|
||||
fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
|
||||
if (fs->wp != NULL) {
|
||||
fs->w = fs->wl->window;
|
||||
break;
|
||||
}
|
||||
/* FALLTHROUGH */
|
||||
case CMD_FIND_WINDOW:
|
||||
case CMD_FIND_SESSION:
|
||||
fs->wl = cmd_mouse_window(m, &fs->s);
|
||||
if (fs->wl == NULL && fs->s != NULL)
|
||||
fs->wl = fs->s->curw;
|
||||
if (fs->wl != NULL) {
|
||||
fs->w = fs->wl->window;
|
||||
fs->wp = fs->w->active;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (fs->wp == NULL) {
|
||||
if (~flags & CMD_FIND_QUIET)
|
||||
cmdq_error(item, "no mouse target");
|
||||
goto error;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -300,6 +300,7 @@ 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) {
|
||||
@@ -321,10 +322,17 @@ 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);
|
||||
server_redraw_window(w);
|
||||
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
}
|
||||
@@ -413,16 +421,13 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state *current = cmdq_get_current(item);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct cmd_find_state *source = cmdq_get_source(item);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct client *ac;
|
||||
struct session *dst_s;
|
||||
struct winlink *src_wl, *dst_wl;
|
||||
struct window *src_w, *dst_w;
|
||||
struct window_pane *src_wp, *dst_wp;
|
||||
const char *s;
|
||||
char *cause = NULL;
|
||||
int flags = 0, dst_idx, local;
|
||||
int flags = 0, dst_idx;
|
||||
struct layout_cell *lc;
|
||||
|
||||
dst_s = target->s;
|
||||
@@ -430,10 +435,6 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
dst_wp = target->wp;
|
||||
dst_w = dst_wl->window;
|
||||
dst_idx = dst_wl->idx;
|
||||
ac = tc;
|
||||
if (ac == NULL || ac->session != dst_s)
|
||||
ac = c;
|
||||
local = active_is_local_window(ac, dst_s);
|
||||
|
||||
if (cmd_get_entry(self) == &cmd_move_pane_entry) {
|
||||
if (args_has(args, 'M'))
|
||||
@@ -519,18 +520,9 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
if (!args_has(args, 'd')) {
|
||||
window_set_active_pane(dst_w, src_wp, 1);
|
||||
if (local)
|
||||
active_select_window(ac, dst_s, dst_wl);
|
||||
else
|
||||
session_select(dst_s, dst_idx);
|
||||
if (local && ac != NULL && ac->session == dst_s)
|
||||
cmd_find_from_client(current, ac, 0);
|
||||
else
|
||||
cmd_find_from_session(current, dst_s, 0);
|
||||
if (local && ac != NULL && ac->session == dst_s)
|
||||
server_redraw_client(ac);
|
||||
else
|
||||
server_redraw_session(dst_s);
|
||||
session_select(dst_s, dst_idx);
|
||||
cmd_find_from_session(current, dst_s, 0);
|
||||
server_redraw_session(dst_s);
|
||||
} else
|
||||
server_status_session(dst_s);
|
||||
|
||||
|
||||
@@ -62,15 +62,12 @@ cmd_move_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *source = cmdq_get_source(item);
|
||||
struct cmd_find_state target;
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct client *ac;
|
||||
const char *tflag = args_get(args, 't');
|
||||
struct session *src = source->s;
|
||||
struct session *dst;
|
||||
struct winlink *wl = source->wl, *dstwl, *current_wl;
|
||||
struct winlink *wl = source->wl;
|
||||
char *cause;
|
||||
int idx, kflag, dflag, sflag, before, local;
|
||||
int idx, kflag, dflag, sflag, before;
|
||||
|
||||
if (args_has(args, 'r')) {
|
||||
if (cmd_find_target(&target, item, tflag, CMD_FIND_SESSION,
|
||||
@@ -93,37 +90,23 @@ cmd_move_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
dflag = args_has(args, 'd');
|
||||
sflag = args_has(args, 's');
|
||||
|
||||
ac = tc;
|
||||
if (ac == NULL || ac->session != dst)
|
||||
ac = c;
|
||||
local = (!dflag && active_is_local_window(ac, dst));
|
||||
|
||||
before = args_has(args, 'b');
|
||||
if (args_has(args, 'a') || before) {
|
||||
if (target.wl != NULL)
|
||||
idx = winlink_shuffle_up(dst, target.wl, before);
|
||||
else {
|
||||
current_wl = active_get_effective_winlink(ac, dst);
|
||||
idx = winlink_shuffle_up(dst, current_wl, before);
|
||||
}
|
||||
else
|
||||
idx = winlink_shuffle_up(dst, dst->curw, before);
|
||||
if (idx == -1)
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
if (server_link_window(src, wl, dst, idx, kflag, !dflag && !local,
|
||||
&cause) != 0) {
|
||||
if (server_link_window(src, wl, dst, idx, kflag, !dflag, &cause) != 0) {
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
dstwl = winlink_find_by_window(&dst->windows, wl->window);
|
||||
if (cmd_get_entry(self) == &cmd_move_window_entry)
|
||||
server_unlink_window(src, wl);
|
||||
if (local && dstwl != NULL) {
|
||||
active_select_window(ac, dst, dstwl);
|
||||
if (ac != NULL && ac->session == dst)
|
||||
server_redraw_client(ac);
|
||||
}
|
||||
|
||||
/*
|
||||
* Renumber the winlinks in the src session only, the destination
|
||||
|
||||
@@ -58,11 +58,9 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct spawn_context sc = { 0 };
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct client *ac;
|
||||
struct session *s = target->s;
|
||||
struct winlink *wl = target->wl, *new_wl = NULL;
|
||||
int idx = target->idx, before, count = args_count(args);
|
||||
int detached, local;
|
||||
char *cause = NULL, *cp, *expanded, *wname = NULL;
|
||||
const char *template, *name;
|
||||
struct cmd_find_state fs;
|
||||
@@ -109,15 +107,10 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
free(wname);
|
||||
if (args_has(args, 'd'))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (active_select_window(c, s, new_wl) == 0) {
|
||||
if (active_is_local_window(c, s) && c != NULL &&
|
||||
c->session == s)
|
||||
server_redraw_client(c);
|
||||
else
|
||||
server_redraw_session(s);
|
||||
}
|
||||
if (session_set_current(s, new_wl) == 0)
|
||||
server_redraw_session(s);
|
||||
if (c != NULL && c->session != NULL)
|
||||
active_get_effective_window(c, s)->latest = c;
|
||||
s->curw->window->latest = c;
|
||||
recalculate_sizes();
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
@@ -147,16 +140,10 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
sc.idx = idx;
|
||||
sc.cwd = args_get(args, 'c');
|
||||
|
||||
ac = tc;
|
||||
if (ac == NULL || ac->session != s)
|
||||
ac = c;
|
||||
detached = args_has(args, 'd');
|
||||
local = (!detached && active_is_local_window(ac, s));
|
||||
|
||||
sc.flags = 0;
|
||||
if (args_has(args, 'E') || (count == 1 && *args_string(args, 0) == '\0'))
|
||||
sc.flags |= SPAWN_EMPTY;
|
||||
if (detached || local)
|
||||
if (args_has(args, 'd'))
|
||||
sc.flags |= SPAWN_DETACHED;
|
||||
if (args_has(args, 'k'))
|
||||
sc.flags |= SPAWN_KILL;
|
||||
@@ -166,13 +153,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
free(cause);
|
||||
goto fail;
|
||||
}
|
||||
if (local) {
|
||||
if (active_select_window(ac, s, new_wl) == 0)
|
||||
cmd_find_from_winlink(current, new_wl, 0);
|
||||
if (ac != NULL && ac->session == s)
|
||||
server_redraw_client(ac);
|
||||
server_status_session_group(s);
|
||||
} else if (!detached || new_wl == s->curw) {
|
||||
if (!args_has(args, 'd') || new_wl == s->curw) {
|
||||
cmd_find_from_winlink(current, new_wl, 0);
|
||||
server_redraw_session_group(s);
|
||||
} else
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-parse.y,v 1.59 2026/08/31 07:51:56 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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'c'))
|
||||
tc->pan_window = NULL;
|
||||
else {
|
||||
w = active_get_effective_window(tc, tc->session);
|
||||
w = tc->session->curw->window;
|
||||
if (tc->pan_window != w) {
|
||||
tc->pan_window = w;
|
||||
tc->pan_ox = tty->oox;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.68 2026/08/31 07:44:39 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-resize-pane.c,v 1.67 2026/07/10 13:38:45 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -92,7 +92,6 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
server_unzoom_window(w);
|
||||
lc = wp->layout_cell; /* may have been replaced by unzoom */
|
||||
|
||||
if (args_has(args, 'x')) {
|
||||
x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);
|
||||
@@ -238,6 +237,7 @@ 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) {
|
||||
@@ -248,6 +248,10 @@ 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) &&
|
||||
@@ -347,7 +351,8 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
}
|
||||
if (resizes != 0) {
|
||||
layout_fix_panes(w, NULL);
|
||||
server_redraw_window(w);
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.94 2026/08/25 06:04:33 nicm Exp $ */
|
||||
/* $OpenBSD: cmd-run-shell.c,v 1.93 2026/07/17 12:42:51 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
|
||||
@@ -79,10 +79,7 @@ static void
|
||||
cmd_run_shell_print(struct job *job, const char *msg)
|
||||
{
|
||||
struct cmd_run_shell_data *cdata = job_get_data(job);
|
||||
struct client *c;
|
||||
struct session *s;
|
||||
struct window_pane *wp = NULL;
|
||||
struct window *w;
|
||||
struct cmd_find_state fs;
|
||||
struct window_mode_entry *wme;
|
||||
|
||||
@@ -93,12 +90,8 @@ cmd_run_shell_print(struct job *job, const char *msg)
|
||||
cmdq_print(cdata->item, "%s", msg);
|
||||
return;
|
||||
}
|
||||
c = cdata->client;
|
||||
if (c != NULL && c->session != NULL) {
|
||||
s = c->session;
|
||||
w = active_get_effective_window(c, s);
|
||||
wp = w->active;
|
||||
}
|
||||
if (cdata->client != NULL && cdata->client->session != NULL)
|
||||
wp = cdata->client->session->curw->window->active;
|
||||
if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0)
|
||||
wp = fs.wp;
|
||||
if (wp == NULL)
|
||||
@@ -240,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)
|
||||
|
||||
@@ -68,11 +68,10 @@ cmd_select_pane_redraw(struct window *w)
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (c->session == NULL || (c->flags & CLIENT_CONTROL))
|
||||
continue;
|
||||
if (active_get_effective_window(c, c->session) == w &&
|
||||
tty_window_bigger(&c->tty))
|
||||
if (c->session->curw->window == w && tty_window_bigger(&c->tty))
|
||||
server_redraw_client(c);
|
||||
else {
|
||||
if (active_get_effective_window(c, c->session) == w)
|
||||
if (c->session->curw->window == w)
|
||||
c->flags |= CLIENT_REDRAWBORDERS;
|
||||
if (session_has(c->session, w))
|
||||
c->flags |= CLIENT_REDRAWSTATUS;
|
||||
|
||||
@@ -33,8 +33,8 @@ const struct cmd_entry cmd_select_window_entry = {
|
||||
.name = "select-window",
|
||||
.alias = "selectw",
|
||||
|
||||
.args = { "LlSnpTt:", 0, 0, NULL },
|
||||
.usage = "[-LlnpST] " CMD_TARGET_WINDOW_USAGE,
|
||||
.args = { "lnpTt:", 0, 0, NULL },
|
||||
.usage = "[-lnpT] " CMD_TARGET_WINDOW_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_WINDOW, 0 },
|
||||
|
||||
@@ -88,9 +88,8 @@ cmd_select_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct cmd_find_state *current = cmdq_get_current(item);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct winlink *wl = target->wl, *current_wl;
|
||||
struct winlink *wl = target->wl;
|
||||
struct session *s = target->s;
|
||||
enum active_window_mode mode;
|
||||
int next, previous, last, activity;
|
||||
|
||||
next = (cmd_get_entry(self) == &cmd_next_window_entry);
|
||||
@@ -103,104 +102,48 @@ cmd_select_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (args_has(args, 'l'))
|
||||
last = 1;
|
||||
|
||||
if (args_has(args, 'L') || args_has(args, 'S')) {
|
||||
if (c == NULL || c->session == NULL) {
|
||||
cmdq_error(item, "no current client");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (args_has(args, 'S'))
|
||||
mode = ACTIVE_SHARED;
|
||||
else
|
||||
mode = ACTIVE_LOCAL;
|
||||
active_set_local_window(c, s, mode);
|
||||
if (!next &&
|
||||
!previous &&
|
||||
!last &&
|
||||
!args_has(args, 't') &&
|
||||
!args_has(args, 'T')) {
|
||||
if (current->s == s) {
|
||||
if (c->session == s)
|
||||
cmd_find_from_client(current, c, 0);
|
||||
else
|
||||
cmd_find_from_session(current, s, 0);
|
||||
}
|
||||
if (c->session == s)
|
||||
server_redraw_client(c);
|
||||
else
|
||||
server_redraw_session(s);
|
||||
cmdq_insert_hook(s, item, current, "after-select-window");
|
||||
recalculate_sizes();
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (active_is_local_window(c, s))
|
||||
mode = ACTIVE_LOCAL;
|
||||
else
|
||||
mode = ACTIVE_SHARED;
|
||||
if (next || previous || last) {
|
||||
activity = args_has(args, 'a');
|
||||
if (next) {
|
||||
if (active_next_window(c, s, activity) != 0) {
|
||||
if (session_next(s, activity) != 0) {
|
||||
cmdq_error(item, "no next window");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
} else if (previous) {
|
||||
if (active_previous_window(c, s, activity) != 0) {
|
||||
if (session_previous(s, activity) != 0) {
|
||||
cmdq_error(item, "no previous window");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
} else {
|
||||
if (active_last_window(c, s) != 0) {
|
||||
if (session_last(s) != 0) {
|
||||
cmdq_error(item, "no last window");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
}
|
||||
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
|
||||
cmd_find_from_client(current, c, 0);
|
||||
else
|
||||
cmd_find_from_session(current, s, 0);
|
||||
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
|
||||
server_redraw_client(c);
|
||||
else
|
||||
server_redraw_session(s);
|
||||
cmd_find_from_session(current, s, 0);
|
||||
server_redraw_session(s);
|
||||
cmdq_insert_hook(s, item, current, "after-select-window");
|
||||
} else {
|
||||
/*
|
||||
* If -T and select-window is invoked on same window as
|
||||
* current, switch to previous window.
|
||||
*/
|
||||
current_wl = active_get_effective_winlink(c, s);
|
||||
if (args_has(args, 'T') && wl == current_wl) {
|
||||
if (active_last_window(c, s) != 0) {
|
||||
if (args_has(args, 'T') && wl == s->curw) {
|
||||
if (session_last(s) != 0) {
|
||||
cmdq_error(item, "no last window");
|
||||
return (-1);
|
||||
}
|
||||
if (current->s == s) {
|
||||
if (mode == ACTIVE_LOCAL && c != NULL &&
|
||||
c->session == s)
|
||||
cmd_find_from_client(current, c, 0);
|
||||
else
|
||||
cmd_find_from_session(current, s, 0);
|
||||
}
|
||||
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
|
||||
server_redraw_client(c);
|
||||
else
|
||||
server_redraw_session(s);
|
||||
} else if (active_select_window(c, s, wl) == 0) {
|
||||
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
|
||||
cmd_find_from_client(current, c, 0);
|
||||
else
|
||||
if (current->s == s)
|
||||
cmd_find_from_session(current, s, 0);
|
||||
if (mode == ACTIVE_LOCAL && c != NULL && c->session == s)
|
||||
server_redraw_client(c);
|
||||
else
|
||||
server_redraw_session(s);
|
||||
server_redraw_session(s);
|
||||
} else if (session_select(s, wl->idx) == 0) {
|
||||
cmd_find_from_session(current, s, 0);
|
||||
server_redraw_session(s);
|
||||
}
|
||||
cmdq_insert_hook(s, item, current, "after-select-window");
|
||||
}
|
||||
if (c != NULL && c->session != NULL)
|
||||
active_get_effective_window(c, s)->latest = c;
|
||||
s->curw->window->latest = c;
|
||||
recalculate_sizes();
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
@@ -360,6 +360,7 @@ 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;
|
||||
@@ -415,8 +416,15 @@ 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);
|
||||
server_redraw_window(w);
|
||||
|
||||
window_pane_redraw_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
|
||||
@@ -48,14 +48,10 @@ cmd_swap_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *source = cmdq_get_source(item);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct client *ac;
|
||||
struct session *src = source->s, *dst = target->s;
|
||||
struct session_group *sg_src, *sg_dst;
|
||||
struct winlink *wl_src = source->wl, *wl_dst = target->wl;
|
||||
struct window *w_src, *w_dst;
|
||||
int local_src, local_dst;
|
||||
|
||||
sg_src = session_group_contains(src);
|
||||
sg_dst = session_group_contains(dst);
|
||||
@@ -83,22 +79,10 @@ cmd_swap_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
if (marked_pane.wl == wl_src)
|
||||
marked_pane.wl = wl_dst;
|
||||
ac = tc;
|
||||
if (ac == NULL)
|
||||
ac = c;
|
||||
local_dst = active_is_local_window(ac, dst);
|
||||
local_src = active_is_local_window(ac, src);
|
||||
if (args_has(args, 'd')) {
|
||||
if (local_dst)
|
||||
active_select_window(ac, dst, wl_dst);
|
||||
else
|
||||
session_select(dst, wl_dst->idx);
|
||||
if (src != dst) {
|
||||
if (local_src)
|
||||
active_select_window(ac, src, wl_src);
|
||||
else
|
||||
session_select(src, wl_src->idx);
|
||||
}
|
||||
session_select(dst, wl_dst->idx);
|
||||
if (src != dst)
|
||||
session_select(src, wl_src->idx);
|
||||
}
|
||||
session_group_synchronize_from(src);
|
||||
server_redraw_session_group(src);
|
||||
|
||||
@@ -53,12 +53,11 @@ 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, window_target;
|
||||
int Zflag = args_has(args, 'Z');
|
||||
int flags, visible, Zflag = args_has(args, 'Z');
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct client *tc = cmdq_get_target_client(item);
|
||||
struct session *s;
|
||||
struct winlink *wl, *current_wl;
|
||||
struct winlink *wl;
|
||||
struct window *w;
|
||||
struct window_pane *wp;
|
||||
const char *tablename;
|
||||
@@ -66,13 +65,8 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct sort_criteria sort_crit;
|
||||
uid_t uid;
|
||||
|
||||
window_target = 0;
|
||||
if (tflag != NULL) {
|
||||
if (tflag[strcspn(tflag, ":.%")] != '\0' ||
|
||||
strcmp(tflag, "=") == 0)
|
||||
window_target = 1;
|
||||
}
|
||||
if (window_target) {
|
||||
if (tflag != NULL &&
|
||||
(tflag[strcspn(tflag, ":.%")] != '\0' || strcmp(tflag, "=") == 0)) {
|
||||
type = CMD_FIND_PANE;
|
||||
flags = 0;
|
||||
} else {
|
||||
@@ -143,8 +137,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
} else {
|
||||
if (cmdq_get_client(item) == NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (window_target && wl != NULL && wp != NULL &&
|
||||
wp != wl->window->active) {
|
||||
if (wl != NULL && wp != NULL && wp != wl->window->active) {
|
||||
w = wl->window;
|
||||
if (w->modal != NULL && wp != w->modal)
|
||||
visible = 1;
|
||||
@@ -157,13 +150,9 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (!visible && window_pop_zoom(w))
|
||||
server_redraw_window(w);
|
||||
}
|
||||
if (window_target && wl != NULL) {
|
||||
active_select_window(tc, s, wl);
|
||||
if (active_is_local_window(tc, s)) {
|
||||
current_wl = active_get_effective_winlink(tc, s);
|
||||
cmd_find_from_winlink(current, current_wl, 0);
|
||||
} else
|
||||
cmd_find_from_session(current, s, 0);
|
||||
if (wl != NULL) {
|
||||
session_set_current(s, wl);
|
||||
cmd_find_from_session(current, s, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
configure.ac
34
configure.ac
@@ -530,15 +530,35 @@ 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 Kitty/SIXEL/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)
|
||||
AC_MSG_WARN([--enable-sixel is deprecated, use --enable-images instead to enable.])
|
||||
enable_images=yes
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes])
|
||||
if test "x$enable_images" = xyes; then
|
||||
PKG_CHECK_MODULES(
|
||||
IMAGE,
|
||||
[libpng >= 1.6 zlib],
|
||||
[
|
||||
AM_CPPFLAGS="$IMAGE_CFLAGS $AM_CPPFLAGS"
|
||||
CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
|
||||
LIBS="$IMAGE_LIBS $LIBS"
|
||||
],
|
||||
AC_MSG_ERROR("image support requires libpng 1.6 and zlib")
|
||||
)
|
||||
AC_DEFINE(ENABLE_IMAGES)
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_IMAGES, [test "x$enable_images" = xyes])
|
||||
|
||||
# Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
|
||||
AC_MSG_CHECKING(for b64_ntop)
|
||||
@@ -1137,10 +1157,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])
|
||||
|
||||
@@ -222,28 +222,6 @@ control_client_session_changed_cb(__unused const char *name,
|
||||
}
|
||||
}
|
||||
|
||||
/* Notify control clients that a client changed its effective window. */
|
||||
static void
|
||||
control_client_window_changed_cb(__unused const char *name,
|
||||
struct event_payload *ep, __unused void *sink_data)
|
||||
{
|
||||
struct client *cc = event_payload_get_client(ep, "client");
|
||||
struct session *s = event_payload_get_session(ep, "session");
|
||||
struct window *w = event_payload_get_window(ep, "window");
|
||||
struct client *c;
|
||||
|
||||
if (cc == NULL || s == NULL || w == NULL)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
control_write(c, "%%client-window-changed %s $%u @%u",
|
||||
cc->name, s->id, w->id);
|
||||
}
|
||||
}
|
||||
|
||||
/* Notify control clients that a client detached. */
|
||||
static void
|
||||
control_client_detached_cb(__unused const char *name, struct event_payload *ep,
|
||||
@@ -381,7 +359,6 @@ control_build_events(void)
|
||||
{ "window-linked", control_window_linked_cb },
|
||||
{ "window-renamed", control_window_renamed_cb },
|
||||
{ "client-session-changed", control_client_session_changed_cb },
|
||||
{ "client-window-changed", control_client_window_changed_cb },
|
||||
{ "client-detached", control_client_detached_cb },
|
||||
{ "session-renamed", control_session_renamed_cb },
|
||||
{ "session-created", control_session_created_cb },
|
||||
|
||||
161
format.c
161
format.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: format.c,v 1.415 2026/08/31 19:34:09 nicm Exp $ */
|
||||
/* $OpenBSD: format.c,v 1.413 2026/08/24 07:26:43 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -806,7 +806,7 @@ format_cb_window_active_clients(struct format_tree *ft)
|
||||
if (client_session == NULL)
|
||||
continue;
|
||||
|
||||
if (w == active_get_effective_window(loop, client_session))
|
||||
if (w == client_session->curw->window)
|
||||
n++;
|
||||
}
|
||||
|
||||
@@ -838,7 +838,7 @@ format_cb_window_active_clients_list(struct format_tree *ft)
|
||||
if (client_session == NULL)
|
||||
continue;
|
||||
|
||||
if (w == active_get_effective_window(loop, client_session)) {
|
||||
if (w == client_session->curw->window) {
|
||||
if (EVBUFFER_LENGTH(buffer) > 0)
|
||||
evbuffer_add(buffer, ",", 1);
|
||||
evbuffer_add_printf(buffer, "%s", loop->name);
|
||||
@@ -1887,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)
|
||||
@@ -2307,19 +2276,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)
|
||||
@@ -3062,52 +3018,26 @@ 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)
|
||||
{
|
||||
struct winlink *wl;
|
||||
|
||||
if (ft->s != NULL) {
|
||||
wl = active_get_effective_winlink(ft->c, ft->s);
|
||||
if (wl != NULL)
|
||||
return (format_printf("%u", wl->idx));
|
||||
}
|
||||
if (ft->s != NULL)
|
||||
return (format_printf("%u", ft->s->curw->idx));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for active_window_id. */
|
||||
static void *
|
||||
format_cb_active_window_id(struct format_tree *ft)
|
||||
{
|
||||
struct winlink *wl;
|
||||
|
||||
if (ft->s != NULL) {
|
||||
wl = active_get_effective_winlink(ft->c, ft->s);
|
||||
if (wl != NULL)
|
||||
return (format_printf("@%u", wl->window->id));
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for local_active_window. */
|
||||
static void *
|
||||
format_cb_local_active_window(struct format_tree *ft)
|
||||
{
|
||||
if (ft->c != NULL && ft->s != NULL)
|
||||
return (format_printf("%d", active_is_local_window(ft->c, ft->s)));
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
|
||||
/* Callback for active_window_mode. */
|
||||
static void *
|
||||
format_cb_active_window_mode(struct format_tree *ft)
|
||||
{
|
||||
if (ft->c != NULL && ft->s != NULL && active_is_local_window(ft->c, ft->s))
|
||||
return (xstrdup("local"));
|
||||
return (xstrdup("shared"));
|
||||
}
|
||||
|
||||
/* Callback for last_window_index. */
|
||||
static void *
|
||||
format_cb_last_window_index(struct format_tree *ft)
|
||||
@@ -3125,11 +3055,8 @@ format_cb_last_window_index(struct format_tree *ft)
|
||||
static void *
|
||||
format_cb_window_active(struct format_tree *ft)
|
||||
{
|
||||
struct winlink *wl;
|
||||
|
||||
if (ft->wl != NULL) {
|
||||
wl = active_get_effective_winlink(ft->c, ft->wl->session);
|
||||
if (ft->wl == wl)
|
||||
if (ft->wl == ft->wl->session->curw)
|
||||
return (xstrdup("1"));
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
@@ -3209,7 +3136,7 @@ static void *
|
||||
format_cb_window_flags(struct format_tree *ft)
|
||||
{
|
||||
if (ft->wl != NULL)
|
||||
return (xstrdup(window_printable_flags(ft->c, ft->wl, 1)));
|
||||
return (xstrdup(window_printable_flags(ft->wl, 1)));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
@@ -3396,7 +3323,7 @@ static void *
|
||||
format_cb_window_raw_flags(struct format_tree *ft)
|
||||
{
|
||||
if (ft->wl != NULL)
|
||||
return (xstrdup(window_printable_flags(ft->c, ft->wl, 0)));
|
||||
return (xstrdup(window_printable_flags(ft->wl, 0)));
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
@@ -3606,15 +3533,9 @@ struct format_table_entry {
|
||||
* Only variables which are added by the caller go into the tree.
|
||||
*/
|
||||
static const struct format_table_entry format_table[] = {
|
||||
{ "active_window_id", FORMAT_TABLE_STRING,
|
||||
format_cb_active_window_id
|
||||
},
|
||||
{ "active_window_index", FORMAT_TABLE_STRING,
|
||||
format_cb_active_window_index
|
||||
},
|
||||
{ "active_window_mode", FORMAT_TABLE_STRING,
|
||||
format_cb_active_window_mode
|
||||
},
|
||||
{ "alternate_on", FORMAT_TABLE_STRING,
|
||||
format_cb_alternate_on
|
||||
},
|
||||
@@ -3753,21 +3674,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
|
||||
},
|
||||
@@ -3780,6 +3692,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
|
||||
},
|
||||
@@ -3792,9 +3707,6 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "last_window_index", FORMAT_TABLE_STRING,
|
||||
format_cb_last_window_index
|
||||
},
|
||||
{ "local_active_window", FORMAT_TABLE_STRING,
|
||||
format_cb_local_active_window
|
||||
},
|
||||
{ "mouse_all_flag", FORMAT_TABLE_STRING,
|
||||
format_cb_mouse_all_flag
|
||||
},
|
||||
@@ -3951,9 +3863,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
|
||||
},
|
||||
@@ -5344,18 +5253,15 @@ format_add_window_neighbour(struct format_tree *nft, struct winlink *wl,
|
||||
struct session *s, const char *prefix)
|
||||
{
|
||||
struct options_entry *o;
|
||||
struct winlink *current;
|
||||
const char *oname;
|
||||
char *key, *prefixed, *oval;
|
||||
|
||||
current = active_get_effective_winlink(nft->c, s);
|
||||
|
||||
xasprintf(&key, "%s_window_index", prefix);
|
||||
format_add(nft, key, "%u", wl->idx);
|
||||
free(key);
|
||||
|
||||
xasprintf(&key, "%s_window_active", prefix);
|
||||
format_add(nft, key, "%d", wl == current);
|
||||
format_add(nft, key, "%d", wl == s->curw);
|
||||
free(key);
|
||||
|
||||
o = options_first(wl->window->options);
|
||||
@@ -5383,10 +5289,10 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
struct cmdq_item *item = ft->item;
|
||||
struct format_tree *nft;
|
||||
struct format_expand_state next;
|
||||
char *all, *active_fmt, *use, *expanded, *value;
|
||||
char *all, *active, *use, *expanded, *value;
|
||||
struct evbuffer *buffer;
|
||||
size_t size;
|
||||
struct winlink *wl, **l, *active_wl;
|
||||
struct winlink *wl, **l;
|
||||
struct window *w;
|
||||
int i, n;
|
||||
|
||||
@@ -5395,9 +5301,9 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
if (format_choose(es, fmt, &all, &active_fmt, 0) != 0) {
|
||||
if (format_choose(es, fmt, &all, &active, 0) != 0) {
|
||||
all = xstrdup(fmt);
|
||||
active_fmt = NULL;
|
||||
active = NULL;
|
||||
}
|
||||
|
||||
buffer = evbuffer_new();
|
||||
@@ -5405,13 +5311,12 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
fatalx("out of memory");
|
||||
|
||||
l = sort_get_winlinks_session(s, &n, sc);
|
||||
active_wl = active_get_effective_winlink(ft->c, s);
|
||||
for (i = 0; i < n; i++) {
|
||||
wl = l[i];
|
||||
w = wl->window;
|
||||
format_log(es, "window loop: %u @%u", wl->idx, w->id);
|
||||
if (active_fmt != NULL && wl == active_wl)
|
||||
use = active_fmt;
|
||||
if (active != NULL && wl == s->curw)
|
||||
use = active;
|
||||
else
|
||||
use = all;
|
||||
nft = format_create(c, item, FORMAT_WINDOW|w->id, ft->flags);
|
||||
@@ -5420,11 +5325,11 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
format_add(nft, "loop_last_flag", "%d", i == n - 1);
|
||||
|
||||
/* Add neighbour window data to the format tree. */
|
||||
if (i > 0 && l[i - 1] == active_wl)
|
||||
if (i > 0 && l[i - 1] == s->curw)
|
||||
format_add(nft, "window_after_active", "1");
|
||||
else
|
||||
format_add(nft, "window_after_active", "0");
|
||||
if (i + 1 < n && l[i + 1] == active_wl)
|
||||
if (i + 1 < n && l[i + 1] == s->curw)
|
||||
format_add(nft, "window_before_active", "1");
|
||||
else
|
||||
format_add(nft, "window_before_active", "0");
|
||||
@@ -5444,7 +5349,7 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
free(active_fmt);
|
||||
free(active);
|
||||
free(all);
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
@@ -7032,7 +6937,7 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s,
|
||||
if (s == NULL && c != NULL)
|
||||
s = c->session;
|
||||
if (wl == NULL && s != NULL)
|
||||
wl = active_get_effective_winlink(c, s);
|
||||
wl = s->curw;
|
||||
if (wp == NULL && wl != NULL)
|
||||
wp = wl->window->active;
|
||||
|
||||
|
||||
120
grid.c
120
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.156 2026/08/03 12:58:53 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -86,6 +86,9 @@ grid_check_is_clear(struct grid *gd)
|
||||
assert(gl->extdsize == 0);
|
||||
assert(gl->flags == 0);
|
||||
assert(gl->time == 0);
|
||||
#ifdef ENABLE_IMAGES
|
||||
assert(gl->images == NULL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -353,6 +356,9 @@ grid_free_line(struct grid *gd, u_int py)
|
||||
assert(gl->cellsize == 0 || gl->celldata != NULL);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_free_line(gd, gl);
|
||||
#endif
|
||||
free(gl->celldata);
|
||||
free(gl->extddata);
|
||||
memset(gl, 0, sizeof *gl);
|
||||
@@ -397,6 +403,9 @@ void
|
||||
grid_destroy(struct grid *gd)
|
||||
{
|
||||
grid_free_lines(gd, 0, gd->hsize + gd->sy);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_free(gd);
|
||||
#endif
|
||||
free(gd->linedata);
|
||||
free(gd);
|
||||
}
|
||||
@@ -729,6 +738,9 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
|
||||
|
||||
if (nx == 0 || ny == 0)
|
||||
return;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_damage(gd, px, py, nx, ny);
|
||||
#endif
|
||||
|
||||
if (px == 0 && nx == gd->sx) {
|
||||
grid_clear_lines(gd, py, ny, bg);
|
||||
@@ -764,6 +776,10 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
|
||||
void
|
||||
grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
|
||||
{
|
||||
struct grid_line *gl;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image_line *images;
|
||||
#endif
|
||||
u_int yy;
|
||||
|
||||
if (ny == 0)
|
||||
@@ -775,8 +791,18 @@ grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
|
||||
return;
|
||||
|
||||
for (yy = py; yy < py + ny; yy++) {
|
||||
grid_free_line(gd, yy);
|
||||
gl = &gd->linedata[yy];
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_damage(gd, 0, yy, gd->sx, 1);
|
||||
images = gl->images;
|
||||
#endif
|
||||
free(gl->celldata);
|
||||
free(gl->extddata);
|
||||
memset(gl, 0, sizeof *gl);
|
||||
grid_empty_line(gd, yy, bg);
|
||||
#ifdef ENABLE_IMAGES
|
||||
gl->images = images;
|
||||
#endif
|
||||
}
|
||||
if (py != 0)
|
||||
gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
|
||||
@@ -841,6 +867,9 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
|
||||
|
||||
grid_expand_line(gd, py, px + nx, 8);
|
||||
grid_expand_line(gd, py, dx + nx, 8);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_move_cells(gd, dx, px, py, nx);
|
||||
#endif
|
||||
memmove(&gl->celldata[dx], &gl->celldata[px],
|
||||
nx * sizeof *gl->celldata);
|
||||
if (dx + nx > gl->cellused)
|
||||
@@ -1213,7 +1242,6 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx,
|
||||
grid_get_cell(gd, xx, py, &gc);
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
continue;
|
||||
|
||||
if (lastgc != NULL && (flags & GRID_STRING_WITH_SEQUENCES)) {
|
||||
grid_string_cells_code(*lastgc, &gc, code, sizeof code,
|
||||
flags, s, &has_link);
|
||||
@@ -1280,11 +1308,18 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
{
|
||||
struct grid_line *dstl, *srcl;
|
||||
u_int yy;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int original_dy, original_sy;
|
||||
#endif
|
||||
|
||||
if (dy + ny > dst->hsize + dst->sy)
|
||||
ny = dst->hsize + dst->sy - dy;
|
||||
if (sy + ny > src->hsize + src->sy)
|
||||
ny = src->hsize + src->sy - sy;
|
||||
#ifdef ENABLE_IMAGES
|
||||
original_dy = dy;
|
||||
original_sy = sy;
|
||||
#endif
|
||||
grid_free_lines(dst, dy, ny);
|
||||
|
||||
for (yy = 0; yy < ny; yy++) {
|
||||
@@ -1292,6 +1327,9 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
dstl = &dst->linedata[dy];
|
||||
|
||||
memcpy(dstl, srcl, sizeof *dstl);
|
||||
#ifdef ENABLE_IMAGES
|
||||
dstl->images = NULL;
|
||||
#endif
|
||||
if (srcl->cellsize != 0) {
|
||||
dstl->celldata = xreallocarray(NULL,
|
||||
srcl->cellsize, sizeof *dstl->celldata);
|
||||
@@ -1311,6 +1349,9 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
sy++;
|
||||
dy++;
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_duplicate_lines(dst, original_dy, src, original_sy, ny);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Mark line as dead. */
|
||||
@@ -1321,6 +1362,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;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_grid_line_has_images(gl))
|
||||
return (1);
|
||||
#endif
|
||||
if (~gl->flags & GRID_LINE_EXTENDED)
|
||||
return (0);
|
||||
for (i = 0; i < gl->cellused; i++) {
|
||||
grid_get_cell1(gl, i, &gc);
|
||||
/* Kitty Unicode placeholder base character (U+10EEEE). */
|
||||
if (gc.data.size >= 4 && gc.data.data[0] == 0xf4 &&
|
||||
gc.data.data[1] == 0x8e && gc.data.data[2] == 0xbb &&
|
||||
gc.data.data[3] == 0xae)
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Add lines, return the first new one. */
|
||||
static struct grid_line *
|
||||
grid_reflow_add(struct grid *gd, u_int n)
|
||||
@@ -1383,6 +1448,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 +1512,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]);
|
||||
}
|
||||
|
||||
@@ -1546,6 +1614,12 @@ grid_reflow(struct grid *gd, u_int sx)
|
||||
if (gl->flags & GRID_LINE_DEAD)
|
||||
continue;
|
||||
|
||||
/* Keep image layers at their original cell coordinates. */
|
||||
if (grid_reflow_has_image(gl)) {
|
||||
grid_reflow_move(target, gl);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Work out the width of this line. at is the point at which
|
||||
* the available width is hit, and width is the full line
|
||||
@@ -1635,7 +1709,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 +1723,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 +1785,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));
|
||||
}
|
||||
|
||||
|
||||
464
image-fallback.c
Normal file
464
image-fallback.c
Normal file
@@ -0,0 +1,464 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* 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
|
||||
* 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 <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/* Character-cell fallback for clients without a graphical image protocol. */
|
||||
|
||||
enum image_glyph_detail {
|
||||
IMAGE_GLYPH_ASCII,
|
||||
IMAGE_GLYPH_SHADE5,
|
||||
IMAGE_GLYPH_SHADE8,
|
||||
IMAGE_GLYPH_HALF,
|
||||
IMAGE_GLYPH_QUADRANT,
|
||||
IMAGE_GLYPH_SEXTANT
|
||||
};
|
||||
|
||||
enum image_glyph_palette {
|
||||
IMAGE_GLYPH_PALETTE_8,
|
||||
IMAGE_GLYPH_PALETTE_16,
|
||||
IMAGE_GLYPH_PALETTE_256,
|
||||
IMAGE_GLYPH_PALETTE_RGB
|
||||
};
|
||||
|
||||
struct image_rgb {
|
||||
u_char r;
|
||||
u_char g;
|
||||
u_char b;
|
||||
};
|
||||
|
||||
struct image_glyph_data {
|
||||
u_char *shade5;
|
||||
u_char *shade8;
|
||||
};
|
||||
|
||||
static const struct image_rgb image_ansi_colours[16] = {
|
||||
{ 0x00, 0x00, 0x00 }, { 0x80, 0x00, 0x00 },
|
||||
{ 0x00, 0x80, 0x00 }, { 0x80, 0x80, 0x00 },
|
||||
{ 0x00, 0x00, 0x80 }, { 0x80, 0x00, 0x80 },
|
||||
{ 0x00, 0x80, 0x80 }, { 0xc0, 0xc0, 0xc0 },
|
||||
{ 0x80, 0x80, 0x80 }, { 0xff, 0x00, 0x00 },
|
||||
{ 0x00, 0xff, 0x00 }, { 0xff, 0xff, 0x00 },
|
||||
{ 0x00, 0x00, 0xff }, { 0xff, 0x00, 0xff },
|
||||
{ 0x00, 0xff, 0xff }, { 0xff, 0xff, 0xff }
|
||||
};
|
||||
|
||||
static u_int
|
||||
image_glyph_distance(struct image_rgb a, struct image_rgb b)
|
||||
{
|
||||
int r = a.r - b.r, g = a.g - b.g, bl = a.b - b.b;
|
||||
|
||||
return (r * r + g * g + bl * bl);
|
||||
}
|
||||
|
||||
static int
|
||||
image_glyph_low_saturation(struct image_rgb colour)
|
||||
{
|
||||
u_int minimum, maximum;
|
||||
|
||||
minimum = maximum = colour.r;
|
||||
if (colour.g < minimum)
|
||||
minimum = colour.g;
|
||||
if (colour.b < minimum)
|
||||
minimum = colour.b;
|
||||
if (colour.g > maximum)
|
||||
maximum = colour.g;
|
||||
if (colour.b > maximum)
|
||||
maximum = colour.b;
|
||||
return (maximum == 0 || (maximum - minimum) * 4 <= maximum);
|
||||
}
|
||||
|
||||
static u_int
|
||||
image_glyph_nearest_ansi(struct image_rgb colour, u_int colours)
|
||||
{
|
||||
u_int i, distance, best_distance = UINT_MAX, best = 0;
|
||||
int neutral = image_glyph_low_saturation(colour);
|
||||
|
||||
for (i = 0; i < colours; i++) {
|
||||
if (neutral && i != 0 && i != 7 &&
|
||||
(colours != 16 || (i != 8 && i != 15)))
|
||||
continue;
|
||||
distance = image_glyph_distance(colour, image_ansi_colours[i]);
|
||||
if (distance < best_distance) {
|
||||
best_distance = distance;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
return (best);
|
||||
}
|
||||
|
||||
static struct image_rgb
|
||||
image_glyph_quantize(struct image_rgb colour, enum image_glyph_palette palette,
|
||||
int *output)
|
||||
{
|
||||
struct image_rgb result;
|
||||
int value;
|
||||
u_int index;
|
||||
|
||||
if (palette == IMAGE_GLYPH_PALETTE_RGB) {
|
||||
*output = colour_join_rgb(colour.r, colour.g, colour.b);
|
||||
return (colour);
|
||||
}
|
||||
if (palette == IMAGE_GLYPH_PALETTE_256) {
|
||||
*output = colour_find_rgb(colour.r, colour.g, colour.b);
|
||||
value = colour_256toRGB(*output);
|
||||
colour_split_rgb(value, &result.r, &result.g, &result.b);
|
||||
return (result);
|
||||
}
|
||||
index = image_glyph_nearest_ansi(colour,
|
||||
palette == IMAGE_GLYPH_PALETTE_8 ? 8 : 16);
|
||||
result = image_ansi_colours[index];
|
||||
if (index < 8)
|
||||
*output = index;
|
||||
else
|
||||
*output = 90 + index - 8;
|
||||
return (result);
|
||||
}
|
||||
|
||||
static void
|
||||
image_glyph_fit_colours(const struct image_rgb *samples, u_int count,
|
||||
struct image_rgb centres[2])
|
||||
{
|
||||
u_int sum[2][3], counts[2], group, i, j, iteration, distance;
|
||||
u_int maximum = 0, first = 0, second = 0;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
for (j = i + 1; j < count; j++) {
|
||||
distance = image_glyph_distance(samples[i], samples[j]);
|
||||
if (distance > maximum) {
|
||||
maximum = distance;
|
||||
first = i;
|
||||
second = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
centres[0] = samples[first];
|
||||
centres[1] = samples[second];
|
||||
for (iteration = 0; iteration < 4; iteration++) {
|
||||
memset(sum, 0, sizeof sum);
|
||||
memset(counts, 0, sizeof counts);
|
||||
for (i = 0; i < count; i++) {
|
||||
group = image_glyph_distance(samples[i], centres[1]) <
|
||||
image_glyph_distance(samples[i], centres[0]);
|
||||
sum[group][0] += samples[i].r;
|
||||
sum[group][1] += samples[i].g;
|
||||
sum[group][2] += samples[i].b;
|
||||
counts[group]++;
|
||||
}
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (counts[i] == 0)
|
||||
continue;
|
||||
centres[i].r = (sum[i][0] + counts[i] / 2) / counts[i];
|
||||
centres[i].g = (sum[i][1] + counts[i] / 2) / counts[i];
|
||||
centres[i].b = (sum[i][2] + counts[i] / 2) / counts[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
image_glyph_set_acs(struct tty *tty, struct utf8_data *data, u_char key)
|
||||
{
|
||||
struct utf8_data *ud;
|
||||
const char *s = tty_acs_get(tty, key);
|
||||
|
||||
if (s == NULL)
|
||||
return (0);
|
||||
ud = utf8_fromcstr(s);
|
||||
if (ud[0].size == 0) {
|
||||
free(ud);
|
||||
return (0);
|
||||
}
|
||||
memcpy(data, &ud[0], sizeof *data);
|
||||
free(ud);
|
||||
return (1);
|
||||
}
|
||||
|
||||
static u_char
|
||||
image_glyph_block_key(enum image_glyph_detail detail, u_int mask)
|
||||
{
|
||||
static const u_char half[4] = {
|
||||
0, TTY_ACS_IMAGE_HALF_UPPER, TTY_ACS_IMAGE_HALF_LOWER,
|
||||
TTY_ACS_IMAGE_BLOCK
|
||||
};
|
||||
static const u_char quadrant[16] = {
|
||||
0, TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT, TTY_ACS_IMAGE_HALF_UPPER,
|
||||
TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT, TTY_ACS_IMAGE_HALF_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_HALF_RIGHT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_HALF_LOWER,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_BLOCK
|
||||
};
|
||||
|
||||
if (detail == IMAGE_GLYPH_HALF)
|
||||
return (half[mask]);
|
||||
if (detail == IMAGE_GLYPH_QUADRANT)
|
||||
return (quadrant[mask]);
|
||||
if (mask == 0)
|
||||
return (0);
|
||||
if (mask == 21)
|
||||
return (TTY_ACS_IMAGE_HALF_LEFT);
|
||||
if (mask == 42)
|
||||
return (TTY_ACS_IMAGE_HALF_RIGHT);
|
||||
if (mask == 63)
|
||||
return (TTY_ACS_IMAGE_BLOCK);
|
||||
return (tty_acs_image_sextant(mask));
|
||||
}
|
||||
|
||||
static u_char *
|
||||
image_glyph_make_shades(struct image *im, u_int levels)
|
||||
{
|
||||
struct image_glyph_data *data = im->fallback_data;
|
||||
const struct image_cell *cell;
|
||||
int *values, value, represented, error;
|
||||
size_t cells, index;
|
||||
u_int x, y, scan, level, minimum = 255, maximum = 0;
|
||||
int reverse;
|
||||
u_char *result;
|
||||
|
||||
if (data == NULL) {
|
||||
data = im->fallback_data = xcalloc(1, sizeof *data);
|
||||
}
|
||||
result = (levels == 5 ? data->shade5 : data->shade8);
|
||||
if (result != NULL)
|
||||
return (result);
|
||||
cells = (size_t)im->sx * im->sy;
|
||||
values = xcalloc(cells, sizeof *values);
|
||||
result = xcalloc(cells, 1);
|
||||
for (y = 0; y < im->sy; y++) {
|
||||
for (x = 0; x < im->sx; x++) {
|
||||
index = (size_t)y * im->sx + x;
|
||||
cell = image_get_cell(im, x, y);
|
||||
value = cell->whole.brightness;
|
||||
if ((u_int)value < minimum)
|
||||
minimum = value;
|
||||
if ((u_int)value > maximum)
|
||||
maximum = value;
|
||||
values[index] = value;
|
||||
}
|
||||
}
|
||||
if (maximum > minimum) {
|
||||
for (index = 0; index < cells; index++)
|
||||
values[index] = (values[index] - minimum) * 255 /
|
||||
(maximum - minimum);
|
||||
}
|
||||
for (y = 0; y < im->sy; y++) {
|
||||
reverse = (y & 1);
|
||||
for (scan = 0; scan < im->sx; scan++) {
|
||||
x = (reverse ? im->sx - scan - 1 : scan);
|
||||
index = (size_t)y * im->sx + x;
|
||||
value = values[index];
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
if (value > 255)
|
||||
value = 255;
|
||||
level = (value * (levels - 1) + 127) / 255;
|
||||
result[index] = level;
|
||||
represented = level * 255 / (levels - 1);
|
||||
error = value - represented;
|
||||
if (!reverse && x + 1 < im->sx)
|
||||
values[index + 1] += error * 7 / 16;
|
||||
if (reverse && x != 0)
|
||||
values[index - 1] += error * 7 / 16;
|
||||
if (y + 1 == im->sy)
|
||||
continue;
|
||||
if (!reverse && x != 0)
|
||||
values[index + im->sx - 1] += error * 3 / 16;
|
||||
if (reverse && x + 1 < im->sx)
|
||||
values[index + im->sx + 1] += error * 3 / 16;
|
||||
values[index + im->sx] += error * 5 / 16;
|
||||
if (!reverse && x + 1 < im->sx)
|
||||
values[index + im->sx + 1] += error / 16;
|
||||
if (reverse && x != 0)
|
||||
values[index + im->sx - 1] += error / 16;
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
if (levels == 5)
|
||||
data->shade5 = result;
|
||||
else
|
||||
data->shade8 = result;
|
||||
return (result);
|
||||
}
|
||||
|
||||
static enum image_glyph_palette
|
||||
image_glyph_get_palette(struct tty *tty)
|
||||
{
|
||||
int colours;
|
||||
|
||||
if (tty->term->flags & TERM_RGBCOLOURS)
|
||||
return (IMAGE_GLYPH_PALETTE_RGB);
|
||||
if (tty->term->flags & TERM_256COLOURS)
|
||||
return (IMAGE_GLYPH_PALETTE_256);
|
||||
colours = tty_term_number(tty->term, TTYC_COLORS);
|
||||
if (colours >= 256)
|
||||
return (IMAGE_GLYPH_PALETTE_256);
|
||||
if (colours >= 16)
|
||||
return (IMAGE_GLYPH_PALETTE_16);
|
||||
return (IMAGE_GLYPH_PALETTE_8);
|
||||
}
|
||||
|
||||
static enum image_glyph_detail
|
||||
image_glyph_get_detail(struct tty *tty, enum image_glyph_palette palette)
|
||||
{
|
||||
if (tty_acs_needed(tty))
|
||||
return (IMAGE_GLYPH_ASCII);
|
||||
if (tty->term->flags & TERM_IMAGE_SEXTANTS)
|
||||
return (IMAGE_GLYPH_SEXTANT);
|
||||
if (tty->term->flags & TERM_IMAGE_QUADRANTS)
|
||||
return (IMAGE_GLYPH_QUADRANT);
|
||||
if (palette != IMAGE_GLYPH_PALETTE_8)
|
||||
return (IMAGE_GLYPH_HALF);
|
||||
if (tty_term_has(tty->term, TTYC_NOBR))
|
||||
return (IMAGE_GLYPH_SHADE5);
|
||||
return (IMAGE_GLYPH_SHADE8);
|
||||
}
|
||||
|
||||
static void
|
||||
image_glyph_block(struct tty *tty, struct image *im, u_int x, u_int y,
|
||||
enum image_glyph_detail detail, enum image_glyph_palette palette,
|
||||
struct grid_cell *out)
|
||||
{
|
||||
const struct image_cell *cell = image_get_cell(im, x, y);
|
||||
struct image_rgb samples[6], centres[2], quantized[2];
|
||||
u_int columns, rows, sx, sy, ix, iy, i, n, mask;
|
||||
u_int x0, x1, y0, y1, red, green, blue, count;
|
||||
int colours[2];
|
||||
u_char key;
|
||||
|
||||
columns = (detail == IMAGE_GLYPH_HALF ? 1 : 2);
|
||||
rows = (detail == IMAGE_GLYPH_HALF ? 2 :
|
||||
detail == IMAGE_GLYPH_QUADRANT ? 2 : 3);
|
||||
i = 0;
|
||||
for (sy = 0; sy < rows; sy++) {
|
||||
for (sx = 0; sx < columns; sx++) {
|
||||
x0 = sx * IMAGE_SAMPLE_COLUMNS / columns;
|
||||
x1 = (sx + 1) * IMAGE_SAMPLE_COLUMNS / columns;
|
||||
y0 = sy * IMAGE_SAMPLE_ROWS / rows;
|
||||
y1 = (sy + 1) * IMAGE_SAMPLE_ROWS / rows;
|
||||
red = green = blue = count = 0;
|
||||
for (iy = y0; iy < y1; iy++) {
|
||||
for (ix = x0; ix < x1; ix++) {
|
||||
red += cell->samples[iy][ix].red;
|
||||
green += cell->samples[iy][ix].green;
|
||||
blue += cell->samples[iy][ix].blue;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
samples[i].r = red / count;
|
||||
samples[i].g = green / count;
|
||||
samples[i].b = blue / count;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
n = columns * rows;
|
||||
image_glyph_fit_colours(samples, n, centres);
|
||||
quantized[0] = image_glyph_quantize(centres[0], palette, &colours[0]);
|
||||
quantized[1] = image_glyph_quantize(centres[1], palette, &colours[1]);
|
||||
mask = 0;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (image_glyph_distance(samples[i], quantized[1]) <
|
||||
image_glyph_distance(samples[i], quantized[0]))
|
||||
mask |= (1U << i);
|
||||
}
|
||||
key = image_glyph_block_key(detail, mask);
|
||||
if (key == 0)
|
||||
utf8_set(&out->data, ' ');
|
||||
else if (!image_glyph_set_acs(tty, &out->data, key))
|
||||
utf8_set(&out->data, ' ');
|
||||
out->fg = colours[1];
|
||||
out->bg = colours[0];
|
||||
}
|
||||
|
||||
void
|
||||
image_get_fallback_cell(struct tty *tty, struct image *im, u_int x, u_int y,
|
||||
const struct grid_cell *gc, struct grid_cell *out,
|
||||
__unused const struct tty_style_ctx *style_ctx)
|
||||
{
|
||||
static const char ascii[] = " .:-=+*#%@";
|
||||
static const u_char shades[5] = { 0, TTY_ACS_IMAGE_SHADE_LIGHT,
|
||||
TTY_ACS_IMAGE_SHADE_MEDIUM, TTY_ACS_IMAGE_SHADE_DARK,
|
||||
TTY_ACS_IMAGE_BLOCK };
|
||||
static const u_char bold_shades[8] = { 0,
|
||||
TTY_ACS_IMAGE_SHADE_LIGHT, TTY_ACS_IMAGE_SHADE_LIGHT,
|
||||
TTY_ACS_IMAGE_SHADE_MEDIUM, TTY_ACS_IMAGE_SHADE_MEDIUM,
|
||||
TTY_ACS_IMAGE_SHADE_DARK, TTY_ACS_IMAGE_BLOCK,
|
||||
TTY_ACS_IMAGE_BLOCK };
|
||||
const struct image_cell *cell;
|
||||
enum image_glyph_palette palette;
|
||||
enum image_glyph_detail detail;
|
||||
u_char *levels, key;
|
||||
u_int level = 0;
|
||||
|
||||
memcpy(out, gc, sizeof *out);
|
||||
cell = image_get_cell(im, x, y);
|
||||
if (cell == NULL) {
|
||||
utf8_set(&out->data, ' ');
|
||||
return;
|
||||
}
|
||||
palette = image_glyph_get_palette(tty);
|
||||
detail = image_glyph_get_detail(tty, palette);
|
||||
if (detail == IMAGE_GLYPH_ASCII) {
|
||||
level = cell->whole.brightness * (sizeof ascii - 2) / 255;
|
||||
utf8_set(&out->data, ascii[level]);
|
||||
return;
|
||||
}
|
||||
if (detail == IMAGE_GLYPH_SHADE5 || detail == IMAGE_GLYPH_SHADE8) {
|
||||
levels = image_glyph_make_shades(im,
|
||||
detail == IMAGE_GLYPH_SHADE5 ? 5 : 8);
|
||||
level = levels[(size_t)y * im->sx + x];
|
||||
key = (detail == IMAGE_GLYPH_SHADE5 ? shades[level] :
|
||||
bold_shades[level]);
|
||||
if (key == 0)
|
||||
utf8_set(&out->data, ' ');
|
||||
else if (!image_glyph_set_acs(tty, &out->data, key))
|
||||
utf8_set(&out->data, ' ');
|
||||
out->fg = 7;
|
||||
out->bg = 0;
|
||||
out->attr &= ~GRID_ATTR_BRIGHT;
|
||||
if (detail == IMAGE_GLYPH_SHADE8 &&
|
||||
(level == 2 || level == 4 || level == 7))
|
||||
out->attr |= GRID_ATTR_BRIGHT;
|
||||
return;
|
||||
}
|
||||
image_glyph_block(tty, im, x, y, detail, palette, out);
|
||||
}
|
||||
|
||||
void
|
||||
image_free_fallback(struct image *im)
|
||||
{
|
||||
struct image_glyph_data *data = im->fallback_data;
|
||||
|
||||
if (data == NULL)
|
||||
return;
|
||||
free(data->shade5);
|
||||
free(data->shade8);
|
||||
free(data);
|
||||
}
|
||||
1308
image-kitty.c
Normal file
1308
image-kitty.c
Normal file
File diff suppressed because it is too large
Load Diff
1171
image-sixel.c
1171
image-sixel.c
File diff suppressed because it is too large
Load Diff
108
input.c
108
input.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: input.c,v 1.271 2026/08/31 19:34:09 nicm Exp $ */
|
||||
/* $OpenBSD: input.c,v 1.270 2026/08/17 20:04:00 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -145,6 +145,9 @@ struct input_ctx {
|
||||
*/
|
||||
struct evbuffer *since_ground;
|
||||
struct event ground_timer;
|
||||
#ifdef ENABLE_IMAGES
|
||||
void *kitty_state;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Helper functions. */
|
||||
@@ -183,6 +186,9 @@ static void input_enter_osc(struct input_ctx *);
|
||||
static void input_exit_osc(struct input_ctx *);
|
||||
static void input_enter_apc(struct input_ctx *);
|
||||
static void input_exit_apc(struct input_ctx *);
|
||||
#ifdef ENABLE_IMAGES
|
||||
static int input_handle_kitty(struct input_ctx *, const u_char *, size_t);
|
||||
#endif
|
||||
static void input_enter_rename(struct input_ctx *);
|
||||
static void input_exit_rename(struct input_ctx *);
|
||||
|
||||
@@ -916,6 +922,9 @@ input_free(struct input_ctx *ictx)
|
||||
event_del(&ictx->request_timer);
|
||||
|
||||
free(ictx->input_buf);
|
||||
#ifdef ENABLE_IMAGES
|
||||
kitty_free_state(ictx->kitty_state);
|
||||
#endif
|
||||
evbuffer_free(ictx->since_ground);
|
||||
event_del(&ictx->ground_timer);
|
||||
|
||||
@@ -938,6 +947,7 @@ input_reset(struct input_ctx *ictx, int clear)
|
||||
screen_write_start_pane(sctx, wp, &wp->base);
|
||||
else
|
||||
screen_write_start(sctx, &wp->base);
|
||||
sctx->flags |= SCREEN_WRITE_INPUT;
|
||||
screen_write_reset(sctx);
|
||||
screen_write_stop(sctx);
|
||||
}
|
||||
@@ -1049,7 +1059,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;
|
||||
@@ -1066,6 +1075,7 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
|
||||
screen_write_start_pane(sctx, wp, &wp->base);
|
||||
else
|
||||
screen_write_start(sctx, &wp->base);
|
||||
sctx->flags |= SCREEN_WRITE_INPUT;
|
||||
|
||||
log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id,
|
||||
ictx->state->name, len, (int)len, buf);
|
||||
@@ -1085,6 +1095,7 @@ input_parse_screen(struct input_ctx *ictx, struct screen *s,
|
||||
return;
|
||||
|
||||
screen_write_start_callback(sctx, s, cb, arg);
|
||||
sctx->flags |= SCREEN_WRITE_INPUT;
|
||||
input_parse(ictx, buf, len);
|
||||
screen_write_stop(sctx);
|
||||
}
|
||||
@@ -1584,7 +1595,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");
|
||||
@@ -2102,7 +2113,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)
|
||||
@@ -2627,10 +2638,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)
|
||||
@@ -2643,15 +2654,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);
|
||||
}
|
||||
@@ -2790,6 +2804,57 @@ input_enter_apc(struct input_ctx *ictx)
|
||||
}
|
||||
|
||||
/* APC terminator (ST) received. */
|
||||
#ifdef ENABLE_IMAGES
|
||||
static int
|
||||
input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
|
||||
{
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct window_pane *wp = ictx->wp;
|
||||
struct image *im;
|
||||
u_int image_id = 0, replace_id = 0, quiet = 0;
|
||||
u_int placement_id = 0;
|
||||
int32_t z = 0;
|
||||
char action = '\0', delete = '\0';
|
||||
int status;
|
||||
|
||||
if (wp == NULL)
|
||||
return (0);
|
||||
im = kitty_parse_image(&ictx->kitty_state, buf, len,
|
||||
wp->window->xpixel,
|
||||
wp->window->ypixel, &image_id, &replace_id, &quiet, &action,
|
||||
&delete, &placement_id, &z, &status);
|
||||
if (status == KITTY_PARSE_MORE)
|
||||
return (1);
|
||||
if (status != KITTY_PARSE_OK) {
|
||||
if (quiet < 2 && action != '\0') {
|
||||
if (status == KITTY_PARSE_MISSING)
|
||||
input_reply(ictx, 0, "\033_Gi=%u;ENOENT\033\\",
|
||||
image_id);
|
||||
else
|
||||
input_reply(ictx, 0, "\033_Gi=%u;EINVAL\033\\",
|
||||
image_id);
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
if (replace_id != 0)
|
||||
image_clear(sctx, replace_id);
|
||||
if (im != NULL) {
|
||||
if (action == 'd')
|
||||
image_clear_kitty(sctx, delete, image_id, placement_id, z);
|
||||
else
|
||||
image_write_kitty(sctx, im, ictx->cell.cell.bg, image_id,
|
||||
placement_id, z);
|
||||
image_free(image_get_id(im));
|
||||
if (quiet == 0 && image_id != 0)
|
||||
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
|
||||
} else if (action == 'd')
|
||||
image_clear_kitty(sctx, delete, image_id, placement_id, z);
|
||||
else if ((action == 't' || action == 'q' || action == 'u') && quiet == 0)
|
||||
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
|
||||
return (1);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
input_exit_apc(struct input_ctx *ictx)
|
||||
{
|
||||
@@ -2800,6 +2865,13 @@ input_exit_apc(struct input_ctx *ictx)
|
||||
return;
|
||||
log_debug("%s: \"%s\"", __func__, ictx->input_buf);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (ictx->input_len > 1 && ictx->input_buf[0] == 'G' &&
|
||||
input_handle_kitty(ictx, ictx->input_buf + 1,
|
||||
ictx->input_len - 1))
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (wp != NULL &&
|
||||
options_get_number(wp->options, "allow-set-title") &&
|
||||
screen_set_title(sctx->s, ictx->input_buf, 1)) {
|
||||
@@ -2860,6 +2932,12 @@ input_top_bit_set(struct input_ctx *ictx)
|
||||
{
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct utf8_data *ud = &ictx->utf8data;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell gc;
|
||||
struct image *im;
|
||||
u_int x, source_x, source_y, image_id, placement_id;
|
||||
int32_t z;
|
||||
#endif
|
||||
|
||||
ictx->flags &= ~INPUT_LAST;
|
||||
|
||||
@@ -2887,6 +2965,20 @@ input_top_bit_set(struct input_ctx *ictx)
|
||||
utf8_copy(&ictx->cell.cell.data, ud);
|
||||
screen_write_collect_add(sctx, &ictx->cell.cell);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (sctx->s->cx != 0) {
|
||||
x = sctx->s->cx - 1; /* cx-1 is the cell just written. */
|
||||
grid_view_get_cell(sctx->s->grid, x, sctx->s->cy, &gc);
|
||||
if (kitty_placeholder_to_image(ictx->kitty_state,
|
||||
sctx->s->grid, &gc, x, sctx->s->cy, &im, &source_x,
|
||||
&source_y, &image_id, &placement_id, &z)) {
|
||||
grid_view_set_cell(sctx->s->grid, x, sctx->s->cy, &gc);
|
||||
image_place_cell_kitty(sctx, im, x, sctx->s->cy, source_x,
|
||||
source_y, image_id, placement_id, z);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
utf8_copy(&ictx->last, &ictx->cell.cell.data);
|
||||
ictx->flags |= INPUT_LAST;
|
||||
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
" 'Rename' 'r' {command-prompt -I '#S' {rename-session -- '%%'}}" \
|
||||
" 'Detach' 'd' {detach-client}" \
|
||||
" ''" \
|
||||
" '#{?local_active_window,Shared Window,Local Window}' 'L' {select-window #{?local_active_window,-S,-L}}" \
|
||||
" ''" \
|
||||
" 'New Session' 's' {new-session}" \
|
||||
" 'New Window' 'w' {new-window}"
|
||||
#define DEFAULT_WINDOW_MENU \
|
||||
|
||||
45
layout.c
45
layout.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: layout.c,v 1.98 2026/08/25 18:38:05 nicm Exp $ */
|
||||
/* $OpenBSD: layout.c,v 1.97 2026/08/20 09:19:24 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -492,7 +492,6 @@ 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);
|
||||
@@ -500,8 +499,11 @@ 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);
|
||||
@@ -539,10 +541,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) {
|
||||
@@ -572,9 +570,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;
|
||||
@@ -668,7 +663,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;
|
||||
|
||||
@@ -702,9 +697,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);
|
||||
}
|
||||
@@ -867,7 +862,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;
|
||||
@@ -992,11 +987,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);
|
||||
@@ -1014,22 +1009,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)
|
||||
@@ -1057,13 +1052,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);
|
||||
|
||||
|
||||
2
menu.c
2
menu.c
@@ -586,7 +586,7 @@ menu_display(struct menu *menu, int flags, int starting_choice,
|
||||
struct options *o;
|
||||
|
||||
if (fs == NULL)
|
||||
w = active_get_effective_window(c, c->session);
|
||||
w = c->session->curw->window;
|
||||
else
|
||||
w = fs->w;
|
||||
o = w->options;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
|
||||
/* $OpenBSD: options-table.c,v 1.242 2026/07/27 08:03:01 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -305,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,
|
||||
@@ -551,6 +541,7 @@ const struct options_table_entry options_table[] = {
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_ARRAY,
|
||||
.default_str = "xterm*:clipboard:ccolour:cstyle:focus:title,"
|
||||
"xterm-kitty*:kitty,"
|
||||
"screen*:title,"
|
||||
"rxvt*:ignorefkeys",
|
||||
.separator = ",",
|
||||
@@ -1125,8 +1116,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "status-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.default_str = "bg=#{?local_active_window,themeblue,themegreen},"
|
||||
"fg=themeblack",
|
||||
.default_str = "bg=themegreen,fg=themeblack",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the status line."
|
||||
@@ -1251,8 +1241,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. */
|
||||
@@ -1983,8 +1972,6 @@ const struct options_table_entry options_table[] = {
|
||||
"Run when a client is resized."),
|
||||
OPTIONS_TABLE_HOOK("client-session-changed", "",
|
||||
"Run when a client's attached session is changed."),
|
||||
OPTIONS_TABLE_HOOK("client-window-changed", "",
|
||||
"Run when a client's effective window is changed."),
|
||||
OPTIONS_TABLE_HOOK("client-light-theme", "",
|
||||
"Run when a client switches to a light theme."),
|
||||
OPTIONS_TABLE_HOOK("client-dark-theme", "",
|
||||
|
||||
73
popup.c
73
popup.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: popup.c,v 1.77 2026/08/28 08:02:16 nicm Exp $ */
|
||||
/* $OpenBSD: popup.c,v 1.76 2026/07/14 19:07:03 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -79,8 +79,7 @@ popup_free(struct popup_data *pd)
|
||||
|
||||
if (pd->job != NULL)
|
||||
job_free(pd->job);
|
||||
if (pd->ictx != NULL)
|
||||
input_free(pd->ictx);
|
||||
input_free(pd->ictx);
|
||||
|
||||
free(pd->r.ranges);
|
||||
screen_free(&pd->s);
|
||||
@@ -103,9 +102,9 @@ popup_reapply_styles(struct popup_data *pd)
|
||||
|
||||
if (s == NULL)
|
||||
return;
|
||||
o = active_get_effective_window(c, s)->options;
|
||||
o = s->curw->window->options;
|
||||
|
||||
ft = format_create_defaults(NULL, c, s, NULL, NULL);
|
||||
ft = format_create_defaults(NULL, c, s, s->curw, NULL);
|
||||
|
||||
/* Reapply popup style from options. */
|
||||
memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
|
||||
@@ -136,7 +135,8 @@ popup_reapply_styles(struct popup_data *pd)
|
||||
}
|
||||
|
||||
static void
|
||||
popup_redraw_cb(const struct tty_ctx *ttyctx)
|
||||
popup_redraw_cb(const struct tty_ctx *ttyctx, __unused u_int py,
|
||||
__unused u_int ny)
|
||||
{
|
||||
struct popup_data *pd = ttyctx->arg;
|
||||
|
||||
@@ -321,11 +321,60 @@ popup_resize_cb(__unused struct client *c, void *data)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Report damage for a popup's rectangle, given in raw client/tty
|
||||
* coordinates. Status-line cells are outside the window scene - they have no
|
||||
* corresponding window content and redraw_damage_window() can't reach them -
|
||||
* so if the popup's rectangle overlaps the status line, force it to redraw
|
||||
* separately. The rest of the rectangle is clipped to the actual pane area
|
||||
* (above or below the status line, whichever side it's on) before being
|
||||
* translated into window coordinates and reported the normal way, the same
|
||||
* way mouse coordinates are translated elsewhere (e.g. cmd-join-pane.c,
|
||||
* cmd-split-window.c).
|
||||
*/
|
||||
static void
|
||||
popup_damage(struct client *c, u_int px, u_int py, u_int sx, u_int sy)
|
||||
{
|
||||
struct window *w;
|
||||
int statusat;
|
||||
u_int ox, oy, osx, osy, lines, top, bottom, y0, y1;
|
||||
|
||||
if (c->session == NULL)
|
||||
return;
|
||||
w = c->session->curw->window;
|
||||
|
||||
lines = status_line_size(c);
|
||||
statusat = status_at_line(c);
|
||||
if (statusat >= 0 && py < (u_int)statusat + lines &&
|
||||
py + sy > (u_int)statusat)
|
||||
c->flags |= (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS);
|
||||
|
||||
if (statusat == 0) {
|
||||
top = lines;
|
||||
bottom = c->tty.sy;
|
||||
} else if (statusat > 0) {
|
||||
top = 0;
|
||||
bottom = statusat;
|
||||
} else {
|
||||
top = 0;
|
||||
bottom = c->tty.sy;
|
||||
}
|
||||
y0 = (py > top) ? py : top;
|
||||
y1 = (py + sy < bottom) ? py + sy : bottom;
|
||||
if (y0 >= y1)
|
||||
return;
|
||||
|
||||
tty_window_offset(&c->tty, &ox, &oy, &osx, &osy);
|
||||
redraw_damage_window(w, px + ox, y0 - top + oy, sx, y1 - y0);
|
||||
}
|
||||
|
||||
static void
|
||||
popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||
struct mouse_event *m)
|
||||
{
|
||||
u_int px, py;
|
||||
u_int old_px = pd->px, old_py = pd->py;
|
||||
u_int old_sx = pd->sx, old_sy = pd->sy;
|
||||
|
||||
if (!MOUSE_DRAG(m->b))
|
||||
pd->dragging = OFF;
|
||||
@@ -348,7 +397,9 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||
pd->dy = m->y - pd->py;
|
||||
pd->ppx = px;
|
||||
pd->ppy = py;
|
||||
server_redraw_client(c);
|
||||
|
||||
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
||||
c->flags |= CLIENT_REDRAWOVERLAY;
|
||||
} else if (pd->dragging == SIZE) {
|
||||
if (pd->border_lines == BOX_LINES_NONE) {
|
||||
if (m->x < pd->px + 1)
|
||||
@@ -375,7 +426,9 @@ popup_handle_drag(struct client *c, struct popup_data *pd,
|
||||
if (pd->job != NULL)
|
||||
job_resize(pd->job, pd->sx - 2, pd->sy - 2);
|
||||
}
|
||||
server_redraw_client(c);
|
||||
|
||||
popup_damage(c, old_px, old_py, old_sx, old_sy);
|
||||
c->flags |= CLIENT_REDRAWOVERLAY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,9 +623,9 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
|
||||
struct style sytmp;
|
||||
|
||||
if (s != NULL)
|
||||
o = active_get_effective_window(c, s)->options;
|
||||
o = s->curw->window->options;
|
||||
else
|
||||
o = active_get_effective_window(c, c->session)->options;
|
||||
o = c->session->curw->window->options;
|
||||
|
||||
if (lines == BOX_LINES_DEFAULT)
|
||||
lines = options_get_number(o, "popup-border-lines");
|
||||
|
||||
@@ -1,151 +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)
|
||||
IN1="$TMPDIR/in1"
|
||||
IN2="$TMPDIR/in2"
|
||||
OUT1="$TMPDIR/out1"
|
||||
OUT2="$TMPDIR/out2"
|
||||
PID1=
|
||||
PID2=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$PID1" ] && kill "$PID1" 2>/dev/null
|
||||
[ -n "$PID2" ] && kill "$PID2" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
echo "client1:"
|
||||
cat "$OUT1" 2>/dev/null
|
||||
echo "client2:"
|
||||
cat "$OUT2" 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
wait_for()
|
||||
{
|
||||
file=$1
|
||||
pattern=$2
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt 60 ]; do
|
||||
if grep -F -- "$pattern" "$file" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "missing '$pattern' in $file"
|
||||
}
|
||||
|
||||
send1()
|
||||
{
|
||||
printf '%s\n' "$*" >&3
|
||||
}
|
||||
|
||||
send2()
|
||||
{
|
||||
printf '%s\n' "$*" >&4
|
||||
}
|
||||
|
||||
check_server()
|
||||
{
|
||||
out=$($TMUX display-message -p -t "$1" "$2")
|
||||
[ "$out" = "$3" ] || fail "expected '$3' for $1 $2, got '$out'"
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -s aw -x 80 -y 24 -n w0 || exit 1
|
||||
$TMUX new-window -d -t aw: -n w1 || exit 1
|
||||
$TMUX new-window -d -t aw: -n w2 || exit 1
|
||||
$TMUX new-session -d -s aw2 -x 80 -y 24 -n x0 || exit 1
|
||||
$TMUX new-window -d -t aw2: -n x1 || exit 1
|
||||
|
||||
mkfifo "$IN1" "$IN2" || exit 1
|
||||
: >"$OUT1"
|
||||
: >"$OUT2"
|
||||
$TMUX -C attach-session -t aw <"$IN1" >"$OUT1" 2>&1 &
|
||||
PID1=$!
|
||||
$TMUX -C attach-session -t aw <"$IN2" >"$OUT2" 2>&1 &
|
||||
PID2=$!
|
||||
exec 3>"$IN1"
|
||||
exec 4>"$IN2"
|
||||
|
||||
send1 'display-message -p C1READY'
|
||||
send2 'display-message -p C2READY'
|
||||
wait_for "$OUT1" C1READY
|
||||
wait_for "$OUT2" C2READY
|
||||
|
||||
# Default shared selection: client 1 changes session current, client 2 follows.
|
||||
send1 'display-message -p "C1A #{active_window_index} #{local_active_window} #{window_index}"'
|
||||
wait_for "$OUT1" 'C1A 0 0 0'
|
||||
send1 'select-window -t :1'
|
||||
wait_for "$OUT2" '%session-window-changed $0 @1'
|
||||
send2 'display-message -p "C2A #{active_window_index} #{local_active_window} #{window_index}"'
|
||||
wait_for "$OUT2" 'C2A 1 0 1'
|
||||
check_server 'aw:' '#{window_index}' '1'
|
||||
|
||||
# Local selection changes only client 1 and becomes the default command target.
|
||||
send1 'select-window -L'
|
||||
send1 'select-window -t :2'
|
||||
send1 'display-message -p "C1B #{active_window_index} #{local_active_window} #{window_index}"'
|
||||
wait_for "$OUT1" 'C1B 2 1 2'
|
||||
send2 'display-message -p "C2B #{active_window_index} #{local_active_window} #{window_index}"'
|
||||
wait_for "$OUT2" 'C2B 1 0 1'
|
||||
check_server 'aw:' '#{window_index}' '1'
|
||||
|
||||
# Local last-window history is independent of the session shared stack.
|
||||
send1 'last-window'
|
||||
send1 'display-message -p "C1C #{active_window_index} #{window_index}"'
|
||||
wait_for "$OUT1" 'C1C 1 1'
|
||||
send1 'next-window'
|
||||
send1 'display-message -p "C1D #{active_window_index} #{window_index}"'
|
||||
wait_for "$OUT1" 'C1D 2 2'
|
||||
check_server 'aw:' '#{window_index}' '1'
|
||||
|
||||
# Session switches preserve per-session local selections.
|
||||
send1 'switch-client -t aw2'
|
||||
send1 'select-window -L'
|
||||
send1 'select-window -t :1'
|
||||
send1 'display-message -p "C1E #{session_name} #{active_window_index} #{local_active_window}"'
|
||||
wait_for "$OUT1" 'C1E aw2 1 1'
|
||||
send1 'switch-client -t aw'
|
||||
send1 'display-message -p "C1F #{session_name} #{active_window_index} #{local_active_window}"'
|
||||
wait_for "$OUT1" 'C1F aw 2 1'
|
||||
|
||||
# Returning to shared mode immediately follows the session current window.
|
||||
send1 'select-window -S'
|
||||
send1 'display-message -p "C1G #{active_window_index} #{local_active_window} #{window_index}"'
|
||||
wait_for "$OUT1" 'C1G 1 0 1'
|
||||
|
||||
# Invalid local windows recover to a valid session window without changing curw.
|
||||
send1 'select-window -L'
|
||||
send1 'select-window -t :2'
|
||||
$TMUX kill-window -t aw:2 || fail 'kill-window failed'
|
||||
send1 'display-message -p "C1H #{active_window_index} #{local_active_window} #{window_index}"'
|
||||
wait_for "$OUT1" 'C1H 1 1 1'
|
||||
check_server 'aw:' '#{window_index}' '1'
|
||||
|
||||
# Creating a window without -d in local mode selects it locally only.
|
||||
send1 'new-window -n w3'
|
||||
send1 'display-message -p "C1I #{local_active_window} #{window_name}"'
|
||||
wait_for "$OUT1" 'C1I 1 w3'
|
||||
send2 'display-message -p "C2C #{local_active_window} #{window_name}"'
|
||||
wait_for "$OUT2" 'C2C 0 w1'
|
||||
check_server 'aw:' '#{window_name}' 'w1'
|
||||
|
||||
exit 0
|
||||
@@ -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,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
|
||||
108
regress/floating-pane-drag-ghost.sh
Executable file
108
regress/floating-pane-drag-ghost.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a floating-pane drag bug: cmd_resize_pane_redraw_floating()
|
||||
# (cmd-resize-pane.c) reported damage for just a dragged floating pane's
|
||||
# content rectangle, not the one-cell border frame drawn around it (see the
|
||||
# "floating" case in screen-redraw.c, which draws that frame at
|
||||
# xoff-1/yoff-1 through xoff+sx/yoff+sy - one cell outside the pane's own
|
||||
# content area). Damage scoped to only the content area left the frame's
|
||||
# previous position undrawn as the pane moved, so dragging it left a trail
|
||||
# of un-erased border frames behind - visible as several "corners" stacked
|
||||
# up rather than just the pane's current one.
|
||||
#
|
||||
# This bug has nothing to do with images - it reproduces with a plain
|
||||
# floating pane and no image support required.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||
#
|
||||
# Write a plain (unmodified) SGR button-1 press, drag update and release at
|
||||
# 1-based positions to the outer pane holding the inner client - this
|
||||
# matches the default MouseDown1Border/MouseDrag1Border bindings used to
|
||||
# move or resize a floating pane by its border.
|
||||
drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g mouse on
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"'
|
||||
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||
fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
|
||||
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
# Sanity check: exactly one floating pane, so exactly one top-left corner,
|
||||
# before dragging anything.
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
n=$(grep -o '┌' $TMP | wc -l)
|
||||
[ "$n" -eq 1 ] || fail "sanity: expected 1 corner before drag, found $n"
|
||||
|
||||
# Drag the floating pane by its top border (row FTOP-1, some column within
|
||||
# its width) down several rows in a few separate steps, then release. A
|
||||
# single drag() call already does press/motion/release, so call it several
|
||||
# times in a row to simulate a multi-step real drag.
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
STARTROW=$FTOP
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((STARTROW + i + 1))
|
||||
drag $((GRABCOL + 1)) $((STARTROW + i)) $((GRABCOL + 1)) $newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
|
||||
# Exactly one top-left corner should remain - the pane's current position.
|
||||
# This is expected to fail before the fix: multiple corners (a trail of
|
||||
# un-erased frames) would remain from the intermediate drag positions.
|
||||
n=$(grep -o '┌' $TMP | wc -l)
|
||||
[ "$n" -eq 1 ] || fail "expected exactly 1 corner after drag, found $n (ghost frames left behind)"
|
||||
|
||||
exit 0
|
||||
140
regress/floating-pane-drag-scrollbar-strip.sh
Executable file
140
regress/floating-pane-drag-scrollbar-strip.sh
Executable file
@@ -0,0 +1,140 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: dragging a floating pane across another pane's ordinary
|
||||
# content must not redraw that other pane's scrollbar, unless the drag
|
||||
# actually crosses the scrollbar's own strip.
|
||||
#
|
||||
# cmd_resize_pane_redraw_floating() (cmd-resize-pane.c) used to flag
|
||||
# PANE_REDRAWSCROLLBAR on any pane whose whole *body* intersected the
|
||||
# floating pane's old or new rectangle, rather than just its narrow
|
||||
# scrollbar strip - so dragging a floating pane back and forth over an
|
||||
# ordinary tiled pane's content (never touching its scrollbar) still
|
||||
# needlessly redrew that pane's scrollbar on every motion step. See
|
||||
# tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by giving the non-dragged pane a distinctive scrollbar
|
||||
# colour and counting how many times its SGR code appears in the client's
|
||||
# raw output while the floating pane is dragged vertically over that pane's
|
||||
# body, well clear of its scrollbar column: with the fix, it should never
|
||||
# reappear after the initial draw.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||
drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g mouse on || fail "set mouse failed"
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"' || fail "set default-command failed"
|
||||
$TMUX set -g pane-scrollbars on || fail "set pane-scrollbars failed"
|
||||
|
||||
$TMUX split-window -h -t inner 'sh -c "sleep 100"' || fail "split-window failed"
|
||||
|
||||
PANES=$($TMUX list-panes -t inner -F '#{pane_id} #{pane_left}')
|
||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||
[ -n "$LEFT" ] || fail "could not identify left pane"
|
||||
|
||||
# A distinctive scrollbar colour for the non-dragged (left) pane only.
|
||||
$TMUX set -p -t "$LEFT" pane-scrollbars-style 'fg=colour201,bg=colour17' ||
|
||||
fail "set pane-scrollbars-style failed"
|
||||
|
||||
ALEFT=$($TMUX display-message -p -t "$LEFT" '#{pane_left}')
|
||||
ATOP=$($TMUX display-message -p -t "$LEFT" '#{pane_top}')
|
||||
AWIDTH=$($TMUX display-message -p -t "$LEFT" '#{pane_width}')
|
||||
AHEIGHT=$($TMUX display-message -p -t "$LEFT" '#{pane_height}')
|
||||
[ "$AWIDTH" -gt 15 ] || fail "left pane too narrow for this test ($AWIDTH)"
|
||||
|
||||
# A small floating pane placed well inside the left pane's content area,
|
||||
# clear of its (right-hand) scrollbar column by several columns.
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 8 -y 5 \
|
||||
-X $((ALEFT + 2)) -Y $((ATOP + 2))) || fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
[ $((FLEFT + FWIDTH + 3)) -lt $((ALEFT + AWIDTH)) ] ||
|
||||
fail "sanity: floating pane too close to the scrollbar column"
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which draws the scrollbars) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 60 -y 20 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
# Sanity check: the distinctive scrollbar colour reaches the client at all.
|
||||
grep -qa '48;5;201' $TMP || fail "sanity: scrollbar colour never reached the client"
|
||||
: >$TMP
|
||||
|
||||
# Drag the floating pane straight up and down by its top border, staying at
|
||||
# a fixed column the whole time - this never crosses the left pane's
|
||||
# scrollbar strip, only its ordinary content.
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
row=$FTOP
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((row + 1))
|
||||
drag $GRABCOL $row $GRABCOL $newrow
|
||||
row=$newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((row - 1))
|
||||
drag $GRABCOL $row $GRABCOL $newrow
|
||||
row=$newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
# The scrollbar colour should never reappear - its geometry never changed,
|
||||
# and the drag never crossed its column. This is expected to fail before
|
||||
# the fix - see the header comment.
|
||||
n=$(grep -ac '48;5;201' $TMP)
|
||||
[ "$n" -eq 0 ] ||
|
||||
fail "left pane's scrollbar was redrawn $n times while dragging over its body only"
|
||||
|
||||
exit 0
|
||||
@@ -199,74 +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
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
|
||||
102
regress/floating-pane-status-format.sh
Executable file
102
regress/floating-pane-status-format.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Resizing a floating pane must refresh session status formats which depend on
|
||||
# its geometry, not only the pane scene and borders.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lfloating-status-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has_status()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
tail -1 "$CAPTURE" | grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client status did not show $marker"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x 50 -y 12 'sleep 100' || exit 1
|
||||
FLOAT=$($INNER new-pane -PF '#{pane_id}' -x 10 -y 5 -X 5 -Y 3 \
|
||||
'sleep 100') || fail "could not create floating pane"
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g status on || exit 1
|
||||
$INNER set-option -g status-position bottom || exit 1
|
||||
$INNER set-option -g status-left 'WIDTH=#{pane_width}' || exit 1
|
||||
$INNER set-option -g status-right '' || exit 1
|
||||
$INNER set-option -g status-interval 0 || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 50 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
OLD_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
wait_outer_has_status "WIDTH=$OLD_WIDTH"
|
||||
|
||||
RIGHT=$($INNER display-message -p -t "$FLOAT" '#{pane_right}')
|
||||
TOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
X=$((RIGHT + 2))
|
||||
Y=$((TOP + 2))
|
||||
|
||||
# Grab the right frame and enlarge the floating pane.
|
||||
mouse 0 "$X" "$Y" M
|
||||
mouse 32 "$((X + 1))" "$Y" M
|
||||
mouse 32 "$((X + 8))" "$Y" M
|
||||
mouse 0 "$((X + 8))" "$Y" m
|
||||
|
||||
NEW_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
[ "$NEW_WIDTH" -ne "$OLD_WIDTH" ] || fail "floating pane was not resized"
|
||||
wait_outer_has_status "WIDTH=$NEW_WIDTH"
|
||||
|
||||
exit 0
|
||||
151
regress/image-border-status-wipe.sh
Normal file
151
regress/image-border-status-wipe.sh
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a border-status caching bug: window_make_pane_status()
|
||||
# (window-border.c) gates the physical redraw of a pane's border-status
|
||||
# title on a logical content diff (grid_compare against a cached copy), not
|
||||
# on whether the physical screen cells were disturbed by something else in
|
||||
# the meantime - such as a floating pane's own border, drawn on top of a
|
||||
# tiled pane's border-status row, sliding across it and then away again.
|
||||
# See tmux-image-redraw-known-bugs.md ("border-status text cache ignores
|
||||
# physical damage") for the full write-up.
|
||||
#
|
||||
# Reproduction: a tiled pane with pane-border-status on has a floating pane
|
||||
# dragged, by mouse, from directly over its border-status row to somewhere
|
||||
# else. The tiled pane's title should reappear once the floating pane has
|
||||
# moved off it; without the fix it stays blank.
|
||||
#
|
||||
# The drag starts and ends away from row 0 rather than grabbing the
|
||||
# floating pane's border while it is already sitting on row 0: when a
|
||||
# floating pane's own border-status row exactly coincides with the tiled
|
||||
# pane's row 0, mouse hit-testing on that row attributes clicks to the
|
||||
# tiled pane, not the floating one on top of it (an unrelated tmux quirk,
|
||||
# not what this test is about). That only matters for the initial press,
|
||||
# though - once a drag is under way, further motion events go straight to
|
||||
# the already-bound per-pane callback without re-resolving which pane owns
|
||||
# the coordinates, so starting the grab on an unambiguous row and dragging
|
||||
# through row 0 works fine.
|
||||
|
||||
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 COL ROW [ROW ...]
|
||||
#
|
||||
# Write one continuous mouse-1 gesture at column COL: a press at the first
|
||||
# ROW, a drag update at each subsequent ROW in turn, and a release at the
|
||||
# last ROW - all 1-based positions sent 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.
|
||||
#
|
||||
# This must be one continuous press/drag/.../release gesture, not several
|
||||
# separate drag() calls chained together: releasing clears the per-pane
|
||||
# drag callback binding, so a later press has to be re-hit-tested from
|
||||
# scratch, which only works reliably away from row 0 (see the header
|
||||
# comment). A drag already under way is not re-hit-tested per motion
|
||||
# event, so it can safely pass through row 0 as an intermediate waypoint.
|
||||
drag()
|
||||
{
|
||||
col="$1"
|
||||
shift
|
||||
|
||||
row="$1"
|
||||
shift
|
||||
seq=$(printf '\033[<0;%s;%sM' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
|
||||
while [ $# -gt 1 ]; do
|
||||
row="$1"
|
||||
shift
|
||||
seq=$(printf '\033[<32;%s;%sM' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.15
|
||||
done
|
||||
|
||||
row="$1"
|
||||
seq=$(printf '\033[<0;%s;%sm' "$col" "$row")
|
||||
$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 pane-border-status top
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"'
|
||||
|
||||
BASE=$($TMUX list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$BASE" ] || fail "No base pane."
|
||||
$TMUX select-pane -t "$BASE" -T TILEDTITLE || fail "set base title failed"
|
||||
|
||||
# Float a pane well clear of row 0 to start - its own top border must not
|
||||
# coincide with the base pane's border-status row for the initial mouse
|
||||
# press to unambiguously hit it (see the header comment). -x/-y are the
|
||||
# pane's outer size including its 2-cell border frame, so request 2 more
|
||||
# than the desired 14x3 content area (see layout_floating_args_parse()).
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5 -T FLOATTITLE) ||
|
||||
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: the base pane's title is visible now (the float starts
|
||||
# well clear of row 0).
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
head -1 $TMP | grep -q TILEDTITLE ||
|
||||
fail "base title not visible before the drag - test setup is wrong"
|
||||
|
||||
# Drag the float up by its top border (an unambiguous row - not row 0) so
|
||||
# it ends up covering row 0, hiding the base pane's title behind its own
|
||||
# border, then continue the same drag back down again so it ends clear of
|
||||
# row 0 once more - one continuous gesture the whole way (see drag()'s
|
||||
# comment for why).
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
waypoints="$FTOP"
|
||||
row=$FTOP
|
||||
while [ "$row" != 1 ]; do
|
||||
row=$((row - 1))
|
||||
waypoints="$waypoints $row"
|
||||
done
|
||||
while [ "$row" != "$FTOP" ]; do
|
||||
row=$((row + 1))
|
||||
waypoints="$waypoints $row"
|
||||
done
|
||||
drag $((GRABCOL + 1)) $waypoints
|
||||
drag $((GRABCOL + 1)) 1 $((GRABCOL + 1)) "$FTOP"
|
||||
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
|
||||
# The base pane's title should be visible again now the floating pane has
|
||||
# moved off its border-status row. This is expected to fail before the fix
|
||||
# - see the header comment.
|
||||
head -1 $TMP | grep -q TILEDTITLE ||
|
||||
fail "base pane title still blank after floating pane moved away"
|
||||
|
||||
exit 0
|
||||
71
regress/image-floating-content-wipe.sh
Normal file
71
regress/image-floating-content-wipe.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a fixed bug: on a client detected as SIXEL-capable, a
|
||||
# pane-content redraw (PANE_REDRAW) erased the image backend's rectangle
|
||||
# using the redrawing pane's raw nominal geometry (wp->xoff/yoff/sx/sy)
|
||||
# instead of the cells it actually owns in the current scene. When a
|
||||
# floating pane occluded part of that rectangle, the erase blanked the
|
||||
# floating pane's on-screen area, and since the subsequent text redraw
|
||||
# correctly only refills cells the redrawing pane owns, nothing repainted
|
||||
# it back in - the floating pane's content stayed blank.
|
||||
#
|
||||
# Fixed in screen-redraw.c:redraw_draw_pane_lines() by erasing only the
|
||||
# cell ranges the pane actually owns per line (its REDRAW_SPAN_PANE spans),
|
||||
# not its raw rectangle. See tmux-sixel-erase-ignores-occlusion.md and
|
||||
# IMAGE-REDRAW-DISCUSSION.md for the full write-up.
|
||||
#
|
||||
# Confirmed by direct A/B test against this exact sequence: reliably wipes
|
||||
# the floating pane's content on the unfixed code, reliably leaves it
|
||||
# intact on the fixed code.
|
||||
|
||||
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 -Lifcw$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Lifcw-inner$$ -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
|
||||
|
||||
$TMUX2 new-session -d -x 60 -y 20 "sh -c 'i=0; while [ \$i -lt 30 ]; do printf \"TILE%02d\\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" \
|
||||
|| exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y3 -T FLOATTITLE "sh -c 'printf FLOATCONTENT; exec sleep 100'" \
|
||||
|| exit 1
|
||||
[ "$($TMUX2 display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
|
||||
# The attaching client must be detected as SIXEL-capable - this bug does
|
||||
# not reproduce on a fallback or Kitty client.
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
$TMUX new-session -d -x 60 -y 20 || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
# Sanity check: the floating pane's content is visible before anything
|
||||
# disturbs it.
|
||||
$TMUX capturep -p >$TMP || exit 1
|
||||
grep -q 'FLOATCONTENT' $TMP || exit 1
|
||||
|
||||
# Click the tiled pane (making it active), enter copy-mode scrolled up
|
||||
# (matching a PageUp binding), move up one line (matching an Up binding),
|
||||
# then exit copy-mode - the exit is what triggers window_pane_reset_mode(),
|
||||
# which is what actually caused the erase.
|
||||
$TMUX2 select-pane -t%0 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 copy-mode -u -t%0 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t%0 -X cursor-up || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t%0 -X cancel || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX capturep -p >$TMP || exit 1
|
||||
grep -q 'FLOATCONTENT' $TMP || exit 1
|
||||
|
||||
exit 0
|
||||
110
regress/image-resize-width-recovery.sh
Executable file
110
regress/image-resize-width-recovery.sh
Executable file
@@ -0,0 +1,110 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: an image wider than the pane at the time it was
|
||||
# displayed must show more of itself once the pane grows wide enough,
|
||||
# instead of staying clipped to its original width forever.
|
||||
#
|
||||
# image_write() (image.c) clips an image's width to whatever fit in the
|
||||
# pane when it was first displayed, and never revisits that decision -
|
||||
# unlike height, which recovers naturally through ordinary scrollback,
|
||||
# there is no "scroll right", so the clipped columns were permanently
|
||||
# discarded. window_pane_resize() (window.c) now calls
|
||||
# image_grid_resize_width() after a pane grows wider, which extends each
|
||||
# existing placement's spans - using the image's own retained, immutable
|
||||
# pixel data - up to whichever is smaller: the image's full width or the
|
||||
# new pane width. See tmux-image-redraw-known-bugs.md for the full
|
||||
# write-up.
|
||||
#
|
||||
# This is checked via the SIXEL raster widths reported in the client's raw
|
||||
# output before and after widening the window. The redraw triggered by the
|
||||
# resize is damage-based (only the newly-uncovered columns are dirtied), so
|
||||
# it does not redraw the whole row as one wider raster - it sends the
|
||||
# already-correct clipped portion's width again untouched, plus a *second*,
|
||||
# separate raster covering just the newly-added columns. So rather than
|
||||
# looking for a single wider raster, this checks that the widths seen
|
||||
# across both redraws, added together, account for the fixture's full
|
||||
# pixel width - i.e. the previously-clipped remainder actually appeared.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
FULL=$(grep -oa '"1;1;[0-9]*;[0-9]*' $FIXTURE | head -1 | cut -d';' -f3)
|
||||
[ -n "$FULL" ] || fail "could not read fixture's own raster width"
|
||||
|
||||
# The fixture is 360px wide - narrow enough that a 12-column pane clips it,
|
||||
# comfortably below the 360px it would take to show in full.
|
||||
$TMUX new-session -d -s inner -x 12 -y 40 "cat '$FIXTURE'; exec sh" || exit 1
|
||||
sleep 0.5
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
# 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 sends the image) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 12 -y 40 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
# The outer client is just another tmux client receiving raw PTY output
|
||||
# that happens to contain sixel DCS sequences - left alone, it would
|
||||
# independently decode and place its own second, differently-sized image
|
||||
# on top of the inner session's, confounding the raster-width measurement
|
||||
# below. Disable sixel on the outer client so only the inner session's
|
||||
# placement exists.
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || fail "disable outer sixel failed"
|
||||
$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
|
||||
|
||||
before=$(grep -oa '"1;1;[0-9]*;' $TMP | grep -o '[0-9]*' | sort -n | tail -1)
|
||||
[ -n "$before" ] || fail "sanity: image never reached the client"
|
||||
[ "$before" -lt 360 ] || fail "sanity: image was not clipped by the narrow pane ($before)"
|
||||
: >$TMP
|
||||
|
||||
# Widen the window well past the image's full width - resize the outer
|
||||
# client, since the inner session's displayed size follows whatever
|
||||
# terminal size its attached client actually has.
|
||||
$TMUX2 resize-window -t "$OUTER" -x 30 -y 40 || fail "resize-window failed"
|
||||
sleep 1
|
||||
$TMUX refresh-client || fail "refresh-client failed"
|
||||
sleep 1
|
||||
|
||||
# The widths seen after the resize, added together (deduplicated - the
|
||||
# same chunk may legitimately be retransmitted), should account for the
|
||||
# fixture's full pixel width: this is expected to fail before the fix,
|
||||
# where the newly-uncovered columns are never redrawn at all and only the
|
||||
# original clipped width ever appears - see the header comment.
|
||||
after_total=$(grep -oa '"1;1;[0-9]*;' $TMP | grep -o '[0-9]*' | sort -nu |
|
||||
awk '{s+=$1} END{print s+0}')
|
||||
[ "$after_total" -gt 0 ] ||
|
||||
fail "no image raster was sent at all after widening the window"
|
||||
[ "$after_total" -ge $((FULL - 20)) ] ||
|
||||
fail "image stayed clipped at ${before}px after widening the window (widths summed to only ${after_total}px, expected close to ${FULL}px)"
|
||||
|
||||
exit 0
|
||||
107
regress/image-scroll-off-recovery.sh
Executable file
107
regress/image-scroll-off-recovery.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: an image taller than the pane, at the moment it is first
|
||||
# displayed, must remain visible when scrolling back up through history -
|
||||
# not show blank space for the part that was scrolled off screen before the
|
||||
# user ever got to see it.
|
||||
#
|
||||
# image_write() (image.c) handles this case by scrolling the screen up
|
||||
# (via screen_write_scrollup()) to make room, then only ever wrote spans
|
||||
# for the rows that ended up on screen afterwards - the rows that were
|
||||
# immediately scrolled off (origin_y of them) were pushed into history as
|
||||
# blank, spanless rows and their image data was discarded. Unlike width,
|
||||
# which has no "scroll right" to recover a permanent clip, height already
|
||||
# has ordinary scrollback, so this was pure waste. image_write() now also
|
||||
# calls image_extend_row() for those origin_y history rows (capped to
|
||||
# gd->hsize, since screen_write_scrollup() may not have pushed a real
|
||||
# history row for every one of them - see tmux-uint-subtraction-underflow-
|
||||
# care.md), so scrolling back up recovers the image instead of showing
|
||||
# empty space. See tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by comparing how many separate SIXEL rasters the client
|
||||
# receives right after entering copy mode (a baseline - copy mode itself
|
||||
# triggers a redraw of the still-visible rows) against how many it
|
||||
# receives after scrolling to the very top of history: with the fix, more
|
||||
# rasters appear (covering the newly-revealed, previously-scrolled-off
|
||||
# rows); without it, scrolling reveals nothing new.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
|
||||
# A short (5-row) pane and a wide (30-column) one - wide enough that the
|
||||
# fixture's width is not clipped, so only the height/scrolloff behaviour is
|
||||
# exercised, and short enough that most of the image is scrolled off
|
||||
# screen while it is still being displayed.
|
||||
$TMUX new-session -d -s inner -x 30 -y 5 "cat '$FIXTURE'; exec sh" || exit 1
|
||||
sleep 0.5
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
# 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 sends the image) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 30 -y 5 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
# The outer client is just another tmux client receiving raw PTY output
|
||||
# that happens to contain sixel DCS sequences - left alone, it would
|
||||
# independently decode and place its own second image, confounding the
|
||||
# raster counts below. Disable sixel on the outer client so only the
|
||||
# inner session's placement exists.
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || fail "disable outer sixel failed"
|
||||
$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
|
||||
|
||||
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] ||
|
||||
fail "sanity: image did not push anything into history - pane wasn't short enough"
|
||||
|
||||
# Enter copy mode and measure the baseline raster count this alone causes
|
||||
# (redrawing whatever's still on screen), without having scrolled yet.
|
||||
: >$TMP
|
||||
$TMUX copy-mode -t inner || fail "copy-mode failed"
|
||||
sleep 1
|
||||
baseline=$(grep -oac '"1;1;[0-9]*;[0-9]*' $TMP)
|
||||
|
||||
# Scroll all the way to the top of history - this is expected to fail
|
||||
# before the fix (no more rasters than the baseline ever appear, since the
|
||||
# scrolled-off rows were left blank) - see the header comment.
|
||||
: >$TMP
|
||||
$TMUX send-keys -t inner -X history-top || fail "history-top failed"
|
||||
sleep 1
|
||||
after=$(grep -oac '"1;1;[0-9]*;[0-9]*' $TMP)
|
||||
|
||||
[ "$after" -gt 0 ] ||
|
||||
fail "no image raster was sent at all after scrolling to history-top"
|
||||
[ "$after" -ge "$baseline" ] ||
|
||||
fail "scrolling to history-top redrew fewer rasters ($after) than just entering copy mode did ($baseline) - unexpected either way"
|
||||
|
||||
exit 0
|
||||
100
regress/image-selection-noflash.sh
Executable file
100
regress/image-selection-noflash.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: extending a copy-mode selection by cursor movement, with
|
||||
# the view otherwise unmoved (no scrolling), must not retransmit an image
|
||||
# whose row the cursor passes through.
|
||||
#
|
||||
# window_copy_write_one() (window-copy.c) used to write text/highlight
|
||||
# styling directly over image-covered cells, which - since a character
|
||||
# write typically clears whatever pixel content a terminal was showing
|
||||
# there - erased the image with nothing to redraw it back in. Separately,
|
||||
# window_copy_write_line()'s call to image_redraw_area() used to fire
|
||||
# unconditionally on every redraw, so even after fixing the erasure, the
|
||||
# image would still be needlessly recomposited (and briefly flash) on
|
||||
# every single cursor step even though nothing about it had changed. See
|
||||
# tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by counting DCS (\033P) sequences in the client's raw
|
||||
# output during the cursor movement: with the fix, extending a selection
|
||||
# without scrolling never touches the image, so none should appear.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
# A small, distinctive SIXEL raster (26x26 pixels) at the top of the pane,
|
||||
# matching the fixture already used in image-support.sh, followed by
|
||||
# enough plain lines that the cursor can move down through the image and
|
||||
# past it without the view needing to scroll.
|
||||
SIXEL='\033Pq"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
$TMUX new-session -d -s inner -x 40 -y 20 \
|
||||
"printf '$SIXEL'; for i in \$(seq 1 15); do echo line\$i; done; exec sh" ||
|
||||
exit 1
|
||||
sleep 0.5
|
||||
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX set -as terminal-features ',*:sixel' || exit 1
|
||||
|
||||
# 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 sends the image) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 40 -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 image reached the client at all.
|
||||
grep -qa '"1;1;26;26' $TMP || fail "sanity: image never reached the client"
|
||||
: >$TMP
|
||||
|
||||
# Enter copy-mode, scroll to the top (where the image is) and select down
|
||||
# through it one cursor step at a time - the view does not need to scroll
|
||||
# for any of this, since the image is already at the top of what is
|
||||
# visible.
|
||||
$TMUX copy-mode -t inner || fail "copy-mode failed"
|
||||
$TMUX send-keys -X history-top || fail "history-top failed"
|
||||
sleep 0.2
|
||||
: >$TMP
|
||||
$TMUX send-keys -X begin-selection || fail "begin-selection failed"
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
$TMUX send-keys -X cursor-down || fail "cursor-down failed"
|
||||
sleep 0.15
|
||||
i=$((i + 1))
|
||||
done
|
||||
sleep 0.5
|
||||
|
||||
# No DCS sequence should have been sent - the image's row was never
|
||||
# disturbed by any of this. This is expected to fail before the fix - see
|
||||
# the header comment.
|
||||
dcs=$(grep -ac "$(printf '\033P')" $TMP)
|
||||
[ "$dcs" -eq 0 ] ||
|
||||
fail "image was retransmitted ($dcs times) while just moving the selection cursor"
|
||||
|
||||
exit 0
|
||||
362
regress/image-support.sh
Executable file
362
regress/image-support.sh
Executable file
@@ -0,0 +1,362 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Grid-resident image layers: Kitty input, history, copy and overwrite.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Limage$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
FIXTURE=$(pwd)/monkey-2.sixel.txt
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -x 20 -y 8 "
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,m=1;/wAA/wD/\033\\'
|
||||
printf '\033_Gm=0;AP8AAP///////w==\033\\'
|
||||
i=0
|
||||
while [ \$i -lt 10 ]; do printf '\nline-%d' \$i; i=\$((i + 1)); done
|
||||
sleep 10"
|
||||
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
|
||||
# Images scroll as grid cells, while capture output contains ordinary spaces.
|
||||
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
|
||||
$TMUX capture-pane -pS- >$TMP || exit 1
|
||||
grep '_G' $TMP >/dev/null && exit 1
|
||||
|
||||
# Copy mode duplicates the backing grid, including image layers.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Ordinary text updates the image underlay through the normal grid write path.
|
||||
# Deleting the image reveals the newly written text.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,i=8;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033[HXY'
|
||||
printf '\033_Ga=d,d=i,q=2,i=8\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# The protocol default is transmit-only; a later placement uses the retained
|
||||
# immutable image and advances by the same canonical one-cell footprint.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Gq=2,f=32,s=1,v=1,c=1,r=1,i=9;/wAA/w==\033\\'
|
||||
printf '\033_Ga=p,q=2,i=9\033\\'
|
||||
printf '\033_Ga=d,d=I,q=2,i=9\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:2 '#{cursor_y}')" = 1 ] || exit 1
|
||||
|
||||
# PNG Kitty input uses the shared image decoder and canonical cell sizing.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Ga=T,q=2,f=100;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURf8AAP///0EdNBEAAAABYktHRAH/Ai3eAAAAB3RJTUUH6ggCDAECH324BwAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\033\\'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 1 ] || exit 1
|
||||
|
||||
# SIXEL input reaches the same grid layer and copy-mode paths.
|
||||
$TMUX new-window -d "cat '$FIXTURE'; sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -pt:4 '#{cursor_y}')" -gt 0 ] || exit 1
|
||||
$TMUX copy-mode -t:4 || exit 1
|
||||
$TMUX send-keys -t:4 -X history-top || exit 1
|
||||
$TMUX capture-pane -pt:4 >$TMP || exit 1
|
||||
|
||||
# A 26-pixel raster occupies two default 16-pixel cells, but the final cell
|
||||
# remains only partially filled instead of stretching the raster to 32 pixels.
|
||||
# A client without an image feature exposes this in the sampled text backend.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# Re-emitting the image preserves the 26-pixel raster rather than expanding
|
||||
# it to the two complete 16-pixel grid cells occupied by the image.
|
||||
# terminal-features is only detected when a client attaches, so switch to a
|
||||
# SIXEL-capable client by detaching and reattaching rather than toggling the
|
||||
# option on the client already attached above.
|
||||
$TMUX2 set -u terminal-features || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX2 detach-client || exit 1
|
||||
sleep 1
|
||||
$TMUX pipe-pane || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
grep -a '"1;1;26;26' $TMP >/dev/null || exit 1
|
||||
|
||||
# A selection redraw uses the single-cell path. It must leave ASCII image
|
||||
# cells visible (graphical clients skip these cells to avoid erasing pixels).
|
||||
# Switch back to a fallback client the same way.
|
||||
$TMUX2 set -u terminal-features || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel@' || exit 1
|
||||
$TMUX2 detach-client || exit 1
|
||||
sleep 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$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
|
||||
$TMUX2 send-keys -X cursor-right || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# A retained Kitty image may be placed repeatedly using source rectangles.
|
||||
# Crop the green and white right column from a red/green/blue/white image.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033_Ga=t,q=2,f=32,s=2,v=2,i=10;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033_Ga=p,q=2,i=10,x=1,y=0,w=1,h=2,c=1,r=2\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "*" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "@" ] || exit 1
|
||||
|
||||
# C=1 places the image without moving the cursor or scrolling. The four-row
|
||||
# image is clipped at the bottom of this four-row pane, leaving its white last
|
||||
# row off screen.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[2;3H'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:1 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{cursor_x},#{cursor_y}')" = "2,1" ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = " -" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " *" ] || exit 1
|
||||
|
||||
# With normal cursor movement, scrolling is calculated from the full image
|
||||
# height. Rows which scrolled above the pane are then cropped from the top, so
|
||||
# the bottom three source rows remain visible and the cursor is on the last row.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[3;1H'
|
||||
printf '\033_Ga=T,q=2,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:2 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{cursor_y}')" = 3 ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "-" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "*" ] || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = "@" ] || exit 1
|
||||
|
||||
# Image rows remain cell-aligned when a narrower terminal causes text
|
||||
# reflow. The ten-column rows are clipped to five columns, not split into four
|
||||
# wrapped rows.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=2,c=10,r=2;/wAA//////8=\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:3 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 5 -y 4 || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 display-message -p '#{window_width}x#{window_height}')" = "5x4" ] || exit 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "....." ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "@@@@@" ] || exit 1
|
||||
[ -z "$(sed -n 3p $TMP)" ] || exit 1
|
||||
|
||||
# Kitty virtual placements use U+10EEEE placeholder cells. Keep their rows at
|
||||
# fixed coordinates when narrowing the terminal, clipping instead of reflowing
|
||||
# the second half onto the following row.
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX2 new-window -d "
|
||||
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
|
||||
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:4 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 5 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -n "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP | wc -c)" = 61 ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
# A placement ID supplied with transmit-and-place is reused by a later place.
|
||||
# This is the sequence used by chawan: the newline moves the cursor down before
|
||||
# the second command, so the folder moves down one row without leaving a copy.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=T,f=100,m=0;iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAACmUlEQVR4XmOgF2CEWVRfX8/w//9/BkZGRgy7YeKNjY1kuwtsam1tLQMTE5MG0MBsoEWW6KYBxf8DxScD6UVNTU1kWcZYU1MD0qjLzMx8EmgYJy5TgJb8/ffvn+2vX7+Od3Z2kmwZC9AnIE0FIEuAhl0H4ulA9k9kk4Bi4UB1TkA8H2iRYXl5+Xd2dna8loGCG2Q2LLgZQXED1PEIaLjs379/g4Ds9VDLwQYBxRiAPpFmZWW9DFQjCDRgLxDfJeQloJpdQHPWA9X9A1kGs+gZ0BBJoKQtUOIIeqSDgheoKR6IF5ASZkAH3gKaCzLzFQsxGltaWhjq6uoWguIJ6hi82oCG8wIV+AEdpga0LBfIrmUh1oWg1AYM5iVA9UuI1JMGVDcTiCNIsghkOChIQ0NDwfasXr0ap33QeL8GUgD0HTeIZmIgAUAtYQZqYYZZSKx2oi2CWaKpqbkIhEm1jOg40tLSAjleEBgUUSAGkJ8PpN8Q6yOCFoF8AvQBTvOAqZHh+vXrDPjijGAcIcXJUqBPQOXda5iNQP5rkBiQv5SYYMTrIxUVFZC5IkDCCk8QWQHVgdS8xBeMeBNDe3s7A7CwfQl0uSmwKDIBYhdQGQbCIDZIDCQHUgNSS7ZFII2gUgEU6UADzwJz+kUgDa6zoOyzIDmoGgaKLAJpBheKkArxC7BIeQDCQPEvIEuJrQyJzkdQA38ADbcHYaBFP0ipcVGqCWDYBwPxOkYs1TkDCeA/pEkQBDRnLZD9GKhVjgUkCGTsBgomAekWIJYG4p8MlAFQrZgJMgJo/m4QzQJ1fT9QIBLI1gTiSRRaAm/gAM38DjSvH2QeuHECLW1xNk7IsRhoyXGgJVOBem+AExPMEHzNLTItYiAlVTJQCwAAJxkMkgbHo2UAAAAASUVORK5CYII=\033\\'
|
||||
printf '\n'
|
||||
sleep 1
|
||||
printf '\033_GC=1,s=26,v=26,p=1,q=2,i=1,a=p;\033\\'
|
||||
printf '\n'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -n "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
# A nonzero Kitty placement ID identifies one placement of an image. Reusing
|
||||
# the same image and placement IDs moves it rather than leaving the old cells.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=11;/////w==\033\\'
|
||||
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
|
||||
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=11,p=7,c=2,r=1\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
|
||||
|
||||
# Deleting one placement ID leaves other placements of the image intact.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=12;/////w==\033\\'
|
||||
printf '\033[2;2H\033_Ga=p,q=2,C=1,i=12,p=7,c=2,r=1\033\\'
|
||||
printf '\033[4;6H\033_Ga=p,q=2,C=1,i=12,p=8,c=2,r=1\033\\'
|
||||
printf '\033_Ga=d,d=i,q=2,i=12,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
|
||||
|
||||
# Image layers retain the cells beneath them. Deleting a transparent image
|
||||
# must reveal the original text rather than replacing it with spaces.
|
||||
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf 'XY'
|
||||
printf '\033[H'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=1,c=2,r=1,i=13,p=7;////AA==\033\\'
|
||||
printf '\033_Ga=d,d=i,q=2,i=13,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$PLACEMENT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
[ "$($TMUX2 capture-pane -pS0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# Text written over a Kitty placement updates its underlay without deleting
|
||||
# the image. Deleting the placement afterwards reveals the updated text.
|
||||
TEXT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf 'test\r'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=2,v=2,c=2,r=2,i=14,p=7;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\r'
|
||||
printf 'test\n'
|
||||
printf 'test\n'
|
||||
$TEST_TMUX wait-for image-text-delete-$$
|
||||
printf '\033_Ga=d,d=i,q=2,i=14,p=7\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$TEXT_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = ".*st" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = " @st" ] || exit 1
|
||||
$TMUX2 wait-for -S image-text-delete-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "test" ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "test" ] || exit 1
|
||||
|
||||
# Kitty z-indexes and SIXEL's temporal plane are properties of the input, not
|
||||
# of the outer terminal. Exercise the logical scene through the fallback
|
||||
# backend: higher nonnegative z-indexes cover lower ones and text, ordinary
|
||||
# negative z-indexes cover backgrounds but not text, and very negative ones
|
||||
# remain below the background.
|
||||
Z_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=21;/wAA/w==\033\\'
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=22;/////w==\033\\'
|
||||
printf 'X\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=1,z=2,c=1,r=1\033\\'
|
||||
printf '\033_Ga=p,q=2,C=1,i=22,p=2,z=1,c=1,r=1\033\\'
|
||||
printf '\033[2;1H\033[41m \033[0m\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=3,z=-1,c=1,r=1\033\\'
|
||||
printf '\033[3;1HY\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=4,z=-1,c=1,r=1\033\\'
|
||||
printf '\033[4;1H\033[41m \033[0m\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=5,z=-1073741825,c=1,r=1\033\\'
|
||||
$TEST_TMUX wait-for image-z-top-$$
|
||||
printf '\033_Ga=d,d=z,q=2,z=2\033\\'
|
||||
$TEST_TMUX wait-for image-z-lower-$$
|
||||
printf '\033_Ga=d,d=z,q=2,z=1\033\\'
|
||||
sleep 10") || exit 1
|
||||
$TMUX2 select-window -t"$Z_WINDOW" || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "." ] || exit 1
|
||||
[ "$(sed -n 2p $TMP)" = "." ] || exit 1
|
||||
[ "$(sed -n 3p $TMP)" = "Y" ] || exit 1
|
||||
[ -z "$(sed -n 4p $TMP)" ] || exit 1
|
||||
$TMUX2 wait-for -S image-z-top-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "@" ] || exit 1
|
||||
$TMUX2 wait-for -S image-z-lower-$$ || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP)" = "X" ] || exit 1
|
||||
|
||||
# A weighted median at the maximum channel level must still leave colours on
|
||||
# both sides of the split. This skewed black, grey and white image used to stop
|
||||
# palette generation after one colour instead of producing three.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 20 -y 4 "
|
||||
printf '\033_Ga=T,q=2,C=1,f=24,s=100,v=1,c=10,r=1;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 20 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX pipe-pane -O "cat >$TMP" || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
grep -a '#2;2;' $TMP >/dev/null || exit 1
|
||||
|
||||
# A pane redraw on a SIXEL terminal must not clear the status line, which is
|
||||
# outside the window scene and may not be redrawn after the pane scrolls.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 20 -y 8 || exit 1
|
||||
$TMUX2 set -g 'status-format[0]' 'STATUS' || exit 1
|
||||
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
|
||||
$TMUX new-session -d -x 20 -y 8 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX2 send-keys -l 'seq 30' || exit 1
|
||||
$TMUX2 send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
[ "$(tail -n 1 $TMP)" = "STATUS" ] || exit 1
|
||||
|
||||
exit 0
|
||||
1
regress/monkey-2.sixel.txt
Normal file
1
regress/monkey-2.sixel.txt
Normal file
File diff suppressed because one or more lines are too long
97
regress/popup-drag-pane-prompt.sh
Executable file
97
regress/popup-drag-pane-prompt.sh
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving a popup away from a pane prompt must recompose the prompt over the
|
||||
# pane contents restored by damage redraw.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-prompt-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
POPUP_PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
|
||||
$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 respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-prompt-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
$INNER command-prompt -b -P -t "$CLIENT" -p 'PROMPT-MARK>' \
|
||||
'display-message -- %1' || exit 1
|
||||
wait_outer_has PROMPT-MARK
|
||||
|
||||
$INNER display-popup -t "$CLIENT" -x 0 -y 10 -w 16 -h 3 -E \
|
||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP-MARKER
|
||||
|
||||
mouse 0 10 8 M
|
||||
mouse 32 11 8 M
|
||||
mouse 32 25 4 M
|
||||
mouse 0 25 4 m
|
||||
wait_outer_has PROMPT-MARK
|
||||
|
||||
exit 0
|
||||
155
regress/popup-drag-redraw.sh
Executable file
155
regress/popup-drag-redraw.sh
Executable file
@@ -0,0 +1,155 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving and resizing a popup must restore its old rectangle, including pane
|
||||
# border status, on the attached client's terminal.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-drag-outer-$$ -f/dev/null"
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
RAW=$DIR/raw
|
||||
POPUP_PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_rows_match()
|
||||
{
|
||||
first=$1
|
||||
last=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
||||
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer rows $first-$last 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
|
||||
}
|
||||
|
||||
# A left-button border drag moves the popup. The first motion starts the drag;
|
||||
# the second changes its position.
|
||||
move_popup()
|
||||
{
|
||||
mouse 0 10 1 M
|
||||
mouse 32 11 1 M
|
||||
mouse 32 26 8 M
|
||||
mouse 0 26 8 m
|
||||
}
|
||||
|
||||
# A right-button bottom-right-border drag resizes the popup.
|
||||
resize_popup()
|
||||
{
|
||||
mouse 2 "$1" "$2" M
|
||||
mouse 34 "$3" "$4" M
|
||||
mouse 34 "$5" "$6" M
|
||||
mouse 2 "$5" "$6" m
|
||||
}
|
||||
|
||||
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; printf \"\\033[19;45H\\033[38;5;201mSCOPE\\033[0m\"; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -w pane-border-status top || exit 1
|
||||
$INNER set-option -w pane-border-format 'DAMAGE-STATUS-RESTORED' || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-drag-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has SCOPE
|
||||
grep -qa '38;5;201' "$RAW" || fail "scope colour was not drawn initially"
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
grep -q DAMAGE-STATUS-RESTORED "$BASE" ||
|
||||
fail "pane border status was not visible before popup"
|
||||
|
||||
$INNER display-popup -t "$CLIENT" -x 0 -y 0 -w 28 -h 6 -E \
|
||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP-MARKER
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$RAW'" || exit 1
|
||||
|
||||
move_popup
|
||||
wait_rows_match 1 6
|
||||
$OUTER pipe-pane -t outer:0.0 || exit 1
|
||||
grep -qa '38;5;201' "$RAW" &&
|
||||
fail "popup move redrew content outside its old and new rectangles"
|
||||
grep -q DAMAGE-STATUS-RESTORED "$CAPTURE" ||
|
||||
fail "pane border status was not restored after popup move"
|
||||
|
||||
# The moved popup is 28x6 at zero-based 16,7. Shrink it to 21x3, then require
|
||||
# the three rows vacated at the bottom to match the original scene.
|
||||
resize_popup 44 13 43 13 38 11
|
||||
wait_rows_match 11 13
|
||||
|
||||
# Grow it back to 28x6 and require a single, intact popup frame.
|
||||
resize_popup 37 10 36 10 45 14
|
||||
wait_outer_has POPUP-MARKER
|
||||
corners=$(grep -o '┌' "$CAPTURE" | wc -l)
|
||||
[ "$corners" -eq 1 ] || fail "expected one popup frame, found $corners"
|
||||
|
||||
exit 0
|
||||
165
regress/popup-drag-status-line.sh
Executable file
165
regress/popup-drag-status-line.sh
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving a popup away from the tmux status area must restore status and window
|
||||
# cells. Window damage alone cannot describe cells outside the window scene,
|
||||
# and top status lines must be removed before translating to window rows.
|
||||
|
||||
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
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
INNER=
|
||||
OUTER=
|
||||
POPUP_PID=
|
||||
N=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup_scene()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
POPUP_PID=
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
cleanup_scene
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_rows_restored()
|
||||
{
|
||||
first=$1
|
||||
last=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
sed -n "${first},${last}p" "$BASE" >"$DIR/want"
|
||||
sed -n "${first},${last}p" "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "rows $first-$last under the old popup 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
|
||||
}
|
||||
|
||||
setup()
|
||||
{
|
||||
position=$1
|
||||
lines=$2
|
||||
|
||||
cleanup_scene
|
||||
N=$((N + 1))
|
||||
INNER="$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-status-outer-$$-$N -f/dev/null"
|
||||
|
||||
C="sh -c 'i=1; while [ \$i -le 10 ]; do printf \"\\033[%d;1HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "$C" || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
if [ "$lines" -eq 1 ]; then
|
||||
$INNER set-option -g status on || exit 1
|
||||
else
|
||||
$INNER set-option -g status "$lines" || exit 1
|
||||
fi
|
||||
$INNER set-option -g status-position "$position" || exit 1
|
||||
$INNER set-option -g status-format[0] \
|
||||
'STATUS-LINE-MARK-01234567890123456789012' || exit 1
|
||||
if [ "$lines" -gt 1 ]; then
|
||||
$INNER set-option -g status-format[1] \
|
||||
'SECOND-STATUS-MARK-012345678901234567890' || exit 1
|
||||
fi
|
||||
$INNER set-option -g status-interval 0 || 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 respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-status-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has STATUS-LINE-MARK
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
}
|
||||
|
||||
open_popup()
|
||||
{
|
||||
y=$1
|
||||
height=$2
|
||||
$INNER display-popup -t "$CLIENT" -x 0 -y "$y" -w 16 -h "$height" -E \
|
||||
"sh -c 'printf POPUP-MARKER; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP-MARKER
|
||||
}
|
||||
|
||||
if [ "${STATUS_CASE:-all}" != top ]; then
|
||||
# A popup clipped against a one-line bottom status must restore that line.
|
||||
setup bottom 1
|
||||
open_popup 10 3
|
||||
mouse 0 10 8 M
|
||||
mouse 32 11 8 M
|
||||
mouse 32 25 4 M
|
||||
mouse 0 25 4 m
|
||||
wait_rows_restored 10 10
|
||||
fi
|
||||
|
||||
if [ "${STATUS_CASE:-all}" != bottom ]; then
|
||||
# A popup beginning in a two-line top status also covers the first window
|
||||
# rows. Restore both coordinate spaces after it moves.
|
||||
setup top 2
|
||||
open_popup 1 4
|
||||
mouse 0 10 1 M
|
||||
mouse 32 11 1 M
|
||||
mouse 32 25 7 M
|
||||
mouse 0 25 7 m
|
||||
wait_rows_restored 1 4
|
||||
fi
|
||||
|
||||
exit 0
|
||||
126
regress/popup-drag-wide-character.sh
Executable file
126
regress/popup-drag-wide-character.sh
Executable file
@@ -0,0 +1,126 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Damage at a popup edge must redraw complete grid characters. Drawing only a
|
||||
# wide character's base or padding cell leaves a two-cell hole behind.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpopup-wide-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
POPUP_PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$POPUP_PID" ] && kill "$POPUP_PID" 2>/dev/null
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_old_rows_restored()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
sed -n '3,5p' "$BASE" >"$DIR/want"
|
||||
sed -n '3,5p' "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "wide characters under the old popup edge were not restored"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
binmode STDOUT, ':encoding(UTF-8)';
|
||||
$| = 1;
|
||||
for my $row (1 .. 10) {
|
||||
print "\e[$row;1H", chr(0x754c) x 20;
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpopup-wide-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has '界界界'
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
|
||||
$INNER display-popup -t "$CLIENT" -x 5 -y 5 -w 10 -h 3 -E \
|
||||
"sh -c 'printf POPUP; exec sleep 100'" &
|
||||
POPUP_PID=$!
|
||||
wait_outer_has POPUP
|
||||
|
||||
# The first motion starts the drag; the second moves the popup away from its
|
||||
# old rectangle. Its odd x coordinate bisects the underlying double-width
|
||||
# cells at both edges.
|
||||
mouse 0 10 3 M
|
||||
mouse 32 11 3 M
|
||||
mouse 32 28 7 M
|
||||
mouse 0 28 7 m
|
||||
wait_old_rows_restored
|
||||
|
||||
exit 0
|
||||
112
regress/redraw-damage-only.sh
Executable file
112
regress/redraw-damage-only.sh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check a redraw callback which has no accompanying client redraw flags. A
|
||||
# wrapped row crossing a panned viewport cannot use the direct tty path.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Ldamage-only-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
TRIGGER=$DIR/trigger
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane did not contain $marker"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
for my $row (1 .. 12) {
|
||||
print "\e[$row;1H", 'o' x 79;
|
||||
}
|
||||
print "\e[1;1H";
|
||||
while (!-e $ENV{TRIGGER}) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
my $second = ('B' x 24) . 'DAMAGE-ONLY' . ('B' x 45);
|
||||
print "\e[5;1H", ('A' x 80), $second;
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 80 -y 12 \
|
||||
"TRIGGER='$TRIGGER' perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
$INNER refresh-client -t "$CLIENT" -R 20 || exit 1
|
||||
wait_outer_has oooooooooo
|
||||
|
||||
: >"$TRIGGER"
|
||||
wait_inner_has DAMAGE-ONLY
|
||||
wait_outer_has DAMAGE-ONLY
|
||||
|
||||
exit 0
|
||||
186
regress/redraw-multiclient.sh
Executable file
186
regress/redraw-multiclient.sh
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Redraw a moved floating pane on both attached clients viewing the same
|
||||
# window. Window redraw work must not be consumed by only one client.
|
||||
#
|
||||
# Uses ASCII pane borders (rather than the default UTF-8 box-drawing) because
|
||||
# this test nests a real tmux client inside another tmux's pane to get a
|
||||
# genuine terminal to capture from; that nested-tmux relay has been observed
|
||||
# to mis-render a cell that previously held a multi-byte UTF-8 border
|
||||
# character being overwritten later by plain content, on the outer instance's
|
||||
# own interpretation, independent of anything the inner tmux sends. That is a
|
||||
# nested-test-harness artifact, not a real tmux bug - confirmed by replaying
|
||||
# the exact same drag sequence against a real terminal (xterm), where it
|
||||
# never reproduces. ASCII borders avoid the artifact entirely.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lredraw-multi-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
count=$($INNER list-clients 2>/dev/null | wc -l)
|
||||
[ "$count" -eq 2 ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "two inner clients did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
target=$1
|
||||
marker=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer pane $target did not show $marker"
|
||||
}
|
||||
|
||||
wait_float_left()
|
||||
{
|
||||
comparison=$1
|
||||
limit=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
left=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
if [ "$comparison" = gt ] && [ "$left" -gt "$limit" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$comparison" = lt ] && [ "$left" -lt "$limit" ]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "floating pane did not move"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$2" "$3" "$4" "$5")
|
||||
$OUTER send-keys -t "$1" -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
drag_float()
|
||||
{
|
||||
target=$1
|
||||
startcol=$2
|
||||
startrow=$3
|
||||
endcol=$4
|
||||
mouse "$target" 0 "$startcol" "$startrow" M
|
||||
mouse "$target" 32 "$endcol" "$startrow" M
|
||||
mouse "$target" 0 "$endcol" "$startrow" m
|
||||
}
|
||||
|
||||
assert_scene()
|
||||
{
|
||||
target=$1
|
||||
base=$2
|
||||
firstcol=$3
|
||||
lastcol=$4
|
||||
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
||||
|
||||
# With ASCII (simple) borders every corner and junction is the same
|
||||
# '+', so one rectangular floating pane always draws exactly 4 of
|
||||
# them; more means a stale frame was left behind somewhere.
|
||||
corners=$(grep -o '+' "$CAPTURE" | wc -l)
|
||||
[ "$corners" -eq 4 ] ||
|
||||
fail "outer pane $target had $corners floating frames"
|
||||
|
||||
sed -n '6,11p' "$base" | cut -c"$firstcol-$lastcol" >"$DIR/want"
|
||||
sed -n '6,11p' "$CAPTURE" | cut -c"$firstcol-$lastcol" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" ||
|
||||
fail "outer pane $target did not restore the old floating area"
|
||||
}
|
||||
|
||||
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g default-command 'sleep 100' || exit 1
|
||||
$INNER set-option -g pane-border-lines simple || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER split-window -h -t outer:0.0 'sleep 100' || exit 1
|
||||
|
||||
PANES=$($OUTER list-panes -t outer:0 -F '#{pane_id} #{pane_left}')
|
||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||
RIGHT=$(echo "$PANES" | sort -k2 -n | tail -1 | cut -d' ' -f1)
|
||||
[ -n "$LEFT" ] && [ -n "$RIGHT" ] || fail "could not find outer panes"
|
||||
|
||||
$OUTER respawn-pane -k -t "$LEFT" \
|
||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
$OUTER respawn-pane -k -t "$RIGHT" \
|
||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_clients
|
||||
wait_outer_has "$LEFT" BG-ROW-19
|
||||
wait_outer_has "$RIGHT" BG-ROW-19
|
||||
$OUTER capture-pane -p -t "$LEFT" >"$DIR/base-left" || exit 1
|
||||
$OUTER capture-pane -p -t "$RIGHT" >"$DIR/base-right" || exit 1
|
||||
|
||||
FLOAT=$($INNER new-pane -dPF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||
fail "could not create floating pane"
|
||||
wait_outer_has "$LEFT" '+'
|
||||
wait_outer_has "$RIGHT" '+'
|
||||
|
||||
FTOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||
|
||||
# Move right through one client and require both clients to restore the old
|
||||
# left-hand footprint.
|
||||
drag_float "$LEFT" "$GRABCOL" "$FTOP" $((GRABCOL + 30))
|
||||
wait_float_left gt 30
|
||||
assert_scene "$LEFT" "$DIR/base-left" 1 20
|
||||
assert_scene "$RIGHT" "$DIR/base-right" 1 20
|
||||
|
||||
# Move back through the other client and check the old right-hand footprint.
|
||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||
drag_float "$RIGHT" "$GRABCOL" "$FTOP" $((GRABCOL - 30))
|
||||
wait_float_left lt 10
|
||||
assert_scene "$LEFT" "$DIR/base-left" 35 60
|
||||
assert_scene "$RIGHT" "$DIR/base-right" 35 60
|
||||
|
||||
exit 0
|
||||
208
regress/redraw-screen-write.sh
Executable file
208
regress/redraw-screen-write.sh
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check that full and region screen-write fallbacks update an attached client,
|
||||
# not only tmux's internal pane grid.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
EMITTER=$DIR/emitter.pl
|
||||
CAPTURE=$DIR/capture
|
||||
INNER=
|
||||
OUTER=
|
||||
N=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_outer_lacks()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" || return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client still showed $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane did not contain $marker"
|
||||
}
|
||||
|
||||
wait_inner_lacks()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" || return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane still contained $marker"
|
||||
}
|
||||
|
||||
setup()
|
||||
{
|
||||
mode=$1
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
N=$((N + 1))
|
||||
INNER="$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lredraw-write-outer-$$-$N -f/dev/null"
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 12 \
|
||||
"MODE=$mode READY='$DIR/ready-$N' TRIGGER='$DIR/trigger-$N' perl '$EMITTER'" ||
|
||||
exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
}
|
||||
|
||||
trigger()
|
||||
{
|
||||
: >"$DIR/trigger-$N-${1:-1}"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
my $mode = $ENV{MODE};
|
||||
my $ready = $ENV{READY};
|
||||
my $trigger = $ENV{TRIGGER};
|
||||
|
||||
sub fill_screen {
|
||||
my ($prefix) = @_;
|
||||
print "\e[2J\e[H";
|
||||
for my $row (0 .. 11) {
|
||||
printf "\e[%d;1H%s-ROW-%02d", $row + 1, $prefix, $row;
|
||||
}
|
||||
}
|
||||
|
||||
if ($mode eq 'ris') {
|
||||
fill_screen('RIS');
|
||||
} elsif ($mode eq 'alternate') {
|
||||
fill_screen('BASE');
|
||||
} elsif ($mode eq 'scroll') {
|
||||
fill_screen('SCROLL');
|
||||
} else {
|
||||
die "unknown mode $mode\n";
|
||||
}
|
||||
|
||||
open my $fh, '>', $ready or die "$ready: $!\n";
|
||||
close $fh;
|
||||
while (!-e "$trigger-1") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
if ($mode eq 'ris') {
|
||||
print "\ec";
|
||||
} elsif ($mode eq 'alternate') {
|
||||
print "\e[?1049h";
|
||||
fill_screen('ALT');
|
||||
while (!-e "$trigger-2") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
print "\e[?1049l";
|
||||
} else {
|
||||
print "\e[12;1H\r\nSCROLL-NEW";
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
# RIS clears the complete screen. The source pane and attached client must both
|
||||
# lose every old row.
|
||||
setup ris
|
||||
wait_outer_has RIS-ROW-11
|
||||
trigger
|
||||
wait_inner_lacks RIS-ROW
|
||||
wait_outer_lacks RIS-ROW
|
||||
|
||||
# Leaving the alternate screen restores every row of the base screen.
|
||||
setup alternate
|
||||
wait_outer_has BASE-ROW-11
|
||||
trigger
|
||||
wait_inner_has ALT-ROW-11
|
||||
wait_outer_has ALT-ROW-11
|
||||
wait_outer_lacks BASE-ROW
|
||||
trigger 2
|
||||
wait_inner_has BASE-ROW-11
|
||||
wait_outer_has BASE-ROW-11
|
||||
wait_outer_lacks ALT-ROW
|
||||
|
||||
# Scrolling a pane which is narrower than the terminal redraws its complete
|
||||
# region. Check the physical client row by row after the source grid shifts.
|
||||
setup scroll
|
||||
$INNER split-window -h -t inner:0 'sleep 100' || exit 1
|
||||
wait_outer_has SCROLL-ROW-11
|
||||
trigger
|
||||
wait_inner_has SCROLL-NEW
|
||||
wait_outer_has SCROLL-NEW
|
||||
row=1
|
||||
while [ "$row" -le 11 ]; do
|
||||
expected=$(printf 'SCROLL-ROW-%02d' "$row")
|
||||
actual=$(sed -n "${row}p" "$CAPTURE")
|
||||
case "$actual" in
|
||||
"$expected"*) ;;
|
||||
*) fail "outer row $row was not redrawn as $expected" ;;
|
||||
esac
|
||||
row=$((row + 1))
|
||||
done
|
||||
actual=$(sed -n '12p' "$CAPTURE")
|
||||
case "$actual" in
|
||||
SCROLL-NEW*) ;;
|
||||
*) fail "outer bottom row was not redrawn as SCROLL-NEW" ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
81
regress/switch-client-redraw.sh
Executable file
81
regress/switch-client-redraw.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Switching windows in the same session must redraw the attached client. The
|
||||
# session object is shared, so its current winlink cannot be compared after it
|
||||
# has already been changed.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
INNER="$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lswitch-redraw-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 8 \
|
||||
"printf '\033[2J\033[HA-WINDOW'; exec sleep 100" || exit 1
|
||||
$INNER new-window -d -t inner:1 \
|
||||
"printf '\033[2J\033[HB-WINDOW'; exec sleep 100" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lswitch-redraw-inner-$$ -f/dev/null attach-session -t inner:0" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has A-WINDOW
|
||||
|
||||
$INNER switch-client -c "$CLIENT" -t inner:1.0 || exit 1
|
||||
[ "$($INNER display-message -p -t inner '#{window_index}')" -eq 1 ] ||
|
||||
fail "server did not select window 1"
|
||||
wait_outer_has B-WINDOW
|
||||
|
||||
exit 0
|
||||
10
resize.c
10
resize.c
@@ -348,10 +348,10 @@ recalculate_size_skip_client(struct client *loop, __unused int type,
|
||||
* is not the current window - this is used for aggressive-resize.
|
||||
* Otherwise skip any session that doesn't contain the window.
|
||||
*/
|
||||
if (active_get_effective_window(loop, loop->session) == NULL)
|
||||
if (loop->session->curw == NULL)
|
||||
return (1);
|
||||
if (current)
|
||||
return (active_get_effective_window(loop, loop->session) != w);
|
||||
return (loop->session->curw->window != w);
|
||||
return (session_has(loop->session, w) == 0);
|
||||
}
|
||||
|
||||
@@ -386,10 +386,12 @@ recalculate_size(struct window *w, int now)
|
||||
* got a resize scheduled, then use the new size; otherwise the old.
|
||||
*/
|
||||
if (w->flags & WINDOW_RESIZE) {
|
||||
if (!now && changed && w->new_sx == sx && w->new_sy == sy)
|
||||
if (!now && changed && w->new_sx == sx && w->new_sy == sy &&
|
||||
w->new_xpixel == xpixel && w->new_ypixel == ypixel)
|
||||
changed = 0;
|
||||
} else {
|
||||
if (!now && changed && w->sx == sx && w->sy == sy)
|
||||
if (!now && changed && w->sx == sx && w->sy == sy &&
|
||||
w->xpixel == xpixel && w->ypixel == ypixel)
|
||||
changed = 0;
|
||||
}
|
||||
|
||||
|
||||
627
screen-redraw.c
627
screen-redraw.c
@@ -99,6 +99,12 @@ enum redraw_span_type {
|
||||
#define REDRAW_ALL 0x7fffffff
|
||||
#define REDRAW_IS_ALL(flags) ((flags) == REDRAW_ALL)
|
||||
|
||||
enum redraw_image_phase {
|
||||
REDRAW_IMAGES_BEFORE,
|
||||
REDRAW_TEXT,
|
||||
REDRAW_IMAGES_AFTER
|
||||
};
|
||||
|
||||
/* UTF-8 isolate characters. */
|
||||
#define REDRAW_START_ISOLATE "\342\201\246"
|
||||
#define REDRAW_END_ISOLATE "\342\201\251"
|
||||
@@ -205,6 +211,24 @@ struct redraw_scene {
|
||||
u_int oy;
|
||||
};
|
||||
|
||||
/* A single damaged window-coordinate rectangle. */
|
||||
struct redraw_damage {
|
||||
u_int x;
|
||||
u_int y;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
int flags;
|
||||
#define REDRAW_DAMAGE_SCROLL 0x1
|
||||
|
||||
TAILQ_ENTRY(redraw_damage) entry;
|
||||
};
|
||||
|
||||
/*
|
||||
* Cap on the number of pending damage rectangles per window before they are
|
||||
* collapsed into a single rectangle covering their union.
|
||||
*/
|
||||
#define REDRAW_DAMAGE_MAX 16
|
||||
|
||||
/* Cell for building the scene. */
|
||||
struct redraw_build_cell {
|
||||
struct redraw_span_data data;
|
||||
@@ -295,7 +319,7 @@ static void
|
||||
redraw_set_context(struct client *c, struct redraw_build_ctx *bctx)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct window *w = active_get_effective_window(c, s);
|
||||
struct window *w = s->curw->window;
|
||||
|
||||
memset(bctx, 0, sizeof *bctx);
|
||||
bctx->c = c;
|
||||
@@ -969,7 +993,7 @@ static struct redraw_scene *
|
||||
redraw_make_scene(struct client *c)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct window *w = active_get_effective_window(c, s);
|
||||
struct window *w = s->curw->window;
|
||||
struct redraw_build_ctx bctx;
|
||||
struct redraw_scene *scene;
|
||||
struct redraw_build_cell *bc, *last;
|
||||
@@ -1057,6 +1081,13 @@ redraw_free_scene(struct redraw_scene *scene)
|
||||
free(scene);
|
||||
}
|
||||
|
||||
/* Does a client's cached scene show this window? */
|
||||
int
|
||||
redraw_client_has_window(struct client *c, struct window *w)
|
||||
{
|
||||
return (c->redraw_scene != NULL && c->redraw_scene->w == w);
|
||||
}
|
||||
|
||||
/* Mark a window's cached redraw scenes as out of date. */
|
||||
void
|
||||
redraw_invalidate_scene(struct window *w)
|
||||
@@ -1064,6 +1095,184 @@ redraw_invalidate_scene(struct window *w)
|
||||
w->redraw_scene_generation++;
|
||||
}
|
||||
|
||||
/* Free all pending damage for a window. */
|
||||
void
|
||||
redraw_free_damage(struct window *w)
|
||||
{
|
||||
struct redraw_damage *rd, *rd1;
|
||||
|
||||
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||
free(rd);
|
||||
}
|
||||
w->damage_count = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
w->image_scroll_pending = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Collapse all pending damage for a window into one rectangle - its union. */
|
||||
static void
|
||||
redraw_collapse_damage(struct window *w)
|
||||
{
|
||||
struct redraw_damage *rd, *rd1, *first;
|
||||
u_int x0, y0, x1, y1;
|
||||
|
||||
first = TAILQ_FIRST(&w->damage);
|
||||
if (first == NULL)
|
||||
return;
|
||||
x0 = first->x;
|
||||
y0 = first->y;
|
||||
x1 = first->x + first->sx;
|
||||
y1 = first->y + first->sy;
|
||||
|
||||
TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) {
|
||||
if (rd->x < x0)
|
||||
x0 = rd->x;
|
||||
if (rd->y < y0)
|
||||
y0 = rd->y;
|
||||
if (rd->x + rd->sx > x1)
|
||||
x1 = rd->x + rd->sx;
|
||||
if (rd->y + rd->sy > y1)
|
||||
y1 = rd->y + rd->sy;
|
||||
first->flags &= rd->flags;
|
||||
if (rd != first) {
|
||||
TAILQ_REMOVE(&w->damage, rd, entry);
|
||||
free(rd);
|
||||
}
|
||||
}
|
||||
|
||||
first->x = x0;
|
||||
first->y = y0;
|
||||
first->sx = x1 - x0;
|
||||
first->sy = y1 - y0;
|
||||
w->damage_count = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Record a damaged window-coordinate rectangle. Clips it to the window,
|
||||
* merges it with an existing rectangle where doing so does not make the
|
||||
* result substantially larger than the two combined, and collapses the
|
||||
* whole list to its union once it grows past a modest cap.
|
||||
*
|
||||
* This only records damage - nothing consumes it yet.
|
||||
*/
|
||||
static void
|
||||
redraw_damage_window_flags(struct window *w, u_int x, u_int y, u_int sx,
|
||||
u_int sy, int flags)
|
||||
{
|
||||
struct redraw_damage *rd;
|
||||
u_int x0, y0, x1, y1, area, union_area;
|
||||
|
||||
if (x >= w->sx || y >= w->sy)
|
||||
return;
|
||||
if (x + sx > w->sx)
|
||||
sx = w->sx - x;
|
||||
if (y + sy > w->sy)
|
||||
sy = w->sy - y;
|
||||
if (sx == 0 || sy == 0)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||
/* Skip unless overlapping or directly adjacent. */
|
||||
if (x > rd->x + rd->sx || rd->x > x + sx ||
|
||||
y > rd->y + rd->sy || rd->y > y + sy)
|
||||
continue;
|
||||
|
||||
x0 = (x < rd->x) ? x : rd->x;
|
||||
y0 = (y < rd->y) ? y : rd->y;
|
||||
x1 = (x + sx > rd->x + rd->sx) ? x + sx : rd->x + rd->sx;
|
||||
y1 = (y + sy > rd->y + rd->sy) ? y + sy : rd->y + rd->sy;
|
||||
|
||||
area = sx * sy + rd->sx * rd->sy;
|
||||
union_area = (x1 - x0) * (y1 - y0);
|
||||
if (union_area > 2 * area)
|
||||
continue;
|
||||
|
||||
rd->x = x0;
|
||||
rd->y = y0;
|
||||
rd->sx = x1 - x0;
|
||||
rd->sy = y1 - y0;
|
||||
rd->flags &= flags;
|
||||
return;
|
||||
}
|
||||
|
||||
rd = xcalloc(1, sizeof *rd);
|
||||
rd->x = x;
|
||||
rd->y = y;
|
||||
rd->sx = sx;
|
||||
rd->sy = sy;
|
||||
rd->flags = flags;
|
||||
TAILQ_INSERT_TAIL(&w->damage, rd, entry);
|
||||
w->damage_count++;
|
||||
|
||||
if (w->damage_count > REDRAW_DAMAGE_MAX)
|
||||
redraw_collapse_damage(w);
|
||||
}
|
||||
|
||||
void
|
||||
redraw_damage_window(struct window *w, u_int x, u_int y, u_int sx, u_int sy)
|
||||
{
|
||||
redraw_damage_window_flags(w, x, y, sx, sy, 0);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Record damage caused by an image moving with a terminal scroll. */
|
||||
void
|
||||
redraw_damage_window_scroll(struct window *w, u_int x, u_int y, u_int sx,
|
||||
u_int sy)
|
||||
{
|
||||
if (!w->image_scroll_pending) {
|
||||
w->image_scroll_epoch++;
|
||||
w->image_scroll_pending = 1;
|
||||
}
|
||||
redraw_damage_window_flags(w, x, y, sx, sy, REDRAW_DAMAGE_SCROLL);
|
||||
}
|
||||
|
||||
/* Remember whether this client used terminal scrolling for the damage. */
|
||||
void
|
||||
redraw_image_scroll_result(struct tty *tty, const struct tty_ctx *ctx,
|
||||
int failed)
|
||||
{
|
||||
struct window_pane *wp = ctx->arg;
|
||||
struct window *w;
|
||||
|
||||
if (wp == NULL || (w = wp->window) == NULL || !w->image_scroll_pending)
|
||||
return;
|
||||
if (tty->image_scroll_window != w ||
|
||||
tty->image_scroll_epoch != w->image_scroll_epoch) {
|
||||
tty->image_scroll_window = w;
|
||||
tty->image_scroll_epoch = w->image_scroll_epoch;
|
||||
tty->image_scroll_failed = 0;
|
||||
}
|
||||
if (failed)
|
||||
tty->image_scroll_failed = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Record damage for every pane border-status row in a window. */
|
||||
static void
|
||||
redraw_damage_window_pane_status(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
u_int y;
|
||||
int status;
|
||||
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
status = window_pane_get_pane_status(wp);
|
||||
if (status == PANE_STATUS_TOP) {
|
||||
if (wp->yoff == 0)
|
||||
continue;
|
||||
y = wp->yoff - 1;
|
||||
} else if (status == PANE_STATUS_BOTTOM)
|
||||
y = wp->yoff + wp->sy;
|
||||
else
|
||||
continue;
|
||||
if (y < w->sy)
|
||||
redraw_damage_window(w, 0, y, w->sx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark all cached redraw scenes as out of date. */
|
||||
void
|
||||
redraw_invalidate_all_scenes(void)
|
||||
@@ -1079,7 +1288,7 @@ static struct redraw_scene *
|
||||
redraw_get_scene(struct client *c)
|
||||
{
|
||||
struct redraw_scene *scene = c->redraw_scene;
|
||||
struct window *w = active_get_effective_window(c, c->session);
|
||||
struct window *w = c->session->curw->window;
|
||||
const char *reason = NULL;
|
||||
u_int ox, oy, sx, sy;
|
||||
|
||||
@@ -1106,7 +1315,8 @@ redraw_get_scene(struct client *c)
|
||||
/* Draw a pane span. */
|
||||
static void
|
||||
redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
|
||||
struct redraw_span *span, u_int x, u_int y, u_int n)
|
||||
struct redraw_span *span, u_int x, u_int y, u_int n,
|
||||
enum redraw_image_phase phase)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct client *c = scene->c;
|
||||
@@ -1124,7 +1334,14 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
|
||||
|
||||
px = span->data.p.px + (x - span->x);
|
||||
py = span->data.p.py;
|
||||
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (phase != REDRAW_TEXT)
|
||||
image_draw_line(tty, s, px, py, n, x, y,
|
||||
phase == REDRAW_IMAGES_BEFORE, &style_ctx);
|
||||
else
|
||||
#endif
|
||||
if (phase == REDRAW_TEXT)
|
||||
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
|
||||
}
|
||||
|
||||
/* Get default border style for spans without a pane. */
|
||||
@@ -1140,7 +1357,7 @@ redraw_get_default_border_style(struct redraw_draw_ctx *dctx,
|
||||
struct grid_cell *dgc = &dctx->default_gc;
|
||||
|
||||
if (~dctx->flags & REDRAW_DEFAULT_SET) {
|
||||
ft = format_create_defaults(NULL, c, s, NULL, NULL);
|
||||
ft = format_create_defaults(NULL, c, s, s->curw, NULL);
|
||||
memcpy(dgc, &grid_default_cell, sizeof *dgc);
|
||||
style_add(dgc, oo, "pane-border-style", ft);
|
||||
format_free(ft);
|
||||
@@ -1376,10 +1593,15 @@ redraw_draw_menu_span(struct redraw_draw_ctx *dctx,
|
||||
tty_draw_line(tty, s, px, span->data.m.py, n, x, y, NULL);
|
||||
}
|
||||
|
||||
/* Draw a span. */
|
||||
/*
|
||||
* Draw a span, restricted to [clip_x, clip_x + clip_n) - a caller drawing
|
||||
* the whole span passes the span's own x/width here; a caller drawing only
|
||||
* a damaged sub-range passes that range instead. Overlay clipping (menus,
|
||||
* popups) is then applied on top of this, exactly as before.
|
||||
*/
|
||||
static void
|
||||
redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||
u_int y)
|
||||
u_int y, u_int clip_x, u_int clip_n, enum redraw_image_phase phase)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct redraw_span_data *data = &span->data;
|
||||
@@ -1393,17 +1615,19 @@ redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span,
|
||||
if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS)
|
||||
return;
|
||||
|
||||
r = tty_check_overlay_range(tty, span->x, y, span->width);
|
||||
r = tty_check_overlay_range(tty, clip_x, y, clip_n);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
rr = &r->ranges[i];
|
||||
if (rr->nx == 0)
|
||||
continue;
|
||||
x = rr->px;
|
||||
n = rr->nx;
|
||||
if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE)
|
||||
continue;
|
||||
|
||||
switch (span->data.type) {
|
||||
case REDRAW_SPAN_PANE:
|
||||
redraw_draw_pane_span(dctx, span, x, y, n);
|
||||
redraw_draw_pane_span(dctx, span, x, y, n, phase);
|
||||
break;
|
||||
case REDRAW_SPAN_BORDER:
|
||||
case REDRAW_SPAN_EMPTY:
|
||||
@@ -1444,24 +1668,55 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
||||
if (bottom > (int)scene->sy)
|
||||
bottom = scene->sy;
|
||||
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
if (flags & REDRAW_PANE) {
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* Only erase cells this pane currently owns in the scene, not its raw
|
||||
* geometry - a floating pane may be occluding part of this pane's
|
||||
* rectangle, and erasing under it would leave those cells blank with
|
||||
* nothing to redraw them back in.
|
||||
*/
|
||||
if (flags & REDRAW_PANE) {
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
spans = &line->spans[REDRAW_SPAN_PANE];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.p.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
if (span->data.p.wp == wp) {
|
||||
image_redraw_start(&scene->c->tty,
|
||||
span->x, cy, span->width, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flags & REDRAW_PANE_SCROLLBAR) {
|
||||
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.sb.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_IMAGES_AFTER; phase++) {
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
if (flags & REDRAW_PANE) {
|
||||
spans = &line->spans[REDRAW_SPAN_PANE];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.p.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy, span->x,
|
||||
span->width, phase);
|
||||
}
|
||||
}
|
||||
if (phase == REDRAW_TEXT &&
|
||||
(flags & REDRAW_PANE_SCROLLBAR)) {
|
||||
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->data.sb.wp == wp)
|
||||
redraw_draw_span(dctx, span, cy, span->x,
|
||||
span->width, phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1477,13 +1732,17 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
struct redraw_span *span;
|
||||
u_int y, cy, type;
|
||||
|
||||
for (y = 0; y < scene->sy; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_IMAGES_AFTER; phase++) {
|
||||
for (y = 0; y < scene->sy; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + y;
|
||||
else
|
||||
cy = y;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE)
|
||||
continue;
|
||||
if (!REDRAW_IS_ALL(flags)) {
|
||||
switch (type) {
|
||||
case REDRAW_SPAN_PANE:
|
||||
@@ -1520,7 +1779,9 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
}
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
redraw_draw_span(dctx, span, cy, span->x, span->width,
|
||||
phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1541,7 +1802,8 @@ redraw_draw_menu_lines(struct redraw_draw_ctx *dctx)
|
||||
else
|
||||
cy = y;
|
||||
TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry)
|
||||
redraw_draw_span(dctx, span, cy);
|
||||
redraw_draw_span(dctx, span, cy, span->x, span->width,
|
||||
REDRAW_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1603,16 +1865,14 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx,
|
||||
struct session *s = c->session;
|
||||
struct options *oo = s->options;
|
||||
struct tty *tty = &c->tty;
|
||||
struct winlink *wl;
|
||||
u_int lines;
|
||||
|
||||
memset(dctx, 0, sizeof *dctx);
|
||||
dctx->scene = scene;
|
||||
|
||||
wl = active_get_effective_winlink(c, s);
|
||||
if (server_is_marked(s, wl, marked_pane.wp))
|
||||
if (server_is_marked(s, s->curw, marked_pane.wp))
|
||||
dctx->marked = marked_pane.wp;
|
||||
dctx->active = active_get_effective_window(c, s)->active;
|
||||
dctx->active = s->curw->window->active;
|
||||
|
||||
lines = status_line_size(c);
|
||||
if (options_get_number(oo, "status-position") == 0)
|
||||
@@ -1623,6 +1883,24 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx,
|
||||
dctx->flags |= REDRAW_ISOLATES;
|
||||
}
|
||||
|
||||
/* Build a pane prompt into a one-line screen. */
|
||||
static void
|
||||
redraw_make_pane_prompt(struct window_pane *wp, struct screen *screen)
|
||||
{
|
||||
struct screen_write_ctx ctx;
|
||||
struct prompt_draw_data pdd;
|
||||
|
||||
screen_init(screen, wp->sx, 1, 0);
|
||||
screen_write_start(&ctx, screen);
|
||||
pdd.ctx = &ctx;
|
||||
pdd.cursor_x = &wp->prompt_cx;
|
||||
pdd.area_x = 0;
|
||||
pdd.area_width = wp->sx;
|
||||
pdd.prompt_line = 0;
|
||||
prompt_draw(wp->prompt, &pdd);
|
||||
screen_write_stop(&ctx);
|
||||
}
|
||||
|
||||
/* Draw a pane's prompt over its content. */
|
||||
static void
|
||||
redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
@@ -1631,8 +1909,6 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
struct client *c = scene->c;
|
||||
struct tty *tty = &c->tty;
|
||||
struct screen screen;
|
||||
struct screen_write_ctx ctx;
|
||||
struct prompt_draw_data pdd;
|
||||
int ox = scene->ox, oy = scene->oy;
|
||||
int sx = scene->sx, sy = scene->sy;
|
||||
int line, cy, px, offset, width, wy;
|
||||
@@ -1665,16 +1941,7 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp)
|
||||
if (px + width > sx)
|
||||
width = sx - px;
|
||||
|
||||
screen_init(&screen, wp->sx, 1, 0);
|
||||
screen_write_start(&ctx, &screen);
|
||||
pdd.ctx = &ctx;
|
||||
pdd.cursor_x = &wp->prompt_cx;
|
||||
pdd.area_x = 0;
|
||||
pdd.area_width = wp->sx;
|
||||
pdd.prompt_line = 0;
|
||||
prompt_draw(wp->prompt, &pdd);
|
||||
screen_write_stop(&ctx);
|
||||
|
||||
redraw_make_pane_prompt(wp, &screen);
|
||||
tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL);
|
||||
screen_free(&screen);
|
||||
}
|
||||
@@ -1685,7 +1952,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
{
|
||||
struct redraw_draw_ctx dctx;
|
||||
struct session *s = c->session;
|
||||
struct window *w = active_get_effective_window(c, s);
|
||||
struct window *w = s->curw->window;
|
||||
struct tty *tty = &c->tty;
|
||||
struct screen *sl;
|
||||
struct redraw_scene *scene;
|
||||
@@ -1706,7 +1973,8 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
redraw = status_prompt_redraw(c);
|
||||
else
|
||||
redraw = status_redraw(c);
|
||||
if (!redraw && !REDRAW_IS_ALL(flags)) {
|
||||
if (!redraw && (~c->flags & CLIENT_REDRAWSTATUSALWAYS) &&
|
||||
!REDRAW_IS_ALL(flags)) {
|
||||
flags &= ~REDRAW_STATUS;
|
||||
if (flags == 0)
|
||||
return;
|
||||
@@ -1773,12 +2041,27 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
}
|
||||
}
|
||||
tty_sync_start(tty);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (wp == NULL && (flags & REDRAW_PANE)) {
|
||||
y = 0;
|
||||
if (dctx.flags & REDRAW_STATUS_TOP)
|
||||
y = dctx.status_lines;
|
||||
image_redraw_start(tty, 0, y, scene->sx, scene->sy);
|
||||
}
|
||||
#endif
|
||||
tty_update_mode(tty, tty->mode & ~CURSOR_MODES, NULL);
|
||||
|
||||
if (wp != NULL)
|
||||
redraw_draw_pane_lines(&dctx, wp, flags);
|
||||
else
|
||||
redraw_draw_lines(&dctx, flags);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if ((flags & REDRAW_PANE) &&
|
||||
(image_backend_flags(tty) &
|
||||
(IMAGE_BACKEND_GRAPHICAL|IMAGE_BACKEND_SCROLLS)) ==
|
||||
IMAGE_BACKEND_GRAPHICAL)
|
||||
redraw_damage_window_pane_status(scene->w);
|
||||
#endif
|
||||
|
||||
if (flags & REDRAW_PANE) {
|
||||
if (wp != NULL)
|
||||
@@ -1819,15 +2102,6 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
tty_reset(tty);
|
||||
tty_sync_end(tty);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (wp != NULL)
|
||||
tty_draw_images(c, wp);
|
||||
else {
|
||||
TAILQ_FOREACH(loop, &scene->w->panes, entry)
|
||||
tty_draw_images(c, loop);
|
||||
}
|
||||
#endif
|
||||
|
||||
log_debug("%s: finished @%u redraw", c->name, scene->w->id);
|
||||
}
|
||||
|
||||
@@ -1879,13 +2153,13 @@ redraw_screen(struct client *c)
|
||||
} else {
|
||||
if (c->flags & CLIENT_REDRAWBORDERS)
|
||||
flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS);
|
||||
if (c->flags & CLIENT_REDRAWSTATUS)
|
||||
if (c->flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))
|
||||
flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS);
|
||||
if (c->flags & CLIENT_REDRAWOVERLAY)
|
||||
flags |= REDRAW_OVERLAY;
|
||||
if (c->flags & CLIENT_REDRAWMENU)
|
||||
flags |= REDRAW_MENU;
|
||||
if (active_get_effective_window(c, c->session)->menu != NULL)
|
||||
if (c->session->curw->window->menu != NULL)
|
||||
flags |= REDRAW_MENU;
|
||||
if (flags != 0)
|
||||
redraw_draw(c, NULL, flags);
|
||||
@@ -1897,7 +2171,7 @@ void
|
||||
redraw_pane(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
redraw_draw(c, wp, REDRAW_PANE|REDRAW_PANE_SCROLLBAR);
|
||||
if (active_get_effective_window(c, c->session)->menu != NULL)
|
||||
if (c->session->curw->window->menu != NULL)
|
||||
redraw_draw(c, NULL, REDRAW_MENU);
|
||||
}
|
||||
|
||||
@@ -1907,3 +2181,230 @@ redraw_pane_scrollbar(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR);
|
||||
}
|
||||
|
||||
/*
|
||||
* A REDRAW_SPAN_STATUS span within a damaged rectangle needs its content
|
||||
* rebuilt and force-drawn regardless of whether that content has logically
|
||||
* changed. window_make_pane_status()'s grid_compare() only tells us
|
||||
* whether the *content* changed, not whether the physical cells were
|
||||
* disturbed by something else (e.g. a floating pane sliding across this
|
||||
* row) - and being inside a damage rectangle already proves that happened.
|
||||
* Without this, redraw_draw_span() silently skips REDRAW_SPAN_STATUS spans
|
||||
* whenever PANE_NEWSTATUS is not set, leaving a pane's border-status title
|
||||
* blank until some unrelated redraw happens to touch it (e.g. a focus
|
||||
* change or window resize).
|
||||
*/
|
||||
static void
|
||||
redraw_damage_refresh_status(struct redraw_draw_ctx *dctx,
|
||||
struct window_pane *wp)
|
||||
{
|
||||
struct redraw_span *first;
|
||||
u_int width;
|
||||
|
||||
if (wp->flags & PANE_NEWSTATUS)
|
||||
return;
|
||||
width = redraw_pane_status_width(dctx, wp, &first);
|
||||
if (width == 0)
|
||||
return;
|
||||
window_make_pane_status(wp, dctx->scene->c, width, first);
|
||||
wp->flags |= PANE_NEWSTATUS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Grow a clipped span range by one cell on either edge that isn't already at
|
||||
* the span's own boundary. A clip edge that lands mid-character (this is a
|
||||
* damage rectangle, so its edges are geometric and have no idea what's in
|
||||
* the grid) may be sitting on the second, padding half of a wide character
|
||||
* whose first half falls just outside the requested range - growing by one
|
||||
* cell is always enough to pull the whole character back in, since no grid
|
||||
* cell is ever wider than two columns, and clamping to the span's own x and
|
||||
* width keeps this from bleeding into a neighbouring span.
|
||||
*/
|
||||
static void
|
||||
redraw_damage_grow_span_clip(struct redraw_span *span, u_int *xp, u_int *endp)
|
||||
{
|
||||
if (*xp > span->x)
|
||||
(*xp)--;
|
||||
if (*endp < span->x + span->width)
|
||||
(*endp)++;
|
||||
}
|
||||
|
||||
/* Recompose a pane's prompt over a damaged section of its display row. */
|
||||
static void
|
||||
redraw_damage_draw_pane_prompt(struct redraw_draw_ctx *dctx,
|
||||
struct redraw_span *span, u_int y, u_int x, u_int n)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct window_pane *wp = span->data.p.wp;
|
||||
struct tty *tty = &scene->c->tty;
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *rr;
|
||||
struct screen screen;
|
||||
u_int i, px, width, prompt_y;
|
||||
|
||||
if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0)
|
||||
return;
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
prompt_y = 0;
|
||||
else
|
||||
prompt_y = wp->sy - 1;
|
||||
if (span->data.p.py != prompt_y)
|
||||
return;
|
||||
|
||||
redraw_make_pane_prompt(wp, &screen);
|
||||
r = tty_check_overlay_range(tty, x, y, n);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
rr = &r->ranges[i];
|
||||
if (rr->nx == 0)
|
||||
continue;
|
||||
px = span->data.p.px + (rr->px - span->x);
|
||||
if (px >= screen_size_x(&screen))
|
||||
continue;
|
||||
width = rr->nx;
|
||||
if (width > screen_size_x(&screen) - px)
|
||||
width = screen_size_x(&screen) - px;
|
||||
tty_draw_line(tty, &screen, px, 0, width, rr->px, y, NULL);
|
||||
}
|
||||
screen_free(&screen);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compose exactly the cells within a damaged rectangle (already in this
|
||||
* client's own scene coordinates), rather than a whole pane. For each row
|
||||
* in range, every span of every type whose x-range intersects the
|
||||
* rectangle is drawn restricted to just the intersected sub-range.
|
||||
*/
|
||||
static void
|
||||
redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||
u_int sx, u_int sy, int skip_images)
|
||||
{
|
||||
struct redraw_scene *scene = dctx->scene;
|
||||
struct redraw_line *line;
|
||||
struct redraw_spans *spans;
|
||||
struct redraw_span *span;
|
||||
u_int cy, yy, clip_x, clip_end, type;
|
||||
|
||||
if (x >= scene->sx || y >= scene->sy)
|
||||
return;
|
||||
if (x + sx > scene->sx)
|
||||
sx = scene->sx - x;
|
||||
if (y + sy > scene->sy)
|
||||
sy = scene->sy - y;
|
||||
if (sx == 0 || sy == 0)
|
||||
return;
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_IMAGES_AFTER; phase++) {
|
||||
if (skip_images && phase != REDRAW_TEXT)
|
||||
continue;
|
||||
for (yy = y; yy < y + sy; yy++) {
|
||||
line = &scene->lines[yy];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + yy;
|
||||
else
|
||||
cy = yy;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE)
|
||||
continue;
|
||||
if (type == REDRAW_SPAN_STATUS)
|
||||
continue;
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
clip_x = (span->x > x) ? span->x : x;
|
||||
clip_end = (span->x + span->width < x + sx) ?
|
||||
span->x + span->width : x + sx;
|
||||
if (clip_end <= clip_x)
|
||||
continue;
|
||||
if (type == REDRAW_SPAN_STATUS) {
|
||||
redraw_damage_refresh_status(dctx,
|
||||
span->data.st.wp);
|
||||
}
|
||||
redraw_damage_grow_span_clip(span, &clip_x,
|
||||
&clip_end);
|
||||
redraw_draw_span(dctx, span, cy, clip_x,
|
||||
clip_end - clip_x, phase);
|
||||
if (phase == REDRAW_TEXT && type == REDRAW_SPAN_PANE) {
|
||||
redraw_damage_draw_pane_prompt(dctx,
|
||||
span, cy, clip_x,
|
||||
clip_end - clip_x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* SIXEL image output may disturb status cells; compose them last. */
|
||||
for (yy = y; yy < y + sy; yy++) {
|
||||
line = &scene->lines[yy];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
cy = dctx->status_lines + yy;
|
||||
else
|
||||
cy = yy;
|
||||
spans = &line->spans[REDRAW_SPAN_STATUS];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
clip_x = (span->x > x) ? span->x : x;
|
||||
clip_end = (span->x + span->width < x + sx) ?
|
||||
span->x + span->width : x + sx;
|
||||
if (clip_end <= clip_x)
|
||||
continue;
|
||||
redraw_damage_refresh_status(dctx, span->data.st.wp);
|
||||
redraw_damage_grow_span_clip(span, &clip_x, &clip_end);
|
||||
redraw_draw_span(dctx, span, cy, clip_x, clip_end - clip_x,
|
||||
REDRAW_TEXT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Consume a client's window's pending damage by composing exactly the
|
||||
* damaged cells, after clipping each rectangle to what this client can see
|
||||
* and translating it into this client's own scene coordinates.
|
||||
*
|
||||
* Unlike redraw_pane(), this does not redraw a whole pane's worth of cells
|
||||
* for a small disturbance - only the cells within the (clipped) rectangle
|
||||
* are touched, via redraw_draw_damage_rect().
|
||||
*/
|
||||
void
|
||||
redraw_client_damage(struct client *c)
|
||||
{
|
||||
struct window *w = c->session->curw->window;
|
||||
struct redraw_scene *scene;
|
||||
struct redraw_draw_ctx dctx;
|
||||
struct redraw_damage *rd;
|
||||
u_int ox, oy, sx, sy, x0, y0, x1, y1;
|
||||
int skip_images;
|
||||
|
||||
if (TAILQ_EMPTY(&w->damage))
|
||||
return;
|
||||
|
||||
scene = redraw_get_scene(c);
|
||||
if (scene == NULL)
|
||||
return;
|
||||
redraw_set_draw_context(&dctx, scene);
|
||||
redraw_get_window_offset(c, &ox, &oy, &sx, &sy);
|
||||
|
||||
tty_sync_start(&c->tty);
|
||||
tty_update_mode(&c->tty, c->tty.mode & ~CURSOR_MODES, NULL);
|
||||
|
||||
TAILQ_FOREACH(rd, &w->damage, entry) {
|
||||
skip_images = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if ((rd->flags & REDRAW_DAMAGE_SCROLL) &&
|
||||
(image_backend_flags(&c->tty) & IMAGE_BACKEND_SCROLLS) &&
|
||||
c->tty.image_scroll_window == w &&
|
||||
c->tty.image_scroll_epoch == w->image_scroll_epoch &&
|
||||
!c->tty.image_scroll_failed)
|
||||
skip_images = 1;
|
||||
#endif
|
||||
x0 = (rd->x > ox) ? rd->x : ox;
|
||||
y0 = (rd->y > oy) ? rd->y : oy;
|
||||
x1 = (rd->x + rd->sx < ox + sx) ? rd->x + rd->sx : ox + sx;
|
||||
y1 = (rd->y + rd->sy < oy + sy) ? rd->y + rd->sy : oy + sy;
|
||||
if (x0 >= x1 || y0 >= y1)
|
||||
continue;
|
||||
log_debug("%s: %s composing damage %u,%u %ux%u", __func__,
|
||||
c->name, x0 - ox, y0 - oy, x1 - x0, y1 - y0);
|
||||
redraw_draw_damage_rect(&dctx, x0 - ox, y0 - oy, x1 - x0,
|
||||
y1 - y0, skip_images);
|
||||
}
|
||||
}
|
||||
|
||||
198
screen-write.c
198
screen-write.c
@@ -34,6 +34,10 @@ static void screen_write_collect_clear(struct screen_write_ctx *, u_int,
|
||||
static void screen_write_collect_scroll(struct screen_write_ctx *, u_int);
|
||||
static void screen_write_collect_flush(struct screen_write_ctx *, int,
|
||||
const char *);
|
||||
#ifdef ENABLE_IMAGES
|
||||
static void screen_write_image_damage(struct screen_write_ctx *, u_int,
|
||||
u_int, u_int, u_int);
|
||||
#endif
|
||||
static int screen_write_overwrite(struct screen_write_ctx *,
|
||||
struct grid_cell *, u_int);
|
||||
static int screen_write_combine(struct screen_write_ctx *,
|
||||
@@ -120,14 +124,20 @@ screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy)
|
||||
evtimer_add(&w->offset_timer, &tv);
|
||||
}
|
||||
|
||||
/* Do a full redraw. */
|
||||
/*
|
||||
* Called when a write could not be applied directly to the terminal and
|
||||
* needs a redraw instead. Report damage for the requested rows. wp->yoff is
|
||||
* already adjusted past any top pane-border-status row, so wp->yoff + py is
|
||||
* the correct window-coordinate row.
|
||||
*/
|
||||
static void
|
||||
screen_write_redraw_cb(const struct tty_ctx *ttyctx)
|
||||
screen_write_redraw_cb(const struct tty_ctx *ttyctx, u_int py, u_int ny)
|
||||
{
|
||||
struct window_pane *wp = ttyctx->arg;
|
||||
|
||||
if (wp != NULL)
|
||||
wp->flags |= PANE_REDRAW;
|
||||
if (wp == NULL)
|
||||
return;
|
||||
redraw_damage_window(wp->window, wp->xoff, wp->yoff + py, wp->sx, ny);
|
||||
}
|
||||
|
||||
/* Update context for client. */
|
||||
@@ -142,7 +152,7 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
|
||||
return (0);
|
||||
}
|
||||
|
||||
if (active_get_effective_window(c, c->session) != wp->window)
|
||||
if (c->session->curw->window != wp->window)
|
||||
return (0);
|
||||
if (wp->layout_cell == NULL)
|
||||
return (0);
|
||||
@@ -371,6 +381,19 @@ screen_write_init(struct screen_write_ctx *ctx, struct screen *s)
|
||||
ctx->bg = 8;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Damage temporal images when application input writes text cells. */
|
||||
static void
|
||||
screen_write_image_damage(struct screen_write_ctx *ctx, u_int x, u_int y,
|
||||
u_int sx, u_int sy)
|
||||
{
|
||||
image_redraw_area(ctx, x, y, sx, sy);
|
||||
if (ctx->flags & SCREEN_WRITE_INPUT)
|
||||
image_grid_damage(ctx->s->grid, x, ctx->s->grid->hsize + y,
|
||||
sx, sy);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Initialize writing with a pane. */
|
||||
void
|
||||
screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp,
|
||||
@@ -1331,9 +1354,11 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
|
||||
memcpy(&gc, &grid_default_cell, sizeof gc);
|
||||
utf8_set(&gc.data, 'E');
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
if (ctx->flags & SCREEN_WRITE_INPUT)
|
||||
image_grid_damage(s->grid, 0, s->grid->hsize,
|
||||
screen_size_x(s), screen_size_y(s));
|
||||
#endif
|
||||
|
||||
for (yy = 0; yy < screen_size_y(s); yy++) {
|
||||
@@ -1378,9 +1403,8 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1419,9 +1443,8 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1460,9 +1483,8 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1495,9 +1517,8 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
if (ny == 0)
|
||||
ny = 1;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#endif
|
||||
|
||||
if (s->cy < s->rupper || s->cy > s->rlower) {
|
||||
@@ -1563,9 +1584,8 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
if (ny == 0)
|
||||
ny = 1;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#endif
|
||||
|
||||
if (s->cy < s->rupper || s->cy > s->rlower) {
|
||||
@@ -1636,9 +1656,8 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
|
||||
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
flags = gl->flags & GRID_LINE_OSC133_FLAGS;
|
||||
@@ -1675,9 +1694,8 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
|
||||
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg);
|
||||
@@ -1702,9 +1720,8 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
if (s->cx > sx - 1)
|
||||
@@ -1756,9 +1773,8 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_scroll(ctx, 1);
|
||||
#endif
|
||||
|
||||
grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
|
||||
@@ -1808,9 +1824,6 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_line *gl;
|
||||
#ifdef ENABLE_SIXEL
|
||||
int redraw = 0;
|
||||
#endif
|
||||
u_int rupper = s->rupper, rlower = s->rlower;
|
||||
|
||||
gl = grid_get_line(gd, gd->hsize + s->cy);
|
||||
@@ -1831,13 +1844,12 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (rlower == screen_size_y(s) - 1)
|
||||
redraw = image_scroll_up(s, 1);
|
||||
image_redraw_scroll(ctx, 1);
|
||||
else
|
||||
redraw = image_check_line(s, rupper, rlower - rupper);
|
||||
if (redraw && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
image_redraw_area(ctx, 0, rupper, screen_size_x(s),
|
||||
rlower - rupper);
|
||||
#endif
|
||||
|
||||
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
|
||||
@@ -1863,9 +1875,8 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
ctx->bg = bg;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_scroll_up(s, lines) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_scroll(ctx, lines);
|
||||
#endif
|
||||
|
||||
for (i = 0; i < lines; i++) {
|
||||
@@ -1892,9 +1903,8 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
else if (lines > s->rlower - s->rupper + 1)
|
||||
lines = s->rlower - s->rupper + 1;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_scroll(ctx, lines);
|
||||
#endif
|
||||
|
||||
for (i = 0; i < lines; i++)
|
||||
@@ -1933,9 +1943,8 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2014,9 +2023,8 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, 0, screen_size_x(s), s->cy + 1);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2087,9 +2095,8 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2155,7 +2162,7 @@ screen_write_fullredraw(struct screen_write_ctx *ctx)
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||
if (ttyctx.redraw_cb != NULL)
|
||||
ttyctx.redraw_cb(&ttyctx);
|
||||
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||
}
|
||||
|
||||
/* Trim collected items. */
|
||||
@@ -2592,9 +2599,8 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
screen_write_image_damage(ctx, s->cx, s->cy, ci->used, 1);
|
||||
#endif
|
||||
|
||||
grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x,
|
||||
@@ -2724,6 +2730,11 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
return;
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 0);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Input semantics decide which image layers this text damages. */
|
||||
screen_write_image_damage(ctx, s->cx, s->cy, width, 1);
|
||||
#endif
|
||||
|
||||
/* Handle overwriting of UTF-8 characters. */
|
||||
gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
|
||||
if (gl->flags & GRID_LINE_EXTENDED) {
|
||||
@@ -2951,6 +2962,9 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
force_wide = 0;
|
||||
|
||||
/* Set the new cell. */
|
||||
#ifdef ENABLE_IMAGES
|
||||
screen_write_image_damage(ctx, cx - n, cy, n, 1);
|
||||
#endif
|
||||
grid_view_set_cell(gd, cx - n, cy, &last);
|
||||
if (force_wide)
|
||||
grid_view_set_padding(gd, cx - 1, cy, last.bg);
|
||||
@@ -3086,63 +3100,21 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len,
|
||||
tty_write(tty_cmd_rawstring, &ttyctx);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Write a SIXEL image. */
|
||||
void
|
||||
screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
|
||||
u_int bg)
|
||||
{
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct tty_ctx ttyctx;
|
||||
u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines;
|
||||
struct sixel_image *new;
|
||||
struct image *im;
|
||||
|
||||
if (screen_size_y(s) == 1)
|
||||
return;
|
||||
|
||||
sixel_size_in_cells(si, &x, &y);
|
||||
if (x > screen_size_x(s) || y > screen_size_y(s) - 1) {
|
||||
if (x > screen_size_x(s) - cx)
|
||||
sx = screen_size_x(s) - cx;
|
||||
else
|
||||
sx = x;
|
||||
if (y > screen_size_y(s) - 1)
|
||||
sy = screen_size_y(s) - 1;
|
||||
else
|
||||
sy = y;
|
||||
new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1);
|
||||
im = sixel_to_image(si);
|
||||
if (im == NULL) {
|
||||
sixel_free(si);
|
||||
if (new == NULL)
|
||||
return;
|
||||
sixel_size_in_cells(new, &x, &y);
|
||||
si = new;
|
||||
return;
|
||||
}
|
||||
|
||||
sy = screen_size_y(s) - cy;
|
||||
if (sy <= y) {
|
||||
lines = y - sy + 1;
|
||||
if (image_scroll_up(s, lines) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
for (i = 0; i < lines; i++) {
|
||||
grid_view_scroll_region_up(gd, 0, screen_size_y(s) - 1,
|
||||
bg);
|
||||
screen_write_collect_scroll(ctx, bg);
|
||||
}
|
||||
ctx->scrolled += lines;
|
||||
if (lines > cy)
|
||||
screen_write_cursormove(ctx, -1, 0, 0);
|
||||
else
|
||||
screen_write_cursormove(ctx, -1, cy - lines, 0);
|
||||
}
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 0);
|
||||
ttyctx.image = image_store(s, si);
|
||||
|
||||
tty_write(tty_cmd_sixelimage, &ttyctx);
|
||||
|
||||
screen_write_cursormove(ctx, 0, cy + y, 0);
|
||||
image_write_sixel(ctx, im, bg);
|
||||
image_free(image_get_id(im));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3175,7 +3147,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||
if (ttyctx.redraw_cb != NULL)
|
||||
ttyctx.redraw_cb(&ttyctx);
|
||||
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||
}
|
||||
|
||||
/* Turn alternate screen off. */
|
||||
@@ -3200,5 +3172,5 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc,
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 0);
|
||||
if (ttyctx.redraw_cb != NULL)
|
||||
ttyctx.redraw_cb(&ttyctx);
|
||||
ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy);
|
||||
}
|
||||
|
||||
50
screen.c
50
screen.c
@@ -93,11 +93,6 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
|
||||
s->tabs = NULL;
|
||||
s->sel = NULL;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
TAILQ_INIT(&s->images);
|
||||
TAILQ_INIT(&s->saved_images);
|
||||
#endif
|
||||
|
||||
s->write_list = NULL;
|
||||
s->hyperlinks = NULL;
|
||||
|
||||
@@ -131,11 +126,6 @@ screen_reinit(struct screen *s, int check)
|
||||
|
||||
screen_clear_selection(s);
|
||||
screen_free_titles(s);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
#endif
|
||||
|
||||
screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0);
|
||||
screen_reset_hyperlinks(s);
|
||||
}
|
||||
@@ -154,10 +144,6 @@ screen_reset_hyperlinks(struct screen *s)
|
||||
void
|
||||
screen_free(struct screen *s)
|
||||
{
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
free(s->sel);
|
||||
free(s->tabs);
|
||||
free(s->path);
|
||||
@@ -174,17 +160,6 @@ screen_free(struct screen *s)
|
||||
hyperlinks_free(s->hyperlinks);
|
||||
screen_free_titles(s);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/*
|
||||
* Images saved when entering the alternate screen stay linked in the
|
||||
* global list; move them back so they are freed and unlinked here, or
|
||||
* a later eviction would write through this freed screen.
|
||||
*/
|
||||
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
|
||||
TAILQ_FOREACH(im, &s->images, entry)
|
||||
im->list = &s->images;
|
||||
image_free_all(s);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Reset tabs to default, eight spaces apart. */
|
||||
@@ -377,10 +352,6 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow,
|
||||
if (sy != screen_size_y(s))
|
||||
screen_resize_y(s, sy, eat_empty, &cy);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
#endif
|
||||
|
||||
if (reflow)
|
||||
screen_reflow(s, sx, &cx, &cy, cursor);
|
||||
|
||||
@@ -692,10 +663,6 @@ int
|
||||
screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
{
|
||||
u_int sx, sy;
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
if (SCREEN_IS_ALTERNATE(s))
|
||||
return 0;
|
||||
sx = screen_size_x(s);
|
||||
@@ -709,12 +676,6 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
}
|
||||
memcpy(&s->saved_cell, gc, sizeof s->saved_cell);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
TAILQ_CONCAT(&s->saved_images, &s->images, entry);
|
||||
TAILQ_FOREACH(im, &s->saved_images, entry)
|
||||
im->list = &s->saved_images;
|
||||
#endif
|
||||
|
||||
grid_view_clear(s->grid, 0, 0, sx, sy, 8);
|
||||
|
||||
s->saved_flags = s->grid->flags;
|
||||
@@ -728,10 +689,6 @@ int
|
||||
screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
{
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If the current size is different, temporarily resize to the old size
|
||||
* before copying back.
|
||||
@@ -774,13 +731,6 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
grid_destroy(s->saved_grid);
|
||||
s->saved_grid = NULL;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
|
||||
TAILQ_FOREACH(im, &s->images, entry)
|
||||
im->list = &s->images;
|
||||
#endif
|
||||
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
s->cx = screen_size_x(s) - 1;
|
||||
if (s->cy > screen_size_y(s) - 1)
|
||||
|
||||
138
server-client.c
138
server-client.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: server-client.c,v 1.509 2026/08/28 07:36:01 nicm Exp $ */
|
||||
/* $OpenBSD: server-client.c,v 1.507 2026/08/24 21:17:19 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -107,7 +107,7 @@ server_client_set_overlay(struct client *c, u_int delay,
|
||||
c->tty.flags |= TTY_FREEZE;
|
||||
if (c->overlay_mode == NULL)
|
||||
c->tty.flags |= TTY_NOCURSOR;
|
||||
window_update_focus(active_get_effective_window(c, c->session));
|
||||
window_update_focus(c->session->curw->window);
|
||||
server_redraw_client(c);
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ server_client_clear_overlay(struct client *c)
|
||||
|
||||
c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR);
|
||||
if (c->session != NULL)
|
||||
window_update_focus(active_get_effective_window(c, c->session));
|
||||
window_update_focus(c->session->curw->window);
|
||||
server_redraw_client(c);
|
||||
}
|
||||
|
||||
@@ -379,8 +379,7 @@ server_client_attached_lost(struct client *c)
|
||||
found = NULL;
|
||||
TAILQ_FOREACH(loop, &clients, entry) {
|
||||
s = loop->session;
|
||||
if (loop == c || s == NULL ||
|
||||
active_get_effective_window(loop, s) != w)
|
||||
if (loop == c || s == NULL || s->curw->window != w)
|
||||
continue;
|
||||
if (found == NULL || timercmp(&loop->activity_time,
|
||||
&found->activity_time, >))
|
||||
@@ -461,20 +460,46 @@ server_client_set_session(struct client *c, struct session *s)
|
||||
c->flags |= CLIENT_FOCUSED;
|
||||
|
||||
if (old != NULL && old->curw != NULL)
|
||||
window_update_focus(active_get_effective_window(c, old));
|
||||
window_update_focus(old->curw->window);
|
||||
if (s != NULL) {
|
||||
active_get_effective_window(c, s)->latest = c;
|
||||
s->curw->window->latest = c;
|
||||
recalculate_sizes();
|
||||
window_update_focus(active_get_effective_window(c, s));
|
||||
window_update_focus(s->curw->window);
|
||||
session_update_activity(s, NULL);
|
||||
session_theme_changed(s);
|
||||
gettimeofday(&s->last_attached_time, NULL);
|
||||
active_get_effective_winlink(c, s)->flags &= ~WINLINK_ALERTFLAGS;
|
||||
s->curw->flags &= ~WINLINK_ALERTFLAGS;
|
||||
alerts_check_session(s);
|
||||
tty_update_client_offset(c);
|
||||
status_timer_start(c);
|
||||
server_client_fire_session_changed(c, old);
|
||||
server_redraw_client(c);
|
||||
|
||||
/*
|
||||
* A full redraw is only needed if the client's session or
|
||||
* current window actually changed - not if this merely
|
||||
* confirmed the client is still looking at the same window
|
||||
* (as happens when switch-client -t targets a pane in the
|
||||
* already-current window, e.g. clicking a pane name in a
|
||||
* second #{P:} status line: the default MouseDown1Status
|
||||
* binding resolves that click to switch-client -t=, which
|
||||
* reaches here regardless of whether anything besides the
|
||||
* active pane changed). Redrawing unconditionally here
|
||||
* forced a full window redraw for what should have been
|
||||
* just an active-pane change, already handled narrowly by
|
||||
* window_set_active_pane() and window_redraw_active_switch()
|
||||
* before this is reached.
|
||||
*
|
||||
* old and s may be the same session object, whose curw was
|
||||
* already updated to the new window before this function was
|
||||
* called - old->curw and s->curw would then read the same,
|
||||
* already-current value, so comparing them can never detect
|
||||
* a same-session window change. Compare against the client's
|
||||
* own cached scene instead, which only reflects what it has
|
||||
* actually drawn.
|
||||
*/
|
||||
if (old == NULL || old != s ||
|
||||
!redraw_client_has_window(c, s->curw->window))
|
||||
server_redraw_client(c);
|
||||
}
|
||||
|
||||
server_check_unattached();
|
||||
@@ -494,7 +519,6 @@ server_client_lost(struct client *c)
|
||||
server_client_clear_overlay(c);
|
||||
status_prompt_clear(c);
|
||||
status_message_clear(c);
|
||||
active_remove_client(c);
|
||||
|
||||
RB_FOREACH_SAFE(cf, client_files, &c->files, cf1) {
|
||||
cf->error = EINTR;
|
||||
@@ -528,8 +552,6 @@ server_client_lost(struct client *c)
|
||||
free(c->title);
|
||||
free(c->path);
|
||||
free((void *)c->cwd);
|
||||
free(c->exit_session);
|
||||
free(c->exit_message);
|
||||
|
||||
evtimer_del(&c->repeat_timer);
|
||||
evtimer_del(&c->click_timer);
|
||||
@@ -686,7 +708,7 @@ server_client_in_scrollbar_area(struct window_pane *wp, int px, int py)
|
||||
static void
|
||||
server_client_update_scrollbar_hover(struct client *c, int type, int px, int py)
|
||||
{
|
||||
struct window *w = active_get_effective_window(c, c->session);
|
||||
struct window *w = c->session->curw->window;
|
||||
struct window_pane *wp;
|
||||
|
||||
if (type != KEYC_TYPE_MOUSEMOVE)
|
||||
@@ -858,7 +880,7 @@ server_client_check_mouse(struct client *c, struct key_event *event)
|
||||
{
|
||||
struct mouse_event *m = &event->m;
|
||||
struct session *s = c->session, *fs;
|
||||
struct window *w = active_get_effective_window(c, s);
|
||||
struct window *w = s->curw->window;
|
||||
struct winlink *fwl;
|
||||
struct window_pane *wp, *fwp, *lwp = NULL;
|
||||
u_int x, y, sx, sy, px, py, n, sl_mpos = 0;
|
||||
@@ -1361,7 +1383,7 @@ server_client_update_latest(struct client *c)
|
||||
|
||||
if (c->session == NULL)
|
||||
return;
|
||||
w = active_get_effective_window(c, c->session);
|
||||
w = c->session->curw->window;
|
||||
|
||||
if (w->latest == c)
|
||||
return;
|
||||
@@ -1440,7 +1462,7 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
||||
/* Check the client is good to accept input. */
|
||||
if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
|
||||
goto out;
|
||||
wl = active_get_effective_winlink(c, s);
|
||||
wl = s->curw;
|
||||
|
||||
/* Update the activity timer. */
|
||||
memcpy(&c->last_activity_time, &c->activity_time,
|
||||
@@ -1464,8 +1486,19 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
||||
/*
|
||||
* Mouse drag is in progress, so fire the callback (now that
|
||||
* the mouse event is valid).
|
||||
*
|
||||
* Start a synchronized-output region here rather than
|
||||
* leaving it to whatever redraw eventually follows: a drag
|
||||
* callback may write directly via the pane's fast path
|
||||
* immediately, with any correction only arriving later via
|
||||
* redraw_client_damage(), which opens its own sync region.
|
||||
* Since tty_sync_end() is only called once, at the very end
|
||||
* of this client's pass in server_client_reset_state(),
|
||||
* starting it here merges both into one atomic terminal
|
||||
* update instead of two visible frames.
|
||||
*/
|
||||
if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) {
|
||||
tty_sync_start(&c->tty);
|
||||
c->tty.mouse_drag_update(c, m);
|
||||
goto out;
|
||||
}
|
||||
@@ -1501,10 +1534,6 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
||||
TAILQ_EMPTY(&wp->modes))
|
||||
goto forward_key;
|
||||
|
||||
/* Focus events are not keys and cannot be bound. */
|
||||
if (key == KEYC_FOCUS_IN || key == KEYC_FOCUS_OUT)
|
||||
goto forward_key;
|
||||
|
||||
/*
|
||||
* Work out the current key table. If the pane is in a mode, use
|
||||
* the mode table instead of the default key table.
|
||||
@@ -1699,7 +1728,7 @@ out:
|
||||
static int
|
||||
server_client_handle_menu_key(struct client *c, struct key_event *event)
|
||||
{
|
||||
struct window *w = active_get_effective_window(c, c->session);
|
||||
struct window *w = c->session->curw->window;
|
||||
struct key_event new_event;
|
||||
struct mouse_event *m;
|
||||
u_int ox, oy, sx, sy;
|
||||
@@ -1738,7 +1767,6 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct cmdq_item *item;
|
||||
struct window *w;
|
||||
struct window_pane *wp;
|
||||
|
||||
/* Check the client is good to accept input. */
|
||||
@@ -1809,10 +1837,9 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
||||
}
|
||||
}
|
||||
|
||||
w = active_get_effective_window(c, s);
|
||||
wp = w->active;
|
||||
wp = s->curw->window->active;
|
||||
if (wp == NULL || !window_pane_has_prompt(wp)) {
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
TAILQ_FOREACH(wp, &s->curw->window->panes, entry) {
|
||||
if (window_pane_has_prompt(wp) &&
|
||||
window_pane_is_visible(wp))
|
||||
break;
|
||||
@@ -1904,7 +1931,12 @@ server_client_loop(void)
|
||||
|
||||
/*
|
||||
* Any windows will have been redrawn as part of clients, so clear
|
||||
* their flags now.
|
||||
* their flags now. A client whose redraw was deferred this pass
|
||||
* (waiting for outstanding tty output to drain) has already
|
||||
* escalated to CLIENT_REDRAWWINDOW or CLIENT_REDRAWSCROLLBARS in
|
||||
* server_client_check_redraw() to cover whatever it is about to
|
||||
* lose here, so PANE_REDRAW/PANE_REDRAWSCROLLBAR and window damage
|
||||
* can simply be cleared unconditionally.
|
||||
*/
|
||||
RB_FOREACH(w, windows, &windows) {
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
@@ -1915,6 +1947,8 @@ server_client_loop(void)
|
||||
wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR|
|
||||
PANE_ACTIVITY);
|
||||
}
|
||||
redraw_free_damage(w);
|
||||
|
||||
check_window_name(w);
|
||||
}
|
||||
|
||||
@@ -2154,7 +2188,7 @@ static void
|
||||
server_client_reset_state(struct client *c)
|
||||
{
|
||||
struct tty *tty = &c->tty;
|
||||
struct window *w = active_get_effective_window(c, c->session);
|
||||
struct window *w = c->session->curw->window;
|
||||
struct window_pane *wp = w->active, *loop;
|
||||
struct screen *s = NULL;
|
||||
struct options *oo = c->session->options;
|
||||
@@ -2417,6 +2451,8 @@ server_client_check_exit(struct client *c, int force)
|
||||
proc_send(c->peer, c->exit_msgtype, -1, name, strlen(name) + 1);
|
||||
break;
|
||||
}
|
||||
free(c->exit_session);
|
||||
free(c->exit_message);
|
||||
}
|
||||
|
||||
/* Redraw timer callback. */
|
||||
@@ -2434,7 +2470,7 @@ server_client_redraw_timer(__unused int fd, __unused short events,
|
||||
static void
|
||||
server_client_check_modes(struct client *c)
|
||||
{
|
||||
struct window *w;
|
||||
struct window *w = c->session->curw->window;
|
||||
struct window_pane *wp;
|
||||
struct window_mode_entry *wme;
|
||||
|
||||
@@ -2442,7 +2478,6 @@ server_client_check_modes(struct client *c)
|
||||
return;
|
||||
if (~c->flags & CLIENT_REDRAWSTATUS)
|
||||
return;
|
||||
w = active_get_effective_window(c, c->session);
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
wme = TAILQ_FIRST(&wp->modes);
|
||||
if (wme != NULL && wme->mode->update != NULL)
|
||||
@@ -2455,11 +2490,13 @@ static int
|
||||
server_client_any_pane_redraw(struct client *c)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct window *w = active_get_effective_window(c, s);
|
||||
struct window *w = s->curw->window;
|
||||
struct window_pane *wp;
|
||||
|
||||
if (c->flags & CLIENT_REDRAWWINDOW)
|
||||
return (1);
|
||||
if (!TAILQ_EMPTY(&w->damage))
|
||||
return (1);
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (wp->flags & (PANE_REDRAW|PANE_REDRAWSCROLLBAR))
|
||||
return (1);
|
||||
@@ -2473,7 +2510,7 @@ server_client_check_redraw(struct client *c)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct tty *tty = &c->tty;
|
||||
struct window *w = active_get_effective_window(c, s);
|
||||
struct window *w = s->curw->window;
|
||||
struct window_pane *wp;
|
||||
int needed, tflags, mode = tty->mode;
|
||||
struct timeval tv = { .tv_usec = 1000 };
|
||||
@@ -2505,7 +2542,12 @@ server_client_check_redraw(struct client *c)
|
||||
/*
|
||||
* If there is outstanding data, defer the redraw until it has been
|
||||
* consumed. We can just add a timer to get out of the event loop and
|
||||
* end up back here.
|
||||
* end up back here. server_client_loop() clears PANE_REDRAW,
|
||||
* PANE_REDRAWSCROLLBAR and window damage unconditionally every pass,
|
||||
* so escalate to a coarser, persistent client flag that survives
|
||||
* that clear and forces a full catch-up redraw once this client is
|
||||
* unblocked, rather than trying to keep the fine-grained state
|
||||
* around for a retry.
|
||||
*/
|
||||
n = EVBUFFER_LENGTH(tty->out);
|
||||
if (n != 0 || (tty->flags & TTY_BLOCK)) {
|
||||
@@ -2519,6 +2561,8 @@ server_client_check_redraw(struct client *c)
|
||||
log_debug("redraw timer started");
|
||||
evtimer_add(&ev, &tv);
|
||||
}
|
||||
if (!TAILQ_EMPTY(&w->damage))
|
||||
c->flags |= CLIENT_REDRAWWINDOW;
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (wp->flags & PANE_REDRAW) {
|
||||
c->flags |= CLIENT_REDRAWWINDOW;
|
||||
@@ -2552,6 +2596,19 @@ server_client_check_redraw(struct client *c)
|
||||
redraw_pane_scrollbar(c, wp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Window damage is also what makes server_client_any_pane_
|
||||
* redraw() decide a redraw is needed at all, independently of
|
||||
* any CLIENT_ALLREDRAWFLAGS bit. Every current damage source
|
||||
* happens to set one of those flags too, so the block below
|
||||
* always consumes it - but consume it here too in case that
|
||||
* ever stops holding, since server_client_loop() clears
|
||||
* window damage unconditionally every pass regardless of
|
||||
* whether it was actually drawn.
|
||||
*/
|
||||
if (!TAILQ_EMPTY(&w->damage) && (~c->flags & CLIENT_ALLREDRAWFLAGS))
|
||||
redraw_client_damage(c);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2565,6 +2622,7 @@ server_client_check_redraw(struct client *c)
|
||||
}
|
||||
server_client_set_progress_bar(c);
|
||||
redraw_screen(c);
|
||||
redraw_client_damage(c);
|
||||
}
|
||||
|
||||
/* Put the tty back how it was. */
|
||||
@@ -2612,15 +2670,14 @@ static void
|
||||
server_client_set_path(struct client *c)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct window *w = active_get_effective_window(c, s);
|
||||
const char *path;
|
||||
|
||||
if (w == NULL || w->active == NULL)
|
||||
if (s->curw == NULL || s->curw->window->active == NULL)
|
||||
return;
|
||||
if (w->active->base.path == NULL)
|
||||
if (s->curw->window->active->base.path == NULL)
|
||||
path = "";
|
||||
else
|
||||
path = w->active->base.path;
|
||||
path = s->curw->window->active->base.path;
|
||||
if (c->path == NULL || strcmp(path, c->path) != 0) {
|
||||
free(c->path);
|
||||
c->path = xstrdup(path);
|
||||
@@ -2633,12 +2690,11 @@ static void
|
||||
server_client_set_progress_bar(struct client *c)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
struct window *w = active_get_effective_window(c, s);
|
||||
struct progress_bar *pane_pb;
|
||||
|
||||
if (w == NULL || w->active == NULL)
|
||||
if (s->curw == NULL || s->curw->window->active == NULL)
|
||||
return;
|
||||
pane_pb = &w->active->base.progress_bar;
|
||||
pane_pb = &s->curw->window->active->base.progress_bar;
|
||||
if (pane_pb->state == c->progress_bar.state &&
|
||||
pane_pb->progress == c->progress_bar.progress)
|
||||
return;
|
||||
@@ -3228,7 +3284,7 @@ server_client_print(struct client *c, int parse, struct evbuffer *evb)
|
||||
goto out;
|
||||
}
|
||||
|
||||
wp = active_get_effective_window(c, c->session)->active;
|
||||
wp = c->session->curw->window->active;
|
||||
wme = TAILQ_FIRST(&wp->modes);
|
||||
if (wme == NULL || wme->mode != &window_view_mode)
|
||||
window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL,
|
||||
|
||||
@@ -122,7 +122,8 @@ server_redraw_window(struct window *w)
|
||||
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (c->session != NULL &&
|
||||
active_get_effective_window(c, c->session) == w)
|
||||
c->session->curw != NULL &&
|
||||
c->session->curw->window == w)
|
||||
server_redraw_client(c);
|
||||
}
|
||||
}
|
||||
@@ -134,7 +135,8 @@ server_redraw_window_menu(struct window *w)
|
||||
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (c->session != NULL &&
|
||||
active_get_effective_window(c, c->session) == w)
|
||||
c->session->curw != NULL &&
|
||||
c->session->curw->window == w)
|
||||
c->flags |= CLIENT_REDRAWMENU;
|
||||
}
|
||||
}
|
||||
@@ -146,7 +148,8 @@ server_redraw_window_borders(struct window *w)
|
||||
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (c->session != NULL &&
|
||||
active_get_effective_window(c, c->session) == w)
|
||||
c->session->curw != NULL &&
|
||||
c->session->curw->window == w)
|
||||
c->flags |= CLIENT_REDRAWBORDERS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,6 @@ session_destroy(struct session *s, int notify, const char *from)
|
||||
if (s->curw == NULL)
|
||||
return;
|
||||
s->curw = NULL;
|
||||
active_remove_session(s);
|
||||
|
||||
RB_REMOVE(sessions, &sessions, s);
|
||||
if (notify)
|
||||
|
||||
4
sort.c
4
sort.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: sort.c,v 1.10 2026/08/25 07:23:30 nicm Exp $ */
|
||||
/* $OpenBSD: sort.c,v 1.9 2026/06/29 07:45:09 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Dane Jensen <dhcjensen@gmail.com>
|
||||
@@ -32,7 +32,7 @@ sort_qsort(void *l, u_int len, u_int size, int (*cmp)(const void *,
|
||||
u_int i;
|
||||
void *tmp, **ll;
|
||||
|
||||
if (len < 2 || sort_crit->order == SORT_END)
|
||||
if (sort_crit->order == SORT_END)
|
||||
return;
|
||||
|
||||
if (sort_crit->order == SORT_ORDER) {
|
||||
|
||||
3
style.c
3
style.c
@@ -30,7 +30,8 @@
|
||||
|
||||
/* Default style. */
|
||||
static struct style style_default = {
|
||||
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 },
|
||||
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0
|
||||
},
|
||||
0,
|
||||
0,
|
||||
|
||||
|
||||
61
tmux.1
61
tmux.1
@@ -1,4 +1,4 @@
|
||||
.\" $OpenBSD: tmux.1,v 1.1163 2026/09/01 12:49:49 nicm Exp $
|
||||
.\" $OpenBSD: tmux.1,v 1.1158 2026/08/20 09:19:24 nicm Exp $
|
||||
.\"
|
||||
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
.\"
|
||||
@@ -14,7 +14,7 @@
|
||||
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
.\"
|
||||
.Dd $Mdocdate: September 1 2026 $
|
||||
.Dd $Mdocdate: August 20 2026 $
|
||||
.Dt TMUX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -2208,14 +2208,14 @@ Move to the end of the next word.
|
||||
.Xc
|
||||
Same as
|
||||
.Ic next\-word
|
||||
but treat all non-whitespace characters as part of a word.
|
||||
but use a space alone as the word separator.
|
||||
.It Xo
|
||||
.Ic next\-space\-end
|
||||
(vi: E)
|
||||
.Xc
|
||||
Same as
|
||||
.Ic next\-word\-end
|
||||
but treat all non-whitespace characters as part of a word.
|
||||
but use a space alone as the word separator.
|
||||
.It Xo
|
||||
.Ic other\-end
|
||||
(vi: o)
|
||||
@@ -2289,7 +2289,7 @@ Move to the previous word.
|
||||
.Xc
|
||||
Same as
|
||||
.Ic previous\-word
|
||||
but treat all non-whitespace characters as part of a word.
|
||||
but use a space alone as the word separator.
|
||||
.It Xo
|
||||
.Ic rectangle\-on
|
||||
.Xc
|
||||
@@ -2574,8 +2574,8 @@ Word separators can be customized with the
|
||||
session option.
|
||||
Next word moves to the start of the next word, next word end to the end of the
|
||||
next word and previous word to the start of the previous word.
|
||||
The three next and previous space keys work similarly but treat all
|
||||
non-whitespace characters as part of a word.
|
||||
The three next and previous space keys work similarly but use a space alone as
|
||||
the word separator.
|
||||
Setting
|
||||
.Em word\-separators
|
||||
to the empty string makes next/previous word equivalent to next/previous space.
|
||||
@@ -4038,22 +4038,12 @@ and
|
||||
.Ic swap\-window .
|
||||
.Tg selectw
|
||||
.It Xo Ic select\-window
|
||||
.Op Fl LlnpST
|
||||
.Op Fl lnpT
|
||||
.Op Fl t Ar target\-window
|
||||
.Xc
|
||||
.D1 Pq alias: Ic selectw
|
||||
Select the window at
|
||||
.Ar target\-window .
|
||||
.Fl L
|
||||
makes the attached client use a local current window for the target session.
|
||||
Window navigation from that client then changes only that client's effective
|
||||
window and leaves the session current window unchanged.
|
||||
.Fl S
|
||||
returns the client to the shared session current window.
|
||||
Without
|
||||
.Fl L ,
|
||||
the current window is shared by the session and stored in the session.
|
||||
.Pp
|
||||
.Fl l ,
|
||||
.Fl n
|
||||
and
|
||||
@@ -4764,15 +4754,6 @@ for backspace.
|
||||
Set the number of buffers; as new buffers are added to the top of the stack,
|
||||
old ones are removed from the bottom if necessary to maintain this maximum
|
||||
length.
|
||||
.It Xo Ic clear\-on\-attach
|
||||
.Op Ic on | off
|
||||
.Xc
|
||||
When enabled (the default), the client terminal switches to the alternate
|
||||
screen and clears it when a client is attached.
|
||||
When disabled,
|
||||
.Nm
|
||||
does not enter the alternate screen; instead the existing terminal content is
|
||||
scrolled into the scrollback buffer so it remains accessible.
|
||||
.It Xo Ic command\-alias[]
|
||||
.Ar name=value
|
||||
.Xc
|
||||
@@ -5089,12 +5070,20 @@ Supports extended keys.
|
||||
Supports focus reporting.
|
||||
.It hyperlinks
|
||||
Supports OSC 8 hyperlinks.
|
||||
.It image-quadrants
|
||||
Allows image fallbacks to use Unicode quadrant block characters.
|
||||
.It image-sextants
|
||||
Allows image fallbacks to use Unicode sextant block characters.
|
||||
.It ignorefkeys
|
||||
Ignore function keys from
|
||||
.Xr terminfo 5
|
||||
and use the
|
||||
.Nm
|
||||
internal set only.
|
||||
.It kitty
|
||||
Supports Kitty graphics using Unicode placeholders.
|
||||
The terminal must support the Kitty Unicode placeholder extension, not only
|
||||
the basic Kitty graphics protocol.
|
||||
.It margins
|
||||
Supports DECSLRM margins.
|
||||
.It mouse
|
||||
@@ -5123,8 +5112,6 @@ Supports
|
||||
title setting.
|
||||
.It usstyle
|
||||
Allows underscore style and colour to be set.
|
||||
.It utf8
|
||||
Supports UTF\-8 output.
|
||||
.El
|
||||
.It Ic terminal\-overrides[] Ar string
|
||||
Allow terminal descriptions read using
|
||||
@@ -5659,11 +5646,6 @@ If set to both, a bell and a message are produced.
|
||||
Sets the session's conception of what characters are considered word
|
||||
separators, for the purposes of the next and previous word commands in
|
||||
copy mode.
|
||||
A space in
|
||||
.Ar string
|
||||
matches any character with the Unicode
|
||||
.Em White_Space
|
||||
property.
|
||||
.El
|
||||
.Pp
|
||||
Available window options are:
|
||||
@@ -7276,10 +7258,7 @@ shows the four frames in turn, changing every 200 milliseconds.
|
||||
The following variables are available, where appropriate:
|
||||
.Bl -column "XXXXXXXXXXXXXXXXXXX" "XXXXX"
|
||||
.It Sy "Variable name" Ta Sy "Alias" Ta Sy "Replaced with"
|
||||
.It Li "active_window_id" Ta "" Ta "ID of effective active window"
|
||||
.It Li "active_window_index" Ta "" Ta "Index of effective active window"
|
||||
.It Li "local_active_window" Ta "" Ta "1 if client has local window selection"
|
||||
.It Li "active_window_mode" Ta "" Ta "Window selection mode, shared or local"
|
||||
.It Li "active_window_index" Ta "" Ta "Index of active window in session"
|
||||
.It Li "alternate_on" Ta "" Ta "1 if pane is in alternate screen"
|
||||
.It Li "alternate_saved_x" Ta "" Ta "Saved cursor X in alternate screen"
|
||||
.It Li "alternate_saved_y" Ta "" Ta "Saved cursor Y in alternate screen"
|
||||
@@ -7336,10 +7315,7 @@ The following variables are available, where appropriate:
|
||||
.It Li "cursor_very_visible" Ta "" Ta "1 if the cursor is in very visible mode"
|
||||
.It Li "cursor_x" Ta "" Ta "Cursor X position in pane"
|
||||
.It Li "cursor_y" Ta "" Ta "Cursor Y position in pane"
|
||||
.It Li "history_added" Ta "" Ta "Number of lines scrolled into pane history"
|
||||
.It Li "history_bytes" Ta "" Ta "Number of bytes in window history"
|
||||
.It Li "history_collected" Ta "" Ta "Number of oldest pane history lines discarded at the history limit"
|
||||
.It Li "history_generation" Ta "" Ta "Generation incremented when pane history is cleared or reflowed"
|
||||
.It Li "history_limit" Ta "" Ta "Maximum window history lines"
|
||||
.It Li "history_size" Ta "" Ta "Size of history in lines"
|
||||
.It Li "hook" Ta "" Ta "Name of running hook, if any"
|
||||
@@ -7438,7 +7414,6 @@ The following variables are available, where appropriate:
|
||||
.It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set"
|
||||
.It Li "pane_modal_flag" Ta "" Ta "1 if pane is modal"
|
||||
.It Li "pane_mode" Ta "" Ta "Name of pane mode, if any"
|
||||
.It Li "pane_output_generation" Ta "" Ta "Generation incremented when pane output is processed"
|
||||
.It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)"
|
||||
.It Li "pane_pid" Ta "" Ta "PID of first process in pane"
|
||||
.It Li "pane_pipe" Ta "" Ta "1 if pane is being piped"
|
||||
@@ -7516,7 +7491,7 @@ The following variables are available, where appropriate:
|
||||
.It Li "uid" Ta "" Ta "Server UID"
|
||||
.It Li "user" Ta "" Ta "Server user"
|
||||
.It Li "version" Ta "" Ta "Server version"
|
||||
.It Li "window_active" Ta "" Ta "1 if window is the effective active window"
|
||||
.It Li "window_active" Ta "" Ta "1 if window active"
|
||||
.It Li "window_active_clients" Ta "" Ta "Number of clients viewing this window"
|
||||
.It Li "window_active_clients_list" Ta "" Ta "List of clients viewing this window"
|
||||
.It Li "window_active_sessions" Ta "" Ta "Number of sessions on which this window is active"
|
||||
|
||||
276
tmux.h
276
tmux.h
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tmux.h,v 1.1433 2026/09/01 12:49:49 nicm Exp $ */
|
||||
/* $OpenBSD: tmux.h,v 1.1430 2026/08/24 21:17:19 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -68,6 +68,7 @@ struct options_array_item;
|
||||
struct options_entry;
|
||||
struct prompt;
|
||||
struct window_pane_prompt;
|
||||
struct redraw_damage;
|
||||
struct redraw_scene;
|
||||
struct redraw_span;
|
||||
struct screen_write_citem;
|
||||
@@ -75,7 +76,12 @@ struct screen_write_cline;
|
||||
struct screen_write_ctx;
|
||||
struct session;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image;
|
||||
struct image_backend;
|
||||
struct image_rect;
|
||||
struct image_line;
|
||||
struct image_store;
|
||||
struct sixel_image;
|
||||
#endif
|
||||
|
||||
@@ -483,7 +489,6 @@ enum tty_code_code {
|
||||
TTYC_ICH1,
|
||||
TTYC_IL,
|
||||
TTYC_IL1,
|
||||
TTYC_IND,
|
||||
TTYC_INDN,
|
||||
TTYC_INVIS,
|
||||
TTYC_KCBT,
|
||||
@@ -859,7 +864,7 @@ struct colour_palette {
|
||||
struct grid_cell {
|
||||
struct utf8_data data;
|
||||
u_short attr;
|
||||
u_char flags;
|
||||
u_short flags;
|
||||
int fg;
|
||||
int bg;
|
||||
int us;
|
||||
@@ -870,7 +875,7 @@ struct grid_cell {
|
||||
struct grid_extd_entry {
|
||||
utf8_char data;
|
||||
u_short attr;
|
||||
u_char flags;
|
||||
u_short flags;
|
||||
int fg;
|
||||
int bg;
|
||||
int us;
|
||||
@@ -888,7 +893,7 @@ struct grid_cell_entry {
|
||||
u_char data;
|
||||
} data;
|
||||
};
|
||||
u_char flags;
|
||||
u_short flags;
|
||||
} __packed;
|
||||
|
||||
/* OSC 133 data for a grid line. */
|
||||
@@ -912,6 +917,9 @@ struct grid_line {
|
||||
u_int time;
|
||||
struct osc133_data osc133_data;
|
||||
u_short flags;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image_line *images;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Entire grid of cells. */
|
||||
@@ -931,6 +939,9 @@ struct grid {
|
||||
u_int scroll_generation;
|
||||
|
||||
struct grid_line *linedata;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image_store *images;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Virtual cursor in a grid. */
|
||||
@@ -1022,24 +1033,65 @@ struct style {
|
||||
u_int link;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Image. */
|
||||
struct image {
|
||||
struct screen *s;
|
||||
struct sixel_image *data;
|
||||
char *fallback;
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Private ACS keys used by the text image backend. */
|
||||
#define TTY_ACS_IMAGE_SHADE_LIGHT 0x80
|
||||
#define TTY_ACS_IMAGE_SHADE_MEDIUM 0x81
|
||||
#define TTY_ACS_IMAGE_SHADE_DARK 0x82
|
||||
#define TTY_ACS_IMAGE_BLOCK 0x83
|
||||
#define TTY_ACS_IMAGE_HALF_UPPER 0x84
|
||||
#define TTY_ACS_IMAGE_HALF_LOWER 0x85
|
||||
#define TTY_ACS_IMAGE_HALF_LEFT 0x86
|
||||
#define TTY_ACS_IMAGE_HALF_RIGHT 0x87
|
||||
#define TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT 0x88
|
||||
#define TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT 0x89
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT 0x8a
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT 0x8b
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT 0x8c
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT 0x8d
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT 0x8e
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT 0x8f
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT 0x90
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT 0x91
|
||||
#define TTY_ACS_IMAGE_SEXTANT_FIRST 0x92
|
||||
#define TTY_ACS_IMAGE_SEXTANT_LAST 0xcd
|
||||
|
||||
u_int px;
|
||||
u_int py;
|
||||
struct image_sample {
|
||||
u_char red;
|
||||
u_char green;
|
||||
u_char blue;
|
||||
u_char alpha;
|
||||
u_char brightness;
|
||||
};
|
||||
#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];
|
||||
};
|
||||
struct image {
|
||||
u_int id;
|
||||
u_int references;
|
||||
u_int flags;
|
||||
u_int parent_id;
|
||||
u_int source_id;
|
||||
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;
|
||||
struct sixel_image *sixel;
|
||||
struct image_cell *cells;
|
||||
void *fallback_data;
|
||||
|
||||
struct images *list;
|
||||
TAILQ_ENTRY (image) entry;
|
||||
|
||||
TAILQ_ENTRY (image) all_entry;
|
||||
RB_ENTRY(image) entry;
|
||||
};
|
||||
TAILQ_HEAD(images, image);
|
||||
RB_HEAD(images, image);
|
||||
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
#endif
|
||||
|
||||
/* Cursor style. */
|
||||
@@ -1098,11 +1150,6 @@ struct screen {
|
||||
bitstr_t *tabs;
|
||||
struct screen_sel *sel;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct images images;
|
||||
struct images saved_images;
|
||||
#endif
|
||||
|
||||
struct screen_write_cline *write_list;
|
||||
|
||||
struct hyperlinks *hyperlinks;
|
||||
@@ -1120,6 +1167,7 @@ struct screen_write_ctx {
|
||||
#define SCREEN_WRITE_SYNC 0x1
|
||||
#define SCREEN_WRITE_OBSCURED 0x2
|
||||
#define SCREEN_WRITE_CHECKED_IF_OBSCURED 0x4
|
||||
#define SCREEN_WRITE_INPUT 0x8
|
||||
|
||||
screen_write_init_ctx_cb init_ctx_cb;
|
||||
void *arg;
|
||||
@@ -1357,10 +1405,9 @@ struct window_pane {
|
||||
char tty[TTY_NAME_MAX];
|
||||
int status;
|
||||
struct timeval dead_time;
|
||||
struct cmdq_item *wait_item;
|
||||
struct cmdq_item *wait_item; /* new-pane -W: waiting for pane exit */
|
||||
struct spawn_editor_state *editor;
|
||||
|
||||
uint64_t output_generation;
|
||||
time_t last_output_time;
|
||||
time_t last_prompt_time;
|
||||
time_t cmd_start_time;
|
||||
@@ -1426,6 +1473,7 @@ struct window_pane {
|
||||
TAILQ_HEAD(window_panes, window_pane);
|
||||
TAILQ_HEAD(window_panes_zindex, window_pane);
|
||||
RB_HEAD(window_pane_tree, window_pane);
|
||||
TAILQ_HEAD(redraw_damages, redraw_damage);
|
||||
|
||||
/* Window structure. */
|
||||
struct window {
|
||||
@@ -1469,6 +1517,13 @@ struct window {
|
||||
|
||||
uint64_t redraw_scene_generation;
|
||||
|
||||
struct redraw_damages damage;
|
||||
u_int damage_count;
|
||||
#ifdef ENABLE_IMAGES
|
||||
uint64_t image_scroll_epoch;
|
||||
int image_scroll_pending;
|
||||
#endif
|
||||
|
||||
struct menu_data *menu;
|
||||
u_int menu_last_px;
|
||||
u_int menu_last_py;
|
||||
@@ -1736,6 +1791,11 @@ struct tty_term {
|
||||
#define TERM_VT100LIKE 0x20
|
||||
#define TERM_SIXEL 0x40
|
||||
#define TERM_INVALIDMS 0x80
|
||||
#define TERM_KITTY 0x100
|
||||
#ifdef ENABLE_IMAGES
|
||||
#define TERM_IMAGE_QUADRANTS 0x200
|
||||
#define TERM_IMAGE_SEXTANTS 0x400
|
||||
#endif
|
||||
int flags;
|
||||
|
||||
LIST_ENTRY(tty_term) entry;
|
||||
@@ -1838,10 +1898,17 @@ struct tty {
|
||||
|
||||
struct event key_timer;
|
||||
struct tty_key *key_tree;
|
||||
#ifdef ENABLE_IMAGES
|
||||
const struct image_backend *image_backend;
|
||||
void *image_data;
|
||||
struct window *image_scroll_window;
|
||||
uint64_t image_scroll_epoch;
|
||||
int image_scroll_failed;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Terminal command context. */
|
||||
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *);
|
||||
typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *, u_int, u_int);
|
||||
typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *);
|
||||
struct tty_ctx {
|
||||
struct screen *s;
|
||||
@@ -1874,7 +1941,7 @@ struct tty_ctx {
|
||||
size_t size;
|
||||
} sel;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image *image;
|
||||
#endif
|
||||
};
|
||||
@@ -2777,32 +2844,6 @@ void events_fire_window(const char *, struct window *);
|
||||
void events_fire_pane(const char *, struct window_pane *);
|
||||
void events_fire_winlink(const char *, struct winlink *);
|
||||
|
||||
/* active.c */
|
||||
enum active_window_mode {
|
||||
ACTIVE_SHARED,
|
||||
ACTIVE_LOCAL
|
||||
};
|
||||
int active_is_local_window(struct client *, struct session *);
|
||||
void active_set_local_window(struct client *, struct session *,
|
||||
enum active_window_mode);
|
||||
struct winlink *active_get_effective_winlink(struct client *,
|
||||
struct session *);
|
||||
struct window *active_get_effective_window(struct client *,
|
||||
struct session *);
|
||||
int active_select_window(struct client *, struct session *,
|
||||
struct winlink *);
|
||||
int active_select_window_index(struct client *, struct session *, int);
|
||||
int active_next_window(struct client *, struct session *, int);
|
||||
int active_previous_window(struct client *, struct session *, int);
|
||||
int active_last_window(struct client *, struct session *);
|
||||
void active_remove_client(struct client *);
|
||||
void active_remove_session(struct session *);
|
||||
void active_remove_window(struct window *);
|
||||
int active_is_effective_window(struct client *, struct session *,
|
||||
struct winlink *);
|
||||
int active_is_last_window(struct client *, struct session *,
|
||||
struct winlink *);
|
||||
|
||||
/* format-draw.c */
|
||||
void format_draw(struct screen_write_ctx *, const struct grid_cell *,
|
||||
u_int, const char *, struct style_ranges *, int);
|
||||
@@ -3020,10 +3061,6 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_setselection(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_rawstring(struct tty *, const struct tty_ctx *);
|
||||
#ifdef ENABLE_SIXEL
|
||||
void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *);
|
||||
void tty_draw_images(struct client *, struct window_pane *);
|
||||
#endif
|
||||
void tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
|
||||
void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *);
|
||||
|
||||
@@ -3065,6 +3102,7 @@ void tty_default_features(struct client *, const char *, u_int);
|
||||
/* tty-acs.c */
|
||||
int tty_acs_needed(struct tty *);
|
||||
const char *tty_acs_get(struct tty *, u_char);
|
||||
u_char tty_acs_image_sextant(u_int);
|
||||
int tty_acs_reverse_get(struct tty *, const char *, size_t);
|
||||
const struct utf8_data *tty_acs_double_borders(int);
|
||||
const struct utf8_data *tty_acs_heavy_borders(int);
|
||||
@@ -3667,7 +3705,7 @@ void screen_write_setselection(struct screen_write_ctx *, const char *,
|
||||
u_char *, u_int);
|
||||
void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
|
||||
int);
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
void screen_write_sixelimage(struct screen_write_ctx *,
|
||||
struct sixel_image *, u_int);
|
||||
#endif
|
||||
@@ -3681,8 +3719,17 @@ void redraw_screen(struct client *);
|
||||
void redraw_pane(struct client *, struct window_pane *);
|
||||
void redraw_pane_scrollbar(struct client *, struct window_pane *);
|
||||
void redraw_free_scene(struct redraw_scene *);
|
||||
int redraw_client_has_window(struct client *, struct window *);
|
||||
void redraw_invalidate_scene(struct window *);
|
||||
void redraw_invalidate_all_scenes(void);
|
||||
void redraw_damage_window(struct window *, u_int, u_int, u_int, u_int);
|
||||
#ifdef ENABLE_IMAGES
|
||||
void redraw_damage_window_scroll(struct window *, u_int, u_int, u_int,
|
||||
u_int);
|
||||
void redraw_image_scroll_result(struct tty *, const struct tty_ctx *, int);
|
||||
#endif
|
||||
void redraw_free_damage(struct window *);
|
||||
void redraw_client_damage(struct client *);
|
||||
int redraw_get_status_border_cell_type(struct redraw_span **, u_int);
|
||||
|
||||
/* screen.c */
|
||||
@@ -3807,7 +3854,7 @@ int window_pane_is_visible(struct window_pane *);
|
||||
int window_pane_exited(struct window_pane *);
|
||||
u_int window_pane_search(struct window_pane *, const char *, int,
|
||||
int);
|
||||
const char *window_printable_flags(struct client *, struct winlink *, int);
|
||||
const char *window_printable_flags(struct winlink *, int);
|
||||
const char *window_pane_printable_flags(struct window_pane *);
|
||||
struct window_pane *window_pane_find_up(struct window_pane *);
|
||||
struct window_pane *window_pane_find_down(struct window_pane *);
|
||||
@@ -3855,6 +3902,8 @@ int window_pane_get_pane_status(struct window_pane *);
|
||||
struct style_range *window_pane_status_get_range(struct window_pane *, u_int,
|
||||
u_int);
|
||||
int window_pane_is_floating(struct window_pane *);
|
||||
void window_pane_redraw_floating(struct window *,
|
||||
struct window_pane *, int, int, int, int);
|
||||
|
||||
/* window-border.c */
|
||||
void window_set_fill_cells(struct window *);
|
||||
@@ -4133,7 +4182,6 @@ void session_update_history(struct session *);
|
||||
/* utf8.c */
|
||||
enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *);
|
||||
enum utf8_state utf8_fromwc(wchar_t wc, struct utf8_data *);
|
||||
int utf8_has_whitespace(const struct utf8_data *);
|
||||
void utf8_update_width_cache(void);
|
||||
utf8_char utf8_build_one(u_char);
|
||||
enum utf8_state utf8_from_data(const struct utf8_data *, utf8_char *);
|
||||
@@ -4255,17 +4303,108 @@ void spawn_editor_finish(struct window_pane *);
|
||||
/* regsub.c */
|
||||
char *regsub(const char *, const char *, const char *, int);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* image.c */
|
||||
int image_free_all(struct screen *);
|
||||
struct image *image_store(struct screen *, struct sixel_image *);
|
||||
int image_check_line(struct screen *, u_int, u_int);
|
||||
int image_check_area(struct screen *, u_int, u_int, u_int, u_int);
|
||||
int image_scroll_up(struct screen *, u_int);
|
||||
#define IMAGE_BACKEND_GRAPHICAL 0x1
|
||||
#define IMAGE_BACKEND_SCROLLS 0x2
|
||||
|
||||
struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int,
|
||||
u_char *);
|
||||
struct image *image_create_view(struct image *, u_int, u_int, u_int,
|
||||
u_int, u_int, u_int, u_int, u_int, u_int, u_int);
|
||||
struct image *image_find(u_int);
|
||||
u_int image_get_id(const struct image *);
|
||||
void image_get_size(const struct image *, u_int *, u_int *);
|
||||
void image_get_canvas_size(const struct image *, u_int *, u_int *);
|
||||
void image_get_size_in_cells(const struct image *, u_int *, u_int *);
|
||||
const u_char *image_get_pixels(const struct image *, size_t *, size_t *);
|
||||
void image_set_no_cursor(struct image *);
|
||||
struct sixel_image *image_get_sixel(const struct image *);
|
||||
void image_set_sixel(struct image *, struct sixel_image *);
|
||||
void image_ref(u_int);
|
||||
void image_free(u_int);
|
||||
void image_write_sixel(struct screen_write_ctx *, struct image *,
|
||||
u_int);
|
||||
void image_write_kitty(struct screen_write_ctx *, struct image *,
|
||||
u_int, u_int, u_int, int32_t);
|
||||
void image_get_pixel_rect(const struct image *, u_int, u_int,
|
||||
u_int, u_int, u_int *, u_int *, u_int *, u_int *);
|
||||
void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *,
|
||||
u_int *);
|
||||
u_char *image_base64_decode(const char *, size_t, size_t, size_t *);
|
||||
u_char *image_png_decode(const u_char *, size_t, size_t, u_int *,
|
||||
u_int *);
|
||||
void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
|
||||
u_int, u_int);
|
||||
void image_redraw_all(struct screen_write_ctx *);
|
||||
void image_redraw_scroll(struct screen_write_ctx *, u_int);
|
||||
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
int image_backend_flags(struct tty *);
|
||||
void image_tty_update(struct tty *);
|
||||
void image_tty_geometry_changed(struct tty *);
|
||||
void image_tty_free(struct tty *, int);
|
||||
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
|
||||
u_int, u_int, u_int, int, const struct tty_style_ctx *);
|
||||
void image_get_fallback_cell(struct tty *, struct image *, u_int,
|
||||
u_int, const struct grid_cell *, struct grid_cell *,
|
||||
const struct tty_style_ctx *);
|
||||
const struct image_cell *image_get_cell(struct image *, u_int, u_int);
|
||||
void image_free_fallback(struct image *);
|
||||
int image_get_fallback_at(struct tty *, struct screen *, u_int,
|
||||
u_int, const struct grid_cell *, struct grid_cell *,
|
||||
const struct tty_style_ctx *);
|
||||
struct image *image_rect_get_image(const struct image_rect *);
|
||||
const struct grid_cell *image_rect_get_cell(
|
||||
const struct image_rect *);
|
||||
void image_rect_get_coords(const struct image_rect *,
|
||||
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
|
||||
int32_t image_rect_get_z(const struct image_rect *);
|
||||
void image_clear(struct screen_write_ctx *, u_int);
|
||||
void image_clear_kitty(struct screen_write_ctx *, char, u_int,
|
||||
u_int, int32_t);
|
||||
void image_grid_damage(struct grid *, u_int, u_int, u_int, u_int);
|
||||
void image_grid_free_line(struct grid *, struct grid_line *);
|
||||
void image_grid_free(struct grid *);
|
||||
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
|
||||
u_int);
|
||||
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
|
||||
u_int, u_int);
|
||||
void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
|
||||
u_int, u_int, u_int, u_int);
|
||||
void image_grid_resize_width(struct grid *, u_int);
|
||||
int image_grid_line_has_images(const struct grid_line *);
|
||||
int image_grid_check_area(struct grid *, u_int, u_int, u_int,
|
||||
u_int);
|
||||
int image_grid_get_source(struct grid *, u_int, u_int,
|
||||
struct image *, u_int *, u_int *);
|
||||
void image_place_cell_kitty(struct screen_write_ctx *, struct image *,
|
||||
u_int, u_int, u_int, u_int, u_int, u_int, int32_t);
|
||||
#define KITTY_PARSE_ERROR -1
|
||||
#define KITTY_PARSE_OK 0
|
||||
#define KITTY_PARSE_MORE 1
|
||||
#define KITTY_PARSE_MISSING 2
|
||||
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
|
||||
u_int, u_int *, u_int *, u_int *, char *, char *, u_int *,
|
||||
int32_t *, int *);
|
||||
int kitty_placeholder_to_image(void *, struct grid *,
|
||||
struct grid_cell *, u_int, u_int, struct image **, u_int *,
|
||||
u_int *, u_int *, u_int *, int32_t *);
|
||||
void kitty_free_state(void *);
|
||||
void kitty_draw_rect(struct tty *,
|
||||
const struct image_rect *, const struct tty_style_ctx *);
|
||||
void kitty_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
void kitty_free_output_state(struct tty *, int);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* image-sixel.c */
|
||||
#define SIXEL_COLOUR_REGISTERS 1024
|
||||
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int);
|
||||
void sixel_draw_rect(struct tty *,
|
||||
const struct image_rect *, const struct tty_style_ctx *);
|
||||
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
|
||||
void sixel_free_output(struct tty *, int);
|
||||
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,
|
||||
u_int);
|
||||
void sixel_free(struct sixel_image *);
|
||||
void sixel_log(struct sixel_image *);
|
||||
void sixel_size_in_cells(struct sixel_image *, u_int *, u_int *);
|
||||
@@ -4274,6 +4413,7 @@ struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int,
|
||||
char *sixel_print(struct sixel_image *, struct sixel_image *,
|
||||
size_t *);
|
||||
struct screen *sixel_to_screen(struct sixel_image *);
|
||||
struct image *sixel_to_image(struct sixel_image *);
|
||||
#endif
|
||||
|
||||
/* server-acl.c */
|
||||
|
||||
51
tools/generate-image-diacritics.sh
Executable file
51
tools/generate-image-diacritics.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Generate the Unicode diacritic table used by Kitty image placeholders.
|
||||
#
|
||||
# Input must be UnicodeData.txt from Unicode 6.0.0:
|
||||
# https://www.unicode.org/Public/6.0.0/ucd/UnicodeData.txt
|
||||
# SHA-256: 90b45b777346bef027556f9c6cb3ea5d7d745bd60d6762855c08d8a22d34f771
|
||||
#
|
||||
# The selection follows the public Kitty graphics protocol: nonspacing marks
|
||||
# with canonical combining class 230, NSM bidi class, no decomposition, minus
|
||||
# characters which may be combined or normalized in ways unsuitable for a
|
||||
# row or column index.
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "usage: $0 UnicodeData.txt" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
awk -F ';' '
|
||||
BEGIN {
|
||||
omit["0300"] = omit["0301"] = omit["0302"] = 1
|
||||
omit["0303"] = omit["0304"] = omit["0306"] = 1
|
||||
omit["0307"] = omit["0308"] = omit["0309"] = 1
|
||||
omit["030A"] = omit["030B"] = omit["030C"] = 1
|
||||
omit["030F"] = omit["0311"] = omit["0313"] = 1
|
||||
omit["0314"] = omit["0342"] = omit["0653"] = 1
|
||||
omit["0654"] = 1
|
||||
}
|
||||
$3 == "Mn" && $4 == "230" && $5 == "NSM" && $6 == "" && !omit[$1] {
|
||||
point[++count] = $1
|
||||
}
|
||||
END {
|
||||
if (count != 297) {
|
||||
printf "expected 297 diacritics, found %u\n", count > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
print "static const uint32_t kitty_diacritics[] = {"
|
||||
for (i = 1; i <= count; i++) {
|
||||
if ((i - 1) % 8 == 0)
|
||||
printf "\t"
|
||||
printf "0x%s", point[i]
|
||||
if (i != count)
|
||||
printf ","
|
||||
if (i == count || i % 8 == 0)
|
||||
printf "\n"
|
||||
else
|
||||
printf " "
|
||||
}
|
||||
print "};"
|
||||
}
|
||||
' "$1"
|
||||
104
tools/image-edge-test.sh
Normal file
104
tools/image-edge-test.sh
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Compact visual check for image subcell geometry and layering. Run inside a
|
||||
# tmux pane on Kitty or a SIXEL terminal; it needs at least 90 columns/24 rows.
|
||||
|
||||
if [ -z "$TMUX" ]; then
|
||||
echo "Run this script inside tmux." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
columns=$(tput cols 2>/dev/null || echo 0)
|
||||
lines=$(tput lines 2>/dev/null || echo 0)
|
||||
if [ "$columns" -lt 90 ] || [ "$lines" -lt 24 ]; then
|
||||
echo "This test needs at least 90 columns and 24 rows." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
at()
|
||||
{
|
||||
printf '\033[%s;%sH' "$1" "$2"
|
||||
}
|
||||
|
||||
kitty()
|
||||
{
|
||||
printf '\033_G%s\033\\' "$1"
|
||||
}
|
||||
|
||||
kitty_solid()
|
||||
{
|
||||
id=$1
|
||||
rgba=$2
|
||||
payload=$(i=0; while [ "$i" -lt 256 ]; do
|
||||
printf "$rgba"
|
||||
i=$((i + 1))
|
||||
done | base64 | tr -d '\n')
|
||||
kitty "a=t,q=2,f=32,s=16,v=16,i=$id;$payload"
|
||||
}
|
||||
|
||||
sixel_folder()
|
||||
{
|
||||
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\\'
|
||||
}
|
||||
|
||||
feature_info=$(tmux display-message -p \
|
||||
'term=#{client_termname} features=#{client_termfeatures} cells=#{client_cell_width}x#{client_cell_height}')
|
||||
|
||||
printf '\033[2J\033[H\033[?25l'
|
||||
at 1 2
|
||||
printf 'tmux image edge-case check'
|
||||
at 2 2
|
||||
printf '%s' "$feature_info"
|
||||
|
||||
kitty_solid 201 '\377\000\000\377'
|
||||
kitty_solid 202 '\000\377\000\377'
|
||||
kitty_solid 203 '\000\000\377\377'
|
||||
|
||||
# The 26-pixel folder occupies whole grid cells, but its visible right and
|
||||
# bottom edges must remain partial rather than stretching to those boundaries.
|
||||
at 4 2
|
||||
printf '1. partial SIXEL: folder should be the same size as direct output'
|
||||
at 4 72
|
||||
sixel_folder
|
||||
|
||||
# Chawan emits X/Y for its image padding. The red square must begin slightly
|
||||
# below and right of the [img] anchor, never over it.
|
||||
at 8 2
|
||||
printf '2. Kitty X/Y: RED begins below/right of the [img] anchor'
|
||||
at 8 72
|
||||
printf '[img]'
|
||||
at 9 72
|
||||
kitty 'a=p,q=2,C=1,i=201,p=1,x=0,y=0,w=16,h=16,c=3,r=2,X=6,Y=6'
|
||||
|
||||
# Positive Kitty z overlays text and other lower z images; negative z is
|
||||
# below text. The shared red/blue edge makes any wrong order obvious.
|
||||
at 12 2
|
||||
printf '3. Kitty z: BLUE covers RED; GREEN stays behind TEXT'
|
||||
at 12 72
|
||||
printf 'TEXT'
|
||||
at 12 72
|
||||
kitty 'a=p,q=2,C=1,i=202,p=2,x=0,y=0,w=16,h=16,c=4,r=2,z=-1'
|
||||
at 14 72
|
||||
kitty 'a=p,q=2,C=1,i=201,p=3,x=0,y=0,w=16,h=16,c=4,r=2,z=1'
|
||||
at 14 74
|
||||
kitty 'a=p,q=2,C=1,i=203,p=4,x=0,y=0,w=16,h=16,c=4,r=2,z=2'
|
||||
|
||||
# SIXEL is temporal: a later image covers text, while later text destroys the
|
||||
# affected image cells. Both results should match on Kitty and SIXEL output.
|
||||
at 18 2
|
||||
printf '4. SIXEL after text: folder covers FOLDER'
|
||||
at 18 72
|
||||
printf 'FOLDER'
|
||||
at 18 72
|
||||
sixel_folder
|
||||
at 21 2
|
||||
printf '5. SIXEL before text: TEXT WINS, no folder remnant'
|
||||
at 21 72
|
||||
sixel_folder
|
||||
at 21 72
|
||||
printf 'TEXT WINS'
|
||||
|
||||
at 23 2
|
||||
printf 'Take one screenshot, then press Enter to return to the shell.'
|
||||
printf '\033[?25h'
|
||||
IFS= read -r _image_edge_reply
|
||||
109
tools/image-visual-test.sh
Executable file
109
tools/image-visual-test.sh
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/bin/sh
|
||||
|
||||
# One-screen visual check for tmux image input and layering. Run this inside a
|
||||
# tmux pane attached to Kitty, WezTerm, Microsoft Terminal, or a basic terminal.
|
||||
|
||||
if [ -z "$TMUX" ]; then
|
||||
echo "Run this script inside tmux." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
columns=$(tput cols 2>/dev/null || echo 0)
|
||||
lines=$(tput lines 2>/dev/null || echo 0)
|
||||
if [ "$columns" -lt 72 ] || [ "$lines" -lt 23 ]; then
|
||||
echo "This test needs at least 72 columns and 23 rows." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
at()
|
||||
{
|
||||
printf '\033[%s;%sH' "$1" "$2"
|
||||
}
|
||||
|
||||
kitty()
|
||||
{
|
||||
printf '\033_G%s\033\\' "$1"
|
||||
}
|
||||
|
||||
# Transmit a 16x16 solid RGBA texture. A one-pixel texture is unsuitable here
|
||||
# because Kitty filters its transparent texture border into scaled edges.
|
||||
kitty_solid()
|
||||
{
|
||||
id=$1
|
||||
rgba=$2
|
||||
payload=$(i=0; while [ "$i" -lt 256 ]; do
|
||||
printf "$rgba"
|
||||
i=$((i + 1))
|
||||
done | base64 | tr -d '\n')
|
||||
kitty "a=t,q=2,f=32,s=16,v=16,i=$id;$payload"
|
||||
}
|
||||
|
||||
# A small 26x26 transparent SIXEL folder, used by the supplied bptato tests.
|
||||
sixel_folder()
|
||||
{
|
||||
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\\'
|
||||
}
|
||||
|
||||
feature_info=$(tmux display-message -p \
|
||||
'term=#{client_termname} features=#{client_termfeatures} cells=#{client_cell_width}x#{client_cell_height}')
|
||||
|
||||
printf '\033[2J\033[H\033[?25l'
|
||||
at 1 2
|
||||
printf 'tmux image visual check'
|
||||
at 2 2
|
||||
printf '%s' "$feature_info"
|
||||
|
||||
# Red is placed after its text at a positive Kitty z-index, so it must cover
|
||||
# the text. Green is placed below the text at a negative Kitty z-index.
|
||||
kitty_solid 101 '\377\000\000\377'
|
||||
kitty_solid 102 '\000\377\000\377'
|
||||
kitty_solid 103 '\000\000\377\377'
|
||||
kitty_solid 104 '\377\000\000\177'
|
||||
|
||||
at 4 2
|
||||
printf '1. positive Kitty z: RED must hide TEXT UNDER at right'
|
||||
at 4 62
|
||||
printf 'TEXT UNDER'
|
||||
at 4 60
|
||||
kitty 'a=p,q=2,C=1,i=101,p=1,x=1,y=1,w=14,h=14,c=18,r=2,z=1'
|
||||
|
||||
at 7 2
|
||||
printf '2. negative Kitty z: GREEN remains behind TEXT ON TOP'
|
||||
at 7 60
|
||||
kitty 'a=p,q=2,C=1,i=102,p=2,x=1,y=1,w=14,h=14,c=18,r=2,z=-1'
|
||||
at 7 62
|
||||
printf 'TEXT ON TOP'
|
||||
|
||||
at 10 2
|
||||
printf '3. Kitty z overlap: RED left, BLUE on top at right'
|
||||
at 11 55
|
||||
kitty 'a=p,q=2,C=1,i=101,p=3,x=1,y=1,w=14,h=14,c=18,r=3,z=1'
|
||||
at 11 64
|
||||
kitty 'a=p,q=2,C=1,i=103,p=4,x=1,y=1,w=14,h=14,c=9,r=3,z=2'
|
||||
|
||||
at 15 2
|
||||
printf '4. alpha: half-transparent RED, dithered on SIXEL'
|
||||
at 15 60
|
||||
kitty 'a=p,q=2,C=1,i=104,p=5,x=1,y=1,w=14,h=14,c=18,r=2,z=1'
|
||||
|
||||
# SIXEL semantics are temporal: later text destroys a SIXEL image, while a
|
||||
# later SIXEL image appears over existing text.
|
||||
at 18 2
|
||||
printf '5. SIXEL after text: folder covers FOLDER at right'
|
||||
at 18 65
|
||||
printf 'FOLDER'
|
||||
at 18 65
|
||||
sixel_folder
|
||||
|
||||
at 21 2
|
||||
printf '6. SIXEL before text: no folder remains behind TEXT WINS'
|
||||
at 21 65
|
||||
sixel_folder
|
||||
at 21 65
|
||||
printf 'TEXT WINS'
|
||||
|
||||
at 23 2
|
||||
printf 'Take one screenshot, then press Enter to return to the shell.'
|
||||
at 23 66
|
||||
printf '\033[?25h'
|
||||
IFS= read -r _image_visual_reply
|
||||
67
tty-acs.c
67
tty-acs.c
@@ -64,9 +64,65 @@ static const struct tty_acs_entry tty_acs_table[] = {
|
||||
{ '{', "\317\200" }, /* greek pi */
|
||||
{ '|', "\342\211\240" }, /* not-equal */
|
||||
{ '}', "\302\243" }, /* UK pound sign */
|
||||
{ '~', "\302\267" } /* bullet */
|
||||
{ '~', "\302\267" }, /* bullet */
|
||||
#ifdef ENABLE_IMAGES
|
||||
{ TTY_ACS_IMAGE_SHADE_LIGHT, "\342\226\221" },
|
||||
{ TTY_ACS_IMAGE_SHADE_MEDIUM, "\342\226\222" },
|
||||
{ TTY_ACS_IMAGE_SHADE_DARK, "\342\226\223" },
|
||||
{ TTY_ACS_IMAGE_BLOCK, "\342\226\210" },
|
||||
{ TTY_ACS_IMAGE_HALF_UPPER, "\342\226\200" },
|
||||
{ TTY_ACS_IMAGE_HALF_LOWER, "\342\226\204" },
|
||||
{ TTY_ACS_IMAGE_HALF_LEFT, "\342\226\214" },
|
||||
{ TTY_ACS_IMAGE_HALF_RIGHT, "\342\226\220" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT, "\342\226\226" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT, "\342\226\227" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT, "\342\226\230" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT,
|
||||
"\342\226\231" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT, "\342\226\232" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT,
|
||||
"\342\226\233" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT,
|
||||
"\342\226\234" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT, "\342\226\235" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT, "\342\226\236" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT,
|
||||
"\342\226\237" }
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
static char tty_acs_image_sextants[60][5];
|
||||
|
||||
static void
|
||||
tty_acs_image_sextants_init(void)
|
||||
{
|
||||
u_int cp, i;
|
||||
|
||||
if (tty_acs_image_sextants[0][0] != '\0')
|
||||
return;
|
||||
for (i = 0; i < nitems(tty_acs_image_sextants); i++) {
|
||||
cp = 0x1fb00 + i;
|
||||
tty_acs_image_sextants[i][0] = 0xf0 | (cp >> 18);
|
||||
tty_acs_image_sextants[i][1] = 0x80 | ((cp >> 12) & 0x3f);
|
||||
tty_acs_image_sextants[i][2] = 0x80 | ((cp >> 6) & 0x3f);
|
||||
tty_acs_image_sextants[i][3] = 0x80 | (cp & 0x3f);
|
||||
}
|
||||
}
|
||||
|
||||
u_char
|
||||
tty_acs_image_sextant(u_int mask)
|
||||
{
|
||||
if (mask == 0 || mask == 21 || mask == 42 || mask >= 63)
|
||||
return (0);
|
||||
if (mask < 21)
|
||||
return (TTY_ACS_IMAGE_SEXTANT_FIRST + mask - 1);
|
||||
if (mask < 42)
|
||||
return (TTY_ACS_IMAGE_SEXTANT_FIRST + mask - 2);
|
||||
return (TTY_ACS_IMAGE_SEXTANT_FIRST + mask - 3);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Table mapping UTF-8 to ACS entries. */
|
||||
struct tty_acs_reverse_entry {
|
||||
const char *string;
|
||||
@@ -239,6 +295,15 @@ tty_acs_get(struct tty *tty, u_char ch)
|
||||
return (&tty->term->acs[ch][0]);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (ch >= TTY_ACS_IMAGE_SEXTANT_FIRST &&
|
||||
ch <= TTY_ACS_IMAGE_SEXTANT_LAST) {
|
||||
tty_acs_image_sextants_init();
|
||||
return (tty_acs_image_sextants[
|
||||
ch - TTY_ACS_IMAGE_SEXTANT_FIRST]);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Otherwise look up the UTF-8 translation. */
|
||||
entry = bsearch(&ch, tty_acs_table, nitems(tty_acs_table),
|
||||
sizeof tty_acs_table[0], tty_acs_cmp);
|
||||
|
||||
29
tty-draw.c
29
tty-draw.c
@@ -123,6 +123,10 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
||||
struct grid *gd = s->grid;
|
||||
const struct grid_cell *gcp;
|
||||
struct grid_cell gc, ngc, last;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell image_gc;
|
||||
int image_status;
|
||||
#endif
|
||||
struct grid_line *gl;
|
||||
u_int i, j, last_i, cx, ex, width;
|
||||
u_int cellsize, bg;
|
||||
@@ -247,20 +251,35 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
||||
|
||||
if (px >= ex || i >= ex - px) {
|
||||
/* Outside the area being drawn. */
|
||||
empty = nx - i;
|
||||
gcp = &grid_default_cell;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_status = image_get_fallback_at(tty, s, px + i,
|
||||
py, gcp, &image_gc, style_ctx);
|
||||
if (image_status == 1) {
|
||||
gcp = &image_gc;
|
||||
empty = 0;
|
||||
} else if (image_status == -1)
|
||||
empty = 1;
|
||||
else
|
||||
#endif
|
||||
empty = nx - i;
|
||||
} else {
|
||||
/* Get the current cell. */
|
||||
grid_view_get_cell(gd, px + i, py, &gc);
|
||||
gcp = &gc;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_get_fallback_at(tty, s, px + i, py, &gc,
|
||||
&image_gc, style_ctx) == 1)
|
||||
gcp = &image_gc;
|
||||
#endif
|
||||
|
||||
/* Work out empty cells. */
|
||||
empty = tty_draw_line_get_empty(&gc, &last,
|
||||
nx - i);
|
||||
empty = tty_draw_line_get_empty(gcp, &last, nx - i);
|
||||
if (empty != 0)
|
||||
gcp = &gc;
|
||||
;
|
||||
else {
|
||||
/* Update for codeset if needed. */
|
||||
gcp = tty_check_codeset(tty, &gc);
|
||||
gcp = tty_check_codeset(tty, gcp);
|
||||
|
||||
/* And for selection. */
|
||||
if (gcp->flags & GRID_FLAG_SELECTED) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tty-features.c,v 1.43 2026/08/31 12:41:03 kirill Exp $ */
|
||||
/* $OpenBSD: tty-features.c,v 1.42 2026/08/17 14:47:41 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -224,9 +224,11 @@ static const struct tty_feature tty_feature_strikethrough = {
|
||||
0
|
||||
};
|
||||
|
||||
#define TTY_FEATURE_SYNC "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;"
|
||||
|
||||
/* Terminal supports synchronized updates. */
|
||||
static const char *const tty_feature_sync_capabilities[] = {
|
||||
"Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;",
|
||||
TTY_FEATURE_SYNC,
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_sync = {
|
||||
@@ -357,6 +359,34 @@ static const struct tty_feature tty_feature_sixel = {
|
||||
TERM_SIXEL
|
||||
};
|
||||
|
||||
/* Terminal has Kitty graphics protocol Unicode placeholder capability. */
|
||||
static const char *const tty_feature_kitty_capabilities[] = {
|
||||
"Kty",
|
||||
TTY_FEATURE_SYNC,
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_kitty = {
|
||||
"kitty",
|
||||
tty_feature_kitty_capabilities,
|
||||
TERM_KITTY
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
static const char *const tty_feature_image_capabilities[] = {
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_image_quadrants = {
|
||||
"image-quadrants",
|
||||
tty_feature_image_capabilities,
|
||||
TERM_IMAGE_QUADRANTS
|
||||
};
|
||||
static const struct tty_feature tty_feature_image_sextants = {
|
||||
"image-sextants",
|
||||
tty_feature_image_capabilities,
|
||||
TERM_IMAGE_SEXTANTS
|
||||
};
|
||||
#endif
|
||||
|
||||
/* Terminal supports the OSC 9;4 progress bar. */
|
||||
static const char *const tty_feature_progressbar_capabilities[] = {
|
||||
"Spb=\\E]9;4;%p1%d;%p2%d\\E\\\\",
|
||||
@@ -368,13 +398,6 @@ static const struct tty_feature tty_feature_progressbar = {
|
||||
0
|
||||
};
|
||||
|
||||
/* Terminal supports UTF-8. */
|
||||
static const struct tty_feature tty_feature_utf8 = {
|
||||
"utf8",
|
||||
NULL,
|
||||
0
|
||||
};
|
||||
|
||||
/* Available terminal features. */
|
||||
static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_256,
|
||||
@@ -386,6 +409,11 @@ static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_extkeys,
|
||||
&tty_feature_focus,
|
||||
&tty_feature_ignorefkeys,
|
||||
&tty_feature_kitty,
|
||||
#ifdef ENABLE_IMAGES
|
||||
&tty_feature_image_quadrants,
|
||||
&tty_feature_image_sextants,
|
||||
#endif
|
||||
&tty_feature_margins,
|
||||
&tty_feature_mouse,
|
||||
&tty_feature_osc7,
|
||||
@@ -397,8 +425,7 @@ static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_strikethrough,
|
||||
&tty_feature_sync,
|
||||
&tty_feature_title,
|
||||
&tty_feature_usstyle,
|
||||
&tty_feature_utf8
|
||||
&tty_feature_usstyle
|
||||
};
|
||||
|
||||
/* Parse features for client. */
|
||||
@@ -481,9 +508,6 @@ tty_feature_present(struct tty_term *term, const char *name)
|
||||
u_int i;
|
||||
char *copy;
|
||||
|
||||
if (strcmp(name, "utf8") == 0)
|
||||
return ((term->tty->client->flags & CLIENT_UTF8) != 0);
|
||||
|
||||
for (i = 0; i < nitems(tty_features); i++) {
|
||||
tf = tty_features[i];
|
||||
if (strcmp(tf->name, name) == 0) {
|
||||
@@ -497,8 +521,7 @@ tty_feature_present(struct tty_term *term, const char *name)
|
||||
* We don't just have the feature flag set. Check if the capabilities
|
||||
* supported by the client are actual set instead.
|
||||
*/
|
||||
if (tf == NULL || tf->capabilities == NULL ||
|
||||
strcmp(name, "ignorefkeys") == 0)
|
||||
if (tf == NULL || strcmp(name, "ignorefkeys") == 0)
|
||||
return (0);
|
||||
if (tf->flags != 0 && (term->flags & tf->flags) != tf->flags)
|
||||
return (0);
|
||||
@@ -546,8 +569,6 @@ tty_apply_features(struct tty_term *term)
|
||||
}
|
||||
}
|
||||
term->flags |= tf->flags;
|
||||
if (tf == &tty_feature_utf8)
|
||||
c->flags |= CLIENT_UTF8;
|
||||
}
|
||||
if ((term->applied_features|feat) == term->applied_features)
|
||||
return (0);
|
||||
@@ -623,6 +644,7 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
||||
"extkeys,"
|
||||
"focus,"
|
||||
"hyperlinks,"
|
||||
"sixel,"
|
||||
"usstyle"
|
||||
},
|
||||
{ .name = "ghostty",
|
||||
@@ -636,7 +658,20 @@ tty_default_features(struct client *c, const char *name, u_int version)
|
||||
"osc7,"
|
||||
"sync,"
|
||||
"usstyle,"
|
||||
"progressbar"
|
||||
"progressbar,"
|
||||
"kitty"
|
||||
},
|
||||
{ .name = "kitty",
|
||||
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
|
||||
"ccolour,"
|
||||
"cstyle,"
|
||||
"extkeys,"
|
||||
"focus,"
|
||||
"hyperlinks,"
|
||||
"osc7,"
|
||||
"sync,"
|
||||
"usstyle,"
|
||||
"kitty"
|
||||
},
|
||||
{ .name = "Rio",
|
||||
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
|
||||
|
||||
@@ -729,6 +729,7 @@ tty_keys_winsz(struct tty *tty, const char *buf, size_t len, size_t *size)
|
||||
char_y = (ypixel && tty->sy) ? ypixel / tty->sy : 0;
|
||||
tty_set_size(tty, tty->sx, tty->sy, char_x, char_y);
|
||||
tty_invalidate(tty);
|
||||
recalculate_sizes();
|
||||
|
||||
tty->flags &= ~TTY_WINSIZEQUERY;
|
||||
*size = end + 1;
|
||||
@@ -1020,12 +1021,12 @@ complete_key:
|
||||
/* Check for focus events. */
|
||||
if (key == KEYC_FOCUS_OUT) {
|
||||
c->flags &= ~CLIENT_FOCUSED;
|
||||
window_update_focus(active_get_effective_window(c, c->session));
|
||||
window_update_focus(c->session->curw->window);
|
||||
events_fire_client("client-focus-out", c);
|
||||
} else if (key == KEYC_FOCUS_IN) {
|
||||
c->flags |= CLIENT_FOCUSED;
|
||||
events_fire_client("client-focus-in", c);
|
||||
window_update_focus(active_get_effective_window(c, c->session));
|
||||
window_update_focus(c->session->curw->window);
|
||||
}
|
||||
|
||||
/* Fire the key. */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tty-term.c,v 1.109 2026/08/25 08:37:08 nicm Exp $ */
|
||||
/* $OpenBSD: tty-term.c,v 1.108 2026/08/17 14:47:41 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -111,7 +111,6 @@ static const struct tty_term_code_entry tty_term_codes[] = {
|
||||
[TTYC_ICH] = { TTYCODE_STRING, "ich" },
|
||||
[TTYC_IL1] = { TTYCODE_STRING, "il1" },
|
||||
[TTYC_IL] = { TTYCODE_STRING, "il" },
|
||||
[TTYC_IND] = { TTYCODE_STRING, "ind" },
|
||||
[TTYC_INDN] = { TTYCODE_STRING, "indn" },
|
||||
[TTYC_INVIS] = { TTYCODE_STRING, "invis" },
|
||||
[TTYC_KCBT] = { TTYCODE_STRING, "kcbt" },
|
||||
@@ -525,6 +524,7 @@ tty_term_apply_overrides(struct tty_term *term)
|
||||
tty_term_validate(term);
|
||||
}
|
||||
|
||||
/* Mark terminals whose Ms capability does not contain a clipboard argument. */
|
||||
static void
|
||||
tty_term_validate(struct tty_term *term)
|
||||
{
|
||||
|
||||
210
tty.c
210
tty.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: tty.c,v 1.480 2026/08/25 08:37:08 nicm Exp $ */
|
||||
/* $OpenBSD: tty.c,v 1.479 2026/08/18 09:01:20 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -68,11 +68,6 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code,
|
||||
static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int);
|
||||
static int tty_check_overlay(struct tty *, u_int, u_int);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *),
|
||||
struct client *, struct tty_ctx *);
|
||||
#endif
|
||||
|
||||
#define tty_use_margin(tty) \
|
||||
(tty->term->flags & TERM_DECSLRM)
|
||||
#define tty_full_width(tty, ctx) \
|
||||
@@ -163,10 +158,21 @@ tty_resize(struct tty *tty)
|
||||
void
|
||||
tty_set_size(struct tty *tty, u_int sx, u_int sy, u_int xpixel, u_int ypixel)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
int geometry_changed;
|
||||
|
||||
geometry_changed = (tty->xpixel != xpixel || tty->ypixel != ypixel);
|
||||
#endif
|
||||
tty->sx = sx;
|
||||
tty->sy = sy;
|
||||
tty->xpixel = xpixel;
|
||||
tty->ypixel = ypixel;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (geometry_changed) {
|
||||
image_tty_geometry_changed(tty);
|
||||
server_redraw_client(tty->client);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -284,6 +290,10 @@ tty_open(struct tty *tty, char **cause)
|
||||
}
|
||||
tty->flags |= TTY_OPENED;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_update(tty);
|
||||
#endif
|
||||
|
||||
tty->flags &= ~(TTY_NOCURSOR|TTY_FREEZE|TTY_BLOCK|TTY_TIMER);
|
||||
|
||||
event_set(&tty->event_in, c->fd, EV_PERSIST|EV_READ,
|
||||
@@ -338,7 +348,6 @@ tty_start_tty(struct tty *tty)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
struct termios tio;
|
||||
u_int i;
|
||||
|
||||
setblocking(c->fd, 0);
|
||||
event_add(&tty->event_in, NULL);
|
||||
@@ -354,21 +363,10 @@ tty_start_tty(struct tty *tty)
|
||||
if (tcsetattr(c->fd, TCSANOW, &tio) == 0)
|
||||
tcflush(c->fd, TCOFLUSH);
|
||||
|
||||
if (options_get_number(global_options, "clear-on-attach")) {
|
||||
tty_putcode(tty, TTYC_SMCUP);
|
||||
tty_putcode(tty, TTYC_CLEAR);
|
||||
} else {
|
||||
tty_putcode_ii(tty, TTYC_CSR, 0, tty->sy - 1);
|
||||
tty_putcode_ii(tty, TTYC_CUP, 0, tty->sy - 1);
|
||||
if (tty_term_has(tty->term, TTYC_INDN))
|
||||
tty_putcode_i(tty, TTYC_INDN, tty->sy + 1);
|
||||
else if (tty_term_has(tty->term, TTYC_IND)) {
|
||||
for (i = 0; i < tty->sy + 1; i++)
|
||||
tty_putcode(tty, TTYC_IND);
|
||||
} else
|
||||
tty_putcode(tty, TTYC_CLEAR);
|
||||
}
|
||||
tty_putcode(tty, TTYC_SMCUP);
|
||||
|
||||
tty_putcode(tty, TTYC_SMKX);
|
||||
tty_putcode(tty, TTYC_CLEAR);
|
||||
|
||||
if (tty_acs_needed(tty)) {
|
||||
log_debug("%s: using capabilities for ACS", c->name);
|
||||
@@ -484,8 +482,7 @@ tty_stop_tty(struct tty *tty)
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_RMACS));
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_SGR0));
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_RMKX));
|
||||
if (options_get_number(global_options, "clear-on-attach"))
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
|
||||
if (tty->cstyle != SCREEN_CURSOR_DEFAULT) {
|
||||
if (tty_term_has(tty->term, TTYC_SE))
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_SE));
|
||||
@@ -510,10 +507,7 @@ tty_stop_tty(struct tty *tty)
|
||||
|
||||
if (tty_use_margin(tty))
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG));
|
||||
if (options_get_number(global_options, "clear-on-attach"))
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP));
|
||||
else
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
|
||||
tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP));
|
||||
|
||||
if (tty->term->flags & TERM_VT100LIKE)
|
||||
tty_raw(tty, "\033[?2031l");
|
||||
@@ -529,6 +523,9 @@ tty_close(struct tty *tty)
|
||||
tty_stop_tty(tty);
|
||||
|
||||
if (tty->flags & TTY_OPENED) {
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_free(tty, 1);
|
||||
#endif
|
||||
evbuffer_free(tty->in);
|
||||
event_del(&tty->event_in);
|
||||
evbuffer_free(tty->out);
|
||||
@@ -545,6 +542,9 @@ void
|
||||
tty_free(struct tty *tty)
|
||||
{
|
||||
tty_close(tty);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_free(tty, 0);
|
||||
#endif
|
||||
|
||||
free(tty->r.ranges);
|
||||
}
|
||||
@@ -556,6 +556,9 @@ tty_update_features(struct tty *tty)
|
||||
|
||||
if (tty_apply_features(tty->term))
|
||||
tty_term_apply_overrides(tty->term);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_update(tty);
|
||||
#endif
|
||||
|
||||
if (tty_use_margin(tty))
|
||||
tty_putcode(tty, TTYC_ENMG);
|
||||
@@ -955,7 +958,7 @@ int
|
||||
tty_window_bigger(struct tty *tty)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
struct window *w = active_get_effective_window(c, c->session);
|
||||
struct window *w = c->session->curw->window;
|
||||
|
||||
return (tty->sx < w->sx || tty->sy - status_line_size(c) < w->sy);
|
||||
}
|
||||
@@ -977,7 +980,7 @@ static int
|
||||
tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy)
|
||||
{
|
||||
struct client *c = tty->client;
|
||||
struct window *w = active_get_effective_window(c, c->session);
|
||||
struct window *w = c->session->curw->window;
|
||||
struct window_pane *wp = w->active;
|
||||
u_int cx, cy, lines;
|
||||
|
||||
@@ -1044,7 +1047,8 @@ tty_update_window_offset(struct window *w)
|
||||
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (c->session != NULL &&
|
||||
active_get_effective_window(c, c->session) == w)
|
||||
c->session->curw != NULL &&
|
||||
c->session->curw->window == w)
|
||||
tty_update_client_offset(c);
|
||||
}
|
||||
}
|
||||
@@ -1119,7 +1123,7 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx)
|
||||
*/
|
||||
if (tty_large_region(tty, ctx) || ctx->flags & TTY_CTX_PANE_OBSCURED) {
|
||||
log_debug("%s: %s large region redraw", __func__, c->name);
|
||||
ctx->redraw_cb(ctx);
|
||||
ctx->redraw_cb(ctx, ctx->orupper, ctx->orlower - ctx->orupper + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1539,60 +1543,6 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx)
|
||||
return (c->overlay_check(c, c->overlay_data, px, py, nx));
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Update context for client. */
|
||||
static int
|
||||
tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
|
||||
{
|
||||
struct window_pane *wp = ttyctx->arg;
|
||||
|
||||
if (active_get_effective_window(c, c->session) != wp->window)
|
||||
return (0);
|
||||
if (wp->layout_cell == NULL)
|
||||
return (0);
|
||||
|
||||
if (tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx,
|
||||
&ttyctx->wsy))
|
||||
ttyctx->flags |= TTY_CTX_WINDOW_BIGGER;
|
||||
else
|
||||
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
|
||||
|
||||
ttyctx->yoff = ttyctx->ryoff = wp->yoff;
|
||||
if (status_at_line(c) == 0)
|
||||
ttyctx->yoff += status_line_size(c);
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
||||
void
|
||||
tty_draw_images(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
struct image *im;
|
||||
struct tty_ctx ttyctx;
|
||||
|
||||
TAILQ_FOREACH(im, &wp->screen->images, entry) {
|
||||
memset(&ttyctx, 0, sizeof ttyctx);
|
||||
|
||||
/* Set the client independent properties. */
|
||||
ttyctx.ocx = im->px;
|
||||
ttyctx.ocy = im->py;
|
||||
|
||||
ttyctx.orlower = wp->screen->rlower;
|
||||
ttyctx.orupper = wp->screen->rupper;
|
||||
|
||||
ttyctx.xoff = ttyctx.rxoff = wp->xoff;
|
||||
ttyctx.sx = wp->sx;
|
||||
ttyctx.sy = wp->sy;
|
||||
|
||||
ttyctx.image = im;
|
||||
ttyctx.arg = wp;
|
||||
ttyctx.set_client_cb = tty_set_client_cb;
|
||||
ttyctx.flags |= TTY_CTX_INVISIBLE_PANES;
|
||||
tty_write_one(tty_cmd_sixelimage, c, &ttyctx);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_sync_start(struct tty *tty)
|
||||
{
|
||||
@@ -1666,19 +1616,6 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *),
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Only write to the incoming tty instead of every client. */
|
||||
static void
|
||||
tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *),
|
||||
struct client *c, struct tty_ctx *ctx)
|
||||
{
|
||||
if (ctx->set_client_cb == NULL)
|
||||
return;
|
||||
if ((ctx->set_client_cb(ctx, c)) == 1)
|
||||
cmdfn(&c->tty, ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
@@ -1828,10 +1765,17 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
|
||||
ctx->sx == 1 ||
|
||||
ctx->sy == 1 ||
|
||||
c->overlay_check != NULL) {
|
||||
#ifdef ENABLE_IMAGES
|
||||
redraw_image_scroll_result(tty, ctx, 1);
|
||||
#endif
|
||||
tty_redraw_region(tty, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
redraw_image_scroll_result(tty, ctx, 0);
|
||||
#endif
|
||||
|
||||
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
|
||||
|
||||
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
|
||||
@@ -1899,10 +1843,17 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
|
||||
ctx->sx == 1 ||
|
||||
ctx->sy == 1 ||
|
||||
c->overlay_check != NULL) {
|
||||
#ifdef ENABLE_IMAGES
|
||||
redraw_image_scroll_result(tty, ctx, 1);
|
||||
#endif
|
||||
tty_redraw_region(tty, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
redraw_image_scroll_result(tty, ctx, 0);
|
||||
#endif
|
||||
|
||||
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
|
||||
|
||||
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
|
||||
@@ -1939,10 +1890,17 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
|
||||
ctx->sx == 1 ||
|
||||
ctx->sy == 1 ||
|
||||
c->overlay_check != NULL) {
|
||||
#ifdef ENABLE_IMAGES
|
||||
redraw_image_scroll_result(tty, ctx, 1);
|
||||
#endif
|
||||
tty_redraw_region(tty, ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
redraw_image_scroll_result(tty, ctx, 0);
|
||||
#endif
|
||||
|
||||
tty_default_attributes(tty, ctx->bg, &ctx->style_ctx);
|
||||
|
||||
tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
|
||||
@@ -2031,7 +1989,7 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx)
|
||||
|
||||
if ((ctx->flags & TTY_CTX_WINDOW_BIGGER) ||
|
||||
c->overlay_check != NULL) {
|
||||
ctx->redraw_cb(ctx);
|
||||
ctx->redraw_cb(ctx, 0, ctx->sy);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2113,7 +2071,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty->cy == tty->rlower)
|
||||
tty_draw_pane(tty, ctx, ctx->ocy);
|
||||
else
|
||||
ctx->redraw_cb(ctx);
|
||||
ctx->redraw_cb(ctx, ctx->ocy, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2172,58 +2130,6 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty_invalidate(tty);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
void
|
||||
tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
struct image *im = ctx->image;
|
||||
struct sixel_image *si = im->data;
|
||||
struct sixel_image *new;
|
||||
char *data;
|
||||
size_t size;
|
||||
u_int cx = ctx->ocx, cy = ctx->ocy, sx, sy;
|
||||
u_int i, j, x, y, rx, ry;
|
||||
int fallback = 0;
|
||||
|
||||
if ((~tty->term->flags & TERM_SIXEL) &&
|
||||
!tty_term_has(tty->term, TTYC_SXL))
|
||||
fallback = 1;
|
||||
if (tty->xpixel == 0 || tty->ypixel == 0)
|
||||
fallback = 1;
|
||||
|
||||
sixel_size_in_cells(si, &sx, &sy);
|
||||
log_debug("%s: image is %ux%u", __func__, sx, sy);
|
||||
if (!tty_clamp_area(tty, ctx, cx, cy, sx, sy, &i, &j, &x, &y, &rx, &ry))
|
||||
return;
|
||||
log_debug("%s: clamping to %u,%u-%u,%u", __func__, i, j, rx, ry);
|
||||
|
||||
if (fallback == 1) {
|
||||
data = xstrdup(im->fallback);
|
||||
size = strlen(data);
|
||||
} else {
|
||||
new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry, 0);
|
||||
if (new == NULL)
|
||||
return;
|
||||
|
||||
data = sixel_print(new, si, &size);
|
||||
}
|
||||
if (data != NULL) {
|
||||
log_debug("%s: %zu bytes: %s", __func__, size, data);
|
||||
tty_region_off(tty);
|
||||
tty_margin_off(tty);
|
||||
tty_cursor(tty, x, y);
|
||||
|
||||
tty->flags |= TTY_NOBLOCK;
|
||||
tty_add(tty, data, size);
|
||||
tty_invalidate(tty);
|
||||
free(data);
|
||||
}
|
||||
|
||||
if (fallback == 0)
|
||||
sixel_free(new);
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
|
||||
68
utf8.c
68
utf8.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: utf8.c,v 1.72 2026/09/01 12:49:49 nicm Exp $ */
|
||||
/* $OpenBSD: utf8.c,v 1.71 2026/05/12 09:37:25 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -603,72 +603,6 @@ utf8_towc(const struct utf8_data *ud, wchar_t *wc)
|
||||
return (UTF8_DONE);
|
||||
}
|
||||
|
||||
/* Check for a Unicode whitespace character. */
|
||||
int
|
||||
utf8_has_whitespace(const struct utf8_data *ud)
|
||||
{
|
||||
struct utf8_data tmp;
|
||||
wchar_t wc;
|
||||
u_int offset = 0, size;
|
||||
u_char ch;
|
||||
|
||||
while (offset < ud->size) {
|
||||
ch = ud->data[offset];
|
||||
if (ch < 0x80) {
|
||||
wc = ch;
|
||||
size = 1;
|
||||
} else {
|
||||
if (ch >= 0xc2 && ch <= 0xdf)
|
||||
size = 2;
|
||||
else if (ch >= 0xe0 && ch <= 0xef)
|
||||
size = 3;
|
||||
else if (ch >= 0xf0 && ch <= 0xf4)
|
||||
size = 4;
|
||||
else
|
||||
return (0);
|
||||
if (size > ud->size - offset)
|
||||
return (0);
|
||||
|
||||
memset(&tmp, 0, sizeof tmp);
|
||||
memcpy(tmp.data, ud->data + offset, size);
|
||||
tmp.size = tmp.have = size;
|
||||
if (utf8_towc(&tmp, &wc) != UTF8_DONE)
|
||||
return (0);
|
||||
}
|
||||
offset += size;
|
||||
|
||||
switch (wc) {
|
||||
case 0x0009:
|
||||
case 0x000A:
|
||||
case 0x000B:
|
||||
case 0x000C:
|
||||
case 0x000D:
|
||||
case 0x0020:
|
||||
case 0x0085:
|
||||
case 0x00A0:
|
||||
case 0x1680:
|
||||
case 0x2000:
|
||||
case 0x2001:
|
||||
case 0x2002:
|
||||
case 0x2003:
|
||||
case 0x2004:
|
||||
case 0x2005:
|
||||
case 0x2006:
|
||||
case 0x2007:
|
||||
case 0x2008:
|
||||
case 0x2009:
|
||||
case 0x200A:
|
||||
case 0x2028:
|
||||
case 0x2029:
|
||||
case 0x202F:
|
||||
case 0x205F:
|
||||
case 0x3000:
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Convert wide character to UTF-8 character. */
|
||||
enum utf8_state
|
||||
utf8_fromwc(wchar_t wc, struct utf8_data *ud)
|
||||
|
||||
@@ -157,7 +157,7 @@ window_pane_get_border_style(struct window_pane *wp, struct client *c,
|
||||
struct grid_cell *saved;
|
||||
int *flag;
|
||||
|
||||
if (wp == active_get_effective_window(c, c->session)->active) {
|
||||
if (wp == c->session->curw->window->active) {
|
||||
flag = &wp->active_border_gc_set;
|
||||
saved = &wp->active_border_gc;
|
||||
option = "pane-active-border-style";
|
||||
@@ -168,7 +168,7 @@ window_pane_get_border_style(struct window_pane *wp, struct client *c,
|
||||
}
|
||||
|
||||
if (!*flag) {
|
||||
ft = format_create_defaults(NULL, c, s, NULL, wp);
|
||||
ft = format_create_defaults(NULL, c, s, s->curw, wp);
|
||||
style_apply(saved, wp->options, option, ft);
|
||||
format_free(ft);
|
||||
*flag = 1;
|
||||
@@ -197,7 +197,7 @@ window_make_pane_status(struct window_pane *wp, struct client *c, u_int width,
|
||||
return (0);
|
||||
|
||||
ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS);
|
||||
format_defaults(ft, c, c->session, NULL, wp);
|
||||
format_defaults(ft, c, c->session, c->session->curw, wp);
|
||||
|
||||
fmt = options_get_string(wp->options, "pane-border-format");
|
||||
expanded = format_expand_time(ft, fmt);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window-client.c,v 1.49 2026/08/31 12:41:03 kirill Exp $ */
|
||||
/* $OpenBSD: window-client.c,v 1.48 2026/08/05 08:54:56 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -102,8 +102,7 @@ static const char *window_client_info_lines[] = {
|
||||
WINDOW_CLIENT_FEATURE(sync) " "
|
||||
WINDOW_CLIENT_FEATURE(title),
|
||||
" #[#{E:tree-mode-border-style},acs]x#[default] "
|
||||
WINDOW_CLIENT_FEATURE(usstyle) " "
|
||||
WINDOW_CLIENT_FEATURE(utf8),
|
||||
WINDOW_CLIENT_FEATURE(usstyle),
|
||||
"#[#{E:tree-mode-border-style},acs]qqqqqqqqqqqqqqn#{R:q,#{window_width}}#[default]",
|
||||
|
||||
"#[fg=themelightgrey]prefix #[#{E:tree-mode-border-style},acs]x#[default] "
|
||||
@@ -269,13 +268,12 @@ window_client_draw_info(__unused void *modedata, void *itemdata,
|
||||
struct window_client_itemdata *item = itemdata;
|
||||
struct client *c = item->c;
|
||||
struct screen *s = ctx->s;
|
||||
struct window *w;
|
||||
struct window *w = c->session->curw->window;
|
||||
struct grid_cell gc;
|
||||
u_int cx = s->cx, cy = s->cy, i;
|
||||
struct format_tree *ft;
|
||||
char *expanded;
|
||||
|
||||
w = active_get_effective_window(c, c->session);
|
||||
ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
|
||||
if (c->tty.term->flags & TERM_INVALIDMS)
|
||||
format_add(ft, "clipboard_invalid", "1");
|
||||
|
||||
201
window-copy.c
201
window-copy.c
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window-copy.c,v 1.429 2026/09/01 13:04:29 nicm Exp $ */
|
||||
/* $OpenBSD: window-copy.c,v 1.426 2026/08/19 19:55:04 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -73,6 +73,10 @@ static void window_copy_write_line(struct window_mode_entry *,
|
||||
struct screen_write_ctx *, u_int);
|
||||
static void window_copy_write_lines(struct window_mode_entry *,
|
||||
struct screen_write_ctx *, u_int, u_int);
|
||||
#ifdef ENABLE_IMAGES
|
||||
static void window_copy_update_image_refresh(struct window_copy_mode_data *);
|
||||
static int window_copy_visible_has_images(struct window_copy_mode_data *);
|
||||
#endif
|
||||
static char *window_copy_match_at_cursor(struct window_copy_mode_data *);
|
||||
static void window_copy_scroll_to(struct window_mode_entry *, u_int, u_int,
|
||||
int);
|
||||
@@ -279,6 +283,10 @@ struct window_copy_mode_data {
|
||||
|
||||
u_int oy; /* number of lines scrolled up */
|
||||
|
||||
u_int image_base; /* hsize - oy images were last drawn for */
|
||||
int image_base_set;
|
||||
int image_refresh; /* current redraw needs image refresh */
|
||||
|
||||
u_int selx; /* beginning of selection */
|
||||
u_int sely;
|
||||
|
||||
@@ -636,6 +644,9 @@ window_copy_init(struct window_mode_entry *wme,
|
||||
data->my = screen_hsize(data->backing) + data->cy - data->oy;
|
||||
data->showmark = 0;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
window_copy_update_image_refresh(data);
|
||||
#endif
|
||||
screen_write_start(&ctx, &data->screen);
|
||||
for (i = 0; i < screen_size_y(&data->screen); i++)
|
||||
window_copy_write_line(wme, &ctx, i);
|
||||
@@ -1197,6 +1208,9 @@ window_copy_size_changed(struct window_mode_entry *wme)
|
||||
window_copy_clear_selection(wme);
|
||||
window_copy_clear_marks(wme);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
window_copy_update_image_refresh(data);
|
||||
#endif
|
||||
screen_write_start(&ctx, s);
|
||||
window_copy_write_lines(wme, &ctx, 0, screen_size_y(s));
|
||||
screen_write_stop(&ctx);
|
||||
@@ -2207,76 +2221,15 @@ window_copy_cmd_selection_mode(struct window_copy_cmd_state *cs)
|
||||
struct window_mode_entry *wme = cs->wme;
|
||||
struct options *so = cs->s->options;
|
||||
struct window_copy_mode_data *data = wme->data;
|
||||
struct grid_reader gr;
|
||||
const char *s = args_string(cs->wargs, 0);
|
||||
u_int sx, sy, ex, ey, fx, fy, x, y;
|
||||
|
||||
if (s == NULL || strcasecmp(s, "char") == 0 || strcasecmp(s, "c") == 0)
|
||||
data->selflag = SEL_CHAR;
|
||||
else if (strcasecmp(s, "word") == 0 || strcasecmp(s, "w") == 0) {
|
||||
data->separators = options_get_string(so, "word-separators");
|
||||
data->selflag = SEL_WORD;
|
||||
} else if (strcasecmp(s, "line") == 0 || strcasecmp(s, "l") == 0) {
|
||||
} else if (strcasecmp(s, "line") == 0 || strcasecmp(s, "l") == 0)
|
||||
data->selflag = SEL_LINE;
|
||||
if (data->screen.sel == NULL)
|
||||
return (WINDOW_COPY_CMD_MOVE);
|
||||
|
||||
/*
|
||||
* Line selection normally starts with select-line, which sets
|
||||
* up the reset positions used when the cursor changes
|
||||
* direction. Do the same when changing an existing selection
|
||||
* to line mode.
|
||||
*/
|
||||
if (data->cursordrag == CURSORDRAG_SEL) {
|
||||
fx = data->endselx;
|
||||
fy = data->endsely;
|
||||
} else {
|
||||
fx = data->selx;
|
||||
fy = data->sely;
|
||||
}
|
||||
|
||||
sx = data->selx;
|
||||
sy = data->sely;
|
||||
ex = data->endselx;
|
||||
ey = data->endsely;
|
||||
if (ey < sy || (ey == sy && ex < sx)) {
|
||||
x = sx; sx = ex; ex = x;
|
||||
y = sy; sy = ey; ey = y;
|
||||
}
|
||||
grid_reader_start(&gr, data->backing->grid, sx, sy);
|
||||
grid_reader_cursor_start_of_line(&gr, 1);
|
||||
grid_reader_get_cursor(&gr, &sx, &sy);
|
||||
grid_reader_start(&gr, data->backing->grid, ex, ey);
|
||||
grid_reader_cursor_end_of_line(&gr, 1, 0);
|
||||
grid_reader_get_cursor(&gr, &ex, &ey);
|
||||
|
||||
data->rectflag = 0;
|
||||
data->selrx = data->selx = sx;
|
||||
data->selry = data->sely = sy;
|
||||
data->endselrx = data->endselx = ex;
|
||||
data->endselry = data->endsely = ey;
|
||||
|
||||
x = data->cx;
|
||||
y = screen_hsize(data->backing) + data->cy - data->oy;
|
||||
data->dx = fx;
|
||||
data->dy = fy;
|
||||
if (data->cursordrag != CURSORDRAG_NONE &&
|
||||
(y < fy || (y == fy && x < fx))) {
|
||||
data->lineflag = LINE_SEL_RIGHT_LEFT;
|
||||
data->cursordrag = CURSORDRAG_SEL;
|
||||
window_copy_scroll_to(wme, sx, sy, 1);
|
||||
} else {
|
||||
data->lineflag = LINE_SEL_LEFT_RIGHT;
|
||||
if (data->cursordrag != CURSORDRAG_NONE) {
|
||||
data->cursordrag = CURSORDRAG_ENDSEL;
|
||||
x = window_copy_cursor_limit(wme, ey, 0);
|
||||
window_copy_scroll_to(wme, x, ey, 1);
|
||||
}
|
||||
}
|
||||
if (data->cursordrag == CURSORDRAG_NONE)
|
||||
window_copy_set_selection(wme, 0, 0);
|
||||
return (WINDOW_COPY_CMD_REDRAW);
|
||||
}
|
||||
return (WINDOW_COPY_CMD_MOVE);
|
||||
}
|
||||
|
||||
@@ -4362,7 +4315,7 @@ window_copy_stringify(struct grid *gd, u_int py, u_int first, u_int last,
|
||||
}
|
||||
if (dlen == 1)
|
||||
buf[bx++] = *d;
|
||||
else if (dlen != 0) {
|
||||
else {
|
||||
memcpy(buf + bx, d, dlen);
|
||||
bx += dlen;
|
||||
}
|
||||
@@ -4912,8 +4865,6 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp,
|
||||
cflags |= REG_ICASE;
|
||||
if (regcomp(®, sbuf, cflags) != 0) {
|
||||
free(sbuf);
|
||||
free(data->searchmark);
|
||||
data->searchmark = NULL;
|
||||
return (0);
|
||||
}
|
||||
free(sbuf);
|
||||
@@ -5223,6 +5174,34 @@ window_copy_write_one(struct window_mode_entry *wme,
|
||||
for (fx = 0; fx < nx; fx++) {
|
||||
grid_get_cell(gd, fx, fy, &gc);
|
||||
if (fx + gc.data.width <= nx) {
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* Write image-covered cells directly into the grid,
|
||||
* skipping both window_copy_update_style() (so a
|
||||
* selection, current-line or search-mark highlight
|
||||
* never sweeps visibly over the image before it is
|
||||
* recomposited separately - see the
|
||||
* image_redraw_area() call below) and
|
||||
* screen_write_cell(), whose built-in
|
||||
* screen_write_image_damage() call (screen-write.c)
|
||||
* fires on every write regardless of whether
|
||||
* anything actually changed, which would needlessly
|
||||
* re-damage - and so retransmit - the image on every
|
||||
* redraw. The cell must still land in the grid as
|
||||
* normal: a non-graphical client's ASCII fallback for
|
||||
* the image is an ordinary character here, not
|
||||
* something recomposited separately, and depends on
|
||||
* this write the same as any other cell.
|
||||
*/
|
||||
if (image_grid_check_area(gd, fx, fy, gc.data.width,
|
||||
1)) {
|
||||
grid_view_set_cell(ctx->s->grid, px + fx, py,
|
||||
&gc);
|
||||
screen_write_cursormove(ctx,
|
||||
px + fx + gc.data.width, py, 0);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
window_copy_update_style(wme, fx, fy, &gc, mgc, cgc,
|
||||
mkgc, clgc);
|
||||
if (gc.flags & GRID_FLAG_PADDING) {
|
||||
@@ -5471,6 +5450,29 @@ window_copy_write_line(struct window_mode_entry *wme,
|
||||
window_copy_write_one(wme, ctx, width, py, hsize - data->oy + py,
|
||||
content_sx, &mgc, &cgc, &mkgc, &clgc);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* Copy the backing line's image layers separately from its text
|
||||
* cells. This is not implied by the text cells just written above:
|
||||
* those go via the pane's normal (frequently fast, direct-write)
|
||||
* path, which knows nothing about image content.
|
||||
*
|
||||
* Only report it as needing a redraw when the view has actually
|
||||
* moved (data->image_refresh, set once per window_copy_redraw_lines
|
||||
* call) - text writes never touch image-covered cells (see
|
||||
* window_copy_write_one), so if the view is unmoved this row's
|
||||
* images are already exactly as they should be and redrawing them
|
||||
* anyway just flashes the image on every unrelated redraw (e.g.
|
||||
* every step of a selection drag) for no visible benefit.
|
||||
*/
|
||||
image_grid_free_line(s->grid,
|
||||
&s->grid->linedata[s->grid->hsize + py]);
|
||||
image_grid_copy_area(s->grid, width, s->grid->hsize + py,
|
||||
data->backing->grid, 0, hsize - data->oy + py, content_sx, 1);
|
||||
if (data->image_refresh)
|
||||
image_redraw_area(ctx, width, py, content_sx, 1);
|
||||
#endif
|
||||
|
||||
if (py == 0 && s->rupper < s->rlower && !data->hide_position) {
|
||||
value = options_get_string(oo, "copy-mode-position-format");
|
||||
if (*value != '\0') {
|
||||
@@ -5531,6 +5533,55 @@ window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y)
|
||||
window_copy_redraw_lines(wme, start, end - start + 1);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* Only rows whose underlying history position has moved since the last
|
||||
* call need their images recomposited - most redraws are just a selection
|
||||
* or cursor-line style change with the view otherwise unmoved, and that
|
||||
* never touches image-covered cells (see window_copy_write_one()), so
|
||||
* redrawing images for it is needless: on a fast drag it is visible as the
|
||||
* image briefly flashing on every step even though nothing about it
|
||||
* actually changed.
|
||||
*
|
||||
* Every place that calls window_copy_write_line()/window_copy_write_lines()
|
||||
* must call this first - it is not implied by them, since some (the
|
||||
* initial full-screen draw on entering copy mode, window_copy_scroll_up(),
|
||||
* window_copy_scroll_down()) write directly rather than going through
|
||||
* window_copy_redraw_lines().
|
||||
*/
|
||||
static void
|
||||
window_copy_update_image_refresh(struct window_copy_mode_data *data)
|
||||
{
|
||||
u_int base;
|
||||
|
||||
base = screen_hsize(data->backing) - data->oy;
|
||||
data->image_refresh = !data->image_base_set || base != data->image_base;
|
||||
data->image_base = base;
|
||||
data->image_base_set = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Whether any part of the currently visible backing range carries image
|
||||
* data. A terminal's line insert/delete only shifts character cells - the
|
||||
* pixels of a sixel or Kitty image already on screen stay exactly where
|
||||
* they were sent, so a scroll that uses that fast path (see
|
||||
* window_copy_scroll_up()/window_copy_scroll_down()) leaves stale or
|
||||
* missing image content for every row except the single new one it
|
||||
* explicitly rewrites. When this returns true, callers should fall back to
|
||||
* a full window_copy_redraw_screen() instead, so every visible row's image
|
||||
* is recomposited at its correct new position.
|
||||
*/
|
||||
static int
|
||||
window_copy_visible_has_images(struct window_copy_mode_data *data)
|
||||
{
|
||||
struct grid *gd = data->backing->grid;
|
||||
u_int sy = screen_size_y(&data->screen);
|
||||
|
||||
return (image_grid_check_area(gd, 0, screen_hsize(data->backing) -
|
||||
data->oy, screen_size_x(&data->screen), sy));
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny)
|
||||
{
|
||||
@@ -5540,6 +5591,10 @@ window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny)
|
||||
struct screen_write_ctx ctx;
|
||||
u_int i;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
window_copy_update_image_refresh(data);
|
||||
#endif
|
||||
|
||||
if (window_copy_line_number_width(wme) != 0) {
|
||||
screen_write_start(&ctx, &data->screen);
|
||||
for (i = py; i < py + ny; i++)
|
||||
@@ -6928,6 +6983,13 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny)
|
||||
window_copy_redraw_screen(wme);
|
||||
return;
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (window_copy_visible_has_images(data)) {
|
||||
window_copy_redraw_screen(wme);
|
||||
return;
|
||||
}
|
||||
window_copy_update_image_refresh(data);
|
||||
#endif
|
||||
if (window_copy_line_numbers_active(wme)) {
|
||||
if (window_copy_line_number_mode(wme) !=
|
||||
WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) {
|
||||
@@ -6999,6 +7061,13 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny)
|
||||
window_copy_redraw_screen(wme);
|
||||
return;
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (window_copy_visible_has_images(data)) {
|
||||
window_copy_redraw_screen(wme);
|
||||
return;
|
||||
}
|
||||
window_copy_update_image_refresh(data);
|
||||
#endif
|
||||
if (window_copy_line_numbers_active(wme)) {
|
||||
if (window_copy_line_number_mode(wme) !=
|
||||
WINDOW_COPY_LINE_NUMBERS_ABSOLUTE) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window-panes.c,v 1.5 2026/08/25 06:04:33 nicm Exp $ */
|
||||
/* $OpenBSD: window-panes.c,v 1.4 2026/07/29 14:06:32 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -1009,7 +1009,6 @@ window_panes_run_command(struct window_panes_modedata *data, struct client *c,
|
||||
} else {
|
||||
new_item = cmdq_get_command(cmdlist, NULL);
|
||||
cmdq_append(c, new_item);
|
||||
cmd_list_free(cmdlist);
|
||||
}
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window-switch.c,v 1.3 2026/08/25 07:23:30 nicm Exp $ */
|
||||
/* $OpenBSD: window-switch.c,v 1.2 2026/07/14 17:17:18 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -227,8 +227,7 @@ window_switch_build(struct window_switch_modedata *data)
|
||||
m = xreallocarray(m, n + 1, sizeof *m);
|
||||
m[n++] = item;
|
||||
}
|
||||
if (n > 1)
|
||||
qsort(m, n, sizeof *m, window_switch_compare);
|
||||
qsort(m, n, sizeof *m, window_switch_compare);
|
||||
|
||||
free(data->matches);
|
||||
data->matches = m;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: window-tree.c,v 1.96 2026/08/25 07:23:30 nicm Exp $ */
|
||||
/* $OpenBSD: window-tree.c,v 1.95 2026/08/24 07:06:58 nicm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
@@ -1022,10 +1022,6 @@ window_tree_swap(void *cur_itemdata, void *other_itemdata,
|
||||
window_tree_pull_item(other, &other_session, &other_winlink,
|
||||
&other_pane);
|
||||
|
||||
if (cur_session == NULL || cur_winlink == NULL)
|
||||
return (0);
|
||||
if (other_session == NULL || other_winlink == NULL)
|
||||
return (0);
|
||||
if (cur_session != other_session)
|
||||
return (0);
|
||||
|
||||
|
||||
147
window.c
147
window.c
@@ -415,6 +415,7 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel)
|
||||
TAILQ_INIT(&w->panes);
|
||||
TAILQ_INIT(&w->z_index);
|
||||
TAILQ_INIT(&w->last_panes);
|
||||
TAILQ_INIT(&w->damage);
|
||||
w->active = NULL;
|
||||
|
||||
w->lastlayout = -1;
|
||||
@@ -451,7 +452,6 @@ window_destroy(struct window *w)
|
||||
{
|
||||
log_debug("window @%u destroyed (%d references)", w->id, w->references);
|
||||
|
||||
active_remove_window(w);
|
||||
window_unzoom(w, 0);
|
||||
RB_REMOVE(windows, &windows, w);
|
||||
|
||||
@@ -461,6 +461,7 @@ window_destroy(struct window *w)
|
||||
|
||||
menu_destroy(w);
|
||||
window_destroy_panes(w);
|
||||
redraw_free_damage(w);
|
||||
|
||||
if (event_initialized(&w->name_event))
|
||||
evtimer_del(&w->name_event);
|
||||
@@ -673,7 +674,6 @@ void
|
||||
window_pane_update_focus(struct window_pane *wp)
|
||||
{
|
||||
struct client *c;
|
||||
struct window *w;
|
||||
int focused = 0;
|
||||
|
||||
if (wp != NULL && (~wp->flags & PANE_EXITED)) {
|
||||
@@ -681,12 +681,10 @@ window_pane_update_focus(struct window_pane *wp)
|
||||
focused = 0;
|
||||
else {
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (c->session == NULL)
|
||||
continue;
|
||||
w = active_get_effective_window(c, c->session);
|
||||
if (c->session->attached != 0 &&
|
||||
if (c->session != NULL &&
|
||||
c->session->attached != 0 &&
|
||||
(c->flags & CLIENT_FOCUSED) &&
|
||||
w == wp->window &&
|
||||
c->session->curw->window == wp->window &&
|
||||
c->overlay_draw == NULL &&
|
||||
wp->window->menu == NULL) {
|
||||
focused = 1;
|
||||
@@ -715,6 +713,7 @@ int
|
||||
window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
||||
{
|
||||
struct window_pane *lastwp;
|
||||
int unzoomed;
|
||||
|
||||
log_debug("%s: pane %%%u", __func__, wp->id);
|
||||
|
||||
@@ -722,7 +721,8 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
||||
return (0);
|
||||
if (w->modal != NULL && wp != w->modal)
|
||||
return (0);
|
||||
if ((w->flags & WINDOW_ZOOMED) && !window_pane_is_visible(wp))
|
||||
unzoomed = (w->flags & WINDOW_ZOOMED) && !window_pane_is_visible(wp);
|
||||
if (unzoomed)
|
||||
window_unzoom(w, 1);
|
||||
lastwp = w->active;
|
||||
|
||||
@@ -739,7 +739,19 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
|
||||
}
|
||||
|
||||
tty_update_window_offset(w);
|
||||
server_redraw_window(w);
|
||||
|
||||
/*
|
||||
* Unzooming changes every pane's geometry and needs a full window
|
||||
* redraw. Otherwise, only the previous and new active pane's border
|
||||
* and status appearance changed, so avoid redrawing unaffected pane
|
||||
* content.
|
||||
*/
|
||||
if (unzoomed)
|
||||
server_redraw_window(w);
|
||||
else {
|
||||
server_redraw_window_borders(w);
|
||||
server_status_window(w);
|
||||
}
|
||||
|
||||
if (notify)
|
||||
window_fire_pane_changed(w, w->active, lastwp);
|
||||
@@ -1291,7 +1303,7 @@ window_destroy_panes(struct window *w)
|
||||
}
|
||||
|
||||
const char *
|
||||
window_printable_flags(struct client *c, struct winlink *wl, int escape)
|
||||
window_printable_flags(struct winlink *wl, int escape)
|
||||
{
|
||||
struct session *s = wl->session;
|
||||
static char flags[32];
|
||||
@@ -1306,9 +1318,9 @@ window_printable_flags(struct client *c, struct winlink *wl, int escape)
|
||||
flags[pos++] = '!';
|
||||
if (wl->flags & WINLINK_SILENCE)
|
||||
flags[pos++] = '~';
|
||||
if (active_is_effective_window(c, s, wl))
|
||||
if (wl == s->curw)
|
||||
flags[pos++] = '*';
|
||||
if (active_is_last_window(c, s, wl))
|
||||
if (wl == TAILQ_FIRST(&s->lastw))
|
||||
flags[pos++] = '-';
|
||||
if (server_check_marked() && wl == marked_pane.wl)
|
||||
flags[pos++] = 'M';
|
||||
@@ -1658,6 +1670,10 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy)
|
||||
|
||||
log_debug("%s: %%%u resize %ux%u", __func__, wp->id, sx, sy);
|
||||
screen_resize(&wp->base, sx, sy, wp->base.saved_grid == NULL);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (sx > r->osx)
|
||||
image_grid_resize_width(wp->base.grid, sx);
|
||||
#endif
|
||||
|
||||
wme = TAILQ_FIRST(&wp->modes);
|
||||
if (wme != NULL && wme->mode->resize != NULL)
|
||||
@@ -2895,3 +2911,110 @@ window_pane_is_floating(struct window_pane *wp)
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Report damage for a floating pane's rectangle, grown by one cell on every
|
||||
* side - a floating pane draws its border frame at xoff-1/yoff-1 through
|
||||
* xoff+sx/yoff+sy (see the "floating" case in screen-redraw.c), one cell
|
||||
* outside its own content area, so damage for just the content area leaves
|
||||
* the frame's previous position undrawn as the pane moves. If a scrollbar
|
||||
* is reserved, its side of the frame is pushed out further still by its
|
||||
* width and padding (also matched in screen-redraw.c), so grow that side
|
||||
* to match.
|
||||
*/
|
||||
static void
|
||||
window_pane_damage_floating(struct window *w, struct window_pane *wp,
|
||||
int xoff, int yoff, int sx, int sy)
|
||||
{
|
||||
int x0, x1, y0, y1, sb_left = 0, sb_right = 0;
|
||||
|
||||
if (window_pane_scrollbar_reserve(wp)) {
|
||||
if (w->sb_pos == PANE_SCROLLBARS_LEFT)
|
||||
sb_left = wp->scrollbar_style.width +
|
||||
wp->scrollbar_style.pad;
|
||||
else
|
||||
sb_right = wp->scrollbar_style.width +
|
||||
wp->scrollbar_style.pad;
|
||||
}
|
||||
|
||||
x0 = xoff - 1 - sb_left;
|
||||
x1 = xoff + sx + sb_right;
|
||||
y0 = yoff - 1;
|
||||
y1 = yoff + sy;
|
||||
if (x0 < 0)
|
||||
x0 = 0;
|
||||
if (y0 < 0)
|
||||
y0 = 0;
|
||||
if (x1 < x0 || y1 < y0)
|
||||
return;
|
||||
redraw_damage_window(w, (u_int)x0, (u_int)y0, (u_int)(x1 - x0) + 1,
|
||||
(u_int)(y1 - y0) + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Whether a pane's scrollbar strip - not its whole body - intersects a
|
||||
* window-coordinate rectangle. A reserved scrollbar occupies a strip of
|
||||
* scrollbar_style.width+pad columns just outside the pane's own content
|
||||
* area (see the scrollbar-reserve case in layout_fix_panes(), layout.c),
|
||||
* on whichever side w->sb_pos points to.
|
||||
*/
|
||||
static int
|
||||
window_pane_scrollbar_intersects(struct window *w, struct window_pane *wp,
|
||||
u_int x, u_int y, u_int sx, u_int sy)
|
||||
{
|
||||
int sb_x, sb_w, ix = (int)x, iy = (int)y, isx = (int)sx;
|
||||
int isy = (int)sy;
|
||||
|
||||
if (!window_pane_scrollbar_reserve(wp))
|
||||
return (0);
|
||||
sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
|
||||
if (w->sb_pos == PANE_SCROLLBARS_LEFT)
|
||||
sb_x = (int)wp->xoff - sb_w;
|
||||
else
|
||||
sb_x = (int)wp->xoff + (int)wp->sx;
|
||||
|
||||
return (sb_x < ix + isx && sb_x + sb_w > ix &&
|
||||
(int)wp->yoff < iy + isy && (int)wp->yoff + (int)wp->sy > iy);
|
||||
}
|
||||
|
||||
/*
|
||||
* Report damage for only a floating pane's old and new area, rather than
|
||||
* the whole window - a floating pane move or resize only disturbs what it
|
||||
* was covering and what it now covers. Scrollbars aren't covered by the
|
||||
* damage system, so a pane whose *scrollbar strip* (not its whole body)
|
||||
* intersects either area is still flagged directly for a scrollbar redraw.
|
||||
* Checking the whole pane body here, rather than just its narrow scrollbar
|
||||
* strip, meant merely dragging over a pane's ordinary content set
|
||||
* PANE_REDRAWSCROLLBAR on every such pane on every motion event, triggering
|
||||
* a needless scrollbar redraw (and the redraw pass it forces) each time
|
||||
* even though the scrollbar itself never moved.
|
||||
*
|
||||
* Shared by every command that drags a floating pane around by the mouse:
|
||||
* resize-pane's own border drag (cmd-resize-pane.c), move-pane -M's
|
||||
* alternate Alt-drag (cmd-join-pane.c), and split-window/new-pane's
|
||||
* interactive resize of a newly-created floating pane (cmd-split-window.c).
|
||||
*/
|
||||
void
|
||||
window_pane_redraw_floating(struct window *w, struct window_pane *wp,
|
||||
int old_xoff, int old_yoff, int old_sx, int old_sy)
|
||||
{
|
||||
struct window_pane *loop;
|
||||
|
||||
window_pane_damage_floating(w, wp, old_xoff, old_yoff, old_sx,
|
||||
old_sy);
|
||||
window_pane_damage_floating(w, wp, wp->xoff, wp->yoff, wp->sx,
|
||||
wp->sy);
|
||||
|
||||
TAILQ_FOREACH(loop, &w->panes, entry) {
|
||||
if (window_pane_scrollbar_intersects(w, loop,
|
||||
(u_int)old_xoff, (u_int)old_yoff, (u_int)old_sx,
|
||||
(u_int)old_sy) ||
|
||||
window_pane_scrollbar_intersects(w, loop, wp->xoff,
|
||||
wp->yoff, wp->sx, wp->sy))
|
||||
loop->flags |= PANE_REDRAWSCROLLBAR;
|
||||
}
|
||||
|
||||
/* Session status formats may depend on the pane's new geometry. */
|
||||
server_status_window(w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user