Compare commits

..

31 Commits

Author SHA1 Message Date
Nicholas Marriott
b3f8598903 Merge branch 'master' into local_windows_panes
# Conflicts:
#	cmd-switch-client.c
2026-09-01 20:23:17 +01:00
tmux update bot
9b0ad73a4b Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  When changing selection-mode to line, set up the selection start and end correctly, GitHub issue 5545.
  Extend word commands to use any Unicode whitespace character, GitHub issue 5562 from Hongyi Zhao.
2026-09-01 14:55:38 +00:00
nicm
bd8218e85d When changing selection-mode to line, set up the selection start and end
correctly, GitHub issue 5545.
2026-09-01 14:55:35 +00:00
nicm
8f3101d820 Extend word commands to use any Unicode whitespace character, GitHub
issue 5562 from Hongyi Zhao.
2026-09-01 14:55:35 +00:00
Nicholas Marriott
77309c07b0 Whitespace regress test from Hongyi Zhao. 2026-09-01 13:50:56 +01:00
tmux update bot
c8e889694d Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Add a generation counter to pane output, from Michael K Darling.
  Add format variables for history change counters, from Michael K Darling.
2026-09-01 00:07:34 +00:00
nicm
b402687fcd Add a generation counter to pane output, from Michael K Darling. 2026-09-01 00:07:32 +00:00
nicm
0530a7d89a Add format variables for history change counters, from Michael K Darling. 2026-09-01 00:07:32 +00:00
tmux update bot
6bdb9ba54c Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  tmux: add utf8 terminal-features
2026-08-31 20:43:13 +00:00
kirill
f2bfd611e2 tmux: add utf8 terminal-features
OK: nicm@
2026-08-31 20:43:11 +00:00
Nicholas Marriott
9c1d5a080b Use hash for actions, from Andreas Fehlner. 2026-08-31 20:52:54 +01:00
tmux update bot
b28244e2fe Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not leak temporary commands when expanding aliases, from Jeong, Heon.
  Do not allow popups for control clients, from Noam Stolero.
  Layout cell can be replaced by unzoom, fixes use after free. From Uzair Aftab.
  Do not try to append zero size cells, from Uzair Aftab.
  Clear stale marks when a search regex fails to compile, GitHub issue 5547 from Uzair Aftab.
2026-08-31 14:56:14 +00:00
nicm
8b3834c34f Do not leak temporary commands when expanding aliases, from Jeong, Heon. 2026-08-31 14:56:12 +00:00
nicm
af3e4d2e5b Do not allow popups for control clients, from Noam Stolero. 2026-08-31 14:56:12 +00:00
nicm
c552d1bf16 Layout cell can be replaced by unzoom, fixes use after free. From Uzair
Aftab.
2026-08-31 14:56:12 +00:00
nicm
6a1ecdcce3 Do not try to append zero size cells, from Uzair Aftab. 2026-08-31 14:56:12 +00:00
nicm
39357c829c Clear stale marks when a search regex fails to compile, GitHub issue
5547 from Uzair Aftab.
2026-08-31 14:56:12 +00:00
tmux update bot
40381bdcd9 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Do not walk off end of grid lines if last line is wrapped, reported by Moe Khalilov.
  Do no free input context if it is NULL when popup create fails.
2026-08-28 10:32:01 +00:00
nicm
7bad4f7722 Do not walk off end of grid lines if last line is wrapped, reported by
Moe Khalilov.
2026-08-28 10:32:00 +00:00
nicm
65e811570b Do no free input context if it is NULL when popup create fails. 2026-08-28 10:32:00 +00:00
tmux update bot
dd31900f80 Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Ignore focus events earlier to avoid them interfering with prefix, GitHub issue 5539.
2026-08-28 09:02:23 +00:00
nicm
013ada58c8 Ignore focus events earlier to avoid them interfering with prefix,
GitHub issue 5539.
2026-08-28 09:02:22 +00:00
Nicholas Marriott
78dad45f3b Add timeout for lock and don't keep credentials for regress, from Andreas
Fehlner.
2026-08-28 09:07:16 +01:00
Nicholas Marriott
52b452948f Check for buffer write error. 2026-08-28 08:56:12 +01:00
tmux update bot
267746603a Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'
* refs/remotes/tmux-openbsd/master:
  Fix tiled pane resizing with floating layout cells, reported by Kevin Hovsäter.
2026-08-25 21:31:27 +00:00
nicm
b26b240613 Fix tiled pane resizing with floating layout cells, reported by Kevin
Hovsäter.
2026-08-25 21:31:25 +00:00
Nicholas Marriott
8e48c889dd Add a resize test. 2026-08-25 19:36:06 +01:00
Nicholas Marriott
1b86039df3 Merge branch 'master' into local_windows_panes 2026-08-17 13:45:26 +01:00
Nicholas Marriott
ff9c89da0c Merge branch 'master' into local_windows_panes 2026-07-21 08:36:11 +01:00
Nicholas Marriott
fde879c347 Add local panes as well. 2026-07-17 16:30:02 +01:00
Nicholas Marriott
38808509d5 Add support for local windows for a client. 2026-07-17 15:48:41 +01:00
50 changed files with 2295 additions and 268 deletions

View File

@@ -17,8 +17,9 @@ jobs:
action:
runs-on: ubuntu-latest
if: github.repository == 'tmux/tmux'
timeout-minutes: 10
steps:
- uses: dessant/lock-threads@v6
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6
with:
github-token: ${{ github.token }}
issue-inactive-days: '30'

View File

