mirror of
https://github.com/tmux/tmux.git
synced 2026-09-05 21:20:45 +00:00
Merge branch 'master' into no_more_overlays
This commit is contained in:
2
.github/workflows/regress.yml
vendored
2
.github/workflows/regress.yml
vendored
@@ -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
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
|
||||
|
||||
|
||||
10
client.c
10
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.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
21
control.c
21
control.c
@@ -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
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)
|
||||
|
||||
4
input.c
4
input.c
@@ -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
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
|
||||
|
||||
87
regress/respawn-pane-control-lag.sh
Normal file
87
regress/respawn-pane-control-lag.sh
Normal 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
|
||||
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 -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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__);
|
||||
}
|
||||
}
|
||||
|
||||
3
server.c
3
server.c
@@ -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
18
spawn.c
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
24
tmux.h
24
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.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);
|
||||
|
||||
@@ -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, ",");
|
||||
}
|
||||
}
|
||||
|
||||
95
tty-keys.c
95
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.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);
|
||||
|
||||
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>
|
||||
@@ -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
8
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.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))
|
||||
|
||||
Reference in New Issue
Block a user