Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'

* refs/remotes/tmux-openbsd/master:
  Queue control mode notifications rather than emitting them inside %begin/%end, GitHub issue 5458 from George Nachman.
This commit is contained in:
tmux update bot
2026-08-03 16:29:11 +00:00
5 changed files with 183 additions and 77 deletions

16
cfg.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cfg.c,v 1.90 2026/07/14 17:17:17 nicm Exp $ */ /* $OpenBSD: cfg.c,v 1.91 2026/08/03 13:38:42 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -223,13 +223,15 @@ cfg_print_causes(struct cmdq_item *item)
{ {
struct client *c = cmdq_get_client(item); struct client *c = cmdq_get_client(item);
u_int i; u_int i;
char *cause;
for (i = 0; i < cfg_ncauses; i++) { for (i = 0; i < cfg_ncauses; i++) {
cause = cfg_causes[i];
if (c != NULL && (c->flags & CLIENT_CONTROL)) if (c != NULL && (c->flags & CLIENT_CONTROL))
control_write(c, "%%config-error %s", cfg_causes[i]); control_notify_write(c, "%%config-error %s", cause);
else else
cmdq_print(item, "%s", cfg_causes[i]); cmdq_print(item, "%s", cause);
free(cfg_causes[i]); free(cause);
} }
free(cfg_causes); free(cfg_causes);
@@ -244,14 +246,16 @@ cfg_show_causes(struct session *s)
struct window_pane *wp; struct window_pane *wp;
struct window_mode_entry *wme; struct window_mode_entry *wme;
u_int i; u_int i;
char *cause;
if (cfg_ncauses == 0) if (cfg_ncauses == 0)
return; return;
if (c != NULL && (c->flags & CLIENT_CONTROL)) { if (c != NULL && (c->flags & CLIENT_CONTROL)) {
for (i = 0; i < cfg_ncauses; i++) { for (i = 0; i < cfg_ncauses; i++) {
control_write(c, "%%config-error %s", cfg_causes[i]); cause = cfg_causes[i];
free(cfg_causes[i]); control_notify_write(c, "%%config-error %s", cause);
free(cause);
} }
goto out; goto out;
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-queue.c,v 1.121 2026/07/14 17:17:17 nicm Exp $ */ /* $OpenBSD: cmd-queue.c,v 1.122 2026/08/03 13:38:42 nicm Exp $ */
/* /*
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -802,7 +802,7 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
u_int number = item->number; u_int number = item->number;
if (c != NULL && (c->flags & CLIENT_CONTROL)) if (c != NULL && (c->flags & CLIENT_CONTROL))
control_write(c, "%%%s %ld %u %d", guard, t, number, flags); control_write_guard(c, guard, t, number, flags);
} }
/* Show message from command. */ /* Show message from command. */

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: control-notify.c,v 1.37 2026/07/10 13:38:45 nicm Exp $ */ /* $OpenBSD: control-notify.c,v 1.38 2026/08/03 13:38:42 nicm Exp $ */
/* /*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -44,7 +44,8 @@ control_pane_mode_changed_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_write(c, "%%pane-mode-changed %%%u", wp->id); control_notify_write(c, "%%pane-mode-changed %%%u",
wp->id);
} }
return; return;
} }
@@ -53,8 +54,9 @@ control_pane_mode_changed_cb(__unused const char *name,
if (value == NULL) if (value == NULL)
return; return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
control_write(c, "%%pane-mode-changed %s", value); continue;
control_notify_write(c, "%%pane-mode-changed %s", value);
} }
free(value); free(value);
} }
@@ -92,7 +94,7 @@ control_window_layout_changed_cb(__unused const char *name,
continue; continue;
s = c->session; s = c->session;
if (winlink_find_by_window_id(&s->windows, w->id) != NULL) if (winlink_find_by_window_id(&s->windows, w->id) != NULL)
control_write(c, "%s", cp); control_notify_write(c, "%s", cp);
} }
free(cp); free(cp);
} }
@@ -111,7 +113,7 @@ control_window_pane_changed_cb(__unused const char *name,
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_write(c, "%%window-pane-changed @%u %%%u", w->id, control_notify_write(c, "%%window-pane-changed @%u %%%u", w->id,
w->active->id); w->active->id);
} }
} }
@@ -133,9 +135,11 @@ control_window_unlinked_cb(__unused const char *name, struct event_payload *ep,
cs = c->session; cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
control_write(c, "%%window-close @%u", w->id); control_notify_write(c, "%%window-close @%u", w->id);
else else {
control_write(c, "%%unlinked-window-close @%u", w->id); control_notify_write(c, "%%unlinked-window-close @%u",
w->id);
}
} }
} }
@@ -156,9 +160,11 @@ control_window_linked_cb(__unused const char *name, struct event_payload *ep,
cs = c->session; cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
control_write(c, "%%window-add @%u", w->id); control_notify_write(c, "%%window-add @%u", w->id);
else else {
control_write(c, "%%unlinked-window-add @%u", w->id); control_notify_write(c, "%%unlinked-window-add @%u",
w->id);
}
} }
} }
@@ -179,11 +185,11 @@ control_window_renamed_cb(__unused const char *name, struct event_payload *ep,
cs = c->session; cs = c->session;
if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) { if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) {
control_write(c, "%%window-renamed @%u %s", w->id, control_notify_write(c, "%%window-renamed @%u %s",
w->name);
} else {
control_write(c, "%%unlinked-window-renamed @%u %s",
w->id, w->name); w->id, w->name);
} else {
control_notify_write(c,
"%%unlinked-window-renamed @%u %s", w->id, w->name);
} }
} }
} }
@@ -206,11 +212,12 @@ control_client_session_changed_cb(__unused const char *name,
continue; continue;
if (cc == c) { if (cc == c) {
control_write(c, "%%session-changed $%u %s", s->id, control_notify_write(c, "%%session-changed $%u %s",
s->name); s->id, s->name);
} else { } else {
control_write(c, "%%client-session-changed %s $%u %s", control_notify_write(c,
cc->name, s->id, s->name); "%%client-session-changed %s $%u %s", cc->name,
s->id, s->name);
} }
} }
} }
@@ -226,8 +233,9 @@ control_client_detached_cb(__unused const char *name, struct event_payload *ep,
if (cc == NULL) if (cc == NULL)
return; return;
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
control_write(c, "%%client-detached %s", cc->name); continue;
control_notify_write(c, "%%client-detached %s", cc->name);
} }
} }
@@ -244,8 +252,8 @@ control_session_renamed_cb(__unused const char *name, struct event_payload *ep,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%session-renamed $%u %s", s->id,
control_write(c, "%%session-renamed $%u %s", s->id, s->name); s->name);
} }
} }
@@ -259,8 +267,7 @@ control_session_created_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%sessions-changed");
control_write(c, "%%sessions-changed");
} }
} }
@@ -274,8 +281,7 @@ control_session_closed_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%sessions-changed");
control_write(c, "%%sessions-changed");
} }
} }
@@ -298,9 +304,8 @@ control_session_window_changed_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%session-window-changed $%u @%u",
control_write(c, "%%session-window-changed $%u @%u", s->id, s->id, s->curw->window->id);
s->curw->window->id);
} }
} }
@@ -317,8 +322,7 @@ control_paste_buffer_changed_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%paste-buffer-changed %s", pbname);
control_write(c, "%%paste-buffer-changed %s", pbname);
} }
} }
@@ -335,8 +339,7 @@ control_paste_buffer_deleted_cb(__unused const char *name,
TAILQ_FOREACH(c, &clients, entry) { TAILQ_FOREACH(c, &clients, entry) {
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
continue; continue;
control_notify_write(c, "%%paste-buffer-deleted %s", pbname);
control_write(c, "%%paste-buffer-deleted %s", pbname);
} }
} }

163
control.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: control.c,v 1.62 2026/07/17 08:37:29 nicm Exp $ */ /* $OpenBSD: control.c,v 1.63 2026/08/03 13:38:42 nicm Exp $ */
/* /*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -53,6 +53,17 @@ struct control_block {
TAILQ_ENTRY(control_block) all_entry; TAILQ_ENTRY(control_block) all_entry;
}; };
/*
* A notification line deferred because it was generated while a command's
* %begin/%end guard block was open. Notifications must never appear inside a
* guard block, so they are held here and flushed once the block closes.
*/
struct control_line {
char *line;
TAILQ_ENTRY(control_line) entry;
};
/* Control client pane. */ /* Control client pane. */
struct control_pane { struct control_pane {
u_int pane; u_int pane;
@@ -102,6 +113,13 @@ struct control_state {
struct bufferevent *write_event; struct bufferevent *write_event;
struct monitor_set *subs; struct monitor_set *subs;
/*
* Depth of open %begin/%end guard blocks and notifications deferred
* until the outermost block closes.
*/
int guard_depth;
TAILQ_HEAD(, control_line) deferred;
}; };
/* Low and high watermarks. */ /* Low and high watermarks. */
@@ -352,7 +370,7 @@ control_continue_pane(struct client *c, struct window_pane *wp)
cp->flags &= ~CONTROL_PANE_PAUSED; cp->flags &= ~CONTROL_PANE_PAUSED;
memcpy(&cp->offset, &wp->offset, sizeof cp->offset); memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued); memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
control_write(c, "%%continue %%%u", wp->id); control_notify_write(c, "%%continue %%%u", wp->id);
} }
} }
@@ -366,52 +384,120 @@ control_pause_pane(struct client *c, struct window_pane *wp)
if (~cp->flags & CONTROL_PANE_PAUSED) { if (~cp->flags & CONTROL_PANE_PAUSED) {
cp->flags |= CONTROL_PANE_PAUSED; cp->flags |= CONTROL_PANE_PAUSED;
control_discard_pane(c, cp); control_discard_pane(c, cp);
control_write(c, "%%pause %%%u", wp->id); control_notify_write(c, "%%pause %%%u", wp->id);
} }
} }
/* Write a line. */ /* Write an already-formatted line, queueing it behind %output if needed. */
static void printflike(2, 0) static void
control_vwrite(struct client *c, const char *fmt, va_list ap) control_write_line(struct client *c, char *line)
{
struct control_state *cs = c->control_state;
char *s;
xvasprintf(&s, fmt, ap);
log_debug("%s: %s: writing line: %s", __func__, c->name, s);
bufferevent_write(cs->write_event, s, strlen(s));
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
free(s);
}
/* Write a line. */
void
control_write(struct client *c, const char *fmt, ...)
{ {
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
struct control_block *cb; struct control_block *cb;
va_list ap;
va_start(ap, fmt);
if (TAILQ_EMPTY(&cs->all_blocks)) { if (TAILQ_EMPTY(&cs->all_blocks)) {
control_vwrite(c, fmt, ap); log_debug("%s: %s: writing line: %s", __func__, c->name, line);
va_end(ap); bufferevent_write(cs->write_event, line, strlen(line));
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
free(line);
return; return;
} }
cb = xcalloc(1, sizeof *cb); cb = xcalloc(1, sizeof *cb);
xvasprintf(&cb->line, fmt, ap); cb->line = line;
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
cb->t = get_timer(); cb->t = get_timer();
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line); log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
bufferevent_enable(cs->write_event, EV_WRITE); bufferevent_enable(cs->write_event, EV_WRITE);
}
/* Flush notifications that were deferred while a guard block was open. */
static void
control_flush_deferred(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_line *cl, *cl1;
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
TAILQ_REMOVE(&cs->deferred, cl, entry);
control_write_line(c, cl->line);
free(cl);
}
}
/*
* Write a line of command output or error text. This is a sink for arbitrary
* user-controlled text (command output, capture-pane, error messages), so it
* must never try to interpret the content: guard tracking is done only in
* control_write_guard.
*/
void
control_write(struct client *c, const char *fmt, ...)
{
va_list ap;
char *line;
va_start(ap, fmt);
xvasprintf(&line, fmt, ap);
va_end(ap); va_end(ap);
control_write_line(c, line);
}
/*
* Write a %begin, %end or %error guard around a command's output. This is the
* only place guard lines are produced, so the block depth is maintained here;
* when the outermost block closes any deferred notifications are flushed after
* it. "guard" is always one of the fixed strings from cmdq_guard, never user
* text.
*/
void
control_write_guard(struct client *c, const char *guard, long t, u_int number,
int flags)
{
struct control_state *cs = c->control_state;
char *line;
if (strcmp(guard, "begin") == 0)
cs->guard_depth++;
xasprintf(&line, "%%%s %ld %u %d", guard, t, number, flags);
control_write_line(c, line);
if (strcmp(guard, "begin") != 0 && cs->guard_depth > 0 &&
--cs->guard_depth == 0)
control_flush_deferred(c);
}
/*
* Write a notification line. Notifications must never appear inside a command's
* %begin/%end guard block, so if one is open the line is deferred until it
* closes.
*/
void
control_notify_write(struct client *c, const char *fmt, ...)
{
struct control_state *cs = c->control_state;
struct control_line *cl;
va_list ap;
char *line;
va_start(ap, fmt);
xvasprintf(&line, fmt, ap);
va_end(ap);
if (cs->guard_depth == 0) {
control_write_line(c, line);
return;
}
log_debug("%s: %s: deferring notification: %s", __func__, c->name,
line);
cl = xcalloc(1, sizeof *cl);
cl->line = line;
TAILQ_INSERT_TAIL(&cs->deferred, cl, entry);
} }
/* Check age for this pane. */ /* Check age for this pane. */
@@ -438,7 +524,7 @@ control_check_age(struct client *c, struct window_pane *wp,
return (0); return (0);
cp->flags |= CONTROL_PANE_PAUSED; cp->flags |= CONTROL_PANE_PAUSED;
control_discard_pane(c, cp); control_discard_pane(c, cp);
control_write(c, "%%pause %%%u", wp->id); control_notify_write(c, "%%pause %%%u", wp->id);
} else { } else {
if (age < CONTROL_MAXIMUM_AGE) if (age < CONTROL_MAXIMUM_AGE)
return (0); return (0);
@@ -805,14 +891,17 @@ control_sub_change(struct monitor_change *change, __unused void *data)
if (wp != NULL) { if (wp != NULL) {
w = wp->window; w = wp->window;
control_write(c, "%%subscription-changed %s $%u @%u %u %%%u : %s", control_notify_write(c,
"%%subscription-changed %s $%u @%u %u %%%u : %s",
change->name, s->id, w->id, wl->idx, wp->id, change->value); change->name, s->id, w->id, wl->idx, wp->id, change->value);
} else if (wl != NULL) { } else if (wl != NULL) {
w = wl->window; w = wl->window;
control_write(c, "%%subscription-changed %s $%u @%u %u - : %s", control_notify_write(c,
"%%subscription-changed %s $%u @%u %u - : %s",
change->name, s->id, w->id, wl->idx, change->value); change->name, s->id, w->id, wl->idx, change->value);
} else { } else {
control_write(c, "%%subscription-changed %s $%u - - - : %s", control_notify_write(c,
"%%subscription-changed %s $%u - - - : %s",
change->name, s->id, change->value); change->name, s->id, change->value);
} }
} }
@@ -835,6 +924,7 @@ control_start(struct client *c)
RB_INIT(&cs->windows); RB_INIT(&cs->windows);
TAILQ_INIT(&cs->pending_list); TAILQ_INIT(&cs->pending_list);
TAILQ_INIT(&cs->all_blocks); TAILQ_INIT(&cs->all_blocks);
TAILQ_INIT(&cs->deferred);
cs->subs = monitor_create_client(c, control_sub_change, NULL); cs->subs = monitor_create_client(c, control_sub_change, NULL);
cs->read_event = bufferevent_new(c->fd, control_read_callback, cs->read_event = bufferevent_new(c->fd, control_read_callback,
@@ -885,12 +975,19 @@ control_stop(struct client *c)
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
struct control_block *cb, *cb1; struct control_block *cb, *cb1;
struct control_window *cw, *cw1; struct control_window *cw, *cw1;
struct control_line *cl, *cl1;
if (cs == NULL) if (cs == NULL)
return; return;
monitor_destroy(cs->subs); monitor_destroy(cs->subs);
TAILQ_FOREACH_SAFE(cl, &cs->deferred, entry, cl1) {
TAILQ_REMOVE(&cs->deferred, cl, entry);
free(cl->line);
free(cl);
}
if (~c->flags & CLIENT_CONTROLCONTROL) if (~c->flags & CLIENT_CONTROLCONTROL)
bufferevent_free(cs->write_event); bufferevent_free(cs->write_event);
bufferevent_free(cs->read_event); bufferevent_free(cs->read_event);

4
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1419 2026/08/03 12:58:53 nicm Exp $ */ /* $OpenBSD: tmux.h,v 1.1420 2026/08/03 13:38:42 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -4035,6 +4035,8 @@ struct window_pane_offset *control_pane_offset(struct client *,
struct window_pane *, int *); struct window_pane *, int *);
void control_reset_offsets(struct client *); void control_reset_offsets(struct client *);
void printflike(2, 3) control_write(struct client *, const char *, ...); void printflike(2, 3) control_write(struct client *, const char *, ...);
void printflike(2, 3) control_notify_write(struct client *, const char *, ...);
void control_write_guard(struct client *, const char *, long, u_int, int);
void control_write_output(struct client *, struct window_pane *); void control_write_output(struct client *, struct window_pane *);
int control_all_done(struct client *); int control_all_done(struct client *);
void control_add_sub(struct client *, const char *, enum monitor_type, int, void control_add_sub(struct client *, const char *, enum monitor_type, int,