Merge branch 'master' into no_more_overlays

This commit is contained in:
Nicholas Marriott
2026-08-19 09:00:04 +01:00
27 changed files with 657 additions and 127 deletions

View File

@@ -17,7 +17,7 @@ jobs:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
if: github.repository == 'tmux/tmux'
timeout-minutes: 45
timeout-minutes: 300
strategy:
fail-fast: false

20
CHANGES
View File

@@ -1,7 +1,4 @@
CHANGES FROM 3.7b TO 3.8
* Build with jemalloc on macOS to avoid what appears to be a bug in the system
calloc(3) (issue 5385).
CHANGES FROM 3.7c TO 3.8
* Many improvements to floating panes:
@@ -121,8 +118,6 @@ CHANGES FROM 3.7b TO 3.8
* Copy mode no longer exits at the bottom while a selection is in progress
(issue 5349).
* Fix scrollbar initial state so scrollbars appear on new windows (issue 5339).
* Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
nolink) (issues 4842 and 4280 from Moritz Angermann).
@@ -169,7 +164,18 @@ CHANGES FROM 3.7b TO 3.8
* Do not crash looking for the next or previous session (issue 5344), or when
no client is available.
* Check time periodically in loops rather than for every item (issue 5367).
CHANGES FROM 3.7b TO 3.7c
* Build with jemalloc on macOS to avoid what appears to be a bug in calloc
(issue 5385).
* Fix scrollbar initial state so they appear on new windows (issue 5339).
* Check time periodically in loops rather than every one (issue 5367).
* Use message-style again as default for message-format.
* Unzoom before creating floating panes to avoid a crash.
CHANGES FROM 3.7a TO 3.7b

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: client.c,v 1.166 2026/07/10 15:45:11 nicm Exp $ */
/* $OpenBSD: client.c,v 1.168 2026/08/17 20:14:31 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -276,7 +276,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
proc_set_signals(client_proc, client_signal);
/* Save the flags. */
client_flags = flags;
client_flags = flags|CLIENT_WRITE_ACK;
log_debug("flags are %#llx", (unsigned long long)client_flags);
/* Initialize the client socket and start the server. */
@@ -712,6 +712,9 @@ client_dispatch_wait(struct imsg *imsg)
fprintf(stderr, "server version is too old for client\n");
proc_exit(client_proc);
break;
default:
log_debug("unknown message type %u", imsg->hdr.type);
break;
}
}
@@ -798,5 +801,8 @@ client_dispatch_attached(struct imsg *imsg)
system(data);
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
break;
default:
log_debug("unknown message type %u", imsg->hdr.type);
break;
}
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-break-pane.c,v 1.75 2026/07/23 09:38:27 nicm Exp $ */
/* $OpenBSD: cmd-break-pane.c,v 1.76 2026/08/17 07:04:45 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -174,13 +174,14 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
}
window_set_fill_cells(w);
if (idx == -1)
idx = -1 - options_get_number(dst_s->options, "base-index");
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
layout_init(w, wp);
wp->flags |= PANE_CHANGED;
colour_palette_from_option(&wp->palette, wp->options);
if (idx == -1)
idx = -1 - options_get_number(dst_s->options, "base-index");
wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
window_remove_ref(w, __func__);
events_fire_window("window-created", w);
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: cmd-source-file.c,v 1.62 2025/11/18 08:42:09 nicm Exp $ */
/* $OpenBSD: cmd-source-file.c,v 1.63 2026/08/18 08:05:05 nicm Exp $ */
/*
* Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org>
@@ -50,6 +50,7 @@ const struct cmd_entry cmd_source_file_entry = {
struct cmd_source_file_data {
struct cmdq_item *item;
struct client *client;
int flags;
struct cmdq_item *after;
@@ -60,10 +61,24 @@ struct cmd_source_file_data {
u_int nfiles;
};
static enum cmd_retval
cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
static void
cmd_source_file_free_data(struct cmd_source_file_data *cdata)
{
struct client *c = cmdq_get_client(item);
u_int i;
for (i = 0; i < cdata->nfiles; i++)
free(cdata->files[i]);
free(cdata->files);
if (cdata->client != NULL)
server_client_unref(cdata->client);
free(cdata);
}
static enum cmd_retval
cmd_source_file_complete_cb(struct cmdq_item *item, void *data)
{
struct cmd_source_file_data *cdata = data;
struct client *c = cdata->client;
if (c == NULL) {
cmd_source_file_depth--;
@@ -74,36 +89,36 @@ cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
}
cfg_print_causes(item);
cmd_source_file_free_data(cdata);
return (CMD_RETURN_NORMAL);
}
static void
cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata)
cmd_source_file_complete(struct cmd_source_file_data *cdata)
{
struct client *c = cdata->client;
struct cmdq_item *new_item;
u_int i;
if (cfg_finished) {
if (cdata->retval == CMD_RETURN_ERROR &&
c != NULL &&
c->session == NULL)
c->retval = 1;
new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL);
cmdq_insert_after(cdata->after, new_item);
if (!cfg_finished) {
cmd_source_file_free_data(cdata);
return;
}
for (i = 0; i < cdata->nfiles; i++)
free(cdata->files[i]);
free(cdata->files);
free(cdata);
if (cdata->retval == CMD_RETURN_ERROR &&
c != NULL &&
c->session == NULL)
c->retval = 1;
new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
cmdq_insert_after(cdata->after, new_item);
}
static void
cmd_source_file_done(struct client *c, const char *path, int error,
int closed, struct evbuffer *buffer, void *data)
cmd_source_file_done(__unused struct client *oc, const char *path,
int error, int closed, struct evbuffer *buffer, void *data)
{
struct cmd_source_file_data *cdata = data;
struct cmdq_item *item = cdata->item;
struct client *c = cdata->client;
void *bdata = EVBUFFER_DATA(buffer);
size_t bsize = EVBUFFER_LENGTH(buffer);
u_int n;
@@ -127,7 +142,7 @@ cmd_source_file_done(struct client *c, const char *path, int error,
if (n < cdata->nfiles)
file_read(c, cdata->files[n], cmd_source_file_done, cdata);
else {
cmd_source_file_complete(c, cdata);
cmd_source_file_complete(cdata);
cmdq_continue(item);
}
}
@@ -187,6 +202,9 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
cdata = xcalloc(1, sizeof *cdata);
cdata->item = item;
cdata->client = c;
if (c != NULL)
c->references++;
if (args_has(args, 'q'))
cdata->flags |= CMD_PARSE_QUIET;
@@ -249,7 +267,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
file_read(c, cdata->files[0], cmd_source_file_done, cdata);
retval = CMD_RETURN_WAIT;
} else
cmd_source_file_complete(c, cdata);
cmd_source_file_complete(cdata);
free(cwd);
return (retval);

View File

@@ -31,7 +31,7 @@ getpeereid(int s, uid_t *uid, gid_t *gid)
{
#ifdef HAVE_SO_PEERCRED
struct ucred uc;
int len = sizeof uc;
socklen_t len = sizeof uc;
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1)
return (-1);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: control.c,v 1.65 2026/08/04 11:18:22 nicm Exp $ */
/* $OpenBSD: control.c,v 1.66 2026/08/18 07:43:44 nicm Exp $ */
/*
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -388,6 +388,25 @@ control_pause_pane(struct client *c, struct window_pane *wp)
}
}
/*
* Reset a pane after its buffer has been replaced: drop any output still
* queued from the old buffer and start again from the pane's own offset.
*/
void
control_reset_pane(struct client *c, struct window_pane *wp)
{
struct control_pane *cp;
if (c->control_state == NULL)
return;
cp = control_get_pane(c, wp);
if (cp == NULL)
return;
control_discard_pane(c, cp);
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
}
/* Write an already-formatted line, queueing it behind %output if needed. */
static void
control_write_line(struct client *c, char *line)

