From 322adfbdde11c51af29def9ae85870c46bed1fd1 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 2 Dec 2025 08:20:32 +0000 Subject: [PATCH] Add a get-clipboard option which when enabled (the default is off) uses the same mechanism as palette requests to request clipboard from the terminal and forward to the requesting pane. Remove the now-redundant forward-to-pane ability from "refresh-client -l". GitHub issue 4275. --- cmd-refresh-client.c | 39 +++---------------- input.c | 91 +++++++++++++++++++++++++++++++++++++------- options-table.c | 15 ++++++++ tmux.1 | 35 +++++++++++++---- tmux.h | 9 ++++- tty-keys.c | 45 +++++++++------------- tty.c | 17 +++------ 7 files changed, 158 insertions(+), 93 deletions(-) diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index 399c83de..cb30749e 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -34,7 +34,7 @@ const struct cmd_entry cmd_refresh_client_entry = { .name = "refresh-client", .alias = "refresh", - .args = { "A:B:cC:Df:r:F:l::LRSt:U", 0, 1, NULL }, + .args = { "A:B:cC:Df:r:F:lLRSt:U", 0, 1, NULL }, .usage = "[-cDlLRSU] [-A pane:state] [-B name:what:format] " "[-C XxY] [-f flags] [-r pane:report] " CMD_TARGET_CLIENT_USAGE " [adjustment]", @@ -163,37 +163,6 @@ out: free(copy); } -static enum cmd_retval -cmd_refresh_client_clipboard(struct cmd *self, struct cmdq_item *item) -{ - struct args *args = cmd_get_args(self); - struct client *tc = cmdq_get_target_client(item); - const char *p; - u_int i; - struct cmd_find_state fs; - - p = args_get(args, 'l'); - if (p == NULL) { - if (tc->flags & CLIENT_CLIPBOARDBUFFER) - return (CMD_RETURN_NORMAL); - tc->flags |= CLIENT_CLIPBOARDBUFFER; - } else { - if (cmd_find_target(&fs, item, p, CMD_FIND_PANE, 0) != 0) - return (CMD_RETURN_ERROR); - for (i = 0; i < tc->clipboard_npanes; i++) { - if (tc->clipboard_panes[i] == fs.wp->id) - break; - } - if (i != tc->clipboard_npanes) - return (CMD_RETURN_NORMAL); - tc->clipboard_panes = xreallocarray(tc->clipboard_panes, - tc->clipboard_npanes + 1, sizeof *tc->clipboard_panes); - tc->clipboard_panes[tc->clipboard_npanes++] = fs.wp->id; - } - tty_clipboard_query(&tc->tty); - return (CMD_RETURN_NORMAL); -} - static void cmd_refresh_report(struct tty *tty, const char *value) { @@ -284,8 +253,10 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - if (args_has(args, 'l')) - return (cmd_refresh_client_clipboard(self, item)); + if (args_has(args, 'l')) { + tty_clipboard_query(&tc->tty); + return (CMD_RETURN_NORMAL); + } if (args_has(args, 'F')) /* -F is an alias for -f */ server_client_set_flags(tc, args_get(args, 'F')); diff --git a/input.c b/input.c index 139f9fd9..ce888887 100644 --- a/input.c +++ b/input.c @@ -3032,18 +3032,41 @@ input_osc_133(struct input_ctx *ictx, const char *p) } } +/* Handle OSC 52 reply. */ +static void +input_osc_52_reply(struct input_ctx *ictx) +{ + struct paste_buffer *pb; + int state; + const char *buf; + size_t len; + + state = options_get_number(global_options, "get-clipboard"); + if (state == 0) + return; + if (state == 1) { + if ((pb = paste_get_top(NULL)) == NULL) + return; + buf = paste_buffer_data(pb, &len); + if (ictx->input_end == INPUT_END_BEL) + input_reply_clipboard(ictx->event, buf, len, "\007"); + else + input_reply_clipboard(ictx->event, buf, len, "\033\\"); + return; + } + input_add_request(ictx, INPUT_REQUEST_CLIPBOARD, ictx->input_end); +} + /* Handle the OSC 52 sequence for setting the clipboard. */ static void input_osc_52(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; + size_t len; char *end; - const char *buf = NULL; - size_t len = 0; u_char *out; int outlen, state; struct screen_write_ctx ctx; - struct paste_buffer *pb; const char* allow = "cpqs01234567"; char flags[sizeof "cpqs01234567"] = ""; u_int i, j = 0; @@ -3068,12 +3091,7 @@ input_osc_52(struct input_ctx *ictx, const char *p) log_debug("%s: %.*s %s", __func__, (int)(end - p - 1), p, flags); if (strcmp(end, "?") == 0) { - if ((pb = paste_get_top(NULL)) != NULL) - buf = paste_buffer_data(pb, &len); - if (ictx->input_end == INPUT_END_BEL) - input_reply_clipboard(ictx->event, buf, len, "\007"); - else - input_reply_clipboard(ictx->event, buf, len, "\033\\"); + input_osc_52_reply(ictx); return; } @@ -3132,6 +3150,7 @@ input_osc_104(struct input_ctx *ictx, const char *p) free(copy); } +/* Send a clipboard reply. */ void input_reply_clipboard(struct bufferevent *bev, const char *buf, size_t len, const char *end) @@ -3268,6 +3287,9 @@ input_add_request(struct input_ctx *ictx, enum input_request_type type, int idx) xsnprintf(s, sizeof s, "\033]4;%d;?\033\\", idx); tty_puts(&c->tty, s); break; + case INPUT_REQUEST_CLIPBOARD: + tty_putcode_ss(&c->tty, TTYC_MS, "", "?"); + break; case INPUT_REQUEST_QUEUE: break; } @@ -3275,6 +3297,39 @@ input_add_request(struct input_ctx *ictx, enum input_request_type type, int idx) return (0); } +/* Handle a palette reply. */ +static void +input_request_palette_reply(struct input_request *ir, void *data) +{ + struct input_request_palette_data *pd = data; + + input_osc_colour_reply(ir->ictx, 0, 4, pd->idx, pd->c, ir->end); +} + +/* Handle a clipboard reply. */ +static void +input_request_clipboard_reply(struct input_request *ir, void *data) +{ + struct input_ctx *ictx = ir->ictx; + struct input_request_clipboard_data *cd = data; + int state; + char *copy; + + state = options_get_number(global_options, "get-clipboard"); + if (state == 0 || state == 1) + return; + if (state == 3) { + copy = xmalloc(cd->len); + memcpy(copy, cd->buf, cd->len); + paste_add(NULL, copy, cd->len); + } + + if (ir->idx == INPUT_END_BEL) + input_reply_clipboard(ictx->event, cd->buf, cd->len, "\007"); + else + input_reply_clipboard(ictx->event, cd->buf, cd->len, "\033\\"); +} + /* Handle a reply to a request. */ void input_request_reply(struct client *c, enum input_request_type type, void *data) @@ -3284,11 +3339,18 @@ input_request_reply(struct client *c, enum input_request_type type, void *data) int complete = 0; TAILQ_FOREACH_SAFE(ir, &c->input_requests, centry, ir1) { - if (ir->type == type && pd->idx == ir->idx) { + if (ir->type != type) { + input_free_request(ir); + continue; + } + if (type == INPUT_REQUEST_PALETTE && pd->idx == ir->idx) { + found = ir; + break; + } + if (type == INPUT_REQUEST_CLIPBOARD) { found = ir; break; } - input_free_request(ir); } if (found == NULL) return; @@ -3298,8 +3360,11 @@ input_request_reply(struct client *c, enum input_request_type type, void *data) break; if (ir->type == INPUT_REQUEST_QUEUE) input_send_reply(ir->ictx, ir->data); - else if (ir == found && ir->type == INPUT_REQUEST_PALETTE) { - input_osc_colour_reply(ir->ictx, 0, 4, pd->idx, pd->c, ir->end); + else if (ir == found) { + if (ir->type == INPUT_REQUEST_PALETTE) + input_request_palette_reply(ir, data); + else if (ir->type == INPUT_REQUEST_CLIPBOARD) + input_request_clipboard_reply(ir, data); complete = 1; } input_free_request(ir); diff --git a/options-table.c b/options-table.c index 980f8684..b328a57f 100644 --- a/options-table.c +++ b/options-table.c @@ -85,6 +85,9 @@ static const char *options_table_popup_border_lines_list[] = { static const char *options_table_set_clipboard_list[] = { "off", "external", "on", NULL }; +static const char *options_table_get_clipboard_list[] = { + "off", "buffer", "request", "both", NULL +}; static const char *options_table_window_size_list[] = { "largest", "smallest", "manual", "latest", NULL }; @@ -406,6 +409,18 @@ const struct options_table_entry options_table[] = { .text = "Whether to send focus events to applications." }, + { .name = "get-clipboard", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_SERVER, + .choices = options_table_get_clipboard_list, + .default_num = 1, + .text = "When an application requests the clipboard, whether to " + "ignore the request ('off'); respond with the newest buffer " + "('buffer'); request the clipboard from the most recently " + "used terminal ('request'); or to request the clipboard, " + "create a buffer, and send it to the application ('both')." + }, + { .name = "history-file", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, diff --git a/tmux.1 b/tmux.1 index 832c0881..dac3a648 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1361,12 +1361,11 @@ and sets an environment variable for the newly created session; it may be specified multiple times. .Tg refresh .It Xo Ic refresh-client -.Op Fl cDLRSU +.Op Fl cDlLRSU .Op Fl A Ar pane:state .Op Fl B Ar name:what:format .Op Fl C Ar size .Op Fl f Ar flags -.Op Fl l Op Ar target-pane .Op Fl r Ar pane:report .Op Fl t Ar target-client .Op Ar adjustment @@ -1487,11 +1486,7 @@ a colon, then a report escape sequence. .Fl l requests the clipboard from the client using the .Xr xterm 1 -escape sequence. -If -.Ar target-pane -is given, the clipboard is sent (in encoded form), otherwise it is stored in a -new paste buffer. +escape sequence and stores it in a new paste buffer. .Pp .Fl L , .Fl R , @@ -4239,6 +4234,32 @@ passed through to applications running in .Nm . Attached clients should be detached and attached again after changing this option. +.It Xo Ic get-clipboard +.Op Ic both | request | buffer | off +.Xc +Controls the behaviour when an application requests the clipboard from +.Nm . +.Pp +If +.Ic off , +the request is ignored; +if +.Ic buffer , +.Nm +responds with the newest paste buffer; +.Ic request +causes +.Nm +to request the clipboard from the most recently used client (if possible) and +send the reply (if any) back to the application; +.Ic buffer +is the same as +.Ic request +but also creates a paste buffer. +.Pp +See also the +.Ic set-clipboard +option. .It Ic history-file Ar path If not empty, a file to which .Nm diff --git a/tmux.h b/tmux.h index 170183e7..9cd11609 100644 --- a/tmux.h +++ b/tmux.h @@ -1098,6 +1098,7 @@ struct window_mode_entry { /* Type of request to client. */ enum input_request_type { INPUT_REQUEST_PALETTE, + INPUT_REQUEST_CLIPBOARD, INPUT_REQUEST_QUEUE }; @@ -1107,6 +1108,12 @@ struct input_request_palette_data { int c; }; +/* Clipboard request reply data. */ +struct input_request_clipboard_data { + char *buf; + size_t len; +}; + /* Request sent to client on behalf of pane. */ TAILQ_HEAD(input_requests, input_request); @@ -1975,7 +1982,7 @@ struct client { #define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL #define CLIENT_CONTROL_WAITEXIT 0x200000000ULL #define CLIENT_WINDOWSIZECHANGED 0x400000000ULL -#define CLIENT_CLIPBOARDBUFFER 0x800000000ULL +/* 0x800000000ULL unused */ #define CLIENT_BRACKETPASTING 0x1000000000ULL #define CLIENT_ASSUMEPASTING 0x2000000000ULL #define CLIENT_REDRAWSCROLLBARS 0x4000000000ULL diff --git a/tty-keys.c b/tty-keys.c index 77254591..7474621c 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1301,12 +1301,11 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, static int tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) { - struct client *c = tty->client; - struct window_pane *wp; - size_t end, terminator = 0, needed; - char *copy, *out; - int outlen; - u_int i; + struct client *c = tty->client; + size_t end, terminator = 0, needed; + char *copy, *out; + int outlen; + struct input_request_clipboard_data cd; *size = 0; @@ -1364,12 +1363,6 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) buf++; end--; - /* If we did not request this, ignore it. */ - if (~tty->flags & TTY_OSC52QUERY) - return (0); - tty->flags &= ~TTY_OSC52QUERY; - evtimer_del(&tty->clipboard_timer); - /* It has to be a string so copy it. */ copy = xmalloc(end + 1); memcpy(copy, buf, end); @@ -1384,22 +1377,22 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) return (0); } free(copy); - - /* Create a new paste buffer and forward to panes. */ log_debug("%s: %.*s", __func__, outlen, out); - if (c->flags & CLIENT_CLIPBOARDBUFFER) { - paste_add(NULL, out, outlen); - c->flags &= ~CLIENT_CLIPBOARDBUFFER; - } - for (i = 0; i < c->clipboard_npanes; i++) { - wp = window_pane_find_by_id(c->clipboard_panes[i]); - if (wp != NULL) - input_reply_clipboard(wp->event, out, outlen, "\033\\"); - } - free(c->clipboard_panes); - c->clipboard_panes = NULL; - c->clipboard_npanes = 0; + /* Set reply if any. */ + cd.buf = out; + cd.len = outlen; + input_request_reply(c, INPUT_REQUEST_CLIPBOARD, &cd); + + /* Create a buffer if requested. */ + if (tty->flags & TTY_OSC52QUERY) { + paste_add(NULL, out, outlen); + out = NULL; + evtimer_del(&tty->clipboard_timer); + tty->flags &= ~TTY_OSC52QUERY; + } + + free(out); return (0); } diff --git a/tty.c b/tty.c index cfff58c4..177d4953 100644 --- a/tty.c +++ b/tty.c @@ -3090,12 +3090,6 @@ static void tty_clipboard_query_callback(__unused int fd, __unused short events, void *data) { struct tty *tty = data; - struct client *c = tty->client; - - c->flags &= ~CLIENT_CLIPBOARDBUFFER; - free(c->clipboard_panes); - c->clipboard_panes = NULL; - c->clipboard_npanes = 0; tty->flags &= ~TTY_OSC52QUERY; } @@ -3105,10 +3099,9 @@ tty_clipboard_query(struct tty *tty) { struct timeval tv = { .tv_sec = TTY_QUERY_TIMEOUT }; - if ((~tty->flags & TTY_STARTED) || (tty->flags & TTY_OSC52QUERY)) - return; - tty_putcode_ss(tty, TTYC_MS, "", "?"); - - tty->flags |= TTY_OSC52QUERY; - evtimer_add(&tty->clipboard_timer, &tv); + if ((tty->flags & TTY_STARTED) && (~tty->flags & TTY_OSC52QUERY)) { + tty_putcode_ss(tty, TTYC_MS, "", "?"); + tty->flags |= TTY_OSC52QUERY; + evtimer_add(&tty->clipboard_timer, &tv); + } }