mirror of
https://github.com/tmux/tmux.git
synced 2026-09-05 21:20:45 +00:00
Merge branch 'master' into 4902-image-support
This commit is contained in:
20
CHANGES
20
CHANGES
@@ -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
|
||||
|
||||
|
||||
4
client.c
4
client.c
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
86
file.c
@@ -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
6
menu.c
@@ -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;
|
||||
}
|
||||
|
||||
4
prompt.c
4
prompt.c
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
173
regress/sync-output-atomic.sh
Normal file
173
regress/sync-output-atomic.sh
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
8
tmux.1
@@ -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
6
tmux.c
@@ -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
19
tmux.h
@@ -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 *);
|
||||
|
||||
@@ -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, ",");
|
||||
}
|
||||
}
|
||||
|
||||
35
tty-keys.c
35
tty-keys.c
@@ -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);
|
||||
|
||||
21
tty-term.c
21
tty-term.c
@@ -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
6
tty.c
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user