@@ -38,7 +38,9 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: dependencies
if: runner.os == 'Linux'
@@ -81,7 +83,7 @@ jobs:
- name: logs
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: regress-logs-${{ matrix.name }}
path: regress/logs/*.log

View File

@@ -85,6 +85,7 @@ endif
# List of sources.
dist_tmux_SOURCES = \
active.c \
alerts.c \
arguments.c \
attributes.c \

737
active.c Normal file
View File

@@ -0,0 +1,737 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <stdlib.h>
#include "tmux.h"
/* Local last-window stack entry. */
struct active_window_entry {
u_int window;
TAILQ_ENTRY(active_window_entry) entry;
};
/* Local last-window stack. */
TAILQ_HEAD(active_window_stack, active_window_entry);
/* Per-client and per-session local active window state. */
struct active_window {
struct client *client;
struct session *session;
u_int window;
struct active_window_stack lastw;
RB_ENTRY(active_window) entry;
};
RB_HEAD(active_windows, active_window);
/* Pane entry in a client-local last-pane stack. */
struct active_pane_entry {
struct window_pane *wp;
TAILQ_ENTRY(active_pane_entry) entry;
};
TAILQ_HEAD(active_pane_stack, active_pane_entry);
/* Per-client and per-window local active pane state. */
struct active_pane {
struct client *client;
struct window *window;
enum active_mode mode;
struct window_pane *pane;
struct active_pane_stack last_panes;
RB_ENTRY(active_pane) entry;
};
RB_HEAD(active_panes, active_pane);
/* Compare local active window states by client and session. */
static int
active_window_cmp(struct active_window *aw1, struct active_window *aw2)
{
uintptr_t c1 = (uintptr_t)aw1->client;
uintptr_t c2 = (uintptr_t)aw2->client;
uintptr_t s1 = (uintptr_t)aw1->session;
uintptr_t s2 = (uintptr_t)aw2->session;
if (c1 < c2)
return (-1);
if (c1 > c2)
return (1);
if (s1 < s2)
return (-1);
if (s1 > s2)
return (1);
return (0);
}
RB_GENERATE_STATIC(active_windows, active_window, entry, active_window_cmp);
/* Compare local active pane states by client and window. */
static int
active_pane_cmp(struct active_pane *ap1, struct active_pane *ap2)
{
uintptr_t c1 = (uintptr_t)ap1->client;
uintptr_t c2 = (uintptr_t)ap2->client;
uintptr_t w1 = (uintptr_t)ap1->window;
uintptr_t w2 = (uintptr_t)ap2->window;
if (c1 < c2)
return (-1);
if (c1 > c2)
return (1);
if (w1 < w2)
return (-1);
if (w1 > w2)
return (1);
return (0);
}
RB_GENERATE_STATIC(active_panes, active_pane, entry, active_pane_cmp);
/* All local active window states. */
static struct active_windows active_windows = RB_INITIALIZER(&active_windows);
/* All local active pane states. */
static struct active_panes active_panes = RB_INITIALIZER(&active_panes);
/* Find local active window state for a client and session. */
static struct active_window *
active_window_get(struct client *c, struct session *s)
{
struct active_window aw;
if (c == NULL || s == NULL)
return (NULL);
aw.client = c;
aw.session = s;
return (RB_FIND(active_windows, &active_windows, &aw));
}
/* Add local active window state for a client and session. */
static struct active_window *
active_window_add(struct client *c, struct session *s, struct winlink *wl)
{
struct active_window *aw;
aw = active_window_get(c, s);
if (aw == NULL) {
aw = xcalloc(1, sizeof *aw);
aw->client = c;
aw->session = s;
TAILQ_INIT(&aw->lastw);
RB_INSERT(active_windows, &active_windows, aw);
}
if (wl != NULL)
aw->window = wl->window->id;
return (aw);
}
/* Remove a window from a local last-window stack. */
static void
active_window_stack_remove(struct active_window_stack *stack, u_int window)
{
struct active_window_entry *awe, *awe1;
TAILQ_FOREACH_SAFE(awe, stack, entry, awe1) {
if (awe->window == window) {
TAILQ_REMOVE(stack, awe, entry);
free(awe);
}
}
}
/* Push a window onto a local last-window stack. */
static void
active_window_stack_push(struct active_window_stack *stack, u_int window)
{
struct active_window_entry *awe;
active_window_stack_remove(stack, window);
awe = xcalloc(1, sizeof *awe);
awe->window = window;
TAILQ_INSERT_HEAD(stack, awe, entry);
}
/* Free local active window state. */
static void
active_window_free(struct active_window *aw)
{
struct active_window_entry *awe;
while (!TAILQ_EMPTY(&aw->lastw)) {
awe = TAILQ_FIRST(&aw->lastw);
TAILQ_REMOVE(&aw->lastw, awe, entry);
free(awe);
}
RB_REMOVE(active_windows, &active_windows, aw);
free(aw);
}
/* Resolve local active window state to a valid winlink. */
static struct winlink *
active_window_resolve(struct active_window *aw)
{
struct winlink *wl, *next;
u_int window;
if (aw == NULL || aw->session->curw == NULL)
return (NULL);
wl = winlink_find_by_window_id(&aw->session->windows, aw->window);
if (wl != NULL)
return (wl);
while (!TAILQ_EMPTY(&aw->lastw)) {
window = TAILQ_FIRST(&aw->lastw)->window;
next = winlink_find_by_window_id(&aw->session->windows, window);
active_window_stack_remove(&aw->lastw, window);
if (next != NULL) {
aw->window = next->window->id;
return (next);
}
}
aw->window = aw->session->curw->window->id;
return (aw->session->curw);
}
/* Notify the server that a client's effective window changed. */
static void
active_window_changed(struct client *c, struct session *s, struct winlink *wl,
struct winlink *old)
{
struct event_payload *ep;
struct cmd_find_state fs;
if (wl == NULL || wl == old)
return;
if (options_get_number(global_options, "focus-events")) {
if (old != NULL)
window_update_focus(old->window);
window_update_focus(wl->window);
}
winlink_clear_flags(wl);
window_update_activity(wl->window);
tty_update_window_offset(wl->window);
if (c != NULL && c->session == s)
wl->window->latest = c;
ep = event_payload_create();
cmd_find_from_winlink(&fs, wl, 0);
event_payload_set_target(ep, &fs);
event_payload_set_client(ep, "client", c);
event_payload_set_session(ep, "session", s);
event_payload_set_window(ep, "window", wl->window);
event_payload_set_window(ep, "new_window", wl->window);
event_payload_set_int(ep, "window_index", wl->idx);
event_payload_set_int(ep, "new_window_index", wl->idx);
if (old != NULL) {
event_payload_set_window(ep, "old_window", old->window);
event_payload_set_int(ep, "old_window_index", old->idx);
}
events_fire("client-window-changed", ep);
if (c != NULL) {
c->flags |= CLIENT_REDRAWWINDOW|CLIENT_REDRAWSTATUS|
CLIENT_REDRAWBORDERS;
tty_update_client_offset(c);
status_update_cache(s);
}
recalculate_sizes();
}
/* Find local active pane state for a client and window. */
static struct active_pane *
active_pane_get(struct client *c, struct window *w)
{
struct active_pane ap;
if (c == NULL || w == NULL)
return (NULL);
ap.client = c;
ap.window = w;
return (RB_FIND(active_panes, &active_panes, &ap));
}
/* Add local active pane state for a client and window. */
static struct active_pane *
active_pane_add(struct client *c, struct window *w)
{
struct active_pane *ap;
ap = active_pane_get(c, w);
if (ap == NULL) {
ap = xcalloc(1, sizeof *ap);
ap->client = c;
ap->window = w;
TAILQ_INIT(&ap->last_panes);
RB_INSERT(active_panes, &active_panes, ap);
}
return (ap);
}
/* Remove a pane from a client-local last-pane stack. */
static void
active_pane_stack_remove(struct active_pane *ap,
struct window_pane *wp)
{
struct active_pane_entry *ape, *ape1;
TAILQ_FOREACH_SAFE(ape, &ap->last_panes, entry, ape1) {
if (ape->wp == wp) {
TAILQ_REMOVE(&ap->last_panes, ape, entry);
free(ape);
}
}
}
/* Push a pane onto a client-local last-pane stack. */
static void
active_pane_stack_push(struct active_pane *ap, struct window_pane *wp)
{
struct active_pane_entry *ape;
if (wp == NULL)
return;
active_pane_stack_remove(ap, wp);
ape = xcalloc(1, sizeof *ape);
ape->wp = wp;
TAILQ_INSERT_HEAD(&ap->last_panes, ape, entry);
}
/* Free local active pane state. */
static void
active_pane_free(struct active_pane *ap)
{
struct active_pane_entry *ape, *ape1;
RB_REMOVE(active_panes, &active_panes, ap);
TAILQ_FOREACH_SAFE(ape, &ap->last_panes, entry, ape1) {
TAILQ_REMOVE(&ap->last_panes, ape, entry);
free(ape);
}
free(ap);
}
/* Check whether a pane can be the effective pane for a window. */
static int
active_pane_valid(struct window *w, struct window_pane *wp)
{
if (w == NULL || wp == NULL || wp->window != w)
return (0);
if (!window_has_pane(w, wp))
return (0);
if (w->modal != NULL && wp != w->modal)
return (0);
return (1);
}
/* Get the shared pane for a window, accounting for modal panes. */
static struct window_pane *
active_pane_default(struct window *w)
{
if (w == NULL)
return (NULL);
if (w->modal != NULL)
return (w->modal);
return (w->active);
}
/* Return if a client has local window selection for a session. */
int
active_is_local_window(struct client *c, struct session *s)
{
return (active_window_get(c, s) != NULL);
}
/* Make a client use local or shared window selection for a session. */
void
active_set_local_window(struct client *c, struct session *s,
enum active_mode mode)
{
struct active_window *aw;
struct winlink *wl, *old;
if (c == NULL || s == NULL || s->curw == NULL)
return;
old = active_get_effective_winlink(c, s);
if (mode == ACTIVE_LOCAL) {
active_window_add(c, s, old);
if (c != NULL && c->session == s)
c->flags |= CLIENT_REDRAWSTATUS;
return;
}
aw = active_window_get(c, s);
if (aw != NULL) {
active_window_free(aw);
wl = active_get_effective_winlink(c, s);
active_window_changed(c, s, wl, old);
if (c != NULL && c->session == s)
c->flags |= CLIENT_REDRAWSTATUS;
}
}
/* Return the effective winlink for a client and session. */
struct winlink *
active_get_effective_winlink(struct client *c, struct session *s)
{
struct active_window *aw;
if (s == NULL)
return (NULL);
aw = active_window_get(c, s);
if (aw != NULL)
return (active_window_resolve(aw));
return (s->curw);
}
/* Return the effective window for a client and session. */
struct window *
active_get_effective_window(struct client *c, struct session *s)
{
struct winlink *wl;
wl = active_get_effective_winlink(c, s);
if (wl == NULL)
return (NULL);
return (wl->window);
}
/* Select a winlink for a client and session. */
int
active_select_window(struct client *c, struct session *s, struct winlink *wl)
{
struct active_window *aw;
struct winlink *old;
int changed;
if (wl == NULL)
return (-1);
aw = active_window_get(c, s);
if (aw == NULL)
return (session_set_current(s, wl));
old = active_window_resolve(aw);
if (old == wl)
return (1);
if (old != NULL)
active_window_stack_push(&aw->lastw, old->window->id);
active_window_stack_remove(&aw->lastw, wl->window->id);
aw->window = wl->window->id;
changed = (old != wl);
active_window_changed(c, s, wl, old);
return (changed ? 0 : 1);
}
/* Select a window by index. */
int
active_select_window_index(struct client *c, struct session *s, int idx)
{
struct winlink *wl;
wl = winlink_find_by_index(&s->windows, idx);
return (active_select_window(c, s, wl));
}
/* Select the next window. */
int
active_next_window(struct client *c, struct session *s, int alert)
{
struct winlink *wl;
wl = active_get_effective_winlink(c, s);
if (wl == NULL)
return (-1);
do {
wl = winlink_next(wl);
if (wl == NULL)
wl = RB_MIN(winlinks, &s->windows);
if (!alert || (wl->flags & WINLINK_ALERTFLAGS))
return (active_select_window(c, s, wl));
} while (wl != active_get_effective_winlink(c, s));
return (-1);
}
/* Select the previous window. */
int
active_previous_window(struct client *c, struct session *s, int alert)
{
struct winlink *wl;
wl = active_get_effective_winlink(c, s);
if (wl == NULL)
return (-1);
do {
wl = winlink_previous(wl);
if (wl == NULL)
wl = RB_MAX(winlinks, &s->windows);
if (!alert || (wl->flags & WINLINK_ALERTFLAGS))
return (active_select_window(c, s, wl));
} while (wl != active_get_effective_winlink(c, s));
return (-1);
}
/* Select the last window. */
int
active_last_window(struct client *c, struct session *s)
{
struct active_window *aw;
struct winlink *wl;
u_int window;
aw = active_window_get(c, s);
if (aw == NULL)
return (session_last(s));
while (!TAILQ_EMPTY(&aw->lastw)) {
window = TAILQ_FIRST(&aw->lastw)->window;
wl = winlink_find_by_window_id(&s->windows, window);
if (wl != NULL)
return (active_select_window(c, s, wl));
active_window_stack_remove(&aw->lastw, window);
}
return (-1);
}
/* Return if this winlink is the effective winlink. */
int
active_is_effective_window(struct client *c, struct session *s,
struct winlink *wl)
{
return (wl != NULL && wl == active_get_effective_winlink(c, s));
}
/* Return if this winlink is the last effective winlink. */
int
active_is_last_window(struct client *c, struct session *s, struct winlink *wl)
{
struct active_window *aw;
struct active_window_entry *awe;
if (wl == NULL)
return (0);
aw = active_window_get(c, s);
if (aw == NULL)
return (wl == TAILQ_FIRST(&s->lastw));
TAILQ_FOREACH(awe, &aw->lastw, entry) {
if (winlink_find_by_window_id(&s->windows, awe->window) == NULL)
continue;
return (wl->window->id == awe->window);
}
return (0);
}
/* Return if a client has local pane selection for a window. */
int
active_has_local_pane(struct client *c, struct window *w)
{
struct active_pane *ap;
ap = active_pane_get(c, w);
return (ap != NULL && ap->mode == ACTIVE_LOCAL);
}
/* Make a client use local or shared pane selection for a window. */
void
active_set_pane_mode(struct client *c, struct window *w, enum active_mode mode)
{
struct active_pane *ap;
struct window_pane *wp;
if (c == NULL || w == NULL)
return;
if (mode == ACTIVE_SHARED) {
ap = active_pane_get(c, w);
if (ap != NULL)
ap->mode = ACTIVE_SHARED;
server_redraw_client(c);
server_status_client(c);
return;
}
if (server_client_how_many() < 2) {
ap = active_pane_get(c, w);
if (ap != NULL)
ap->mode = ACTIVE_SHARED;
server_redraw_client(c);
server_status_client(c);
return;
}
wp = active_get_effective_pane(c, w);
if (wp == NULL)
wp = active_pane_default(w);
ap = active_pane_add(c, w);
ap->mode = ACTIVE_LOCAL;
ap->pane = wp;
server_redraw_client(c);
server_status_client(c);
}
/* Return the effective active pane for a client and window. */
struct window_pane *
active_get_effective_pane(struct client *c, struct window *w)
{
struct active_pane *ap;
struct window_pane *wp;
wp = active_pane_default(w);
ap = active_pane_get(c, w);
if (ap == NULL || ap->mode != ACTIVE_LOCAL)
return (wp);
if (!active_pane_valid(w, ap->pane)) {
ap->pane = wp;
if (wp != NULL)
active_pane_stack_remove(ap, wp);
return (wp);
}
return (ap->pane);
}
/* Return the effective last pane for a client and window. */
struct window_pane *
active_get_last_pane(struct client *c, struct window *w)
{
struct active_pane *ap;
struct active_pane_entry *ape, *ape1;
if (!active_has_local_pane(c, w))
return (w == NULL ? NULL : TAILQ_FIRST(&w->last_panes));
ap = active_pane_get(c, w);
TAILQ_FOREACH_SAFE(ape, &ap->last_panes, entry, ape1) {
if (active_pane_valid(w, ape->wp))
return (ape->wp);
TAILQ_REMOVE(&ap->last_panes, ape, entry);
free(ape);
}
return (NULL);
}
/* Select a pane using either shared or local state. */
int
active_set_pane(struct client *c, struct window *w, struct window_pane *wp,
int notify)
{
struct active_pane *ap;
struct window_pane *lastwp;
if (!active_pane_valid(w, wp))
return (0);
if (!active_has_local_pane(c, w))
return (window_set_active_pane(w, wp, notify));
lastwp = active_get_effective_pane(c, w);
if (wp == lastwp)
return (0);
ap = active_pane_add(c, w);
active_pane_stack_remove(ap, wp);
active_pane_stack_push(ap, lastwp);
ap->pane = wp;
if (c != NULL) {
server_redraw_client(c);
server_status_client(c);
}
return (1);
}
/* Remove all local pane state if fewer than two clients remain. */
void
active_check_clients(void)
{
struct active_pane *ap, *ap1;
if (server_client_how_many() >= 2)
return;
RB_FOREACH_SAFE(ap, active_panes, &active_panes, ap1) {
if (ap->mode == ACTIVE_LOCAL) {
server_redraw_client(ap->client);
server_status_client(ap->client);
}
active_pane_free(ap);
}
}
/* Remove all state for a client. */
void
active_remove_client(struct client *c)
{
struct active_window *aw, *aw1;
struct active_pane *ap, *ap1;
RB_FOREACH_SAFE(aw, active_windows, &active_windows, aw1) {
if (aw->client == c)
active_window_free(aw);
}
RB_FOREACH_SAFE(ap, active_panes, &active_panes, ap1) {
if (ap->client == c)
active_pane_free(ap);
}
}
/* Remove all window state for a session. */
void
active_remove_session(struct session *s)
{
struct active_window *aw, *aw1;
RB_FOREACH_SAFE(aw, active_windows, &active_windows, aw1) {
if (aw->session == s)
active_window_free(aw);
}
}
/* Remove a window from all local state. */
void
active_remove_window(struct window *w)
{
struct active_window *aw;
struct active_pane *ap, *ap1;
RB_FOREACH(aw, active_windows, &active_windows) {
active_window_stack_remove(&aw->lastw, w->id);
if (aw->window == w->id && aw->session->curw != NULL)
aw->window = aw->session->curw->window->id;
}
RB_FOREACH_SAFE(ap, active_panes, &active_panes, ap1) {
if (ap->window == w)
active_pane_free(ap);
}
}
/* Remove a pane from all local pane state. */
void
active_remove_pane(struct window_pane *wp)
{
struct active_pane *ap, *ap1;
RB_FOREACH_SAFE(ap, active_panes, &active_panes, ap1) {
active_pane_stack_remove(ap, wp);
if (ap->pane == wp)
ap->pane = NULL;
}
}

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-display-menu.c,v 1.52 2026/07/14 19:07:03 nicm Exp $ */
/* $OpenBSD: cmd-display-menu.c,v 1.53 2026/08/31 07:46:55 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -483,7 +483,7 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
char *title, *cause = NULL;
int flags = 0, starting_choice = 0;
u_int px, py, i, count = args_count(args);
struct options *o = target->s->curw->window->options;
struct options *o = target->w->options;
struct options_entry *oe;
if (args_has(args, 'C')) {
@@ -584,13 +584,15 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
u_int px, py, w, h, count = args_count(args);
struct args_value *av;
struct environ *env = NULL;
struct options *o = s->curw->window->options;
struct options *o = target->w->options;
struct options_entry *oe;
if (args_has(args, 'C')) {
server_client_clear_overlay(tc);
return (CMD_RETURN_NORMAL);
}
if (tc->flags & CLIENT_CONTROL)
return (CMD_RETURN_NORMAL);
if (!modify && tc->overlay_draw != NULL)
return (CMD_RETURN_NORMAL);

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-parse.y,v 1.58 2026/04/27 12:31:11 nicm Exp $ */
/* $OpenBSD: cmd-parse.y,v 1.59 2026/08/31 07:51:56 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -802,6 +802,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
if (last == NULL) {
pr->status = CMD_PARSE_SUCCESS;
pr->cmdlist = cmd_list_new();
cmd_parse_free_commands(cmds);
return (1);
}
@@ -814,6 +815,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd,
pi->flags |= CMD_PARSE_NOALIAS;
cmd_parse_build_commands(cmds, pi, pr);
pi->flags &= ~CMD_PARSE_NOALIAS;
cmd_parse_free_commands(cmds);
return (1);
}

View File

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

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-resize-pane.c,v 1.67 2026/07/10 13:38:45 nicm Exp $ */
/* $OpenBSD: cmd-resize-pane.c,v 1.68 2026/08/31 07:44:39 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -92,6 +92,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
}
server_unzoom_window(w);
lc = wp->layout_cell; /* may have been replaced by unzoom */
if (args_has(args, 'x')) {
x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);