86
file.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: file.c,v 1.21 2026/07/26 15:08:15 nicm Exp $ */
/* $OpenBSD: file.c,v 1.22 2026/08/17 07:56:56 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -502,7 +502,8 @@ file_push(struct client_file *cf)
} else if (cf->stream > 2) {
close.stream = cf->stream;
proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
file_fire_done(cf);
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
file_fire_done(cf);
}
free(msg);
}
@@ -527,14 +528,48 @@ file_write_left(struct client_files *files)
return (waiting != 0);
}
/* Finish writing a client file. */
static void
file_write_finished(struct client_file *cf)
{
struct msg_write_done msg;
if (cf->event != NULL) {
bufferevent_free(cf->event);
cf->event = NULL;
}
if (cf->fd != -1) {
if (close(cf->fd) != 0 && cf->error == 0)
cf->error = errno;
cf->fd = -1;
}
msg.stream = cf->stream;
msg.error = cf->error;
proc_send(cf->peer, MSG_WRITE_DONE, -1, &msg, sizeof msg);
if (cf->cb != NULL)
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
file_free(cf);
}
/* Client file write error callback. */
static void
file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
file_write_error_callback(__unused struct bufferevent *bev, short what,
void *arg)
{
struct client_file *cf = arg;
int error;
if (what & EVBUFFER_ERROR)
error = errno;
else
error = EIO;
if (error == 0)
error = EIO;
log_debug("write error file %d", cf->stream);
cf->error = error;
bufferevent_free(cf->event);
cf->event = NULL;
@@ -542,7 +577,9 @@ file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
close(cf->fd);
cf->fd = -1;
if (cf->cb != NULL)
if (cf->closed)
file_write_finished(cf);
else if (cf->cb != NULL)
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
}
@@ -554,15 +591,10 @@ file_write_callback(__unused struct bufferevent *bev, void *arg)
log_debug("write check file %d", cf->stream);
if (cf->cb != NULL)
if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0)
file_write_finished(cf);
else if (cf->cb != NULL)
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) {
bufferevent_free(cf->event);
close(cf->fd);
RB_REMOVE(client_files, cf->tree, cf);
file_free(cf);
}
}
/* Handle a file write open message (client). */
@@ -663,14 +695,10 @@ file_write_close(struct client_files *files, struct imsg *imsg)
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
fatalx("unknown stream number");
log_debug("close file %d", cf->stream);
cf->closed = 1;
if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) {
if (cf->event != NULL)
bufferevent_free(cf->event);
if (cf->fd != -1)
close(cf->fd);
RB_REMOVE(client_files, files, cf);
file_free(cf);
file_write_finished(cf);
}
}
@@ -829,6 +857,28 @@ file_write_ready(struct client_files *files, struct imsg *imsg)
return (0);
}
/* Handle a write done message (server). */
int
file_write_done(struct client_files *files, struct imsg *imsg)
{
struct msg_write_done *msg = imsg->data;
size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
struct client_file find, *cf;
if (msglen != sizeof *msg)
return (-1);
find.stream = msg->stream;
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
return (0);
if (cf->c == NULL || (~cf->c->flags & CLIENT_WRITE_ACK))
return (0);
log_debug("file %d write done", cf->stream);
cf->error = msg->error;
file_fire_done(cf);
return (0);
}
/* Handle read data message (server). */
int
file_read_data(struct client_files *files, struct imsg *imsg)

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: input.c,v 1.269 2026/07/20 11:16:33 nicm Exp $ */
/* $OpenBSD: input.c,v 1.270 2026/08/17 20:04:00 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1976,7 +1976,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx)
screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
break;
case 2026:
screen_write_stop_sync(ictx->wp);
screen_write_end_sync(sctx);
break;
case 2031:
screen_write_mode_clear(sctx, MODE_THEME_UPDATES);

6
menu.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: menu.c,v 1.69 2026/07/14 19:07:03 nicm Exp $ */
/* $OpenBSD: menu.c,v 1.70 2026/08/17 07:33:55 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -337,6 +337,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
struct cmdq_state *state;
enum cmd_parse_status status;
char *error;
key_code key;
if (KEYC_IS_MOUSE(event->key)) {
/*
@@ -387,7 +388,8 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
name = menu->items[i].name;
if (name == NULL || *name == '-')
continue;
if ((event->key & ~KEYC_MASK_FLAGS) == menu->items[i].key) {
key = (event->key & ~KEYC_MASK_FLAGS);
if (key == (menu->items[i].key & ~KEYC_MASK_FLAGS)) {
md->choice = i;
goto chosen;
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: prompt.c,v 1.5 2026/07/13 10:29:17 nicm Exp $ */
/* $OpenBSD: prompt.c,v 1.6 2026/08/17 06:45:16 nicm Exp $ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1334,7 +1334,7 @@ process_key:
if (pr->index != size) {
memmove(pr->buffer + pr->index,
pr->buffer + pr->index + 1,
(size + 1 - pr->index) *
(size - pr->index) *
sizeof *pr->buffer);
goto changed;
}

View File

@@ -269,6 +269,22 @@ test_customize_mode()
assert_alive "customize-mode exit"
}
test_customize_break_pane()
{
start_client option-break
side=$($TMUX split-window -d -P -F '#{pane_id}' \
-t option-break:0 'cat') || fail "customize split failed"
$TMUX customize-mode -t "$side" || fail "customize-mode failed"
wait_mode "$side" 1
$TMUX break-pane -d -s "$side" || fail "customize break-pane failed"
assert_alive "customize-mode break-pane"
wait_mode "$side" 1
$TMUX send-keys -t "$side" q || fail "customize-mode quit failed"
wait_mode "$side" 0
}
test_copy_mode()
{
start_client copy-a 'i=0; while [ $i -lt 200 ]; do echo "copy mutation line $i"; i=$((i + 1)); done; cat'
@@ -302,6 +318,7 @@ test_choose_tree
test_choose_buffer
test_choose_client
test_customize_mode
test_customize_break_pane
test_copy_mode
cleanup
exit 0

View File

@@ -0,0 +1,87 @@
#!/bin/sh
# respawn-pane frees the pane's buffer and creates an empty one. The pane
# offsets and each control client's offsets used to keep pointing into the old
# buffer, so if an attached control client had not caught up on the pane's
# output, the next read from the new process ran off the end of the new buffer
# and the server crashed in input_parse.
#
# Two ways for a client to be behind are tested: a control client whose output
# goes down a fifo that is never read (a second, healthy control client keeps
# the pane being read), and a healthy control client viewing a session that
# the pane's window has been moved out of.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest"
$TMUX kill-server 2>/dev/null
DIR=$(mktemp -d)
FIFO=$DIR/fifo
SERVER=
mkfifo "$FIFO" || exit 1
cleanup() {
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
$TMUX kill-server 2>/dev/null
exec 8<&- 2>/dev/null
rm -rf "$DIR"
}
trap cleanup 0 1 15
alive() {
if ! kill -0 "$SERVER" 2>/dev/null; then
SERVER=
echo "server died after $1"
exit 1
fi
$TMUX has -t rt || exit 1
}
wait_clients() {
n=0
while [ $n -lt 50 ]; do
[ "$($TMUX lsc 2>/dev/null | wc -l)" -ge $1 ] && return
sleep 0.1
n=$((n + 1))
done
echo "control clients did not attach"; exit 1
}
# A detached session with a pane that writes a line every 10 milliseconds.
$TMUX -f/dev/null new -d -x 80 -y 24 -s rt \
'while :; do echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; sleep 0.01; done' ||
exit 1
SERVER=$($TMUX display -pt rt '#{pid}')
# One control client with output down the unread fifo and one reading
# normally; stdin of both held open by sleep so they stay attached.
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >"$FIFO" 2>&1 &
exec 8<"$FIFO"
( sleep 60 ) | $TMUX -f/dev/null -C attach -t rt >/dev/null 2>&1 &
wait_clients 2
# Let the first client fall behind, then respawn the pane. The new process
# writes at once and the server must survive reading it.
sleep 3
$TMUX respawn-pane -k -t rt:0 'echo respawned; sleep 60' || exit 1
sleep 1
alive "respawn-pane with a lagging control client"
# Now a pane whose window is moved out of the session both clients view: their
# offsets for it stop advancing, so they fall behind until it is respawned.
$TMUX neww -d -t rt \
'while :; do echo yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy; sleep 0.01; done' ||
exit 1
sleep 1
$TMUX new -d -s other || exit 1
$TMUX movew -d -s rt:1 -t other: || exit 1
sleep 2
$TMUX respawn-pane -k -t other:1 'echo respawned; sleep 60' || exit 1
sleep 1
alive "respawn-pane on a window moved out of the clients' session"
exit 0

View File

@@ -0,0 +1,173 @@
#!/bin/sh
# Application synchronized updates must remain one physical client transaction,
# including when BSU, printable cells and ESU arrive in one pane read.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
DIR=$(mktemp -d) || exit 1
TMUX_TMPDIR=$DIR
export TMUX_TMPDIR
INNER="$TEST_TMUX -Li$$ -f/dev/null"
OUTER="$TEST_TMUX -Lo$$ -f/dev/null"
CLIENT_BYTES=$DIR/client-bytes
CONTROL=$DIR/control
EMITTER=$DIR/emitter.pl
ASSERT=$DIR/assert-sync-output.pl
fail()
{
echo "$*" >&2
exit 1
}
cleanup()
{
$OUTER kill-server 2>/dev/null
$INNER kill-server 2>/dev/null
rm -rf "$DIR"
}
trap cleanup 0 1 15
wait_for_client()
{
i=0
while [ "$i" -lt 50 ]; do
$INNER list-clients -F '#{client_termfeatures}' 2>/dev/null |
grep -q 'sync' && return 0
sleep 0.1
i=$((i + 1))
done
fail "sync-capable client did not attach"
}
wait_for_marker()
{
i=0
while [ "$i" -lt 50 ]; do
grep -q 'FRAME_000001_ROW_23' "$CLIENT_BYTES" 2>/dev/null && return 0
sleep 0.1
i=$((i + 1))
done
fail "client did not receive both synchronized frames"
}
wait_for_stable_bytes()
{
previous=-1
stable=0
i=0
while [ "$i" -lt 50 ]; do
current=$(wc -c <"$CLIENT_BYTES" 2>/dev/null) || current=0
if [ "$current" -gt 0 ] && [ "$current" -eq "$previous" ]; then
stable=$((stable + 1))
[ "$stable" -eq 5 ] && return 0
else
stable=0
fi
previous=$current
sleep 0.1
i=$((i + 1))
done
fail "client byte stream did not become stable"
}
cat >"$EMITTER" <<'PERL'
use strict;
use warnings;
my $control = $ENV{CONTROL};
(my $dir = $control) =~ s{/[^/]+$}{};
open my $ready, '>', "$dir/ready" or die "$dir/ready: $!\n";
close $ready;
while (!-e $control) {
select undef, undef, undef, 0.01;
}
for my $frame (0 .. 1) {
my @rows;
for my $row (0 .. 23) {
my $marker = sprintf "FRAME_%06d_ROW_%02d_", $frame, $row;
push @rows, substr($marker . ('X' x 79), 0, 79);
}
my $frame = "\e[?2026h\e[H" . join("\r\n", @rows) .
"\e[?2026l";
my $written = syswrite STDOUT, $frame;
die "short synchronized frame write\n"
unless defined $written && $written == length $frame;
select undef, undef, undef, 0.2;
}
select undef, undef, undef, 2;
PERL
$INNER new-session -d -s inner -x 80 -y 24 \
"CONTROL='$CONTROL' perl '$EMITTER'" || exit 1
$INNER set-option -g status off || exit 1
$INNER set-option -g window-size manual || exit 1
$INNER set-option -as terminal-features '*:sync' || exit 1
$OUTER new-session -d -s outer -x 80 -y 24 \
"$TEST_TMUX -Li$$ -f/dev/null attach-session -t inner" ||
exit 1
$OUTER set-option -g status off || exit 1
$OUTER set-option -g window-size manual || exit 1
wait_for_client
i=0
while [ "$i" -lt 50 ] && [ ! -e "$DIR/ready" ]; do
sleep 0.1
i=$((i + 1))
done
[ -e "$DIR/ready" ] || fail "application emitter did not become ready"
$OUTER pipe-pane -O -t outer:0.0 "cat >'$CLIENT_BYTES'" || exit 1
: >"$CONTROL"
wait_for_marker
wait_for_stable_bytes
$OUTER pipe-pane -t outer:0.0 || exit 1
cat >"$ASSERT" <<'PERL'
use strict;
use warnings;
my $path = shift;
open my $fh, '<:raw', $path or die "$path: $!\n";
local $/;
my $bytes = <$fh>;
my ($on, $off) = ("\e[?2026h", "\e[?2026l");
my ($active, %seen);
for (my $i = 0; $i < length($bytes);) {
if (substr($bytes, $i, length($on)) eq $on) {
die "duplicate synchronized-output start at byte $i\n" if $active;
$active = 1;
$i += length($on);
next;
}
if (substr($bytes, $i, length($off)) eq $off) {
die "unmatched synchronized-output end at byte $i\n" unless $active;
$active = 0;
$i += length($off);
next;
}
if (substr($bytes, $i) =~ /\AFRAME_(\d{6})_ROW_(\d{2})_/) {
my $marker = "$1:$2";
die "marker $marker outside synchronized output\n" unless $active;
$seen{$marker}++;
}
$i++;
}
die "unterminated synchronized-output transaction\n" if $active;
for my $frame (0 .. 1) {
for my $row (0 .. 23) {
my $marker = sprintf "%06d:%02d", $frame, $row;
die "missing marker $marker\n" unless $seen{$marker};
}
}
PERL
perl "$ASSERT" "$CLIENT_BYTES" || exit 1

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: screen-write.c,v 1.286 2026/08/03 12:58:53 nicm Exp $ */
/* $OpenBSD: screen-write.c,v 1.287 2026/08/17 20:04:00 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1054,6 +1054,19 @@ screen_write_stop_sync(struct window_pane *wp)
log_debug("%s: %%%u stopped sync mode", __func__, wp->id);
}
/* Flush pending output before clearing sync mode. */
void
screen_write_end_sync(struct screen_write_ctx *ctx)
{
struct window_pane *wp = ctx->wp;
if (wp == NULL)
return;
if (wp->base.mode & MODE_SYNC)
screen_write_collect_flush(ctx, 0, __func__);
screen_write_stop_sync(wp);
}
/* Cursor up by ny. */
void
screen_write_cursorup(struct screen_write_ctx *ctx, u_int ny)

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server-client.c,v 1.502 2026/08/04 11:18:22 nicm Exp $ */
/* $OpenBSD: server-client.c,v 1.503 2026/08/17 07:56:56 nicm Exp $ */
/*
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -2554,6 +2554,10 @@ server_client_dispatch(struct imsg *imsg, void *arg)
if (file_write_ready(&c->files, imsg) != 0)
goto bad;
break;
case MSG_WRITE_DONE:
if (file_write_done(&c->files, imsg) != 0)
goto bad;
break;
case MSG_READ:
if (file_read_data(&c->files, imsg) != 0)
goto bad;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server-fn.c,v 1.149 2026/07/27 14:25:46 nicm Exp $ */
/* $OpenBSD: server-fn.c,v 1.150 2026/08/18 07:32:09 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -551,6 +551,7 @@ server_check_unattached(void)
continue;
break;
}
server_destroy_session(s);
session_destroy(s, 1, __func__);
}
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: server.c,v 1.215 2026/08/03 10:07:57 nicm Exp $ */
/* $OpenBSD: server.c,v 1.216 2026/08/18 07:24:49 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -129,6 +129,7 @@ server_create_socket(uint64_t flags, char **cause)
mask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
saved_errno = errno;
umask(mask);
close(fd);
errno = saved_errno;
goto fail;

18
spawn.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: spawn.c,v 1.50 2026/07/23 09:38:27 nicm Exp $ */
/* $OpenBSD: spawn.c,v 1.51 2026/08/18 07:43:44 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -243,7 +243,7 @@ struct window_pane *
spawn_pane(struct spawn_context *sc, char **cause)
{
struct cmdq_item *item = sc->item;
struct client *c;
struct client *c, *loop;
struct session *s = sc->s;
struct session *ts;
struct window *w = sc->wl->window;
@@ -331,6 +331,20 @@ spawn_pane(struct spawn_context *sc, char **cause)
input_free(sc->wp0->ictx);
sc->wp0->ictx = NULL;
}
/*
* The old buffer is gone and the new one starts empty, so
* offsets into the old buffer no longer mean anything. Reset
* them, and drop output control clients still had queued.
*/
sc->wp0->offset.used = 0;
sc->wp0->base_offset = 0;
sc->wp0->pipe_offset.used = 0;
TAILQ_FOREACH(loop, &clients, entry) {
if (loop->flags & CLIENT_CONTROL)
control_reset_pane(loop, sc->wp0);
}
new_wp = sc->wp0;
new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN);
} else {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux-protocol.h,v 1.2 2023/01/06 07:09:27 nicm Exp $ */
/* $OpenBSD: tmux-protocol.h,v 1.3 2026/08/17 07:56:56 nicm Exp $ */
/*
* Copyright (c) 2021 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -67,7 +67,8 @@ enum msgtype {
MSG_WRITE,
MSG_WRITE_READY,
MSG_WRITE_CLOSE,
MSG_READ_CANCEL
MSG_READ_CANCEL,
MSG_WRITE_DONE
};
/*
@@ -116,4 +117,9 @@ struct msg_write_close {
int stream;
};
struct msg_write_done {
int stream;
int error;
};
#endif /* TMUX_PROTOCOL_H */

8
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1155 2026/08/06 09:05:04 nicm Exp $
.\" $OpenBSD: tmux.1,v 1.1156 2026/08/17 14:47:41 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 6 2026 $
.Dd $Mdocdate: August 17 2026 $
.Dt TMUX 1
.Os
.Sh NAME
@@ -5047,6 +5047,10 @@ This is an array option where each entry is a colon-separated string made up
of a terminal type pattern (matched using
.Xr glob 7
patterns) followed by a list of terminal features.
A feature may be suffixed with
.Ql @
to disable it; for example,
.Ql xterm*:sync@ .
The available features are:
.Bl -tag -width Ds
.It 256

6
tmux.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.c,v 1.222 2026/07/19 19:09:30 nicm Exp $ */
/* $OpenBSD: tmux.c,v 1.223 2026/08/17 14:47:41 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -425,7 +425,7 @@ main(int argc, char **argv)
while ((opt = getopt(argc, argv, "2c:CDdf:hlL:NqS:T:uUvV")) != -1) {
switch (opt) {
case '2':
tty_add_features(&feat, "256", ":,");
tty_parse_features("256", ":,", &feat, NULL);
break;
case 'c':
shell_command = optarg;
@@ -473,7 +473,7 @@ main(int argc, char **argv)
path = xstrdup(optarg);
break;
case 'T':
tty_add_features(&feat, optarg, ":,");
tty_parse_features(optarg, ":,", &feat, NULL);
break;
case 'u':
flags |= CLIENT_UTF8;

24
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1422 2026/08/05 08:54:56 nicm Exp $ */
/* $OpenBSD: tmux.h,v 1.1427 2026/08/18 09:01:20 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1718,7 +1718,7 @@ struct key_event {
struct tty_term {
char *name;
struct tty *tty;
int features;
int applied_features;
char acs[UCHAR_MAX + 1][2];
@@ -1811,8 +1811,9 @@ struct tty {
#define TTY_WAITFG 0x2000
#define TTY_WAITBG 0x4000
#define TTY_BRACKETPASTE 0x8000
#define TTY_HAVESYNC 0x10000
#define TTY_ALL_REQUEST_FLAGS \
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA)
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA|TTY_HAVESYNC)
int flags;
struct tty_term *term;
@@ -2215,6 +2216,7 @@ struct client {
char *term_name;
int term_features;
int term_nofeatures;
char *term_type;
char **term_caps;
u_int term_ncaps;
@@ -2282,7 +2284,7 @@ struct client {
/* 0x800000000ULL unused */
#define CLIENT_BRACKETPASTING 0x1000000000ULL
#define CLIENT_ASSUMEPASTING 0x2000000000ULL
/* 0x4000000000ULL unused */
#define CLIENT_WRITE_ACK 0x4000000000ULL
#define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL
#define CLIENT_ALLREDRAWFLAGS \
(CLIENT_REDRAWWINDOW| \
@@ -2976,8 +2978,7 @@ extern struct tty_terms tty_terms;
u_int tty_term_ncodes(void);
void tty_term_apply(struct tty_term *, const char *, int);
void tty_term_apply_overrides(struct tty_term *);
struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, int *,
char **);
struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, char **);
void tty_term_free(struct tty_term *);
int tty_term_read_list(const char *, int, char ***, u_int *,
char **);
@@ -2999,11 +3000,13 @@ int tty_term_flag(struct tty_term *, enum tty_code_code);
const char *tty_term_describe(struct tty_term *, enum tty_code_code);
/* tty-features.c */
void tty_add_features(int *, const char *, const char *);
void tty_parse_client_features(struct client *, const char *,
const char *);
void tty_parse_features(const char *, const char *, int *, int *);
const char *tty_get_features(int);
int tty_feature_present(struct tty_term *, const char *);
int tty_apply_features(struct tty_term *, int);
void tty_default_features(int *, const char *, u_int);
int tty_apply_features(struct tty_term *);
void tty_default_features(struct client *, const char *, u_int);
/* tty-acs.c */
int tty_acs_needed(struct tty *);
@@ -3257,6 +3260,7 @@ void file_write_close(struct client_files *, struct imsg *);
void file_read_open(struct client_files *, struct tmuxpeer *, struct imsg *,
int, int, client_file_cb, void *);
int file_write_ready(struct client_files *, struct imsg *);
int file_write_done(struct client_files *, struct imsg *);
int file_read_data(struct client_files *, struct imsg *);
int file_read_done(struct client_files *, struct imsg *);
void file_read_cancel(struct client_files *, struct imsg *);
@@ -3568,6 +3572,7 @@ void screen_write_mode_set(struct screen_write_ctx *, int);
void screen_write_mode_clear(struct screen_write_ctx *, int);
void screen_write_start_sync(struct window_pane *);
void screen_write_stop_sync(struct window_pane *);
void screen_write_end_sync(struct screen_write_ctx *);
void screen_write_clear_dirty(struct window_pane *);
void screen_write_cursorup(struct screen_write_ctx *, u_int);
void screen_write_cursordown(struct screen_write_ctx *, u_int);
@@ -4003,6 +4008,7 @@ void control_set_pane_on(struct client *, struct window_pane *);
void control_set_pane_off(struct client *, struct window_pane *);
void control_continue_pane(struct client *, struct window_pane *);
void control_pause_pane(struct client *, struct window_pane *);
void control_reset_pane(struct client *, struct window_pane *);
void control_set_window_size(struct client *, u_int, u_int, u_int);
int control_get_window_size(struct client *, u_int, u_int *, u_int *);
void control_clear_window_size(struct client *, u_int);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tty-features.c,v 1.40 2026/07/01 06:17:58 nicm Exp $ */
/* $OpenBSD: tty-features.c,v 1.42 2026/08/17 14:47:41 nicm Exp $ */
/*
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -393,17 +393,29 @@ static const struct tty_feature *const tty_features[] = {
&tty_feature_usstyle
};
/* Parse features for client. */
void
tty_add_features(int *feat, const char *s, const char *separators)
tty_parse_client_features(struct client *c, const char *s, const char *sep)
{
tty_parse_features(s, sep, &c->term_features, &c->term_nofeatures);
}
/* Parse features list. */
void
tty_parse_features(const char *s, const char *sep, int *enabled, int *disabled)
{
const struct tty_feature *tf;
char *next, *loop, *copy;
u_int i;
int remove;
log_debug("adding terminal features %s", s);
loop = copy = xstrdup(s);
while ((next = strsep(&loop, separators)) != NULL) {
while ((next = strsep(&loop, sep)) != NULL) {
remove = (*next != '\0' && next[strlen(next) - 1] == '@');
if (remove)
next[strlen(next) - 1] = '\0';
for (i = 0; i < nitems(tty_features); i++) {
tf = tty_features[i];
if (strcasecmp(tf->name, next) == 0)
@@ -413,14 +425,24 @@ tty_add_features(int *feat, const char *s, const char *separators)
log_debug("unknown terminal feature: %s", next);
break;
}
if (~(*feat) & (1 << i)) {
if (remove) {
log_debug("removing terminal feature: %s", tf->name);
*enabled &= ~(1 << i);
if (disabled != NULL)
*disabled |= 1 << i;
continue;
}
if (disabled != NULL && *disabled & (1 << i))
continue;
if (~(*enabled) & (1 << i)) {
log_debug("adding terminal feature: %s", tf->name);
(*feat) |= (1 << i);
(*enabled) |= (1 << i);
}
}
free(copy);
}
/* Get features as string. */
const char *
tty_get_features(int feat)
{
@@ -442,6 +464,7 @@ tty_get_features(int feat)
return (s);
}
/* Check if feature is present. */
int
tty_feature_present(struct tty_term *term, const char *name)
{
@@ -453,8 +476,8 @@ tty_feature_present(struct tty_term *term, const char *name)
for (i = 0; i < nitems(tty_features); i++) {
tf = tty_features[i];
if (strcmp(tf->name, name) == 0) {
if (term->features & (1 << i))
return (1);
if (term->applied_features & (1 << i))
return (1);
break;
}
}
@@ -481,19 +504,23 @@ tty_feature_present(struct tty_term *term, const char *name)
return (1);
}
/* Apply featurs to terminal. */
int
tty_apply_features(struct tty_term *term, int feat)
tty_apply_features(struct tty_term *term)
{
struct client *c = term->tty->client;
const struct tty_feature *tf;
const char *const *capability;
int feat;
u_int i;
feat = (c->term_features & ~c->term_nofeatures);
if (feat == 0)
return (0);
log_debug("applying terminal features: %s", tty_get_features(feat));
for (i = 0; i < nitems(tty_features); i++) {
if ((term->features & (1 << i)) || (~feat & (1 << i)))
if ((term->applied_features & (1 << i)) || (~feat & (1 << i)))
continue;
tf = tty_features[i];
@@ -508,14 +535,15 @@ tty_apply_features(struct tty_term *term, int feat)
}
term->flags |= tf->flags;
}
if ((term->features | feat) == term->features)
if ((term->applied_features|feat) == term->applied_features)
return (0);
term->features |= feat;
term->applied_features |= feat;
return (1);
}
/* Add default features for a terminal identified by name and version. */
void
tty_default_features(int *feat, const char *name, u_int version)
tty_default_features(struct client *c, const char *name, u_int version)
{
static const struct {
const char *name;
@@ -596,6 +624,18 @@ tty_default_features(int *feat, const char *name, u_int version)
"usstyle,"
"progressbar"
},
{ .name = "Rio",
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
"ccolour,"
"cstyle,"
"focus,"
"overline,"
"hyperlinks,"
"osc7,"
"sync,"
"usstyle,"
"progressbar"
},
{ .name = "XTerm",
/*
* xterm also supports DECSLRM and DECFRA, but they can be
@@ -616,6 +656,6 @@ tty_default_features(int *feat, const char *name, u_int version)
continue;
if (version != 0 && version < table[i].version)
continue;
tty_add_features(feat, table[i].features, ",");
tty_parse_client_features(c, table[i].features, ",");
}
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tty-keys.c,v 1.211 2026/07/21 07:12:49 nicm Exp $ */
/* $OpenBSD: tty-keys.c,v 1.214 2026/08/18 09:01:20 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -59,6 +59,7 @@ static int tty_keys_device_attributes2(struct tty *, const char *, size_t,
size_t *);
static int tty_keys_extended_device_attributes(struct tty *, const char *,
size_t, size_t *);
static int tty_keys_sync(struct tty *, const char *, size_t, size_t *);
static int tty_keys_palette(struct tty *, const char *, size_t, size_t *);
/* A key tree entry. */
@@ -771,6 +772,17 @@ tty_keys_next(struct tty *tty)
goto partial_key;
}
/* Is this a synchronized update mode response? */
switch (tty_keys_sync(tty, buf, len, &size)) {
case 0: /* yes */
key = KEYC_UNKNOWN;
goto complete_key;
case -1: /* no, or not valid */
break;
case 1: /* partial */
goto partial_key;
}
/* Is this a primary device attributes response? */
switch (tty_keys_device_attributes(tty, buf, len, &size)) {
case 0: /* yes */
@@ -1447,7 +1459,6 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
size_t *size)
{
struct client *c = tty->client;
int *features = &c->term_features;
u_int i, n = 0;
char tmp[128], *endptr, p[32] = { 0 }, *cp, *next;
@@ -1504,13 +1515,13 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
for (i = 1; i < n; i++) {
log_debug("%s: DA feature: %d", c->name, p[i]);
if (p[i] == 4)
tty_add_features(features, "sixel", ",");
tty_parse_client_features(c, "sixel", ",");
if (p[i] == 21)
tty_add_features(features, "margins", ",");
tty_parse_client_features(c, "margins", ",");
if (p[i] == 28)
tty_add_features(features, "rectfill", ",");
tty_parse_client_features(c, "rectfill", ",");
if (p[i] == 52)
tty_add_features(features, "clipboard", ",");
tty_parse_client_features(c, "clipboard", ",");
}
break;
}
@@ -1522,6 +1533,54 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
return (0);
}
/*
* Handle a synchronized update mode response. Returns 0 for success, -1 for
* failure, 1 for partial.
*/
static int
tty_keys_sync(struct tty *tty, const char *buf, size_t len, size_t *size)
{
struct client *c = tty->client;
static const char prefix[] = "\033[?2026;";
size_t i;
int status;
*size = 0;
if (tty->flags & TTY_HAVESYNC)
return (-1);
/* The response is always \033[?2026;Ps$y. */
for (i = 0; i < (sizeof prefix) - 1; i++) {
if (i == len)
return (1);
if (buf[i] != prefix[i])
return (-1);
}
if (i == len)
return (1);
if (buf[i] < '0' || buf[i] > '4')
return (-1);
status = buf[i++] - '0';
if (i == len)
return (1);
if (buf[i++] != '$')
return (-1);
if (i == len)
return (1);
if (buf[i++] != 'y')
return (-1);
*size = i;
if (status == 1 || status == 2 || status == 3) {
tty_parse_client_features(c, "sync", ",");
tty_update_features(tty);
}
log_debug("%s: received DECRPM %.*s", c->name, (int)*size, buf);
tty->flags |= TTY_HAVESYNC;
return (0);
}
/*
* Handle secondary device attributes input. Returns 0 for success, -1 for
* failure, 1 for partial.
@@ -1531,7 +1590,6 @@ tty_keys_device_attributes2(struct tty *tty, const char *buf, size_t len,
size_t *size)
{
struct client *c = tty->client;
int *features = &c->term_features;
u_int i, n = 0;
char tmp[128], *endptr, p[32] = { 0 }, *cp, *next;
@@ -1585,13 +1643,13 @@ tty_keys_device_attributes2(struct tty *tty, const char *buf, size_t len,
*/
switch (p[0]) {
case 'M': /* mintty */
tty_default_features(features, "mintty", 0);
tty_default_features(c, "mintty", 0);
break;
case 'T': /* tmux */
tty_default_features(features, "tmux", 0);
tty_default_features(c, "tmux", 0);
break;
case 'U': /* rxvt-unicode */
tty_default_features(features, "rxvt-unicode", 0);
tty_default_features(c, "rxvt-unicode", 0);
break;
}
log_debug("%s: received secondary DA %.*s", c->name, (int)*size, buf);
@@ -1611,7 +1669,6 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf,
size_t len, size_t *size)
{
struct client *c = tty->client;
int *features = &c->term_features;
u_int i;
char tmp[128];
@@ -1654,19 +1711,21 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf,
/* Add terminal features. */
if (strncmp(tmp, "iTerm2 ", 7) == 0)
tty_default_features(features, "iTerm2", 0);
tty_default_features(c, "iTerm2", 0);
else if (strncmp(tmp, "tmux ", 5) == 0)
tty_default_features(features, "tmux", 0);
tty_default_features(c, "tmux", 0);
else if (strncmp(tmp, "XTerm(", 6) == 0)
tty_default_features(features, "XTerm", 0);
tty_default_features(c, "XTerm", 0);
else if (strncmp(tmp, "mintty ", 7) == 0)
tty_default_features(features, "mintty", 0);
tty_default_features(c, "mintty", 0);
else if (strncmp(tmp, "foot(", 5) == 0)
tty_default_features(features, "foot", 0);
tty_default_features(c, "foot", 0);
else if (strncmp(tmp, "WezTerm ", 7) == 0)
tty_default_features(features, "WezTerm", 0);
tty_default_features(c, "WezTerm", 0);
else if (strncmp(tmp, "ghostty ", 8) == 0)
tty_default_features(features, "ghostty", 0);
tty_default_features(c, "ghostty", 0);
else if (strncmp(tmp, "Rio ", 4) == 0)
tty_default_features(c, "Rio", 0);
log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf);
free(c->term_type);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tty-term.c,v 1.107 2026/08/05 08:54:56 nicm Exp $ */
/* $OpenBSD: tty-term.c,v 1.108 2026/08/17 14:47:41 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -544,8 +544,9 @@ tty_term_validate(struct tty_term *term)
struct tty_term *
tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps,
int *feat, char **cause)
char **cause)
{
struct client *c = tty->client;
struct tty_term *term;
const struct tty_term_code_entry *ent;
struct tty_code *code;
@@ -617,7 +618,7 @@ tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps,
offset = 0;
first = tty_term_override_next(s, &offset);
if (first != NULL && fnmatch(first, term->name, 0) == 0)
tty_add_features(feat, s + offset, ":");
tty_parse_client_features(c, s + offset, ":");
a = options_array_next(a);
}
@@ -627,14 +628,14 @@ tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps,
del_curterm(cur_term);
#endif
/* Check for COLORTERM. */
envent = environ_find(tty->client->environ, "COLORTERM");
envent = environ_find(c->environ, "COLORTERM");
if (envent != NULL) {
log_debug("%s COLORTERM=%s", tty->client->name, envent->value);
log_debug("%s COLORTERM=%s", c->name, envent->value);
if (strcasecmp(envent->value, "truecolor") == 0 ||
strcasecmp(envent->value, "24bit") == 0)
tty_add_features(feat, "RGB", ",");
tty_parse_client_features(c, "RGB", ",");
else if (strstr(envent->value, "256") != NULL)
tty_add_features(feat, "256", ",");
tty_parse_client_features(c, "256", ",");
}
/* Apply overrides so any capabilities used for features are changed. */
@@ -665,17 +666,17 @@ tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps,
s = tty_term_string(term, TTYC_CLEAR);
if (tty_term_flag(term, TTYC_XT) || strncmp(s, "\033[", 2) == 0) {
term->flags |= TERM_VT100LIKE;
tty_add_features(feat, "bpaste,focus,title", ",");
tty_parse_client_features(c, "bpaste,focus,title", ",");
}
/* Add RGB feature if terminal has RGB colours. */
if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) &&
(!tty_term_has(term, TTYC_SETRGBF) ||
!tty_term_has(term, TTYC_SETRGBB)))
tty_add_features(feat, "RGB", ",");
tty_parse_client_features(c, "RGB", ",");
/* Apply the features and overrides again. */
if (tty_apply_features(term, *feat))
if (tty_apply_features(term))
tty_term_apply_overrides(term);
/* Log the capabilities. */

