diff --git a/Makefile.am b/Makefile.am index cbdb4e863..9662df29c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -85,6 +85,7 @@ endif # List of sources. dist_tmux_SOURCES = \ + active.c \ alerts.c \ arguments.c \ attributes.c \ diff --git a/active.c b/active.c new file mode 100644 index 000000000..e1bea5d92 --- /dev/null +++ b/active.c @@ -0,0 +1,449 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* Local last-window stack entry. */ +struct active_window_entry { + u_int window; + TAILQ_ENTRY(active_window_entry) entry; +}; + +/* Local last-window stack. */ +TAILQ_HEAD(active_window_stack, active_window_entry); + +/* Per-client and per-session local active window state. */ +struct active_window { + struct client *client; + struct session *session; + + u_int window; + struct active_window_stack lastw; + + RB_ENTRY(active_window) entry; +}; +RB_HEAD(active_windows, active_window); + +static int +active_window_cmp(struct active_window *aw1, struct active_window *aw2) +{ + uintptr_t c1 = (uintptr_t)aw1->client; + uintptr_t c2 = (uintptr_t)aw2->client; + uintptr_t s1 = (uintptr_t)aw1->session; + uintptr_t s2 = (uintptr_t)aw2->session; + + if (c1 < c2) + return (-1); + if (c1 > c2) + return (1); + if (s1 < s2) + return (-1); + if (s1 > s2) + return (1); + return (0); +} +RB_GENERATE_STATIC(active_windows, active_window, entry, active_window_cmp); + +static struct active_window *active_get(struct client *, struct session *); +static struct active_window *active_add(struct client *, struct session *, + struct winlink *); +static void active_free(struct active_window *); +static struct winlink *active_resolve(struct active_window *); +static void active_stack_remove(struct active_window_stack *, u_int); +static void active_stack_push(struct active_window_stack *, u_int); +static void active_changed(struct client *, struct session *, + struct winlink *, struct winlink *); + +/* All local active window states. */ +static struct active_windows active_windows = RB_INITIALIZER(&active_windows); + +/* Find local active window state for a client and session. */ +static struct active_window * +active_get(struct client *c, struct session *s) +{ + struct active_window aw; + + if (c == NULL || s == NULL) + return (NULL); + aw.client = c; + aw.session = s; + return (RB_FIND(active_windows, &active_windows, &aw)); +} + +/* Add local active window state for a client and session. */ +static struct active_window * +active_add(struct client *c, struct session *s, struct winlink *wl) +{ + struct active_window *aw; + + aw = active_get(c, s); + if (aw == NULL) { + aw = xcalloc(1, sizeof *aw); + aw->client = c; + aw->session = s; + TAILQ_INIT(&aw->lastw); + RB_INSERT(active_windows, &active_windows, aw); + } + if (wl != NULL) + aw->window = wl->window->id; + return (aw); +} + +/* Free local active window state. */ +static void +active_free(struct active_window *aw) +{ + struct active_window_entry *awe; + + while (!TAILQ_EMPTY(&aw->lastw)) { + awe = TAILQ_FIRST(&aw->lastw); + TAILQ_REMOVE(&aw->lastw, awe, entry); + free(awe); + } + RB_REMOVE(active_windows, &active_windows, aw); + free(aw); +} + +/* Resolve local active window state to a valid winlink. */ +static struct winlink * +active_resolve(struct active_window *aw) +{ + struct winlink *wl, *next; + u_int window; + + if (aw == NULL || aw->session->curw == NULL) + return (NULL); + + wl = winlink_find_by_window_id(&aw->session->windows, aw->window); + if (wl != NULL) + return (wl); + + while (!TAILQ_EMPTY(&aw->lastw)) { + window = TAILQ_FIRST(&aw->lastw)->window; + + next = winlink_find_by_window_id(&aw->session->windows, window); + active_stack_remove(&aw->lastw, window); + if (next != NULL) { + aw->window = next->window->id; + return (next); + } + } + + aw->window = aw->session->curw->window->id; + return (aw->session->curw); +} + +/* Remove a window from a local last-window stack. */ +static void +active_stack_remove(struct active_window_stack *stack, u_int window) +{ + struct active_window_entry *awe, *awe1; + + TAILQ_FOREACH_SAFE(awe, stack, entry, awe1) { + if (awe->window == window) { + TAILQ_REMOVE(stack, awe, entry); + free(awe); + } + } +} + +/* Push a window onto a local last-window stack. */ +static void +active_stack_push(struct active_window_stack *stack, u_int window) +{ + struct active_window_entry *awe; + + active_stack_remove(stack, window); + awe = xcalloc(1, sizeof *awe); + awe->window = window; + TAILQ_INSERT_HEAD(stack, awe, entry); +} + +/* Notify the server that a client's effective window changed. */ +static void +active_changed(struct client *c, struct session *s, struct winlink *wl, + struct winlink *old) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + if (wl == NULL || wl == old) + return; + + if (options_get_number(global_options, "focus-events")) { + if (old != NULL) + window_update_focus(old->window); + window_update_focus(wl->window); + } + winlink_clear_flags(wl); + window_update_activity(wl->window); + tty_update_window_offset(wl->window); + + if (c != NULL && c->session == s) + wl->window->latest = c; + + ep = event_payload_create(); + cmd_find_from_winlink(&fs, wl, 0); + event_payload_set_target(ep, &fs); + event_payload_set_client(ep, "client", c); + event_payload_set_session(ep, "session", s); + event_payload_set_window(ep, "window", wl->window); + event_payload_set_window(ep, "new_window", wl->window); + event_payload_set_int(ep, "window_index", wl->idx); + event_payload_set_int(ep, "new_window_index", wl->idx); + if (old != NULL) { + event_payload_set_window(ep, "old_window", old->window); + event_payload_set_int(ep, "old_window_index", old->idx); + } + events_fire("client-window-changed", ep); + + if (c != NULL) { + c->flags |= CLIENT_REDRAWWINDOW|CLIENT_REDRAWSTATUS| + CLIENT_REDRAWBORDERS; + tty_update_client_offset(c); + status_update_cache(s); + } + recalculate_sizes(); +} + +/* Return if a client has local window selection for a session. */ +int +active_is_local_window(struct client *c, struct session *s) +{ + return (active_get(c, s) != NULL); +} + +/* Make a client use local or shared window selection for a session. */ +void +active_set_local_window(struct client *c, struct session *s, + enum active_window_mode mode) +{ + struct active_window *aw; + struct winlink *wl, *old; + + if (c == NULL || s == NULL || s->curw == NULL) + return; + + old = active_get_effective_winlink(c, s); + if (mode == ACTIVE_LOCAL) { + active_add(c, s, old); + if (c != NULL && c->session == s) + c->flags |= CLIENT_REDRAWSTATUS; + return; + } + + aw = active_get(c, s); + if (aw != NULL) { + active_free(aw); + wl = active_get_effective_winlink(c, s); + active_changed(c, s, wl, old); + if (c != NULL && c->session == s) + c->flags |= CLIENT_REDRAWSTATUS; + } +} + +/* Return the effective winlink for a client and session. */ +struct winlink * +active_get_effective_winlink(struct client *c, struct session *s) +{ + struct active_window *aw; + + if (s == NULL) + return (NULL); + aw = active_get(c, s); + if (aw != NULL) + return (active_resolve(aw)); + return (s->curw); +} + +/* Return the effective window for a client and session. */ +struct window * +active_get_effective_window(struct client *c, struct session *s) +{ + struct winlink *wl; + + wl = active_get_effective_winlink(c, s); + if (wl == NULL) + return (NULL); + return (wl->window); +} + +/* Select a winlink for a client and session. */ +int +active_select_window(struct client *c, struct session *s, struct winlink *wl) +{ + struct active_window *aw; + struct winlink *old; + int changed; + + if (wl == NULL) + return (-1); + aw = active_get(c, s); + if (aw == NULL) + return (session_set_current(s, wl)); + + old = active_resolve(aw); + if (old == wl) + return (1); + if (old != NULL) + active_stack_push(&aw->lastw, old->window->id); + active_stack_remove(&aw->lastw, wl->window->id); + aw->window = wl->window->id; + changed = (old != wl); + active_changed(c, s, wl, old); + return (changed ? 0 : 1); +} + +/* Select a window by index. */ +int +active_select_window_index(struct client *c, struct session *s, int idx) +{ + struct winlink *wl; + + wl = winlink_find_by_index(&s->windows, idx); + return (active_select_window(c, s, wl)); +} + +/* Select the next window. */ +int +active_next_window(struct client *c, struct session *s, int alert) +{ + struct winlink *wl; + + wl = active_get_effective_winlink(c, s); + if (wl == NULL) + return (-1); + do { + wl = winlink_next(wl); + if (wl == NULL) + wl = RB_MIN(winlinks, &s->windows); + if (!alert || (wl->flags & WINLINK_ALERTFLAGS)) + return (active_select_window(c, s, wl)); + } while (wl != active_get_effective_winlink(c, s)); + return (-1); +} + +/* Select the previous window. */ +int +active_previous_window(struct client *c, struct session *s, int alert) +{ + struct winlink *wl; + + wl = active_get_effective_winlink(c, s); + if (wl == NULL) + return (-1); + do { + wl = winlink_previous(wl); + if (wl == NULL) + wl = RB_MAX(winlinks, &s->windows); + if (!alert || (wl->flags & WINLINK_ALERTFLAGS)) + return (active_select_window(c, s, wl)); + } while (wl != active_get_effective_winlink(c, s)); + return (-1); +} + +/* Select the last window. */ +int +active_last_window(struct client *c, struct session *s) +{ + struct active_window *aw; + struct winlink *wl; + u_int window; + + aw = active_get(c, s); + if (aw == NULL) + return (session_last(s)); + while (!TAILQ_EMPTY(&aw->lastw)) { + window = TAILQ_FIRST(&aw->lastw)->window; + + wl = winlink_find_by_window_id(&s->windows, window); + if (wl != NULL) + return (active_select_window(c, s, wl)); + active_stack_remove(&aw->lastw, window); + } + return (-1); +} + +/* Remove all state for a client. */ +void +active_remove_client(struct client *c) +{ + struct active_window *aw, *aw1; + + RB_FOREACH_SAFE(aw, active_windows, &active_windows, aw1) { + if (aw->client == c) + active_free(aw); + } +} + +/* Remove all state for a session. */ +void +active_remove_session(struct session *s) +{ + struct active_window *aw, *aw1; + + RB_FOREACH_SAFE(aw, active_windows, &active_windows, aw1) { + if (aw->session == s) + active_free(aw); + } +} + +/* Remove a window from all local state. */ +void +active_remove_window(struct window *w) +{ + struct active_window *aw; + + RB_FOREACH(aw, active_windows, &active_windows) { + active_stack_remove(&aw->lastw, w->id); + if (aw->window == w->id && aw->session->curw != NULL) + aw->window = aw->session->curw->window->id; + } +} + +/* Return if this winlink is the effective winlink. */ +int +active_is_effective_window(struct client *c, struct session *s, + struct winlink *wl) +{ + return (wl != NULL && wl == active_get_effective_winlink(c, s)); +} + +/* Return if this winlink is the last effective winlink. */ +int +active_is_last_window(struct client *c, struct session *s, struct winlink *wl) +{ + struct active_window *aw; + struct active_window_entry *awe; + + if (wl == NULL) + return (0); + aw = active_get(c, s); + if (aw == NULL) + return (wl == TAILQ_FIRST(&s->lastw)); + TAILQ_FOREACH(awe, &aw->lastw, entry) { + if (winlink_find_by_window_id(&s->windows, awe->window) == NULL) + continue; + return (wl->window->id == awe->window); + } + return (0); +} diff --git a/alerts.c b/alerts.c index ef4ae4061..855ecf7f8 100644 --- a/alerts.c +++ b/alerts.c @@ -314,7 +314,7 @@ alerts_set_message(struct winlink *wl, const char *type, const char *option) tty_putcode(&c->tty, TTYC_BEL); if (visual == VISUAL_OFF) continue; - if (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 { diff --git a/cmd-attach-session.c b/cmd-attach-session.c index 050c7ca38..55b66ec99 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -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 diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 4fe6397c9..e96bbb2d0 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -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; } @@ -183,9 +201,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); diff --git a/cmd-display-menu.c b/cmd-display-menu.c index 2aaf3238c..766710a78 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -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,7 +584,7 @@ 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')) { diff --git a/cmd-find.c b/cmd-find.c index f9621b53e..090e8b4cb 100644 --- a/cmd-find.c +++ b/cmd-find.c @@ -884,13 +884,13 @@ cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags) if (c->session != NULL) { cmd_find_clear_state(fs, flags); - fs->wp = c->session->curw->window->active; + fs->wl = active_get_effective_winlink(c, c->session); + fs->wp = fs->wl->window->active; if (fs->wp == NULL) { cmd_find_from_session(fs, c->session, flags); return (0); } fs->s = c->session; - fs->wl = fs->s->curw; fs->w = fs->wl->window; cmd_find_log_state(__func__, fs); @@ -931,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; diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 1a1a0d8b8..283bdda72 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -410,13 +410,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; @@ -424,6 +427,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')) @@ -511,9 +518,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); diff --git a/cmd-move-window.c b/cmd-move-window.c index 1cd4f10a8..302060704 100644 --- a/cmd-move-window.c +++ b/cmd-move-window.c @@ -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 diff --git a/cmd-new-window.c b/cmd-new-window.c index d1e2ab4e5..d11e42850 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -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 diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index 11b43bf22..a842df2ef 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -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; diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 59d5aeea6..72a4cbad6 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -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; + struct session *s; struct window_pane *wp = NULL; + struct window *w; struct cmd_find_state fs; struct window_mode_entry *wme; @@ -90,8 +93,12 @@ 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; + c = cdata->client; + if (c != NULL && c->session != NULL) { + s = c->session; + w = active_get_effective_window(c, s); + wp = w->active; + } if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0) wp = fs.wp; if (wp == NULL) diff --git a/cmd-select-pane.c b/cmd-select-pane.c index dbf147cb5..393a5c62c 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -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; diff --git a/cmd-select-window.c b/cmd-select-window.c index 32b4821bf..ed7b86556 100644 --- a/cmd-select-window.c +++ b/cmd-select-window.c @@ -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_window_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); diff --git a/cmd-swap-window.c b/cmd-swap-window.c index 980da0395..8394e58a5 100644 --- a/cmd-swap-window.c +++ b/cmd-swap-window.c @@ -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); diff --git a/cmd-switch-client.c b/cmd-switch-client.c index 177107b47..89e7c9189 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -53,11 +53,11 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) struct cmd_find_state target; const char *tflag = args_get(args, 't'); enum cmd_find_type type; - int flags; + int flags, window_target; 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 +65,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 +142,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 (window_push_zoom(w, 0, args_has(args, 'Z'))) server_redraw_window(w); @@ -146,9 +152,13 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) if (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); } } diff --git a/control-notify.c b/control-notify.c index 6fec1683f..58a1b0bc7 100644 --- a/control-notify.c +++ b/control-notify.c @@ -215,6 +215,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, @@ -356,6 +378,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 }, diff --git a/format.c b/format.c index 3b5d3c421..c3099fc51 100644 --- a/format.c +++ b/format.c @@ -800,7 +800,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++; } @@ -832,7 +832,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); @@ -2886,11 +2886,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) @@ -2908,8 +2945,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")); } @@ -2989,7 +3029,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); } @@ -3176,7 +3216,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); } @@ -3386,9 +3426,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 }, @@ -3557,6 +3603,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 }, @@ -5092,15 +5141,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); @@ -5128,10 +5180,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; @@ -5140,9 +5192,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(); @@ -5150,12 +5202,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); @@ -5164,11 +5217,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"); @@ -5188,7 +5241,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) @@ -6691,7 +6744,7 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s, if (s == NULL && c != NULL) s = c->session; if (wl == NULL && s != NULL) - wl = s->curw; + wl = active_get_effective_winlink(c, s); if (wp == NULL && wl != NULL) wp = wl->window->active; diff --git a/key-bindings.c b/key-bindings.c index ff6111870..b81cc5cab 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -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 \ diff --git a/menu.c b/menu.c index aab25d7c5..d5c8f5488 100644 --- a/menu.c +++ b/menu.c @@ -584,7 +584,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; diff --git a/options-table.c b/options-table.c index d0a175b9f..70efc9bbc 100644 --- a/options-table.c +++ b/options-table.c @@ -1115,7 +1115,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." @@ -1960,6 +1961,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", "", diff --git a/popup.c b/popup.c index 3bc7eafa6..43bdbb61f 100644 --- a/popup.c +++ b/popup.c @@ -102,9 +102,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 +569,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"); diff --git a/regress/active-window.sh b/regress/active-window.sh new file mode 100644 index 000000000..8b724aec2 --- /dev/null +++ b/regress/active-window.sh @@ -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 diff --git a/resize.c b/resize.c index 4f7ee490a..014318b3d 100644 --- a/resize.c +++ b/resize.c @@ -348,10 +348,10 @@ recalculate_size_skip_client(struct client *loop, __unused int type, * is not the current window - this is used for aggressive-resize. * Otherwise skip any session that doesn't contain the window. */ - if (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); } diff --git a/screen-redraw.c b/screen-redraw.c index 45ed68509..cdf3990b1 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -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; @@ -966,7 +966,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; @@ -1076,7 +1076,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; @@ -1137,7 +1137,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); @@ -1592,14 +1592,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_window(c, s)->active; lines = status_line_size(c); if (options_get_number(oo, "status-position") == 0) @@ -1672,7 +1674,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; @@ -1872,7 +1874,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); @@ -1884,7 +1886,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); } diff --git a/screen-write.c b/screen-write.c index a311296e7..84ca6fe6f 100644 --- a/screen-write.c +++ b/screen-write.c @@ -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); diff --git a/server-client.c b/server-client.c index 15e561877..116162a7d 100644 --- a/server-client.c +++ b/server-client.c @@ -105,7 +105,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); } @@ -132,7 +132,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); } @@ -376,7 +376,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, >)) @@ -457,15 +458,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); @@ -490,6 +491,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; @@ -675,7 +677,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) @@ -848,7 +850,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; @@ -1346,7 +1348,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; @@ -1410,7 +1412,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, @@ -1664,7 +1666,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; @@ -1701,6 +1703,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. */ @@ -1756,9 +1759,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; @@ -2100,7 +2104,7 @@ static void server_client_reset_state(struct client *c) { struct tty *tty = &c->tty; - struct window *w = c->session->curw->window; + struct window *w = active_get_effective_window(c, c->session); struct window_pane *wp = w->active, *loop; struct screen *s = NULL; struct options *oo = c->session->options; @@ -2341,7 +2345,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; @@ -2349,6 +2353,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) @@ -2361,7 +2366,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) @@ -2379,7 +2384,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 }; @@ -2510,14 +2515,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); @@ -2530,11 +2536,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; @@ -3120,7 +3127,7 @@ server_client_print(struct client *c, int parse, struct evbuffer *evb) goto out; } - wp = c->session->curw->window->active; + wp = active_get_effective_window(c, c->session)->active; wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL, diff --git a/server-fn.c b/server-fn.c index 862f4d836..9a135ba2b 100644 --- a/server-fn.c +++ b/server-fn.c @@ -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; } } diff --git a/session.c b/session.c index d52b70b88..99af60215 100644 --- a/session.c +++ b/session.c @@ -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) diff --git a/tmux.1 b/tmux.1 index c5d6cf86d..e2eb856a4 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4026,12 +4026,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 @@ -7177,7 +7187,10 @@ will be replaced by 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" @@ -7392,7 +7405,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" diff --git a/tmux.h b/tmux.h index 9fef3b10c..132699dd6 100644 --- a/tmux.h +++ b/tmux.h @@ -2741,6 +2741,32 @@ void events_fire_window(const char *, struct window *); void events_fire_pane(const char *, struct window_pane *); void events_fire_winlink(const char *, struct winlink *); +/* active.c */ +enum active_window_mode { + ACTIVE_SHARED, + ACTIVE_LOCAL +}; +int active_is_local_window(struct client *, struct session *); +void active_set_local_window(struct client *, struct session *, + enum active_window_mode); +struct winlink *active_get_effective_winlink(struct client *, + struct session *); +struct window *active_get_effective_window(struct client *, + struct session *); +int active_select_window(struct client *, struct session *, + struct winlink *); +int active_select_window_index(struct client *, struct session *, int); +int active_next_window(struct client *, struct session *, int); +int active_previous_window(struct client *, struct session *, int); +int active_last_window(struct client *, struct session *); +void active_remove_client(struct client *); +void active_remove_session(struct session *); +void active_remove_window(struct window *); +int active_is_effective_window(struct client *, struct session *, + struct winlink *); +int active_is_last_window(struct client *, struct session *, + struct winlink *); + /* format-draw.c */ void format_draw(struct screen_write_ctx *, const struct grid_cell *, u_int, const char *, struct style_ranges *, int); @@ -3733,7 +3759,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 *); diff --git a/tty-keys.c b/tty-keys.c index 61e541165..60a663945 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1007,12 +1007,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. */ diff --git a/tty.c b/tty.c index c5b443cee..dc45e825c 100644 --- a/tty.c +++ b/tty.c @@ -937,7 +937,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); } @@ -959,7 +959,7 @@ static int tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy) { struct client *c = tty->client; - struct window *w = c->session->curw->window; + struct window *w = active_get_effective_window(c, c->session); struct window_pane *wp = w->active; u_int cx, cy, lines; @@ -1026,8 +1026,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); } } @@ -1529,7 +1528,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); diff --git a/window-border.c b/window-border.c index 5cd88c2bb..1be8c70a5 100644 --- a/window-border.c +++ b/window-border.c @@ -93,7 +93,7 @@ window_pane_get_border_style(struct window_pane *wp, struct client *c, struct grid_cell *saved; int *flag; - if (wp == c->session->curw->window->active) { + if (wp == active_get_effective_window(c, c->session)->active) { flag = &wp->active_border_gc_set; saved = &wp->active_border_gc; option = "pane-active-border-style"; @@ -104,7 +104,7 @@ window_pane_get_border_style(struct window_pane *wp, struct client *c, } if (!*flag) { - ft = format_create_defaults(NULL, c, s, s->curw, wp); + ft = format_create_defaults(NULL, c, s, NULL, wp); style_apply(saved, wp->options, option, ft); format_free(ft); *flag = 1; @@ -133,7 +133,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); diff --git a/window-client.c b/window-client.c index b94c22119..f59bae34c 100644 --- a/window-client.c +++ b/window-client.c @@ -266,12 +266,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); screen_write_cursormove(ctx, cx, cy, 0); diff --git a/window.c b/window.c index ff2ff443c..97cfd48d8 100644 --- a/window.c +++ b/window.c @@ -453,6 +453,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); @@ -675,6 +676,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)) { @@ -682,10 +684,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; @@ -1222,7 +1226,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]; @@ -1237,9 +1241,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';