View File

@@ -79,7 +79,10 @@ static void
cmd_run_shell_print(struct job *job, const char *msg)
{
struct cmd_run_shell_data *cdata = job_get_data(job);
struct client *c = cdata->client;
struct session *s;
struct window_pane *wp = NULL;
struct window *w;
struct cmd_find_state fs;
struct window_mode_entry *wme;
@@ -90,8 +93,11 @@ cmd_run_shell_print(struct job *job, const char *msg)
cmdq_print(cdata->item, "%s", msg);
return;
}
if (cdata->client != NULL && cdata->client->session != NULL)
wp = cdata->client->session->curw->window->active;
if (c != NULL && c->session != NULL) {
s = c->session;
w = active_get_effective_window(c, s);
wp = active_get_effective_pane(c, w);
}
if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0)
wp = fs.wp;
if (wp == NULL)

View File

@@ -33,8 +33,8 @@ const struct cmd_entry cmd_select_pane_entry = {
.name = "select-pane",
.alias = "selectp",
.args = { "DdegLlMmP:RT:t:UZ", 0, 0, NULL }, /* -P and -g deprecated */
.usage = "[-DdeLlMmRUZ] [-T title] " CMD_TARGET_PANE_USAGE,
.args = { "DdegLlMmP:RsST:t:UZ", 0, 0, NULL }, /* -P and -g deprecated */
.usage = "[-DdeLlMmRsSUZ] [-T title] " CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -68,10 +68,11 @@ cmd_select_pane_redraw(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session == NULL || (c->flags & CLIENT_CONTROL))
continue;
if (c->session->curw->window == w && tty_window_bigger(&c->tty))
if (active_get_effective_window(c, c->session) == w &&
tty_window_bigger(&c->tty))
server_redraw_client(c);
else {
if (c->session->curw->window == w)
if (active_get_effective_window(c, c->session) == w)
c->flags |= CLIENT_REDRAWBORDERS;
if (session_has(c->session, w))
c->flags |= CLIENT_REDRAWSTATUS;
@@ -84,6 +85,7 @@ static enum cmd_retval
cmd_select_pane_marked_pane(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
struct client *c = cmdq_get_client(item);
struct cmd_find_state *target = cmdq_get_target(item);
struct event_payload *ep;
struct cmd_find_state fs;
@@ -137,8 +139,9 @@ cmd_select_pane_marked_pane(struct cmd *self, struct cmdq_item *item)
server_status_window(mwp->window);
}
if (window_pane_is_floating(wp)) {
window_redraw_active_switch(wp->window, wp);
window_set_active_pane(wp->window, wp, 1);
if (!active_has_local_pane(c, wp->window))
window_redraw_active_switch(wp->window, wp);
active_set_pane(c, wp->window, wp, 1);
}
return (CMD_RETURN_NORMAL);
}
@@ -152,26 +155,40 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item);
struct event_payload *ep;
struct cmd_find_state fs;
struct client *c = cmdq_get_client(item);
struct winlink *wl = target->wl;
struct window *w = wl->window;
struct session *s = target->s;
struct window_pane *wp = target->wp, *lastwp;
struct window_pane *wp = target->wp, *lastwp, *activewp;
struct options *oo = wp->options;
char *title;
const char *style;
struct options_entry *o;
int visible, Zflag = args_has(args, 'Z');
if (args_has(args, 's') || args_has(args, 'S')) {
if (c == NULL || c->session == NULL) {
cmdq_error(item, "no current client");
return (CMD_RETURN_ERROR);
}
if (args_has(args, 's'))
active_set_pane_mode(c, w, ACTIVE_LOCAL);
else
active_set_pane_mode(c, w, ACTIVE_SHARED);
return (CMD_RETURN_NORMAL);
}
if (entry == &cmd_last_pane_entry || args_has(args, 'l')) {
/*
* Check for no last pane found in case the other pane was
* spawned without being visited (for example split-window -d).
*/
lastwp = TAILQ_FIRST(&w->last_panes);
lastwp = active_get_last_pane(c, w);
if (lastwp == NULL && window_count_panes(w, 1) == 2) {
lastwp = TAILQ_PREV(w->active, window_panes, entry);
activewp = active_get_effective_pane(c, w);
lastwp = TAILQ_PREV(activewp, window_panes, entry);
if (lastwp == NULL)
lastwp = TAILQ_NEXT(w->active, entry);
lastwp = TAILQ_NEXT(activewp, entry);
}
if (lastwp == NULL) {
cmdq_error(item, "no last pane");
@@ -192,8 +209,9 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
visible = window_pane_is_visible(lastwp);
if (!visible && window_push_zoom(w, 0, Zflag))
server_redraw_window(w);
window_redraw_active_switch(w, lastwp);
if (window_set_active_pane(w, lastwp, 1)) {
if (!active_has_local_pane(c, w))
window_redraw_active_switch(w, lastwp);
if (active_set_pane(c, w, lastwp, 1)) {
cmd_find_from_winlink(current, wl, 0);
cmd_select_pane_redraw(w);
}
@@ -271,7 +289,8 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_NORMAL);
}
if (wp == w->active)
activewp = active_get_effective_pane(c, w);
if (wp == activewp)
return (CMD_RETURN_NORMAL);
if (w->modal != NULL && wp != w->modal)
visible = 1;
@@ -279,8 +298,9 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
visible = window_pane_is_visible(wp);
if (!visible && window_push_zoom(w, 0, Zflag))
server_redraw_window(w);
window_redraw_active_switch(w, wp);
if (window_set_active_pane(w, wp, 1))
if (!active_has_local_pane(c, w))
window_redraw_active_switch(w, wp);
if (active_set_pane(c, w, wp, 1))
cmd_find_from_winlink_pane(current, wl, wp, 0);
cmdq_insert_hook(s, item, current, "after-select-pane");
cmd_select_pane_redraw(w);

View File

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

View File

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

View File

@@ -53,11 +53,12 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state target;
const char *tflag = args_get(args, 't');
enum cmd_find_type type;
int flags, visible, Zflag = args_has(args, 'Z');
int flags, visible, window_target;
int Zflag = args_has(args, 'Z');
struct client *c = cmdq_get_client(item);
struct client *tc = cmdq_get_target_client(item);
struct session *s;
struct winlink *wl;
struct winlink *wl, *current_wl;
struct window *w;
struct window_pane *wp;
const char *tablename;
@@ -65,8 +66,13 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
struct sort_criteria sort_crit;
uid_t uid;
if (tflag != NULL &&
(tflag[strcspn(tflag, ":.%")] != '\0' || strcmp(tflag, "=") == 0)) {
window_target = 0;
if (tflag != NULL) {
if (tflag[strcspn(tflag, ":.%")] != '\0' ||
strcmp(tflag, "=") == 0)
window_target = 1;
}
if (window_target) {
type = CMD_FIND_PANE;
flags = 0;
} else {
@@ -137,7 +143,8 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
} else {
if (cmdq_get_client(item) == NULL)
return (CMD_RETURN_NORMAL);
if (wl != NULL && wp != NULL && wp != wl->window->active) {
if (window_target && wl != NULL && wp != NULL &&
wp != wl->window->active) {
w = wl->window;
if (w->modal != NULL && wp != w->modal)
visible = 1;
@@ -150,9 +157,13 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
if (!visible && window_pop_zoom(w))
server_redraw_window(w);
}
if (wl != NULL) {
session_set_current(s, wl);
cmd_find_from_session(current, s, 0);
if (window_target && wl != NULL) {
active_select_window(tc, s, wl);
if (active_is_local_window(tc, s)) {
current_wl = active_get_effective_winlink(tc, s);
cmd_find_from_winlink(current, current_wl, 0);
} else
cmd_find_from_session(current, s, 0);
}
}

View File

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

196
format.c
View File

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

38
grid.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: grid.c,v 1.156 2026/08/03 12:58:53 nicm Exp $ */
/* $OpenBSD: grid.c,v 1.158 2026/09/01 12:49:49 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1635,7 +1635,7 @@ grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy)
void
grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
{
u_int yy, ay = 0;
u_int yy, ay = 0, ey = gd->hsize + gd->sy - 1;
for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) {
if (ay == wy)
@@ -1649,7 +1649,7 @@ grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
* until we find the end or the line now containing wx.
*/
if (wx == UINT_MAX) {
while (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
while (yy < ey && gd->linedata[yy].flags & GRID_LINE_WRAPPED)
yy++;
wx = gd->linedata[yy].cellused;
} else {
@@ -1711,21 +1711,29 @@ grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
{
struct grid_cell gc, tmp_gc;
u_int pxx;
int has_tab, has_space;
has_tab = (strchr(set, '\t') != NULL);
has_space = (strchr(set, ' ') != NULL);
grid_get_cell(gd, px, py, &gc);
if (strchr(set, '\t')) {
if (gc.flags & GRID_FLAG_PADDING) {
pxx = px;
do
grid_get_cell(gd, --pxx, py, &tmp_gc);
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
if (tmp_gc.flags & GRID_FLAG_TAB)
return (tmp_gc.data.width - (px - pxx));
} else if (gc.flags & GRID_FLAG_TAB)
return (gc.data.width);
}
if (gc.flags & GRID_FLAG_PADDING)
if (gc.flags & GRID_FLAG_PADDING) {
if (!has_tab && !has_space)
return (0);
pxx = px;
do
grid_get_cell(gd, --pxx, py, &tmp_gc);
while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
if (((has_tab || has_space) &&
(tmp_gc.flags & GRID_FLAG_TAB)) ||
(has_space && utf8_has_whitespace(&tmp_gc.data)))
return (tmp_gc.data.width - (px - pxx));
return (0);
}
if ((has_tab || has_space) && (gc.flags & GRID_FLAG_TAB))
return (gc.data.width);
if (has_space && utf8_has_whitespace(&gc.data))
return (gc.data.width == 0 ? 1 : gc.data.width);
return (utf8_cstrhas(set, &gc.data));
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: input.c,v 1.270 2026/08/17 20:04:00 nicm Exp $ */
/* $OpenBSD: input.c,v 1.271 2026/08/31 19:34:09 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1049,6 +1049,7 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
if (len == 0)
return;
wp->output_generation++;
window_update_activity(wp->window);
if (~wp->flags & PANE_ACTIVITY) {
wp->flags |= PANE_ACTIVITY;
@@ -3077,7 +3078,8 @@ input_osc_10(struct input_ctx *ictx, const char *p)
return;
c = window_pane_get_fg_control_client(wp);
if (c == -1) {
tty_default_colours(&defaults, wp, NULL);
tty_default_colours(&defaults, wp, wp->window->active,
NULL);
if (COLOUR_DEFAULT(defaults.fg))
c = window_pane_get_fg(wp);
else

View File

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

View File

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

2
menu.c
View File

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

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: options-table.c,v 1.243 2026/08/25 08:37:08 nicm Exp $ */
/* $OpenBSD: options-table.c,v 1.244 2026/09/01 12:49:49 nicm Exp $ */
/*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1125,7 +1125,8 @@ const struct options_table_entry options_table[] = {
{ .name = "status-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SESSION,
.default_str = "bg=themegreen,fg=themeblack",
.default_str = "bg=#{?local_active_window,themeblue,themegreen},"
"fg=themeblack",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Style of the status line."
@@ -1250,7 +1251,8 @@ const struct options_table_entry options_table[] = {
* underscore.
*/
.default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~",
.text = "Characters considered to separate words."
.text = "Characters considered to separate words; a space matches "
"any character with the Unicode White_Space property."
},
/* Window options. */
@@ -1552,8 +1554,9 @@ const struct options_table_entry options_table[] = {
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
.default_str = "fg=#{?pane_modal_flag,themeblue,"
"#{?pane_marked,thememagenta,"
"#{?pane_local_active,themecyan,"
"#{?synchronize-panes,themered,"
"#{?pane_in_mode,themeyellow,themegreen}}}}",
"#{?pane_in_mode,themeyellow,themegreen}}}}}",
.flags = OPTIONS_TABLE_IS_STYLE,
.separator = ",",
.text = "Style of the active pane border."
@@ -1981,6 +1984,8 @@ const struct options_table_entry options_table[] = {
"Run when a client is resized."),
OPTIONS_TABLE_HOOK("client-session-changed", "",
"Run when a client's attached session is changed."),
OPTIONS_TABLE_HOOK("client-window-changed", "",
"Run when a client's effective window is changed."),
OPTIONS_TABLE_HOOK("client-light-theme", "",
"Run when a client switches to a light theme."),
OPTIONS_TABLE_HOOK("client-dark-theme", "",

13
popup.c
View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -348,10 +348,10 @@ recalculate_size_skip_client(struct client *loop, __unused int type,
* is not the current window - this is used for aggressive-resize.
* Otherwise skip any session that doesn't contain the window.
*/
if (loop->session->curw == NULL)
if (active_get_effective_window(loop, loop->session) == NULL)
return (1);
if (current)
return (loop->session->curw->window != w);
return (active_get_effective_window(loop, loop->session) != w);
return (session_has(loop->session, w) == 0);
}

View File

@@ -295,7 +295,7 @@ static void
redraw_set_context(struct client *c, struct redraw_build_ctx *bctx)
{
struct session *s = c->session;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
memset(bctx, 0, sizeof *bctx);
bctx->c = c;
@@ -969,7 +969,7 @@ static struct redraw_scene *
redraw_make_scene(struct client *c)
{
struct session *s = c->session;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct redraw_build_ctx bctx;
struct redraw_scene *scene;
struct redraw_build_cell *bc, *last;
@@ -1079,7 +1079,7 @@ static struct redraw_scene *
redraw_get_scene(struct client *c)
{
struct redraw_scene *scene = c->redraw_scene;
struct window *w = c->session->curw->window;
struct window *w = active_get_effective_window(c, c->session);
const char *reason = NULL;
u_int ox, oy, sx, sy;
@@ -1117,7 +1117,7 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
struct tty_style_ctx style_ctx;
u_int px, py;
tty_default_colours(&defaults, wp, &style_ctx.dim);
tty_default_colours(&defaults, wp, dctx->active, &style_ctx.dim);
style_ctx.defaults = &defaults;
style_ctx.palette = &wp->palette;
style_ctx.hyperlinks = s->hyperlinks;
@@ -1140,7 +1140,7 @@ redraw_get_default_border_style(struct redraw_draw_ctx *dctx,
struct grid_cell *dgc = &dctx->default_gc;
if (~dctx->flags & REDRAW_DEFAULT_SET) {
ft = format_create_defaults(NULL, c, s, s->curw, NULL);
ft = format_create_defaults(NULL, c, s, NULL, NULL);
memcpy(dgc, &grid_default_cell, sizeof *dgc);
style_add(dgc, oo, "pane-border-style", ft);
format_free(ft);
@@ -1334,7 +1334,7 @@ redraw_draw_scrollbar_span(struct redraw_draw_ctx *dctx,
memcpy(&slgc, &gc, sizeof slgc);
slgc.fg = gc.bg;
slgc.bg = gc.fg;
tty_default_colours(&pad_gc, wp, NULL);
tty_default_colours(&pad_gc, wp, dctx->active, NULL);
sb_w = sb_style->width;
sb_pad = sb_style->pad;
@@ -1603,14 +1603,16 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx,
struct session *s = c->session;
struct options *oo = s->options;
struct tty *tty = &c->tty;
struct winlink *wl;
u_int lines;
memset(dctx, 0, sizeof *dctx);
dctx->scene = scene;
if (server_is_marked(s, s->curw, marked_pane.wp))
wl = active_get_effective_winlink(c, s);
if (server_is_marked(s, wl, marked_pane.wp))
dctx->marked = marked_pane.wp;
dctx->active = s->curw->window->active;
dctx->active = active_get_effective_pane(c, wl->window);
lines = status_line_size(c);
if (options_get_number(oo, "status-position") == 0)
@@ -1683,7 +1685,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
{
struct redraw_draw_ctx dctx;
struct session *s = c->session;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct tty *tty = &c->tty;
struct screen *sl;
struct redraw_scene *scene;
@@ -1883,7 +1885,7 @@ redraw_screen(struct client *c)
flags |= REDRAW_OVERLAY;
if (c->flags & CLIENT_REDRAWMENU)
flags |= REDRAW_MENU;
if (c->session->curw->window->menu != NULL)
if (active_get_effective_window(c, c->session)->menu != NULL)
flags |= REDRAW_MENU;
if (flags != 0)
redraw_draw(c, NULL, flags);
@@ -1895,7 +1897,7 @@ void
redraw_pane(struct client *c, struct window_pane *wp)
{
redraw_draw(c, wp, REDRAW_PANE|REDRAW_PANE_SCROLLBAR);
if (c->session->curw->window->menu != NULL)
if (active_get_effective_window(c, c->session)->menu != NULL)
redraw_draw(c, NULL, REDRAW_MENU);
}

View File

@@ -134,7 +134,7 @@ screen_write_redraw_cb(const struct tty_ctx *ttyctx)
static int
screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
{
struct window_pane *wp = ttyctx->arg;
struct window_pane *wp = ttyctx->arg, *active;
if (ttyctx->flags & TTY_CTX_INVISIBLE_PANES) {
if (session_has(c->session, wp->window))
@@ -142,7 +142,7 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
return (0);
}
if (c->session->curw->window != wp->window)
if (active_get_effective_window(c, c->session) != wp->window)
return (0);
if (wp->layout_cell == NULL)
return (0);
@@ -160,11 +160,14 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
return (-1);
}
active = active_get_effective_pane(c, wp->window);
if (tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx,
&ttyctx->wsy))
ttyctx->flags |= TTY_CTX_WINDOW_BIGGER;
else
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
tty_default_colours(&ttyctx->defaults, wp, active,
&ttyctx->style_ctx.dim);
ttyctx->xoff = ttyctx->rxoff = wp->xoff;
ttyctx->yoff = ttyctx->ryoff = wp->yoff;
@@ -296,7 +299,7 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
ttyctx->redraw_cb = screen_write_redraw_cb;
if (ctx->wp != NULL) {
tty_default_colours(&ttyctx->defaults, ctx->wp,
&ttyctx->style_ctx.dim);
ctx->wp->window->active, &ttyctx->style_ctx.dim);
ttyctx->style_ctx.palette = &ctx->wp->palette;
ttyctx->set_client_cb = screen_write_set_client_cb;
ttyctx->arg = ctx->wp;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server-client.c,v 1.508 2026/08/25 06:14:16 nicm Exp $ */
/* $OpenBSD: server-client.c,v 1.509 2026/08/28 07:36:01 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -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(c->session->curw->window);
window_update_focus(active_get_effective_window(c, c->session));
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(c->session->curw->window);
window_update_focus(active_get_effective_window(c, c->session));
server_redraw_client(c);
}
@@ -379,7 +379,8 @@ server_client_attached_lost(struct client *c)
found = NULL;
TAILQ_FOREACH(loop, &clients, entry) {
s = loop->session;
if (loop == c || s == NULL || s->curw->window != w)
if (loop == c || s == NULL ||
active_get_effective_window(loop, s) != w)
continue;
if (found == NULL || timercmp(&loop->activity_time,
&found->activity_time, >))
@@ -460,15 +461,15 @@ server_client_set_session(struct client *c, struct session *s)
c->flags |= CLIENT_FOCUSED;
if (old != NULL && old->curw != NULL)
window_update_focus(old->curw->window);
window_update_focus(active_get_effective_window(c, old));
if (s != NULL) {
s->curw->window->latest = c;
active_get_effective_window(c, s)->latest = c;
recalculate_sizes();
window_update_focus(s->curw->window);
window_update_focus(active_get_effective_window(c, s));
session_update_activity(s, NULL);
session_theme_changed(s);
gettimeofday(&s->last_attached_time, NULL);
s->curw->flags &= ~WINLINK_ALERTFLAGS;
active_get_effective_winlink(c, s)->flags &= ~WINLINK_ALERTFLAGS;
alerts_check_session(s);
tty_update_client_offset(c);
status_timer_start(c);
@@ -493,6 +494,7 @@ server_client_lost(struct client *c)
server_client_clear_overlay(c);
status_prompt_clear(c);
status_message_clear(c);
active_remove_client(c);
RB_FOREACH_SAFE(cf, client_files, &c->files, cf1) {
cf->error = EINTR;
@@ -506,6 +508,7 @@ server_client_lost(struct client *c)
server_client_attached_lost(c);
events_fire_client("client-detached", c);
}
active_check_clients();
if (c->name != NULL && (c->flags & (CLIENT_CONTROL|CLIENT_TERMINAL)))
events_fire_client("client-closed", c);
@@ -684,7 +687,7 @@ server_client_in_scrollbar_area(struct window_pane *wp, int px, int py)
static void
server_client_update_scrollbar_hover(struct client *c, int type, int px, int py)
{
struct window *w = c->session->curw->window;
struct window *w = active_get_effective_window(c, c->session);
struct window_pane *wp;
if (type != KEYC_TYPE_MOUSEMOVE)
@@ -856,7 +859,7 @@ server_client_check_mouse(struct client *c, struct key_event *event)
{
struct mouse_event *m = &event->m;
struct session *s = c->session, *fs;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct winlink *fwl;
struct window_pane *wp, *fwp, *lwp = NULL;
u_int x, y, sx, sy, px, py, n, sl_mpos = 0;
@@ -1359,7 +1362,7 @@ server_client_update_latest(struct client *c)
if (c->session == NULL)
return;
w = c->session->curw->window;
w = active_get_effective_window(c, c->session);
if (w->latest == c)
return;
@@ -1438,7 +1441,7 @@ server_client_key_callback(struct cmdq_item *item, void *data)
/* Check the client is good to accept input. */
if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
goto out;
wl = s->curw;
wl = active_get_effective_winlink(c, s);
/* Update the activity timer. */
memcpy(&c->last_activity_time, &c->activity_time,
@@ -1499,6 +1502,10 @@ 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.
@@ -1693,7 +1700,7 @@ out:
static int
server_client_handle_menu_key(struct client *c, struct key_event *event)
{
struct window *w = c->session->curw->window;
struct window *w = active_get_effective_window(c, c->session);
struct key_event new_event;
struct mouse_event *m;
u_int ox, oy, sx, sy;
@@ -1732,6 +1739,7 @@ server_client_handle_key0(struct client *c, struct key_event *event,
{
struct session *s = c->session;
struct cmdq_item *item;
struct window *w;
struct window_pane *wp;
/* Check the client is good to accept input. */
@@ -1802,9 +1810,10 @@ server_client_handle_key0(struct client *c, struct key_event *event,
}
}
wp = s->curw->window->active;
w = active_get_effective_window(c, s);
wp = w->active;
if (wp == NULL || !window_pane_has_prompt(wp)) {
TAILQ_FOREACH(wp, &s->curw->window->panes, entry) {
TAILQ_FOREACH(wp, &w->panes, entry) {
if (window_pane_has_prompt(wp) &&
window_pane_is_visible(wp))
break;
@@ -2146,8 +2155,8 @@ static void
server_client_reset_state(struct client *c)
{
struct tty *tty = &c->tty;
struct window *w = c->session->curw->window;
struct window_pane *wp = w->active, *loop;
struct window *w = active_get_effective_window(c, c->session);
struct window_pane *wp, *loop;
struct screen *s = NULL;
struct options *oo = c->session->options;
int mode = 0, cursor, flags, pane_mode = 0;
@@ -2155,6 +2164,7 @@ server_client_reset_state(struct client *c)
u_int sb_w;
struct visible_ranges *r;
wp = active_get_effective_pane(c, w);
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
return;
@@ -2426,7 +2436,7 @@ server_client_redraw_timer(__unused int fd, __unused short events,
static void
server_client_check_modes(struct client *c)
{
struct window *w = c->session->curw->window;
struct window *w;
struct window_pane *wp;
struct window_mode_entry *wme;
@@ -2434,6 +2444,7 @@ server_client_check_modes(struct client *c)
return;
if (~c->flags & CLIENT_REDRAWSTATUS)
return;
w = active_get_effective_window(c, c->session);
TAILQ_FOREACH(wp, &w->panes, entry) {
wme = TAILQ_FIRST(&wp->modes);
if (wme != NULL && wme->mode->update != NULL)
@@ -2446,7 +2457,7 @@ static int
server_client_any_pane_redraw(struct client *c)
{
struct session *s = c->session;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct window_pane *wp;
if (c->flags & CLIENT_REDRAWWINDOW)
@@ -2464,7 +2475,7 @@ server_client_check_redraw(struct client *c)
{
struct session *s = c->session;
struct tty *tty = &c->tty;
struct window *w = s->curw->window;
struct window *w = active_get_effective_window(c, s);
struct window_pane *wp;
int needed, tflags, mode = tty->mode;
struct timeval tv = { .tv_usec = 1000 };
@@ -2603,14 +2614,15 @@ static void
server_client_set_path(struct client *c)
{
struct session *s = c->session;
struct window *w = active_get_effective_window(c, s);
const char *path;
if (s->curw == NULL || s->curw->window->active == NULL)
if (w == NULL || w->active == NULL)
return;
if (s->curw->window->active->base.path == NULL)
if (w->active->base.path == NULL)
path = "";
else
path = s->curw->window->active->base.path;
path = w->active->base.path;
if (c->path == NULL || strcmp(path, c->path) != 0) {
free(c->path);
c->path = xstrdup(path);
@@ -2623,11 +2635,12 @@ static void
server_client_set_progress_bar(struct client *c)
{
struct session *s = c->session;
struct window *w = active_get_effective_window(c, s);
struct progress_bar *pane_pb;
if (s->curw == NULL || s->curw->window->active == NULL)
if (w == NULL || w->active == NULL)
return;
pane_pb = &s->curw->window->active->base.progress_bar;
pane_pb = &w->active->base.progress_bar;
if (pane_pb->state == c->progress_bar.state &&
pane_pb->progress == c->progress_bar.progress)
return;
@@ -3164,6 +3177,7 @@ server_client_remove_pane(struct window_pane *wp)
{
struct client *c;
active_remove_pane(wp);
TAILQ_FOREACH(c, &clients, entry) {
if (c->tty.mouse_last_pane == (int)wp->id) {
c->tty.mouse_last_pane = -1;
@@ -3179,6 +3193,7 @@ server_client_print(struct client *c, int parse, struct evbuffer *evb)
{
void *data = EVBUFFER_DATA(evb);
size_t size = EVBUFFER_LENGTH(evb);
struct window *w;
struct window_pane *wp;
struct window_mode_entry *wme;
char *sanitized, *msg, *line, empty = '\0';
@@ -3217,7 +3232,8 @@ server_client_print(struct client *c, int parse, struct evbuffer *evb)
goto out;
}
wp = c->session->curw->window->active;
w = active_get_effective_window(c, c->session);
wp = active_get_effective_pane(c, w);
wme = TAILQ_FIRST(&wp->modes);
if (wme == NULL || wme->mode != &window_view_mode)
window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL,

View File

@@ -122,8 +122,7 @@ server_redraw_window(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->curw != NULL &&
c->session->curw->window == w)
active_get_effective_window(c, c->session) == w)
server_redraw_client(c);
}
}
@@ -135,8 +134,7 @@ server_redraw_window_menu(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->curw != NULL &&
c->session->curw->window == w)
active_get_effective_window(c, c->session) == w)
c->flags |= CLIENT_REDRAWMENU;
}
}
@@ -148,8 +146,7 @@ server_redraw_window_borders(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->curw != NULL &&
c->session->curw->window == w)
active_get_effective_window(c, c->session) == w)
c->flags |= CLIENT_REDRAWBORDERS;
}
}

View File

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

59
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1159 2026/08/25 08:37:08 nicm Exp $
.\" $OpenBSD: tmux.1,v 1.1163 2026/09/01 12:49:49 nicm Exp $
.\"
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
.\"
@@ -14,7 +14,7 @@
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: August 25 2026 $
.Dd $Mdocdate: September 1 2026 $
.Dt TMUX 1
.Os
.Sh NAME
@@ -2208,14 +2208,14 @@ Move to the end of the next word.
.Xc
Same as
.Ic next\-word
but use a space alone as the word separator.
but treat all non-whitespace characters as part of a word.
.It Xo
.Ic next\-space\-end
(vi: E)
.Xc
Same as
.Ic next\-word\-end
but use a space alone as the word separator.
but treat all non-whitespace characters as part of a word.
.It Xo
.Ic other\-end
(vi: o)
@@ -2289,7 +2289,7 @@ Move to the previous word.
.Xc
Same as
.Ic previous\-word
but use a space alone as the word separator.
but treat all non-whitespace characters as part of a word.
.It Xo
.Ic rectangle\-on
.Xc
@@ -2574,8 +2574,8 @@ Word separators can be customized with the
session option.
Next word moves to the start of the next word, next word end to the end of the
next word and previous word to the start of the previous word.
The three next and previous space keys work similarly but use a space alone as
the word separator.
The three next and previous space keys work similarly but treat all
non-whitespace characters as part of a word.
Setting
.Em word\-separators
to the empty string makes next/previous word equivalent to next/previous space.
@@ -3993,7 +3993,7 @@ applies the last set layout if possible (undoes the most recent layout change).
spreads the current pane and any panes next to it out evenly.
.Tg selectp
.It Xo Ic select\-pane
.Op Fl DdeLlMmRUZ
.Op Fl ADdeLlMmRsSUZ
.Op Fl T Ar title
.Op Fl t Ar target\-pane
.Xc
@@ -4001,6 +4001,8 @@ spreads the current pane and any panes next to it out evenly.
Make pane
.Ar target\-pane
the active pane in its window.
.Fl A
raises the pane if it is floating.
If one of
.Fl D ,
.Fl L ,
@@ -4015,6 +4017,15 @@ keeps the window zoomed if it was zoomed.
is the same as using the
.Ic last\-pane
command.
.Fl s
switches the client to local pane selection for the target window when more
than one client is attached; the pane currently active for the client is
retained.
Local pane selection is disabled automatically when fewer than two clients
remain attached.
.Fl S
switches the client back to shared pane selection for the target window, so the
window's active pane is used.
.Fl e
enables or
.Fl d
@@ -4038,12 +4049,22 @@ and
.Ic swap\-window .
.Tg selectw
.It Xo Ic select\-window
.Op Fl lnpT
.Op Fl LlnpST
.Op Fl t Ar target\-window
.Xc
.D1 Pq alias: Ic selectw
Select the window at
.Ar target\-window .
.Fl L
makes the attached client use a local current window for the target session.
Window navigation from that client then changes only that client's effective
window and leaves the session current window unchanged.
.Fl S
returns the client to the shared session current window.
Without
.Fl L ,
the current window is shared by the session and stored in the session.
.Pp
.Fl l ,
.Fl n
and
@@ -5113,6 +5134,8 @@ Supports
title setting.
.It usstyle
Allows underscore style and colour to be set.
.It utf8
Supports UTF\-8 output.
.El
.It Ic terminal\-overrides[] Ar string
Allow terminal descriptions read using
@@ -5647,6 +5670,11 @@ If set to both, a bell and a message are produced.
Sets the session's conception of what characters are considered word
separators, for the purposes of the next and previous word commands in
copy mode.
A space in
.Ar string
matches any character with the Unicode
.Em White_Space
property.
.El
.Pp
Available window options are:
@@ -7259,7 +7287,10 @@ shows the four frames in turn, changing every 200 milliseconds.
The following variables are available, where appropriate:
.Bl -column "XXXXXXXXXXXXXXXXXXX" "XXXXX"
.It Sy "Variable name" Ta Sy "Alias" Ta Sy "Replaced with"
.It Li "active_window_index" Ta "" Ta "Index of active window in session"
.It Li "active_window_id" Ta "" Ta "ID of effective active window"
.It Li "active_window_index" Ta "" Ta "Index of effective active window"
.It Li "local_active_window" Ta "" Ta "1 if client has local window selection"
.It Li "active_window_mode" Ta "" Ta "Window selection mode, shared or local"
.It Li "alternate_on" Ta "" Ta "1 if pane is in alternate screen"
.It Li "alternate_saved_x" Ta "" Ta "Saved cursor X in alternate screen"
.It Li "alternate_saved_y" Ta "" Ta "Saved cursor Y in alternate screen"
@@ -7316,7 +7347,10 @@ The following variables are available, where appropriate:
.It Li "cursor_very_visible" Ta "" Ta "1 if the cursor is in very visible mode"
.It Li "cursor_x" Ta "" Ta "Cursor X position in pane"
.It Li "cursor_y" Ta "" Ta "Cursor Y position in pane"
.It Li "history_added" Ta "" Ta "Number of lines scrolled into pane history"
.It Li "history_bytes" Ta "" Ta "Number of bytes in window history"
.It Li "history_collected" Ta "" Ta "Number of oldest pane history lines discarded at the history limit"
.It Li "history_generation" Ta "" Ta "Generation incremented when pane history is cleared or reflowed"
.It Li "history_limit" Ta "" Ta "Maximum window history lines"
.It Li "history_size" Ta "" Ta "Size of history in lines"
.It Li "hook" Ta "" Ta "Name of running hook, if any"
@@ -7410,11 +7444,14 @@ The following variables are available, where appropriate:
.It Li "pane_last" Ta "" Ta "1 if last pane"
.It Li "pane_last_output_time" Ta "" Ta "Time pane last produced output"
.It Li "pane_last_prompt_time" Ta "" Ta "Time most recent OSC 133 prompt began"
.It Li "pane_local_active" Ta "" Ta "1 if active pane is client-local"
.It Li "pane_local_mode" Ta "" Ta "1 if client has local pane selection"
.It Li "pane_left" Ta "" Ta "Left of pane"
.It Li "pane_marked" Ta "" Ta "1 if this is the marked pane"
.It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set"
.It Li "pane_modal_flag" Ta "" Ta "1 if pane is modal"
.It Li "pane_mode" Ta "" Ta "Name of pane mode, if any"
.It Li "pane_output_generation" Ta "" Ta "Generation incremented when pane output is processed"
.It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)"
.It Li "pane_pid" Ta "" Ta "PID of first process in pane"
.It Li "pane_pipe" Ta "" Ta "1 if pane is being piped"
@@ -7492,7 +7529,7 @@ The following variables are available, where appropriate:
.It Li "uid" Ta "" Ta "Server UID"
.It Li "user" Ta "" Ta "Server user"
.It Li "version" Ta "" Ta "Server version"
.It Li "window_active" Ta "" Ta "1 if window active"
.It Li "window_active" Ta "" Ta "1 if window is the effective active window"
.It Li "window_active_clients" Ta "" Ta "Number of clients viewing this window"
.It Li "window_active_clients_list" Ta "" Ta "List of clients viewing this window"
.It Li "window_active_sessions" Ta "" Ta "Number of sessions on which this window is active"

47
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1431 2026/08/25 08:37:08 nicm Exp $ */
/* $OpenBSD: tmux.h,v 1.1433 2026/09/01 12:49:49 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1357,9 +1357,10 @@ struct window_pane {
char tty[TTY_NAME_MAX];
int status;
struct timeval dead_time;
struct cmdq_item *wait_item; /* new-pane -W: waiting for pane exit */
struct cmdq_item *wait_item;
struct spawn_editor_state *editor;
uint64_t output_generation;
time_t last_output_time;
time_t last_prompt_time;
time_t cmd_start_time;
@@ -2776,6 +2777,42 @@ void events_fire_window(const char *, struct window *);
void events_fire_pane(const char *, struct window_pane *);
void events_fire_winlink(const char *, struct winlink *);
/* active.c */
enum active_mode {
ACTIVE_SHARED,
ACTIVE_LOCAL
};
int active_is_local_window(struct client *, struct session *);
void active_set_local_window(struct client *, struct session *,
enum active_mode);
struct winlink *active_get_effective_winlink(struct client *,
struct session *);
struct window *active_get_effective_window(struct client *,
struct session *);
int active_select_window(struct client *, struct session *,
struct winlink *);
int active_select_window_index(struct client *, struct session *, int);
int active_next_window(struct client *, struct session *, int);
int active_previous_window(struct client *, struct session *, int);
int active_last_window(struct client *, struct session *);
void active_remove_client(struct client *);
void active_remove_session(struct session *);
void active_remove_window(struct window *);
int active_is_effective_window(struct client *, struct session *,
struct winlink *);
int active_is_last_window(struct client *, struct session *,
struct winlink *);
int active_has_local_pane(struct client *, struct window *);
void active_set_pane_mode(struct client *, struct window *,
enum active_mode);
struct window_pane *active_get_effective_pane(struct client *,
struct window *);
struct window_pane *active_get_last_pane(struct client *, struct window *);
int active_set_pane(struct client *, struct window *,
struct window_pane *, int);
void active_check_clients(void);
void active_remove_pane(struct window_pane *);
/* format-draw.c */
void format_draw(struct screen_write_ctx *, const struct grid_cell *,
u_int, const char *, struct style_ranges *, int);
@@ -2998,7 +3035,8 @@ void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *);
void tty_draw_images(struct client *, struct window_pane *);
#endif
void tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *);
void tty_default_colours(struct grid_cell *, struct window_pane *,
struct window_pane *, u_int *);
/* tty-term.c */
extern struct tty_terms tty_terms;
@@ -3780,7 +3818,7 @@ int window_pane_is_visible(struct window_pane *);
int window_pane_exited(struct window_pane *);
u_int window_pane_search(struct window_pane *, const char *, int,
int);
const char *window_printable_flags(struct winlink *, int);
const char *window_printable_flags(struct client *, struct winlink *, int);
const char *window_pane_printable_flags(struct window_pane *);
struct window_pane *window_pane_find_up(struct window_pane *);
struct window_pane *window_pane_find_down(struct window_pane *);
@@ -4106,6 +4144,7 @@ void session_update_history(struct session *);
/* utf8.c */
enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *);
enum utf8_state utf8_fromwc(wchar_t wc, struct utf8_data *);
int utf8_has_whitespace(const struct utf8_data *);
void utf8_update_width_cache(void);
utf8_char utf8_build_one(u_char);
enum utf8_state utf8_from_data(const struct utf8_data *, utf8_char *);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tty-features.c,v 1.42 2026/08/17 14:47:41 nicm Exp $ */
/* $OpenBSD: tty-features.c,v 1.43 2026/08/31 12:41:03 kirill Exp $ */
/*
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -368,6 +368,13 @@ static const struct tty_feature tty_feature_progressbar = {
0
};
/* Terminal supports UTF-8. */
static const struct tty_feature tty_feature_utf8 = {
"utf8",
NULL,
0
};
/* Available terminal features. */
static const struct tty_feature *const tty_features[] = {
&tty_feature_256,
@@ -390,7 +397,8 @@ static const struct tty_feature *const tty_features[] = {
&tty_feature_strikethrough,
&tty_feature_sync,
&tty_feature_title,
&tty_feature_usstyle
&tty_feature_usstyle,
&tty_feature_utf8
};
/* Parse features for client. */
@@ -473,6 +481,9 @@ tty_feature_present(struct tty_term *term, const char *name)
u_int i;
char *copy;
if (strcmp(name, "utf8") == 0)
return ((term->tty->client->flags & CLIENT_UTF8) != 0);
for (i = 0; i < nitems(tty_features); i++) {
tf = tty_features[i];
if (strcmp(tf->name, name) == 0) {
@@ -486,7 +497,8 @@ tty_feature_present(struct tty_term *term, const char *name)
* We don't just have the feature flag set. Check if the capabilities
* supported by the client are actual set instead.
*/
if (tf == NULL || strcmp(name, "ignorefkeys") == 0)
if (tf == NULL || tf->capabilities == NULL ||
strcmp(name, "ignorefkeys") == 0)
return (0);
if (tf->flags != 0 && (term->flags & tf->flags) != tf->flags)
return (0);
@@ -534,6 +546,8 @@ tty_apply_features(struct tty_term *term)
}
}
term->flags |= tf->flags;
if (tf == &tty_feature_utf8)
c->flags |= CLIENT_UTF8;
}
if ((term->applied_features|feat) == term->applied_features)
return (0);

View File

@@ -1020,12 +1020,12 @@ complete_key:
/* Check for focus events. */
if (key == KEYC_FOCUS_OUT) {
c->flags &= ~CLIENT_FOCUSED;
window_update_focus(c->session->curw->window);
window_update_focus(active_get_effective_window(c, c->session));
events_fire_client("client-focus-out", c);
} else if (key == KEYC_FOCUS_IN) {
c->flags |= CLIENT_FOCUSED;
events_fire_client("client-focus-in", c);
window_update_focus(c->session->curw->window);
window_update_focus(active_get_effective_window(c, c->session));
}
/* Fire the key. */

21
tty.c
View File

@@ -955,7 +955,7 @@ int
tty_window_bigger(struct tty *tty)
{
struct client *c = tty->client;
struct window *w = c->session->curw->window;
struct window *w = active_get_effective_window(c, c->session);
return (tty->sx < w->sx || tty->sy - status_line_size(c) < w->sy);
}
@@ -977,10 +977,11 @@ static int
tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy)
{
struct client *c = tty->client;
struct window *w = c->session->curw->window;
struct window_pane *wp = w->active;
struct window *w = active_get_effective_window(c, c->session);
struct window_pane *wp;
u_int cx, cy, lines;
wp = active_get_effective_pane(c, w);
lines = status_line_size(c);
if (tty->sx >= w->sx && tty->sy - lines >= w->sy) {
@@ -1044,8 +1045,7 @@ tty_update_window_offset(struct window *w)
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->curw != NULL &&
c->session->curw->window == w)
active_get_effective_window(c, c->session) == w)
tty_update_client_offset(c);
}
}
@@ -1547,7 +1547,7 @@ tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
{
struct window_pane *wp = ttyctx->arg;
if (c->session->curw->window != wp->window)
if (active_get_effective_window(c, c->session) != wp->window)
return (0);
if (wp->layout_cell == NULL)
return (0);
@@ -3159,23 +3159,24 @@ tty_style_changed(struct window_pane *wp)
}
void
tty_default_colours(struct grid_cell *gc, struct window_pane *wp, u_int *dim)
tty_default_colours(struct grid_cell *gc, struct window_pane *wp,
struct window_pane *active, u_int *dim)
{
if (wp->flags & PANE_STYLECHANGED)
tty_style_changed (wp);
memcpy(gc, &grid_default_cell, sizeof *gc);
if (wp == wp->window->active && wp->cached_active_gc.fg != 8)
if (wp == active && wp->cached_active_gc.fg != 8)
gc->fg = wp->cached_active_gc.fg;
else
gc->fg = wp->cached_gc.fg;
if (wp == wp->window->active && wp->cached_active_gc.bg != 8)
if (wp == active && wp->cached_active_gc.bg != 8)
gc->bg = wp->cached_active_gc.bg;
else
gc->bg = wp->cached_gc.bg;
if (dim != NULL) {
if (wp == wp->window->active)
if (wp == active)
*dim = wp->cached_active_dim;
else
*dim = wp->cached_dim;

68
utf8.c
View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-copy.c,v 1.426 2026/08/19 19:55:04 nicm Exp $ */
/* $OpenBSD: window-copy.c,v 1.429 2026/09/01 13:04:29 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -2207,15 +2207,76 @@ window_copy_cmd_selection_mode(struct window_copy_cmd_state *cs)
struct window_mode_entry *wme = cs->wme;
struct options *so = cs->s->options;
struct window_copy_mode_data *data = wme->data;
struct grid_reader gr;
const char *s = args_string(cs->wargs, 0);
u_int sx, sy, ex, ey, fx, fy, x, y;
if (s == NULL || strcasecmp(s, "char") == 0 || strcasecmp(s, "c") == 0)
data->selflag = SEL_CHAR;
else if (strcasecmp(s, "word") == 0 || strcasecmp(s, "w") == 0) {
data->separators = options_get_string(so, "word-separators");
data->selflag = SEL_WORD;
} else if (strcasecmp(s, "line") == 0 || strcasecmp(s, "l") == 0)
} else if (strcasecmp(s, "line") == 0 || strcasecmp(s, "l") == 0) {
data->selflag = SEL_LINE;
if (data->screen.sel == NULL)
return (WINDOW_COPY_CMD_MOVE);
/*
* Line selection normally starts with select-line, which sets
* up the reset positions used when the cursor changes
* direction. Do the same when changing an existing selection
* to line mode.
*/
if (data->cursordrag == CURSORDRAG_SEL) {
fx = data->endselx;
fy = data->endsely;
} else {
fx = data->selx;
fy = data->sely;
}
sx = data->selx;
sy = data->sely;
ex = data->endselx;
ey = data->endsely;
if (ey < sy || (ey == sy && ex < sx)) {
x = sx; sx = ex; ex = x;
y = sy; sy = ey; ey = y;
}
grid_reader_start(&gr, data->backing->grid, sx, sy);
grid_reader_cursor_start_of_line(&gr, 1);
grid_reader_get_cursor(&gr, &sx, &sy);
grid_reader_start(&gr, data->backing->grid, ex, ey);
grid_reader_cursor_end_of_line(&gr, 1, 0);
grid_reader_get_cursor(&gr, &ex, &ey);
data->rectflag = 0;
data->selrx = data->selx = sx;
data->selry = data->sely = sy;
data->endselrx = data->endselx = ex;
data->endselry = data->endsely = ey;
x = data->cx;
y = screen_hsize(data->backing) + data->cy - data->oy;
data->dx = fx;
data->dy = fy;
if (data->cursordrag != CURSORDRAG_NONE &&
(y < fy || (y == fy && x < fx))) {
data->lineflag = LINE_SEL_RIGHT_LEFT;
data->cursordrag = CURSORDRAG_SEL;
window_copy_scroll_to(wme, sx, sy, 1);
} else {
data->lineflag = LINE_SEL_LEFT_RIGHT;
if (data->cursordrag != CURSORDRAG_NONE) {
data->cursordrag = CURSORDRAG_ENDSEL;
x = window_copy_cursor_limit(wme, ey, 0);
window_copy_scroll_to(wme, x, ey, 1);
}
}
if (data->cursordrag == CURSORDRAG_NONE)
window_copy_set_selection(wme, 0, 0);
return (WINDOW_COPY_CMD_REDRAW);
}
return (WINDOW_COPY_CMD_MOVE);
}
@@ -4301,7 +4362,7 @@ window_copy_stringify(struct grid *gd, u_int py, u_int first, u_int last,
}
if (dlen == 1)
buf[bx++] = *d;
else {
else if (dlen != 0) {
memcpy(buf + bx, d, dlen);
bx += dlen;
}
@@ -4851,6 +4912,8 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp,
cflags |= REG_ICASE;
if (regcomp(&reg, sbuf, cflags) != 0) {
free(sbuf);
free(data->searchmark);
data->searchmark = NULL;
return (0);
}
free(sbuf);

View File

@@ -451,6 +451,7 @@ window_destroy(struct window *w)
{
log_debug("window @%u destroyed (%d references)", w->id, w->references);
active_remove_window(w);
window_unzoom(w, 0);
RB_REMOVE(windows, &windows, w);
@@ -672,6 +673,7 @@ void
window_pane_update_focus(struct window_pane *wp)
{
struct client *c;
struct window *w;
int focused = 0;
if (wp != NULL && (~wp->flags & PANE_EXITED)) {
@@ -679,10 +681,12 @@ window_pane_update_focus(struct window_pane *wp)
focused = 0;
else {
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->attached != 0 &&
if (c->session == NULL)
continue;
w = active_get_effective_window(c, c->session);
if (c->session->attached != 0 &&
(c->flags & CLIENT_FOCUSED) &&
c->session->curw->window == wp->window &&
w == wp->window &&
c->overlay_draw == NULL &&
wp->window->menu == NULL) {
focused = 1;
@@ -788,7 +792,8 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp)
break;
/* If the pane is floating, move to the front. */
if (window_pane_is_floating(wp)) {
if (window_pane_is_floating(wp) &&
TAILQ_FIRST(&w->z_index) != wp) {
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
wp->flags |= PANE_REDRAW;
@@ -1287,7 +1292,7 @@ window_destroy_panes(struct window *w)
}
const char *
window_printable_flags(struct winlink *wl, int escape)
window_printable_flags(struct client *c, struct winlink *wl, int escape)
{
struct session *s = wl->session;
static char flags[32];
@@ -1302,9 +1307,9 @@ window_printable_flags(struct winlink *wl, int escape)
flags[pos++] = '!';
if (wl->flags & WINLINK_SILENCE)
flags[pos++] = '~';
if (wl == s->curw)
if (active_is_effective_window(c, s, wl))
flags[pos++] = '*';
if (wl == TAILQ_FIRST(&s->lastw))
if (active_is_last_window(c, s, wl))
flags[pos++] = '-';
if (server_check_marked() && wl == marked_pane.wl)
flags[pos++] = 'M';
@@ -2634,7 +2639,7 @@ window_pane_get_bg(struct window_pane *wp)
c = window_pane_get_bg_control_client(wp);
if (c == -1) {
tty_default_colours(&defaults, wp, NULL);
tty_default_colours(&defaults, wp, wp->window->active, NULL);
if (COLOUR_DEFAULT(defaults.bg))
c = window_get_bg_client(wp);
else