8
tty.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tty.c,v 1.477 2026/07/17 12:42:51 nicm Exp $ */
/* $OpenBSD: tty.c,v 1.479 2026/08/18 09:01:20 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -276,7 +276,7 @@ tty_open(struct tty *tty, char **cause)
struct client *c = tty->client;
tty->term = tty_term_create(tty, c->term_name, c->term_caps,
c->term_ncaps, &c->term_features, cause);
c->term_ncaps, cause);
if (tty->term == NULL) {
tty_close(tty);
return (-1);
@@ -402,6 +402,8 @@ tty_send_requests(struct tty *tty)
tty_puts(tty, "\033[>c");
if (~tty->flags & TTY_HAVEXDA)
tty_puts(tty, "\033[>q");
if (~tty->flags & TTY_HAVESYNC)
tty_puts(tty, "\033[?2026$p");
tty_puts(tty, "\033]10;?\033\\\033]11;?\033\\");
tty->flags |= (TTY_WAITBG|TTY_WAITFG);
} else
@@ -535,7 +537,7 @@ tty_update_features(struct tty *tty)
{
struct client *c = tty->client;
if (tty_apply_features(tty->term, c->term_features))
if (tty_apply_features(tty->term))
tty_term_apply_overrides(tty->term);
if (tty_use_margin(tty))