Merge branch 'master' into 4902-image-support

This commit is contained in:
Michael Grant
2026-08-17 21:47:49 +01:00
18 changed files with 402 additions and 96 deletions

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.167 2026/08/17 07:56:56 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. */

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

@@ -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);

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)

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,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 -Lsync-output-inner-$$ -f/dev/null"
OUTER="$TEST_TMUX -Lsync-output-outer-$$ -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 -Lsync-output-inner-$$ -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: 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>
@@ -2695,6 +2695,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: 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;

19
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.1424 2026/08/17 14:47:41 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1709,7 +1709,7 @@ struct key_event {
struct tty_term {
char *name;
struct tty *tty;
int features;
int applied_features;
char acs[UCHAR_MAX + 1][2];
@@ -2222,6 +2222,7 @@ struct client {
char *term_name;
int term_features;
int term_nofeatures;
char *term_type;
char **term_caps;
u_int term_ncaps;
@@ -2289,7 +2290,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| \
@@ -2991,8 +2992,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 **);
@@ -3014,11 +3014,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 *);
@@ -3272,6 +3274,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 *);

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>
@@ -408,17 +408,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)
@@ -428,14 +440,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)
{
@@ -457,6 +479,7 @@ tty_get_features(int feat)
return (s);
}
/* Check if feature is present. */
int
tty_feature_present(struct tty_term *term, const char *name)
{
@@ -468,8 +491,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;
}
}
@@ -496,19 +519,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];
@@ -523,14 +550,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;
@@ -625,6 +653,18 @@ tty_default_features(int *feat, const char *name, u_int version)
"usstyle,"
"kitty"
},
{ .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
@@ -645,6 +685,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.213 2026/08/17 14:47:41 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1448,7 +1448,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;
@@ -1505,13 +1504,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;
}
@@ -1532,7 +1531,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;
@@ -1586,13 +1584,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);
@@ -1612,7 +1610,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];
@@ -1655,19 +1652,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>
@@ -545,8 +545,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;
@@ -618,7 +619,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);
}
@@ -628,14 +629,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. */
@@ -666,17 +667,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. */

6
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.478 2026/08/17 14:47:41 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -283,7 +283,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);
@@ -552,7 +552,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);
#ifdef ENABLE_IMAGES
image_tty_update(tty);