mirror of
https://github.com/tmux/tmux.git
synced 2026-10-12 21:54:23 +00:00
Merge branch 'master' into command_parser
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 }}
|
name: ${{ matrix.name }}
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
if: github.repository == 'tmux/tmux'
|
if: github.repository == 'tmux/tmux'
|
||||||
timeout-minutes: 45
|
timeout-minutes: 300
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|||||||
20
CHANGES
20
CHANGES
@@ -1,7 +1,4 @@
|
|||||||
CHANGES FROM 3.7b TO 3.8
|
CHANGES FROM 3.7c TO 3.8
|
||||||
|
|
||||||
* Build with jemalloc on macOS to avoid what appears to be a bug in the system
|
|
||||||
calloc(3) (issue 5385).
|
|
||||||
|
|
||||||
* Many improvements to floating panes:
|
* 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
|
* Copy mode no longer exits at the bottom while a selection is in progress
|
||||||
(issue 5349).
|
(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
|
* Add style attributes for dimming colours (dim=) and for hyperlinks (link= and
|
||||||
nolink) (issues 4842 and 4280 from Moritz Angermann).
|
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
|
* Do not crash looking for the next or previous session (issue 5344), or when
|
||||||
no client is available.
|
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
|
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>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -279,7 +279,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
|
|||||||
proc_set_signals(client_proc, client_signal);
|
proc_set_signals(client_proc, client_signal);
|
||||||
|
|
||||||
/* Save the flags. */
|
/* Save the flags. */
|
||||||
client_flags = flags;
|
client_flags = flags|CLIENT_WRITE_ACK;
|
||||||
log_debug("flags are %#llx", (unsigned long long)client_flags);
|
log_debug("flags are %#llx", (unsigned long long)client_flags);
|
||||||
|
|
||||||
/* Initialize the client socket and start the server. */
|
/* Initialize the client socket and start the server. */
|
||||||
@@ -715,6 +715,9 @@ client_dispatch_wait(struct imsg *imsg)
|
|||||||
fprintf(stderr, "server version is too old for client\n");
|
fprintf(stderr, "server version is too old for client\n");
|
||||||
proc_exit(client_proc);
|
proc_exit(client_proc);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
log_debug("unknown message type %u", imsg->hdr.type);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,5 +804,8 @@ client_dispatch_attached(struct imsg *imsg)
|
|||||||
system(data);
|
system(data);
|
||||||
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
|
proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
|
||||||
break;
|
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>
|
* 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);
|
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);
|
layout_init(w, wp);
|
||||||
wp->flags |= PANE_CHANGED;
|
wp->flags |= PANE_CHANGED;
|
||||||
colour_palette_from_option(&wp->palette, wp->options);
|
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__);
|
window_remove_ref(w, __func__);
|
||||||
events_fire_window("window-created", w);
|
events_fire_window("window-created", w);
|
||||||
window_fire_pane_moved(wp, old_w, old_idx, w, wl->idx);
|
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>
|
* 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 cmd_source_file_data {
|
||||||
struct cmdq_item *item;
|
struct cmdq_item *item;
|
||||||
|
struct client *client;
|
||||||
int flags;
|
int flags;
|
||||||
|
|
||||||
struct cmdq_item *after;
|
struct cmdq_item *after;
|
||||||
@@ -60,10 +61,24 @@ struct cmd_source_file_data {
|
|||||||
u_int nfiles;
|
u_int nfiles;
|
||||||
};
|
};
|
||||||
|
|
||||||
static enum cmd_retval
|
static void
|
||||||
cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
|
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) {
|
if (c == NULL) {
|
||||||
cmd_source_file_depth--;
|
cmd_source_file_depth--;
|
||||||
@@ -74,36 +89,36 @@ cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg_print_causes(item);
|
cfg_print_causes(item);
|
||||||
|
cmd_source_file_free_data(cdata);
|
||||||
return (CMD_RETURN_NORMAL);
|
return (CMD_RETURN_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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;
|
struct cmdq_item *new_item;
|
||||||
u_int i;
|
|
||||||
|
|
||||||
if (cfg_finished) {
|
if (!cfg_finished) {
|
||||||
if (cdata->retval == CMD_RETURN_ERROR &&
|
cmd_source_file_free_data(cdata);
|
||||||
c != NULL &&
|
return;
|
||||||
c->session == NULL)
|
|
||||||
c->retval = 1;
|
|
||||||
new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL);
|
|
||||||
cmdq_insert_after(cdata->after, new_item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < cdata->nfiles; i++)
|
if (cdata->retval == CMD_RETURN_ERROR &&
|
||||||
free(cdata->files[i]);
|
c != NULL &&
|
||||||
free(cdata->files);
|
c->session == NULL)
|
||||||
free(cdata);
|
c->retval = 1;
|
||||||
|
new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
|
||||||
|
cmdq_insert_after(cdata->after, new_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cmd_source_file_done(struct client *c, const char *path, int error,
|
cmd_source_file_done(__unused struct client *oc, const char *path,
|
||||||
int closed, struct evbuffer *buffer, void *data)
|
int error, int closed, struct evbuffer *buffer, void *data)
|
||||||
{
|
{
|
||||||
struct cmd_source_file_data *cdata = data;
|
struct cmd_source_file_data *cdata = data;
|
||||||
struct cmdq_item *item = cdata->item;
|
struct cmdq_item *item = cdata->item;
|
||||||
|
struct client *c = cdata->client;
|
||||||
void *bdata = EVBUFFER_DATA(buffer);
|
void *bdata = EVBUFFER_DATA(buffer);
|
||||||
size_t bsize = EVBUFFER_LENGTH(buffer);
|
size_t bsize = EVBUFFER_LENGTH(buffer);
|
||||||
u_int n;
|
u_int n;
|
||||||
@@ -127,7 +142,7 @@ cmd_source_file_done(struct client *c, const char *path, int error,
|
|||||||
if (n < cdata->nfiles)
|
if (n < cdata->nfiles)
|
||||||
file_read(c, cdata->files[n], cmd_source_file_done, cdata);
|
file_read(c, cdata->files[n], cmd_source_file_done, cdata);
|
||||||
else {
|
else {
|
||||||
cmd_source_file_complete(c, cdata);
|
cmd_source_file_complete(cdata);
|
||||||
cmdq_continue(item);
|
cmdq_continue(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,6 +202,9 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
|
|
||||||
cdata = xcalloc(1, sizeof *cdata);
|
cdata = xcalloc(1, sizeof *cdata);
|
||||||
cdata->item = item;
|
cdata->item = item;
|
||||||
|
cdata->client = c;
|
||||||
|
if (c != NULL)
|
||||||
|
c->references++;
|
||||||
|
|
||||||
if (args_has(args, 'q'))
|
if (args_has(args, 'q'))
|
||||||
cdata->flags |= CMD_PARSE_QUIET;
|
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);
|
file_read(c, cdata->files[0], cmd_source_file_done, cdata);
|
||||||
retval = CMD_RETURN_WAIT;
|
retval = CMD_RETURN_WAIT;
|
||||||
} else
|
} else
|
||||||
cmd_source_file_complete(c, cdata);
|
cmd_source_file_complete(cdata);
|
||||||
|
|
||||||
free(cwd);
|
free(cwd);
|
||||||
return (retval);
|
return (retval);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* $OpenBSD: cmd-split-window.c,v 1.148 2026/08/07 08:10:53 nicm Exp $ */
|
/* $OpenBSD: cmd-split-window.c,v 1.149 2026/08/19 10:56:10 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = {
|
|||||||
.name = "new-pane",
|
.name = "new-pane",
|
||||||
.alias = "newp",
|
.alias = "newp",
|
||||||
|
|
||||||
.args = { "bB:Cc:de:EfF:hIkl:LMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
.args = { "bB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||||
.usage = "[-bCdefhIklMOPvWZ] [-B border-lines] "
|
.usage = "[-bCdefhIkKLMOPvWZ] [-B border-lines] "
|
||||||
"[-c start-directory] [-e environment] "
|
"[-c start-directory] [-e environment] "
|
||||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||||
"[-s style] [-S active-border-style] "
|
"[-s style] [-S active-border-style] "
|
||||||
@@ -202,6 +202,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
|||||||
*/
|
*/
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
if (args_has(args, 'K') && args_has(args, 'O'))
|
||||||
|
new_wp->flags |= PANE_CAPTUREALLKEYS;
|
||||||
if (args_has(args, 'C') && args_has(args, 'O'))
|
if (args_has(args, 'C') && args_has(args, 'O'))
|
||||||
new_wp->flags |= PANE_CLOSEONCLICK;
|
new_wp->flags |= PANE_CLOSEONCLICK;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ getpeereid(int s, uid_t *uid, gid_t *gid)
|
|||||||
{
|
{
|
||||||
#ifdef HAVE_SO_PEERCRED
|
#ifdef HAVE_SO_PEERCRED
|
||||||
struct ucred uc;
|
struct ucred uc;
|
||||||
int len = sizeof uc;
|
socklen_t len = sizeof uc;
|
||||||
|
|
||||||
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1)
|
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1)
|
||||||
return (-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>
|
* 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. */
|
/* Write an already-formatted line, queueing it behind %output if needed. */
|
||||||
static void
|
static void
|
||||||
control_write_line(struct client *c, char *line)
|
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>
|
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -502,7 +502,8 @@ file_push(struct client_file *cf)
|
|||||||
} else if (cf->stream > 2) {
|
} else if (cf->stream > 2) {
|
||||||
close.stream = cf->stream;
|
close.stream = cf->stream;
|
||||||
proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
|
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);
|
free(msg);
|
||||||
}
|
}
|
||||||
@@ -527,14 +528,48 @@ file_write_left(struct client_files *files)
|
|||||||
return (waiting != 0);
|
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. */
|
/* Client file write error callback. */
|
||||||
static void
|
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)
|
void *arg)
|
||||||
{
|
{
|
||||||
struct client_file *cf = 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);
|
log_debug("write error file %d", cf->stream);
|
||||||
|
cf->error = error;
|
||||||
|
|
||||||
bufferevent_free(cf->event);
|
bufferevent_free(cf->event);
|
||||||
cf->event = NULL;
|
cf->event = NULL;
|
||||||
@@ -542,7 +577,9 @@ file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
|
|||||||
close(cf->fd);
|
close(cf->fd);
|
||||||
cf->fd = -1;
|
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);
|
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);
|
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);
|
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). */
|
/* 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)
|
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
|
||||||
fatalx("unknown stream number");
|
fatalx("unknown stream number");
|
||||||
log_debug("close file %d", cf->stream);
|
log_debug("close file %d", cf->stream);
|
||||||
|
cf->closed = 1;
|
||||||
|
|
||||||
if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) {
|
if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) {
|
||||||
if (cf->event != NULL)
|
file_write_finished(cf);
|
||||||
bufferevent_free(cf->event);
|
|
||||||
if (cf->fd != -1)
|
|
||||||
close(cf->fd);
|
|
||||||
RB_REMOVE(client_files, files, cf);
|
|
||||||
file_free(cf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -829,6 +857,28 @@ file_write_ready(struct client_files *files, struct imsg *imsg)
|
|||||||
return (0);
|
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). */
|
/* Handle read data message (server). */
|
||||||
int
|
int
|
||||||
file_read_data(struct client_files *files, struct imsg *imsg)
|
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>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1978,7 +1978,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx)
|
|||||||
screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
|
screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
|
||||||
break;
|
break;
|
||||||
case 2026:
|
case 2026:
|
||||||
screen_write_stop_sync(ictx->wp);
|
screen_write_end_sync(sctx);
|
||||||
break;
|
break;
|
||||||
case 2031:
|
case 2031:
|
||||||
screen_write_mode_clear(sctx, MODE_THEME_UPDATES);
|
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>
|
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -338,6 +338,7 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
|||||||
struct key_event saved_event;
|
struct key_event saved_event;
|
||||||
struct cmdq_state *state;
|
struct cmdq_state *state;
|
||||||
char *error;
|
char *error;
|
||||||
|
key_code key;
|
||||||
|
|
||||||
if (KEYC_IS_MOUSE(event->key)) {
|
if (KEYC_IS_MOUSE(event->key)) {
|
||||||
/*
|
/*
|
||||||
@@ -388,7 +389,8 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
|
|||||||
name = menu->items[i].name;
|
name = menu->items[i].name;
|
||||||
if (name == NULL || *name == '-')
|
if (name == NULL || *name == '-')
|
||||||
continue;
|
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;
|
md->choice = i;
|
||||||
goto chosen;
|
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>
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1334,7 +1334,7 @@ process_key:
|
|||||||
if (pr->index != size) {
|
if (pr->index != size) {
|
||||||
memmove(pr->buffer + pr->index,
|
memmove(pr->buffer + pr->index,
|
||||||
pr->buffer + pr->index + 1,
|
pr->buffer + pr->index + 1,
|
||||||
(size + 1 - pr->index) *
|
(size - pr->index) *
|
||||||
sizeof *pr->buffer);
|
sizeof *pr->buffer);
|
||||||
goto changed;
|
goto changed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,24 @@ drag()
|
|||||||
sleep 1
|
sleep 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meta_drag()
|
||||||
|
{
|
||||||
|
scol="$1"
|
||||||
|
srow="$2"
|
||||||
|
ecol="$3"
|
||||||
|
erow="$4"
|
||||||
|
|
||||||
|
seq=$(printf '\033[<8;%s;%sM' "$scol" "$srow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<40;%s;%sM' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 0.2
|
||||||
|
seq=$(printf '\033[<8;%s;%sm' "$ecol" "$erow")
|
||||||
|
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||||
|
sleep 1
|
||||||
|
}
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
|
|
||||||
check_ok new-session -d -s modal -x 80 -y 24 'cat'
|
check_ok new-session -d -s modal -x 80 -y 24 'cat'
|
||||||
@@ -304,5 +322,35 @@ sleep 1
|
|||||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||||
must_equal "$(fmt modal:0 '#{pane_id}')" "$p0"
|
must_equal "$(fmt modal:0 '#{pane_id}')" "$p0"
|
||||||
|
|
||||||
|
ignored=$($TMUX new-pane -KdPF '#{pane_id}' -t "$p0" 'cat') ||
|
||||||
|
fail "new-pane -K without -O failed"
|
||||||
|
check_ok kill-pane -t "$ignored"
|
||||||
|
|
||||||
|
$TMUX set -g @modal-prefix no
|
||||||
|
$TMUX set -g @modal-root no
|
||||||
|
$TMUX bind -n z set -g @modal-root yes
|
||||||
|
|
||||||
|
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
|
||||||
|
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||||
|
fail "new-pane -OK failed"
|
||||||
|
sleep 1
|
||||||
|
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||||
|
sleep 1
|
||||||
|
must_equal "$($TMUX show -gv @modal-prefix)" no
|
||||||
|
must_equal "$($TMUX show -gv @modal-root)" no
|
||||||
|
case "$($TMUX capture-pane -pt "$modal")" in
|
||||||
|
*xz*) ;;
|
||||||
|
*) fail "keys did not reach key-capturing modal pane" ;;
|
||||||
|
esac
|
||||||
|
left=$(fmt "$modal" '#{pane_left}')
|
||||||
|
top=$(fmt "$modal" '#{pane_top}')
|
||||||
|
meta_drag $((left + 2)) $((top + 2)) $((left + 7)) $((top + 4))
|
||||||
|
new_left=$(fmt "$modal" '#{pane_left}')
|
||||||
|
new_top=$(fmt "$modal" '#{pane_top}')
|
||||||
|
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||||
|
fail "key-capturing modal pane did not move"
|
||||||
|
check_ok kill-pane -t "$modal"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -269,6 +269,22 @@ test_customize_mode()
|
|||||||
assert_alive "customize-mode exit"
|
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()
|
test_copy_mode()
|
||||||
{
|
{
|
||||||
start_client copy-a 'i=0; while [ $i -lt 200 ]; do echo "copy mutation line $i"; i=$((i + 1)); done; cat'
|
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_buffer
|
||||||
test_choose_client
|
test_choose_client
|
||||||
test_customize_mode
|
test_customize_mode
|
||||||
|
test_customize_break_pane
|
||||||
test_copy_mode
|
test_copy_mode
|
||||||
cleanup
|
cleanup
|
||||||
exit 0
|
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>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1076,6 +1076,19 @@ screen_write_stop_sync(struct window_pane *wp)
|
|||||||
log_debug("%s: %%%u stopped sync mode", __func__, wp->id);
|
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. */
|
/* Cursor up by ny. */
|
||||||
void
|
void
|
||||||
screen_write_cursorup(struct screen_write_ctx *ctx, u_int ny)
|
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.504 2026/08/19 10:56:10 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -45,6 +45,7 @@ static void server_client_set_path(struct client *);
|
|||||||
static void server_client_set_progress_bar(struct client *);
|
static void server_client_set_progress_bar(struct client *);
|
||||||
static void server_client_reset_state(struct client *);
|
static void server_client_reset_state(struct client *);
|
||||||
static void server_client_update_latest(struct client *);
|
static void server_client_update_latest(struct client *);
|
||||||
|
static int server_client_handle_dead_key(struct window_pane *, key_code);
|
||||||
static void server_client_dispatch(struct imsg *, void *);
|
static void server_client_dispatch(struct imsg *, void *);
|
||||||
static int server_client_dispatch_command(struct client *, struct imsg *);
|
static int server_client_dispatch_command(struct client *, struct imsg *);
|
||||||
static int server_client_dispatch_identify(struct client *, struct imsg *);
|
static int server_client_dispatch_identify(struct client *, struct imsg *);
|
||||||
@@ -1388,6 +1389,21 @@ server_client_repeat_time(struct client *c, struct key_binding *bd)
|
|||||||
return (repeat);
|
return (repeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Handle a key press on a dead pane waiting for a key. */
|
||||||
|
static int
|
||||||
|
server_client_handle_dead_key(struct window_pane *wp, key_code key)
|
||||||
|
{
|
||||||
|
if (wp == NULL ||
|
||||||
|
(~wp->flags & PANE_EXITED) ||
|
||||||
|
KEYC_IS_MOUSE(key) ||
|
||||||
|
KEYC_IS_PASTE(key) ||
|
||||||
|
options_get_number(wp->options, "remain-on-exit") != 3)
|
||||||
|
return (0);
|
||||||
|
options_set_number(wp->options, "remain-on-exit", 0);
|
||||||
|
server_destroy_pane(wp, 0);
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle data key input from client. This owns and can modify the key event it
|
* Handle data key input from client. This owns and can modify the key event it
|
||||||
* is given and is responsible for freeing it.
|
* is given and is responsible for freeing it.
|
||||||
@@ -1473,6 +1489,14 @@ server_client_key_callback(struct cmdq_item *item, void *data)
|
|||||||
server_client_is_assume_paste(c))
|
server_client_is_assume_paste(c))
|
||||||
goto paste_key;
|
goto paste_key;
|
||||||
|
|
||||||
|
/* Forward keys directly if this pane is capturing all keys. */
|
||||||
|
if (wp != NULL &&
|
||||||
|
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
||||||
|
(~wp->flags & PANE_EXITED) &&
|
||||||
|
!KEYC_IS_MOUSE(key) &&
|
||||||
|
TAILQ_EMPTY(&wp->modes))
|
||||||
|
goto forward_key;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Work out the current key table. If the pane is in a mode, use
|
* Work out the current key table. If the pane is in a mode, use
|
||||||
* the mode table instead of the default key table.
|
* the mode table instead of the default key table.
|
||||||
@@ -1637,15 +1661,8 @@ try_again:
|
|||||||
}
|
}
|
||||||
|
|
||||||
forward_key:
|
forward_key:
|
||||||
if (wp != NULL &&
|
if (server_client_handle_dead_key(wp, key))
|
||||||
(wp->flags & PANE_EXITED) &&
|
|
||||||
!KEYC_IS_MOUSE(key) &&
|
|
||||||
!KEYC_IS_PASTE(key) &&
|
|
||||||
options_get_number(wp->options, "remain-on-exit") == 3) {
|
|
||||||
options_set_number(wp->options, "remain-on-exit", 0);
|
|
||||||
server_destroy_pane(wp, 0);
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
|
||||||
if (c->flags & CLIENT_READONLY)
|
if (c->flags & CLIENT_READONLY)
|
||||||
goto out;
|
goto out;
|
||||||
if (wp != NULL)
|
if (wp != NULL)
|
||||||
@@ -1733,9 +1750,9 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Key presses in overlay mode and the command prompt are a special
|
* Key presses in overlay mode, for panes capturing all keys and in the
|
||||||
* case. The queue might be blocked so they need to be processed
|
* command prompt are a special case. The queue might be blocked so they
|
||||||
* immediately rather than queued.
|
* need to be processed immediately rather than queued.
|
||||||
*/
|
*/
|
||||||
if (~c->flags & CLIENT_READONLY) {
|
if (~c->flags & CLIENT_READONLY) {
|
||||||
if (c->message_string != NULL) {
|
if (c->message_string != NULL) {
|
||||||
@@ -1755,6 +1772,21 @@ server_client_handle_key0(struct client *c, struct key_event *event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
server_client_clear_overlay(c);
|
server_client_clear_overlay(c);
|
||||||
|
|
||||||
|
wp = s->curw->window->active;
|
||||||
|
if (wp != NULL &&
|
||||||
|
(wp->flags & PANE_CAPTUREALLKEYS) &&
|
||||||
|
TAILQ_EMPTY(&wp->modes) &&
|
||||||
|
!KEYC_IS_MOUSE(event->key)) {
|
||||||
|
if (server_client_handle_dead_key(wp, event->key))
|
||||||
|
return (0);
|
||||||
|
if (~wp->flags & PANE_EXITED) {
|
||||||
|
window_pane_key(wp, c, s, s->curw, event->key,
|
||||||
|
&event->m);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (server_client_handle_menu_key(c, event))
|
if (server_client_handle_menu_key(c, event))
|
||||||
return (0);
|
return (0);
|
||||||
if (c->prompt != NULL) {
|
if (c->prompt != NULL) {
|
||||||
@@ -2695,6 +2727,10 @@ server_client_dispatch(struct imsg *imsg, void *arg)
|
|||||||
if (file_write_ready(&c->files, imsg) != 0)
|
if (file_write_ready(&c->files, imsg) != 0)
|
||||||
goto bad;
|
goto bad;
|
||||||
break;
|
break;
|
||||||
|
case MSG_WRITE_DONE:
|
||||||
|
if (file_write_done(&c->files, imsg) != 0)
|
||||||
|
goto bad;
|
||||||
|
break;
|
||||||
case MSG_READ:
|
case MSG_READ:
|
||||||
if (file_read_data(&c->files, imsg) != 0)
|
if (file_read_data(&c->files, imsg) != 0)
|
||||||
goto bad;
|
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>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -551,6 +551,7 @@ server_check_unattached(void)
|
|||||||
continue;
|
continue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
server_destroy_session(s);
|
||||||
session_destroy(s, 1, __func__);
|
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>
|
* 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);
|
mask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
|
||||||
if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
|
if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
|
||||||
saved_errno = errno;
|
saved_errno = errno;
|
||||||
|
umask(mask);
|
||||||
close(fd);
|
close(fd);
|
||||||
errno = saved_errno;
|
errno = saved_errno;
|
||||||
goto fail;
|
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>
|
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -243,7 +243,7 @@ struct window_pane *
|
|||||||
spawn_pane(struct spawn_context *sc, char **cause)
|
spawn_pane(struct spawn_context *sc, char **cause)
|
||||||
{
|
{
|
||||||
struct cmdq_item *item = sc->item;
|
struct cmdq_item *item = sc->item;
|
||||||
struct client *c;
|
struct client *c, *loop;
|
||||||
struct session *s = sc->s;
|
struct session *s = sc->s;
|
||||||
struct session *ts;
|
struct session *ts;
|
||||||
struct window *w = sc->wl->window;
|
struct window *w = sc->wl->window;
|
||||||
@@ -331,6 +331,20 @@ spawn_pane(struct spawn_context *sc, char **cause)
|
|||||||
input_free(sc->wp0->ictx);
|
input_free(sc->wp0->ictx);
|
||||||
sc->wp0->ictx = NULL;
|
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 = sc->wp0;
|
||||||
new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN);
|
new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN);
|
||||||
} else {
|
} 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>
|
* Copyright (c) 2021 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -67,7 +67,8 @@ enum msgtype {
|
|||||||
MSG_WRITE,
|
MSG_WRITE,
|
||||||
MSG_WRITE_READY,
|
MSG_WRITE_READY,
|
||||||
MSG_WRITE_CLOSE,
|
MSG_WRITE_CLOSE,
|
||||||
MSG_READ_CANCEL
|
MSG_READ_CANCEL,
|
||||||
|
MSG_WRITE_DONE
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -116,4 +117,9 @@ struct msg_write_close {
|
|||||||
int stream;
|
int stream;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct msg_write_done {
|
||||||
|
int stream;
|
||||||
|
int error;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* TMUX_PROTOCOL_H */
|
#endif /* TMUX_PROTOCOL_H */
|
||||||
|
|||||||
13
tmux.1
13
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.1157 2026/08/19 10:56:10 nicm Exp $
|
||||||
.\"
|
.\"
|
||||||
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
.\"
|
.\"
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||||
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
.\"
|
.\"
|
||||||
.Dd $Mdocdate: August 6 2026 $
|
.Dd $Mdocdate: August 19 2026 $
|
||||||
.Dt TMUX 1
|
.Dt TMUX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -3666,7 +3666,7 @@ but a different format may be specified with
|
|||||||
.Fl F .
|
.Fl F .
|
||||||
.Tg newp
|
.Tg newp
|
||||||
.It Xo Ic new\-pane
|
.It Xo Ic new\-pane
|
||||||
.Op Fl bCdefhIkLMOPvWZ
|
.Op Fl bCdefhIkKLMOPvWZ
|
||||||
.Op Fl B Ar border\-lines
|
.Op Fl B Ar border\-lines
|
||||||
.Op Fl c Ar start\-directory
|
.Op Fl c Ar start\-directory
|
||||||
.Op Fl e Ar environment
|
.Op Fl e Ar environment
|
||||||
@@ -3723,6 +3723,9 @@ A modal pane is always the active pane and prevents interaction with any other
|
|||||||
panes while it is active.
|
panes while it is active.
|
||||||
A window can only have one modal pane and it must be a floating pane.
|
A window can only have one modal pane and it must be a floating pane.
|
||||||
With
|
With
|
||||||
|
.Fl K ,
|
||||||
|
all keys, including the prefix key, are passed directly to the modal pane.
|
||||||
|
With
|
||||||
.Fl C ,
|
.Fl C ,
|
||||||
the modal pane is closed when the mouse is clicked outside it.
|
the modal pane is closed when the mouse is clicked outside it.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -5076,6 +5079,10 @@ This is an array option where each entry is a colon-separated string made up
|
|||||||
of a terminal type pattern (matched using
|
of a terminal type pattern (matched using
|
||||||
.Xr glob 7
|
.Xr glob 7
|
||||||
patterns) followed by a list of terminal features.
|
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:
|
The available features are:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It 256
|
.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>
|
* 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) {
|
while ((opt = getopt(argc, argv, "2c:CDdf:hlL:NqS:T:uUvV")) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case '2':
|
case '2':
|
||||||
tty_add_features(&feat, "256", ":,");
|
tty_parse_features("256", ":,", &feat, NULL);
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
shell_command = optarg;
|
shell_command = optarg;
|
||||||
@@ -473,7 +473,7 @@ main(int argc, char **argv)
|
|||||||
path = xstrdup(optarg);
|
path = xstrdup(optarg);
|
||||||
break;
|
break;
|
||||||
case 'T':
|
case 'T':
|
||||||
tty_add_features(&feat, optarg, ":,");
|
tty_parse_features(optarg, ":,", &feat, NULL);
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
flags |= CLIENT_UTF8;
|
flags |= CLIENT_UTF8;
|
||||||
|
|||||||
25
tmux.h
25
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.1428 2026/08/19 10:56:10 nicm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -1338,6 +1338,7 @@ struct window_pane {
|
|||||||
#define PANE_CMDRUNNING 0x20000
|
#define PANE_CMDRUNNING 0x20000
|
||||||
#define PANE_ACTIVITY 0x40000
|
#define PANE_ACTIVITY 0x40000
|
||||||
#define PANE_CLOSEONCLICK 0x80000
|
#define PANE_CLOSEONCLICK 0x80000
|
||||||
|
#define PANE_CAPTUREALLKEYS 0x100000
|
||||||
|
|
||||||
bitstr_t *sync_dirty;
|
bitstr_t *sync_dirty;
|
||||||
u_int sync_dirty_size;
|
u_int sync_dirty_size;
|
||||||
@@ -1721,7 +1722,7 @@ struct key_event {
|
|||||||
struct tty_term {
|
struct tty_term {
|
||||||
char *name;
|
char *name;
|
||||||
struct tty *tty;
|
struct tty *tty;
|
||||||
int features;
|
int applied_features;
|
||||||
|
|
||||||
char acs[UCHAR_MAX + 1][2];
|
char acs[UCHAR_MAX + 1][2];
|
||||||
|
|
||||||
@@ -1814,8 +1815,9 @@ struct tty {
|
|||||||
#define TTY_WAITFG 0x2000
|
#define TTY_WAITFG 0x2000
|
||||||
#define TTY_WAITBG 0x4000
|
#define TTY_WAITBG 0x4000
|
||||||
#define TTY_BRACKETPASTE 0x8000
|
#define TTY_BRACKETPASTE 0x8000
|
||||||
|
#define TTY_HAVESYNC 0x10000
|
||||||
#define TTY_ALL_REQUEST_FLAGS \
|
#define TTY_ALL_REQUEST_FLAGS \
|
||||||
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA)
|
(TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA|TTY_HAVESYNC)
|
||||||
int flags;
|
int flags;
|
||||||
|
|
||||||
struct tty_term *term;
|
struct tty_term *term;
|
||||||
@@ -2202,6 +2204,7 @@ struct client {
|
|||||||
|
|
||||||
char *term_name;
|
char *term_name;
|
||||||
int term_features;
|
int term_features;
|
||||||
|
int term_nofeatures;
|
||||||
char *term_type;
|
char *term_type;
|
||||||
char **term_caps;
|
char **term_caps;
|
||||||
u_int term_ncaps;
|
u_int term_ncaps;
|
||||||
@@ -2269,7 +2272,7 @@ struct client {
|
|||||||
/* 0x800000000ULL unused */
|
/* 0x800000000ULL unused */
|
||||||
#define CLIENT_BRACKETPASTING 0x1000000000ULL
|
#define CLIENT_BRACKETPASTING 0x1000000000ULL
|
||||||
#define CLIENT_ASSUMEPASTING 0x2000000000ULL
|
#define CLIENT_ASSUMEPASTING 0x2000000000ULL
|
||||||
/* 0x4000000000ULL unused */
|
#define CLIENT_WRITE_ACK 0x4000000000ULL
|
||||||
#define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL
|
#define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL
|
||||||
#define CLIENT_ALLREDRAWFLAGS \
|
#define CLIENT_ALLREDRAWFLAGS \
|
||||||
(CLIENT_REDRAWWINDOW| \
|
(CLIENT_REDRAWWINDOW| \
|
||||||
@@ -2975,8 +2978,7 @@ extern struct tty_terms tty_terms;
|
|||||||
u_int tty_term_ncodes(void);
|
u_int tty_term_ncodes(void);
|
||||||
void tty_term_apply(struct tty_term *, const char *, int);
|
void tty_term_apply(struct tty_term *, const char *, int);
|
||||||
void tty_term_apply_overrides(struct tty_term *);
|
void tty_term_apply_overrides(struct tty_term *);
|
||||||
struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, int *,
|
struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, char **);
|
||||||
char **);
|
|
||||||
void tty_term_free(struct tty_term *);
|
void tty_term_free(struct tty_term *);
|
||||||
int tty_term_read_list(const char *, int, char ***, u_int *,
|
int tty_term_read_list(const char *, int, char ***, u_int *,
|
||||||
char **);
|
char **);
|
||||||
@@ -2998,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);
|
const char *tty_term_describe(struct tty_term *, enum tty_code_code);
|
||||||
|
|
||||||
/* tty-features.c */
|
/* 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);
|
const char *tty_get_features(int);
|
||||||
int tty_feature_present(struct tty_term *, const char *);
|
int tty_feature_present(struct tty_term *, const char *);
|
||||||
int tty_apply_features(struct tty_term *, int);
|
int tty_apply_features(struct tty_term *);
|
||||||
void tty_default_features(int *, const char *, u_int);
|
void tty_default_features(struct client *, const char *, u_int);
|
||||||
|
|
||||||
/* tty-acs.c */
|
/* tty-acs.c */
|
||||||
int tty_acs_needed(struct tty *);
|
int tty_acs_needed(struct tty *);
|
||||||
@@ -3231,6 +3235,7 @@ void file_write_close(struct client_files *, struct imsg *);
|
|||||||
void file_read_open(struct client_files *, struct tmuxpeer *, struct imsg *,
|
void file_read_open(struct client_files *, struct tmuxpeer *, struct imsg *,
|
||||||
int, int, client_file_cb, void *);
|
int, int, client_file_cb, void *);
|
||||||
int file_write_ready(struct client_files *, struct imsg *);
|
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_data(struct client_files *, struct imsg *);
|
||||||
int file_read_done(struct client_files *, struct imsg *);
|
int file_read_done(struct client_files *, struct imsg *);
|
||||||
void file_read_cancel(struct client_files *, struct imsg *);
|
void file_read_cancel(struct client_files *, struct imsg *);
|
||||||
@@ -3548,6 +3553,7 @@ void screen_write_mode_set(struct screen_write_ctx *, int);
|
|||||||
void screen_write_mode_clear(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_start_sync(struct window_pane *);
|
||||||
void screen_write_stop_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_clear_dirty(struct window_pane *);
|
||||||
void screen_write_cursorup(struct screen_write_ctx *, u_int);
|
void screen_write_cursorup(struct screen_write_ctx *, u_int);
|
||||||
void screen_write_cursordown(struct screen_write_ctx *, u_int);
|
void screen_write_cursordown(struct screen_write_ctx *, u_int);
|
||||||
@@ -3983,6 +3989,7 @@ void control_set_pane_on(struct client *, struct window_pane *);
|
|||||||
void control_set_pane_off(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_continue_pane(struct client *, struct window_pane *);
|
||||||
void control_pause_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);
|
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 *);
|
int control_get_window_size(struct client *, u_int, u_int *, u_int *);
|
||||||
void control_clear_window_size(struct client *, 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>
|
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -393,17 +393,29 @@ static const struct tty_feature *const tty_features[] = {
|
|||||||
&tty_feature_usstyle
|
&tty_feature_usstyle
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Parse features for client. */
|
||||||
void
|
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;
|
const struct tty_feature *tf;
|
||||||
char *next, *loop, *copy;
|
char *next, *loop, *copy;
|
||||||
u_int i;
|
u_int i;
|
||||||
|
int remove;
|
||||||
|
|
||||||
log_debug("adding terminal features %s", s);
|
log_debug("adding terminal features %s", s);
|
||||||
|
|
||||||
loop = copy = xstrdup(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++) {
|
for (i = 0; i < nitems(tty_features); i++) {
|
||||||
tf = tty_features[i];
|
tf = tty_features[i];
|
||||||
if (strcasecmp(tf->name, next) == 0)
|
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);
|
log_debug("unknown terminal feature: %s", next);
|
||||||
break;
|
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);
|
log_debug("adding terminal feature: %s", tf->name);
|
||||||
(*feat) |= (1 << i);
|
(*enabled) |= (1 << i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(copy);
|
free(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get features as string. */
|
||||||
const char *
|
const char *
|
||||||
tty_get_features(int feat)
|
tty_get_features(int feat)
|
||||||
{
|
{
|
||||||
@@ -442,6 +464,7 @@ tty_get_features(int feat)
|
|||||||
return (s);
|
return (s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check if feature is present. */
|
||||||
int
|
int
|
||||||
tty_feature_present(struct tty_term *term, const char *name)
|
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++) {
|
for (i = 0; i < nitems(tty_features); i++) {
|
||||||
tf = tty_features[i];
|
tf = tty_features[i];
|
||||||
if (strcmp(tf->name, name) == 0) {
|
if (strcmp(tf->name, name) == 0) {
|
||||||
if (term->features & (1 << i))
|
if (term->applied_features & (1 << i))
|
||||||
return (1);
|
return (1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -481,19 +504,23 @@ tty_feature_present(struct tty_term *term, const char *name)
|
|||||||
return (1);
|
return (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Apply featurs to terminal. */
|
||||||
int
|
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 struct tty_feature *tf;
|
||||||
const char *const *capability;
|
const char *const *capability;
|
||||||
|
int feat;
|
||||||
u_int i;
|
u_int i;
|
||||||
|
|
||||||
|
feat = (c->term_features & ~c->term_nofeatures);
|
||||||
if (feat == 0)
|
if (feat == 0)
|
||||||
return (0);
|
return (0);
|
||||||
log_debug("applying terminal features: %s", tty_get_features(feat));
|
log_debug("applying terminal features: %s", tty_get_features(feat));
|
||||||
|
|
||||||
for (i = 0; i < nitems(tty_features); i++) {
|
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;
|
continue;
|
||||||
tf = tty_features[i];
|
tf = tty_features[i];
|
||||||
|
|
||||||
@@ -508,14 +535,15 @@ tty_apply_features(struct tty_term *term, int feat)
|
|||||||
}
|
}
|
||||||
term->flags |= tf->flags;
|
term->flags |= tf->flags;
|
||||||
}
|
}
|
||||||
if ((term->features | feat) == term->features)
|
if ((term->applied_features|feat) == term->applied_features)
|
||||||
return (0);
|
return (0);
|
||||||
term->features |= feat;
|
term->applied_features |= feat;
|
||||||
return (1);
|
return (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add default features for a terminal identified by name and version. */
|
||||||
void
|
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 {
|
static const struct {
|
||||||
const char *name;
|
const char *name;
|
||||||
@@ -596,6 +624,18 @@ tty_default_features(int *feat, const char *name, u_int version)
|
|||||||
"usstyle,"
|
"usstyle,"
|
||||||
"progressbar"
|
"progressbar"
|
||||||
},
|
},
|
||||||
|
{ .name = "Rio",
|
||||||
|
.features = TTY_FEATURES_BASE_MODERN_XTERM ","
|
||||||
|
"ccolour,"
|
||||||
|
"cstyle,"
|
||||||
|
"focus,"
|
||||||
|
"overline,"
|
||||||
|
"hyperlinks,"
|
||||||
|
"osc7,"
|
||||||
|
"sync,"
|
||||||
|
"usstyle,"
|
||||||
|
"progressbar"
|
||||||
|
},
|
||||||
{ .name = "XTerm",
|
{ .name = "XTerm",
|
||||||
/*
|
/*
|
||||||
* xterm also supports DECSLRM and DECFRA, but they can be
|
* 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;
|
continue;
|
||||||
if (version != 0 && version < table[i].version)
|
if (version != 0 && version < table[i].version)
|
||||||
continue;
|
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>
|
* 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 *);
|
size_t *);
|
||||||
static int tty_keys_extended_device_attributes(struct tty *, const char *,
|
static int tty_keys_extended_device_attributes(struct tty *, const char *,
|
||||||
size_t, size_t *);
|
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 *);
|
static int tty_keys_palette(struct tty *, const char *, size_t, size_t *);
|
||||||
|
|
||||||
/* A key tree entry. */
|
/* A key tree entry. */
|
||||||
@@ -771,6 +772,17 @@ tty_keys_next(struct tty *tty)
|
|||||||
goto partial_key;
|
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? */
|
/* Is this a primary device attributes response? */
|
||||||
switch (tty_keys_device_attributes(tty, buf, len, &size)) {
|
switch (tty_keys_device_attributes(tty, buf, len, &size)) {
|
||||||
case 0: /* yes */
|
case 0: /* yes */
|
||||||
@@ -1447,7 +1459,6 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
|
|||||||
size_t *size)
|
size_t *size)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
struct client *c = tty->client;
|
||||||
int *features = &c->term_features;
|
|
||||||
u_int i, n = 0;
|
u_int i, n = 0;
|
||||||
char tmp[128], *endptr, p[32] = { 0 }, *cp, *next;
|
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++) {
|
for (i = 1; i < n; i++) {
|
||||||
log_debug("%s: DA feature: %d", c->name, p[i]);
|
log_debug("%s: DA feature: %d", c->name, p[i]);
|
||||||
if (p[i] == 4)
|
if (p[i] == 4)
|
||||||
tty_add_features(features, "sixel", ",");
|
tty_parse_client_features(c, "sixel", ",");
|
||||||
if (p[i] == 21)
|
if (p[i] == 21)
|
||||||
tty_add_features(features, "margins", ",");
|
tty_parse_client_features(c, "margins", ",");
|
||||||
if (p[i] == 28)
|
if (p[i] == 28)
|
||||||
tty_add_features(features, "rectfill", ",");
|
tty_parse_client_features(c, "rectfill", ",");
|
||||||
if (p[i] == 52)
|
if (p[i] == 52)
|
||||||
tty_add_features(features, "clipboard", ",");
|
tty_parse_client_features(c, "clipboard", ",");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1522,6 +1533,54 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
|
|||||||
return (0);
|
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
|
* Handle secondary device attributes input. Returns 0 for success, -1 for
|
||||||
* failure, 1 for partial.
|
* failure, 1 for partial.
|
||||||
@@ -1531,7 +1590,6 @@ tty_keys_device_attributes2(struct tty *tty, const char *buf, size_t len,
|
|||||||
size_t *size)
|
size_t *size)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
struct client *c = tty->client;
|
||||||
int *features = &c->term_features;
|
|
||||||
u_int i, n = 0;
|
u_int i, n = 0;
|
||||||
char tmp[128], *endptr, p[32] = { 0 }, *cp, *next;
|
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]) {
|
switch (p[0]) {
|
||||||
case 'M': /* mintty */
|
case 'M': /* mintty */
|
||||||
tty_default_features(features, "mintty", 0);
|
tty_default_features(c, "mintty", 0);
|
||||||
break;
|
break;
|
||||||
case 'T': /* tmux */
|
case 'T': /* tmux */
|
||||||
tty_default_features(features, "tmux", 0);
|
tty_default_features(c, "tmux", 0);
|
||||||
break;
|
break;
|
||||||
case 'U': /* rxvt-unicode */
|
case 'U': /* rxvt-unicode */
|
||||||
tty_default_features(features, "rxvt-unicode", 0);
|
tty_default_features(c, "rxvt-unicode", 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
log_debug("%s: received secondary DA %.*s", c->name, (int)*size, buf);
|
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)
|
size_t len, size_t *size)
|
||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
struct client *c = tty->client;
|
||||||
int *features = &c->term_features;
|
|
||||||
u_int i;
|
u_int i;
|
||||||
char tmp[128];
|
char tmp[128];
|
||||||
|
|
||||||
@@ -1654,19 +1711,21 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf,
|
|||||||
|
|
||||||
/* Add terminal features. */
|
/* Add terminal features. */
|
||||||
if (strncmp(tmp, "iTerm2 ", 7) == 0)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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);
|
log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf);
|
||||||
|
|
||||||
free(c->term_type);
|
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>
|
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -544,8 +544,9 @@ tty_term_validate(struct tty_term *term)
|
|||||||
|
|
||||||
struct tty_term *
|
struct tty_term *
|
||||||
tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps,
|
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;
|
struct tty_term *term;
|
||||||
const struct tty_term_code_entry *ent;
|
const struct tty_term_code_entry *ent;
|
||||||
struct tty_code *code;
|
struct tty_code *code;
|
||||||
@@ -617,7 +618,7 @@ tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps,
|
|||||||
offset = 0;
|
offset = 0;
|
||||||
first = tty_term_override_next(s, &offset);
|
first = tty_term_override_next(s, &offset);
|
||||||
if (first != NULL && fnmatch(first, term->name, 0) == 0)
|
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);
|
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);
|
del_curterm(cur_term);
|
||||||
#endif
|
#endif
|
||||||
/* Check for COLORTERM. */
|
/* Check for COLORTERM. */
|
||||||
envent = environ_find(tty->client->environ, "COLORTERM");
|
envent = environ_find(c->environ, "COLORTERM");
|
||||||
if (envent != NULL) {
|
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 ||
|
if (strcasecmp(envent->value, "truecolor") == 0 ||
|
||||||
strcasecmp(envent->value, "24bit") == 0)
|
strcasecmp(envent->value, "24bit") == 0)
|
||||||
tty_add_features(feat, "RGB", ",");
|
tty_parse_client_features(c, "RGB", ",");
|
||||||
else if (strstr(envent->value, "256") != NULL)
|
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. */
|
/* 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);
|
s = tty_term_string(term, TTYC_CLEAR);
|
||||||
if (tty_term_flag(term, TTYC_XT) || strncmp(s, "\033[", 2) == 0) {
|
if (tty_term_flag(term, TTYC_XT) || strncmp(s, "\033[", 2) == 0) {
|
||||||
term->flags |= TERM_VT100LIKE;
|
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. */
|
/* Add RGB feature if terminal has RGB colours. */
|
||||||
if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) &&
|
if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) &&
|
||||||
(!tty_term_has(term, TTYC_SETRGBF) ||
|
(!tty_term_has(term, TTYC_SETRGBF) ||
|
||||||
!tty_term_has(term, TTYC_SETRGBB)))
|
!tty_term_has(term, TTYC_SETRGBB)))
|
||||||
tty_add_features(feat, "RGB", ",");
|
tty_parse_client_features(c, "RGB", ",");
|
||||||
|
|
||||||
/* Apply the features and overrides again. */
|
/* Apply the features and overrides again. */
|
||||||
if (tty_apply_features(term, *feat))
|
if (tty_apply_features(term))
|
||||||
tty_term_apply_overrides(term);
|
tty_term_apply_overrides(term);
|
||||||
|
|
||||||
/* Log the capabilities. */
|
/* 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>
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||||
@@ -277,7 +277,7 @@ tty_open(struct tty *tty, char **cause)
|
|||||||
struct client *c = tty->client;
|
struct client *c = tty->client;
|
||||||
|
|
||||||
tty->term = tty_term_create(tty, c->term_name, c->term_caps,
|
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) {
|
if (tty->term == NULL) {
|
||||||
tty_close(tty);
|
tty_close(tty);
|
||||||
return (-1);
|
return (-1);
|
||||||
@@ -403,6 +403,8 @@ tty_send_requests(struct tty *tty)
|
|||||||
tty_puts(tty, "\033[>c");
|
tty_puts(tty, "\033[>c");
|
||||||
if (~tty->flags & TTY_HAVEXDA)
|
if (~tty->flags & TTY_HAVEXDA)
|
||||||
tty_puts(tty, "\033[>q");
|
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_puts(tty, "\033]10;?\033\\\033]11;?\033\\");
|
||||||
tty->flags |= (TTY_WAITBG|TTY_WAITFG);
|
tty->flags |= (TTY_WAITBG|TTY_WAITFG);
|
||||||
} else
|
} else
|
||||||
@@ -536,7 +538,7 @@ tty_update_features(struct tty *tty)
|
|||||||
{
|
{
|
||||||
struct client *c = tty->client;
|
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);
|
tty_term_apply_overrides(tty->term);
|
||||||
|
|
||||||
if (tty_use_margin(tty))
|
if (tty_use_margin(tty))
|
||||||
|
|||||||
Reference in New Issue
Block a user