mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-05-29 21:38:03 -04:00
osc: implement OSC-5522, kitty's extended version of OSC-52
OSC-5522[^1] gives the terminal application access to all offered mime-types when reading the clipboard, and allows setting multiple mime-types when writing to the clipboard (with different contents, if it so wishes). In addition to the base protocol, we also implement the _event extension_[^2], where a paste action (e.g. ctrl+shift+v) results in an unsolicited mime-type listing being sent to the terminal application (as if it had issued a mime-type query itself), instead of the clipboard content being pasted directly. The application follows up with an explicit read request (or chooses to ignore the event). The protocol supports "passwords", as a way of bypassing terminal popups asking the user for approval, after the first popup has been approved by the user. Foot doesn't implement this kind of user approval, but all read and write requests are denied with EPERM if the user has disabled OSC copy/pasting with the security.osc52 configuration option. In addition, if disabled, event reporting cannot be enabled at all (i.e. 'CSI ? 5522 h' is ignored). [^1]: https://sw.kovidgoyal.net/kitty/clipboard/ [^2]: https://rockorager.dev/misc/bracketed-paste-mime/
This commit is contained in:
parent
4bc8a39d6c
commit
c366e322eb
13 changed files with 839 additions and 126 deletions
18
csi.c
18
csi.c
|
|
@ -576,6 +576,21 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
|
|||
term_disable_size_notifications(term);
|
||||
break;
|
||||
|
||||
case 5522:
|
||||
if (enable) {
|
||||
if (term_osc_paste_allowed(term))
|
||||
term->kitty_clipboard.emit_events = true;
|
||||
else {
|
||||
static bool have_warned = false;
|
||||
if (!have_warned) {
|
||||
have_warned = true;
|
||||
LOG_WARN("attempt to enable private mode 5522 ignored: disabled in configuration");
|
||||
}
|
||||
}
|
||||
} else
|
||||
term->kitty_clipboard.emit_events = false;
|
||||
break;
|
||||
|
||||
case 8452:
|
||||
term->sixel.cursor_right_of_graphics = enable;
|
||||
break;
|
||||
|
|
@ -665,6 +680,7 @@ decrqm(const struct terminal *term, unsigned param)
|
|||
: decrpm(term->grapheme_shaping);
|
||||
case 2031: return decrpm(term->report_theme_changes);
|
||||
case 2048: return decrpm(term->size_notifications);
|
||||
case 5522: return decrpm(term->kitty_clipboard.emit_events);
|
||||
case 8452: return decrpm(term->sixel.cursor_right_of_graphics);
|
||||
case 737769: return decrpm(term_ime_is_enabled(term));
|
||||
}
|
||||
|
|
@ -711,6 +727,7 @@ xtsave(struct terminal *term, unsigned param)
|
|||
case 2027: term->xtsave.grapheme_shaping = term->grapheme_shaping; break;
|
||||
case 2031: term->xtsave.report_theme_changes = term->report_theme_changes; break;
|
||||
case 2048: term->xtsave.size_notifications = term->size_notifications; break;
|
||||
case 5522: term->xtsave.kitty_clipboard = term->kitty_clipboard.emit_events; break;
|
||||
case 8452: term->xtsave.sixel_cursor_right_of_graphics = term->sixel.cursor_right_of_graphics; break;
|
||||
case 737769: term->xtsave.ime = term_ime_is_enabled(term); break;
|
||||
}
|
||||
|
|
@ -756,6 +773,7 @@ xtrestore(struct terminal *term, unsigned param)
|
|||
case 2027: enable = term->xtsave.grapheme_shaping; break;
|
||||
case 2031: enable = term->xtsave.report_theme_changes; break;
|
||||
case 2048: enable = term->xtsave.size_notifications; break;
|
||||
case 5522: enable = term->xtsave.kitty_clipboard; break;
|
||||
case 8452: enable = term->xtsave.sixel_cursor_right_of_graphics; break;
|
||||
case 737769: enable = term->xtsave.ime; break;
|
||||
|
||||
|
|
|
|||
21
input.c
21
input.c
|
|
@ -29,18 +29,15 @@
|
|||
#include "keymap.h"
|
||||
#include "kitty-keymap.h"
|
||||
#include "macros.h"
|
||||
#include "quirks.h"
|
||||
#include "osc.h"
|
||||
#include "render.h"
|
||||
#include "search.h"
|
||||
#include "selection.h"
|
||||
#include "spawn.h"
|
||||
#include "terminal.h"
|
||||
#include "tokenize.h"
|
||||
#include "unicode-mode.h"
|
||||
#include "url-mode.h"
|
||||
#include "util.h"
|
||||
#include "vt.h"
|
||||
#include "xkbcommon-vmod.h"
|
||||
#include "xmalloc.h"
|
||||
#include "xsnprintf.h"
|
||||
|
||||
|
|
@ -182,13 +179,21 @@ execute_binding(struct seat *seat, struct terminal *term,
|
|||
return true;
|
||||
|
||||
case BIND_ACTION_CLIPBOARD_PASTE:
|
||||
selection_from_clipboard(seat, term, serial);
|
||||
term_reset_view(term);
|
||||
if (likely(!term->kitty_clipboard.emit_events)) {
|
||||
selection_from_clipboard(seat, term, serial);
|
||||
term_reset_view(term);
|
||||
} else {
|
||||
kitty_clipboard_query(term, false);
|
||||
}
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_PRIMARY_PASTE:
|
||||
selection_from_primary(seat, term);
|
||||
term_reset_view(term);
|
||||
if (likely(!term->kitty_clipboard.emit_events)) {
|
||||
selection_from_primary(seat, term);
|
||||
term_reset_view(term);
|
||||
} else {
|
||||
kitty_clipboard_query(term, true);
|
||||
}
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SEARCH_START:
|
||||
|
|
|
|||
537
osc.c
537
osc.c
|
|
@ -51,24 +51,14 @@ osc_to_clipboard(struct terminal *term, const char *target,
|
|||
}
|
||||
|
||||
/* Find a seat in which the terminal has focus */
|
||||
struct seat *seat = NULL;
|
||||
tll_foreach(term->wl->seats, it) {
|
||||
if (it->item.kbd_focus == term) {
|
||||
seat = &it->item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct seat *seat = term_first_focused_seat(term);
|
||||
if (seat == NULL) {
|
||||
LOG_WARN("OSC52: client tried to write to clipboard data while window was unfocused");
|
||||
LOG_WARN("OSC-52: client tried to write to clipboard data while window was unfocused");
|
||||
return;
|
||||
}
|
||||
|
||||
const bool copy_allowed = term->conf->security.osc52 == OSC52_ENABLED
|
||||
|| term->conf->security.osc52 == OSC52_COPY_ENABLED;
|
||||
|
||||
if (!copy_allowed) {
|
||||
LOG_DBG("ignoring copy request: disabled in configuration");
|
||||
if (!term_osc_copy_allowed(term)) {
|
||||
LOG_DBG("OSC-52: ignoring copy request: disabled in configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -93,13 +83,13 @@ osc_to_clipboard(struct terminal *term, const char *target,
|
|||
|
||||
if (to_clipboard) {
|
||||
char *copy = xstrdup(decoded);
|
||||
if (!text_to_clipboard(seat, term, copy, seat->kbd.serial))
|
||||
if (!text_to_clipboard(seat, term, copy, NULL, seat->kbd.serial))
|
||||
free(copy);
|
||||
}
|
||||
|
||||
if (to_primary) {
|
||||
char *copy = xstrdup(decoded);
|
||||
if (!text_to_primary(seat, term, copy, seat->kbd.serial))
|
||||
if (!text_to_primary(seat, term, copy, NULL, seat->kbd.serial))
|
||||
free(copy);
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +101,14 @@ struct clip_context {
|
|||
struct terminal *term;
|
||||
uint8_t buf[3];
|
||||
int idx;
|
||||
|
||||
struct {
|
||||
bool is_kitty;
|
||||
bool from_primary;
|
||||
size_t chunk_bytes_written; /* Bytes written so far, in current chunk */
|
||||
char *encoded_mime_type; /* Currently "active" mime-type, base64 encoded */
|
||||
tll(char *) mime_types; /* Remaining mime-types */
|
||||
} kitty;
|
||||
};
|
||||
|
||||
static void
|
||||
|
|
@ -137,6 +135,7 @@ from_clipboard_cb(char *text, size_t size, void *user)
|
|||
term_paste_data_to_slave(term, chunk, 4);
|
||||
free(chunk);
|
||||
|
||||
ctx->kitty.chunk_bytes_written += 3;
|
||||
ctx->idx = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -146,18 +145,33 @@ from_clipboard_cb(char *text, size_t size, void *user)
|
|||
|
||||
xassert(ctx->idx == 0);
|
||||
|
||||
int remaining = left % 3;
|
||||
const int remaining = left % 3;
|
||||
for (int i = remaining; i > 0; i--)
|
||||
ctx->buf[ctx->idx++] = text[size - i];
|
||||
xassert(ctx->idx == remaining);
|
||||
|
||||
char *chunk = base64_encode((const uint8_t *)t, left / 3 * 3);
|
||||
const size_t count = left / 3 * 3;
|
||||
char *chunk = base64_encode(t, count);
|
||||
xassert(chunk != NULL);
|
||||
xassert(strlen(chunk) % 4 == 0);
|
||||
|
||||
if (unlikely(ctx->kitty.is_kitty && ctx->kitty.chunk_bytes_written + count > 4096)) {
|
||||
/* New chunk */
|
||||
term_paste_data_to_slave(term, "\033\\", 2);
|
||||
term_paste_data_to_slave(term, "\033]5522;type=read:status=DATA:mime=", 34);
|
||||
term_paste_data_to_slave(term, ctx->kitty.encoded_mime_type,
|
||||
strlen(ctx->kitty.encoded_mime_type));
|
||||
term_paste_data_to_slave(term, ";", 1);
|
||||
ctx->kitty.chunk_bytes_written = 0;
|
||||
}
|
||||
|
||||
term_paste_data_to_slave(term, chunk, strlen(chunk));
|
||||
ctx->kitty.chunk_bytes_written += count;
|
||||
free(chunk);
|
||||
}
|
||||
|
||||
static void kitty_clipboard_read_next_mime_type(struct clip_context *ctx);
|
||||
|
||||
static void
|
||||
from_clipboard_done(void *user)
|
||||
{
|
||||
|
|
@ -168,6 +182,7 @@ from_clipboard_done(void *user)
|
|||
char res[4];
|
||||
base64_encode_final(ctx->buf, ctx->idx, res);
|
||||
term_paste_data_to_slave(term, res, 4);
|
||||
ctx->idx = 0;
|
||||
}
|
||||
|
||||
if (term->vt.osc.bel)
|
||||
|
|
@ -175,12 +190,27 @@ from_clipboard_done(void *user)
|
|||
else
|
||||
term_paste_data_to_slave(term, "\033\\", 2);
|
||||
|
||||
if (ctx->kitty.is_kitty) {
|
||||
free(ctx->kitty.encoded_mime_type);
|
||||
ctx->kitty.encoded_mime_type = NULL;
|
||||
|
||||
/* Continue with the next mime-type, if there are any left */
|
||||
if (tll_length(ctx->kitty.mime_types) > 0) {
|
||||
kitty_clipboard_read_next_mime_type(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If not, we're done! */
|
||||
term_paste_data_to_slave(term, "\033]5522;type=read:status=DONE\033\\", 30);
|
||||
}
|
||||
|
||||
term->is_sending_paste_data = false;
|
||||
|
||||
/* Make sure we send any queued up non-paste data */
|
||||
if (tll_length(term->ptmx_buffers) > 0)
|
||||
fdm_event_add(term->fdm, term->ptmx, EPOLLOUT);
|
||||
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
|
|
@ -188,23 +218,14 @@ static void
|
|||
osc_from_clipboard(struct terminal *term, const char *source)
|
||||
{
|
||||
/* Find a seat in which the terminal has focus */
|
||||
struct seat *seat = NULL;
|
||||
tll_foreach(term->wl->seats, it) {
|
||||
if (it->item.kbd_focus == term) {
|
||||
seat = &it->item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct seat *seat = term_first_focused_seat(term);
|
||||
if (seat == NULL) {
|
||||
LOG_WARN("OSC52: client tried to read clipboard data while window was unfocused");
|
||||
LOG_WARN("OSC-52: client tried to read clipboard data while window was unfocused");
|
||||
return;
|
||||
}
|
||||
|
||||
const bool paste_allowed = term->conf->security.osc52 == OSC52_ENABLED
|
||||
|| term->conf->security.osc52 == OSC52_PASTE_ENABLED;
|
||||
if (!paste_allowed) {
|
||||
LOG_DBG("ignoring paste request: disabled in configuration");
|
||||
if (!term_osc_paste_allowed(term)) {
|
||||
LOG_DBG("OSC-52: ignoring paste request: disabled in configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -261,12 +282,12 @@ osc_from_clipboard(struct terminal *term, const char *source)
|
|||
|
||||
if (from_clipboard) {
|
||||
text_from_clipboard(
|
||||
seat, term, true, &from_clipboard_cb, &from_clipboard_done, ctx);
|
||||
seat, term, true, &from_clipboard_cb, &from_clipboard_done, ctx, NULL);
|
||||
}
|
||||
|
||||
if (from_primary) {
|
||||
text_from_primary(
|
||||
seat, term, true, &from_clipboard_cb, &from_clipboard_done, ctx);
|
||||
seat, term, true, &from_clipboard_cb, &from_clipboard_done, ctx, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -296,6 +317,453 @@ osc_selection(struct terminal *term, char *string)
|
|||
osc_to_clipboard(term, string, p);
|
||||
}
|
||||
|
||||
void
|
||||
kitty_clipboard_query(struct terminal *term, bool primary)
|
||||
{
|
||||
/*
|
||||
* Enumerate the available mime-types
|
||||
*/
|
||||
|
||||
LOG_DBG("OSC-5522: query mime-types: primary=%d", primary);
|
||||
|
||||
/* Find a seat in which the terminal has focus */
|
||||
struct seat *seat = term_first_focused_seat(term);
|
||||
if (seat == NULL) {
|
||||
LOG_WARN("OSC-5522: client tried to read clipboard data "
|
||||
"while window was unfocused");
|
||||
term_to_slave(term, "\033]5522;type=read:status=ENOSYS\033\\", 32);
|
||||
return;
|
||||
}
|
||||
|
||||
const mime_list_t *mime_list = !primary
|
||||
? &seat->clipboard.all_mime_types
|
||||
: &seat->primary.all_mime_types;
|
||||
|
||||
term_to_slave(term, "\033]5522;type=read:", 17);
|
||||
if (primary)
|
||||
term_to_slave(term, "loc=primary:", 12);
|
||||
term_to_slave(term, "status=OK\033\\", 11);
|
||||
term_to_slave(term, "\033]5522;type=read:status=DATA:mime=Lg==;", 39); /* base64(".") == "Lg==" */
|
||||
|
||||
char *mime_types = NULL;
|
||||
size_t len = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
/* Calculate total length of the mime list */
|
||||
tll_foreach(*mime_list, it)
|
||||
len += strlen(it->item) + 1;
|
||||
|
||||
mime_types = xmalloc(len + 1);
|
||||
mime_types[0] = '\0';
|
||||
|
||||
tll_foreach(*mime_list, it) {
|
||||
strcpy(mime_types + pos, it->item);
|
||||
pos += strlen(it->item);
|
||||
mime_types[pos++] = ' ';
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
/* Shave off the last ' ' */
|
||||
len--;
|
||||
mime_types[len] = '\0';
|
||||
} else {
|
||||
mime_types[0] = '\0';
|
||||
}
|
||||
|
||||
char *encoded_mime_list = base64_encode_oneshot(mime_types, len);
|
||||
term_to_slave(term, encoded_mime_list, strlen(encoded_mime_list));
|
||||
free(encoded_mime_list);
|
||||
|
||||
free(mime_types);
|
||||
term_to_slave(term, "\033\\", 2);
|
||||
term_to_slave(term, "\033]5522;type=read:status=DONE\033\\", 30);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
kitty_clipboard_read_next_mime_type(struct clip_context *ctx)
|
||||
{
|
||||
struct terminal *term = ctx->term;
|
||||
struct seat *seat = ctx->seat;
|
||||
|
||||
xassert(term->is_sending_paste_data);
|
||||
xassert(ctx->kitty.encoded_mime_type == NULL);
|
||||
xassert(tll_length(ctx->kitty.mime_types) > 0);
|
||||
|
||||
/* Pop next mime-type to read */
|
||||
char *mime_type = tll_pop_front(ctx->kitty.mime_types);
|
||||
const size_t mime_len = strlen(mime_type);
|
||||
|
||||
ctx->kitty.encoded_mime_type = base64_encode_oneshot(mime_type, mime_len);
|
||||
|
||||
term_paste_data_to_slave(term, "\033]5522;type=read:status=OK\033\\", 28);
|
||||
term_paste_data_to_slave(term, "\033]5522;type=read:status=DATA:mime=", 34);
|
||||
term_paste_data_to_slave(term, ctx->kitty.encoded_mime_type,
|
||||
strlen(ctx->kitty.encoded_mime_type));
|
||||
term_paste_data_to_slave(term, ";", 1);
|
||||
|
||||
if (!ctx->kitty.from_primary) {
|
||||
text_from_clipboard(
|
||||
seat, term, true, &from_clipboard_cb, &from_clipboard_done,
|
||||
ctx, mime_type);
|
||||
} else {
|
||||
text_from_primary(
|
||||
seat, term, true, &from_clipboard_cb, &from_clipboard_done,
|
||||
ctx, mime_type);
|
||||
}
|
||||
|
||||
free(mime_type);
|
||||
}
|
||||
|
||||
static void
|
||||
kitty_clipboard_read(struct terminal *term, bool primary, char *mime_types)
|
||||
{
|
||||
LOG_DBG("OSC-5522: read: primary=%d, mime-type=%s", primary, mime_types);
|
||||
|
||||
if (!term_osc_paste_allowed(term)) {
|
||||
LOG_DBG("OSC-5522: ignoring paste request: disabled in configuration");
|
||||
term_to_slave(term, "\033]5522;type=read:status=EPERM\033\\", 31);
|
||||
return;
|
||||
}
|
||||
|
||||
if (term->is_sending_paste_data) {
|
||||
term_to_slave(term, "\033]5522;type=read:status=EBUSY\033\\", 31);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find a seat in which the terminal has focus */
|
||||
struct seat *seat = term_first_focused_seat(term);
|
||||
if (seat == NULL) {
|
||||
LOG_WARN("OSC-5522: client tried to read clipboard data "
|
||||
"while window was unfocused");
|
||||
term_to_slave(term, "\033]5522;type=read:status=ENOSYS\033\\", 32);
|
||||
return;
|
||||
}
|
||||
|
||||
struct clip_context *ctx = xmalloc(sizeof(*ctx));
|
||||
*ctx = (struct clip_context) {
|
||||
.seat = seat,
|
||||
.term = term,
|
||||
.kitty = {
|
||||
.is_kitty = true,
|
||||
.from_primary = primary,
|
||||
.mime_types = tll_init(),
|
||||
},
|
||||
};
|
||||
|
||||
/* Split upt the space-separated list of mime-types we're about to read */
|
||||
for (char *save = NULL, *mime_type = strtok_r(mime_types, " ", &save);
|
||||
mime_type != NULL;
|
||||
mime_type = strtok_r(NULL, " ", &save))
|
||||
{
|
||||
tll_push_back(ctx->kitty.mime_types, xstrdup(mime_type));
|
||||
}
|
||||
|
||||
/* Start reading the first mime-type */
|
||||
term->is_sending_paste_data = true;
|
||||
kitty_clipboard_read_next_mime_type(ctx);
|
||||
}
|
||||
|
||||
void
|
||||
kitty_clipboard_reset(struct terminal *term)
|
||||
{
|
||||
free(term->kitty_clipboard.active_mime_type);
|
||||
free(term->kitty_clipboard.data);
|
||||
|
||||
tll_foreach(term->kitty_clipboard.committed_mime_data, it) {
|
||||
struct kitty_mime_data *mime_data = &it->item;
|
||||
free(mime_data->mime_type);
|
||||
free(mime_data->data);
|
||||
tll_remove(term->kitty_clipboard.committed_mime_data, it);
|
||||
}
|
||||
|
||||
tll_foreach(term->kitty_clipboard.mime_aliases, it) {
|
||||
struct kitty_mime_alias *alias = &it->item;
|
||||
free(alias->target);
|
||||
free(alias->alias);
|
||||
tll_remove(term->kitty_clipboard.mime_aliases, it);
|
||||
}
|
||||
|
||||
term->kitty_clipboard.for_primary = false;
|
||||
term->kitty_clipboard.has_error = false;
|
||||
term->kitty_clipboard.error = NULL;
|
||||
term->kitty_clipboard.active_mime_type = NULL;
|
||||
term->kitty_clipboard.data = NULL;
|
||||
term->kitty_clipboard.data_len = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
kitty_clipboard_wdata_commit(struct terminal *term)
|
||||
{
|
||||
if (term->kitty_clipboard.active_mime_type == NULL)
|
||||
return;
|
||||
|
||||
LOG_DBG("committing %zu bytes of %s data",
|
||||
term->kitty_clipboard.data_len,
|
||||
term->kitty_clipboard.active_mime_type);
|
||||
|
||||
tll_push_back(term->kitty_clipboard.committed_mime_data,
|
||||
((struct kitty_mime_data){
|
||||
.mime_type = term->kitty_clipboard.active_mime_type,
|
||||
.data = term->kitty_clipboard.data,
|
||||
.data_len = term->kitty_clipboard.data_len}));
|
||||
|
||||
term->kitty_clipboard.active_mime_type = NULL;
|
||||
term->kitty_clipboard.data = NULL;
|
||||
term->kitty_clipboard.data_len = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
kitty_clipboard_write_finish(struct terminal *term)
|
||||
{
|
||||
if (term->kitty_clipboard.has_error) {
|
||||
/* There were error(s) along the way - report the first error */
|
||||
char reply[64];
|
||||
int n = snprintf(reply, sizeof(reply),
|
||||
"\033]5522;type=write:status=%s\033\\",
|
||||
term->kitty_clipboard.error);
|
||||
term_to_slave(term, reply, n);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* No errors, now it's time to actually write to the clipboard
|
||||
*/
|
||||
|
||||
struct seat *seat = term_first_focused_seat(term);
|
||||
if (seat == NULL) {
|
||||
LOG_WARN("OSC-5522: client tried to write6 clipboard data "
|
||||
"while window was unfocused");
|
||||
term_to_slave(term, "\033]5522;type=write:status=ENOSYS\033\\", 33);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t data_count = tll_length(term->kitty_clipboard.committed_mime_data);
|
||||
const size_t mime_count = data_count + tll_length(term->kitty_clipboard.mime_aliases);
|
||||
|
||||
/* Package clipboard contents for text_to_{clipboard,primary}() */
|
||||
struct kitty_clipboard_offer clip;
|
||||
clip.data = xmalloc(data_count * sizeof(seat->clipboard.kitty.data[0]));
|
||||
clip.data_len = xmalloc(data_count * sizeof(seat->clipboard.kitty.data_len[0]));
|
||||
clip.data_count = data_count;
|
||||
clip.mime_data_map = xmalloc(mime_count * sizeof(seat->clipboard.kitty.mime_data_map[0]));
|
||||
clip.mime_data_map_count = mime_count;
|
||||
|
||||
size_t i = 0;
|
||||
tll_foreach(term->kitty_clipboard.committed_mime_data, it) {
|
||||
clip.data[i] = it->item.data;
|
||||
clip.data_len[i] = it->item.data_len;
|
||||
clip.mime_data_map[i].mime_type = it->item.mime_type;
|
||||
clip.mime_data_map[i].data_idx = i;
|
||||
|
||||
i++;
|
||||
|
||||
/* Remove without freeing, data is now owned by clip */
|
||||
tll_remove(term->kitty_clipboard.committed_mime_data, it);
|
||||
}
|
||||
|
||||
tll_foreach(term->kitty_clipboard.mime_aliases, it) {
|
||||
const struct kitty_mime_alias *alias = &it->item;
|
||||
|
||||
/* Find target index */
|
||||
ssize_t idx = -1;
|
||||
for (size_t j = 0; j < data_count; j++) {
|
||||
if (streq(clip.mime_data_map[j].mime_type, alias->target)) {
|
||||
idx = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
xassert(idx >= 0);
|
||||
xassert(idx < data_count);
|
||||
|
||||
clip.mime_data_map[i].mime_type = alias->alias;
|
||||
clip.mime_data_map[i].data_idx = idx;
|
||||
|
||||
/* TODO: can we make target point to the original mime-type, so that we don't have to free it here? */
|
||||
free(alias->target);
|
||||
tll_remove(term->kitty_clipboard.mime_aliases, it);
|
||||
}
|
||||
|
||||
if (!term->kitty_clipboard.for_primary)
|
||||
text_to_clipboard(seat, term, NULL, &clip, seat->kbd.serial);
|
||||
else
|
||||
text_to_primary(seat, term, NULL, &clip, seat->kbd.serial);
|
||||
|
||||
term_to_slave(term, "\033]5522;type=write:status=DONE\033\\", 31);
|
||||
}
|
||||
|
||||
static void
|
||||
kitty_clipboard(struct terminal *term, char *string)
|
||||
{
|
||||
char *ctx = NULL;
|
||||
char *params = strtok_r(string, ";", &ctx);
|
||||
char *encoded_payload = strtok_r(NULL, ";", &ctx);
|
||||
|
||||
enum { UNSPECIFIED, READ, WRITE, WDATA, WALIAS } type = UNSPECIFIED;
|
||||
enum { CLIPBOARD, PRIMARY } location = CLIPBOARD;
|
||||
const char *encoded_mime_type = NULL;
|
||||
|
||||
/* Parse the parameter (key/value pairs) section */
|
||||
ctx = NULL;
|
||||
for (char *p = strtok_r(params, ":", &ctx);
|
||||
p != NULL;
|
||||
p = strtok_r(NULL, ":", &ctx))
|
||||
{
|
||||
char *value = strchr(p, '=');
|
||||
if (value == NULL)
|
||||
continue;
|
||||
|
||||
*value = '\0';
|
||||
value++;
|
||||
|
||||
LOG_DBG("OSC-5522: param: %s=%s", p, value);
|
||||
|
||||
if (streq(p, "type")) {
|
||||
if (streq(value, "read"))
|
||||
type = READ;
|
||||
else if (streq(value, "write"))
|
||||
type = WRITE;
|
||||
else if (streq(value, "wdata"))
|
||||
type = WDATA;
|
||||
else if (streq(value, "walias"))
|
||||
type = WALIAS;
|
||||
else
|
||||
LOG_WARN("OSC-5522: invalid 'type': %s", value);
|
||||
}
|
||||
|
||||
else if (streq(p, "loc")) {
|
||||
if (streq(value, "primary"))
|
||||
location = PRIMARY;
|
||||
else
|
||||
LOG_WARN("OSC-5522: invalid 'loc': %s", value);
|
||||
}
|
||||
|
||||
else if (streq(p, "mime")) {
|
||||
encoded_mime_type = value;
|
||||
}
|
||||
|
||||
else if (streq(p, "pw"))
|
||||
;
|
||||
else if (streq(p, "name"))
|
||||
;
|
||||
else
|
||||
LOG_WARN("OSC-5522: unrecognized key/value parameter: %s=%s",
|
||||
p, value);
|
||||
}
|
||||
|
||||
if (type == UNSPECIFIED) {
|
||||
LOG_WARN("OSC-5522: 'type' not specified");
|
||||
/* Can't reply with status=EINVAL since we don't have a valid type */
|
||||
return;
|
||||
}
|
||||
|
||||
char *mime_type = encoded_mime_type != NULL
|
||||
? base64_decode(encoded_mime_type, NULL)
|
||||
: NULL;
|
||||
|
||||
size_t payload_len = 0;
|
||||
char *payload = encoded_payload != NULL
|
||||
? base64_decode(encoded_payload, &payload_len)
|
||||
: NULL;
|
||||
|
||||
switch (type) {
|
||||
case READ:
|
||||
if (unlikely(payload == NULL))
|
||||
term_to_slave(term, "\033]5522;type=read:status=EINVAL\033\\", 32);
|
||||
else {
|
||||
if (streq(payload, "."))
|
||||
kitty_clipboard_query(term, location == PRIMARY);
|
||||
else
|
||||
kitty_clipboard_read(term, location == PRIMARY, payload);
|
||||
}
|
||||
break;
|
||||
|
||||
case WRITE: {
|
||||
/* Reset */
|
||||
kitty_clipboard_reset(term);
|
||||
|
||||
if (!term_osc_copy_allowed(term)) {
|
||||
LOG_DBG("OSC-5522: ignoring copy request: disabled in configuration");
|
||||
term->kitty_clipboard.has_error = true;
|
||||
term->kitty_clipboard.error = "EPERM";
|
||||
} else if (term->is_sending_paste_data) {
|
||||
term->kitty_clipboard.has_error = true;
|
||||
term->kitty_clipboard.error = "EBUSY";
|
||||
} else
|
||||
term->kitty_clipboard.for_primary = location == PRIMARY;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WDATA:
|
||||
if (encoded_mime_type == NULL && encoded_payload == NULL) {
|
||||
/* This was the last packet */
|
||||
kitty_clipboard_wdata_commit(term);
|
||||
kitty_clipboard_write_finish(term);
|
||||
kitty_clipboard_reset(term);
|
||||
} else if (!term->kitty_clipboard.has_error) {
|
||||
if (unlikely(mime_type == NULL || payload == NULL)) {
|
||||
term->kitty_clipboard.has_error = true;
|
||||
term->kitty_clipboard.error = "EINVAL";
|
||||
} else {
|
||||
if (term->kitty_clipboard.active_mime_type == NULL ||
|
||||
!streq(term->kitty_clipboard.active_mime_type, mime_type))
|
||||
{
|
||||
/*
|
||||
* This chunk has a different mime-type than the
|
||||
* last one. We need to commit the last one, start
|
||||
* a new buffer for the new mime-type.
|
||||
*/
|
||||
|
||||
if (term->kitty_clipboard.active_mime_type != NULL)
|
||||
kitty_clipboard_wdata_commit(term);
|
||||
|
||||
term->kitty_clipboard.active_mime_type = mime_type;
|
||||
mime_type = NULL;
|
||||
}
|
||||
|
||||
const size_t old_len = term->kitty_clipboard.data_len;
|
||||
const size_t new_len = old_len + payload_len;
|
||||
term->kitty_clipboard.data = xrealloc(term->kitty_clipboard.data, new_len);
|
||||
|
||||
memcpy(&term->kitty_clipboard.data[old_len], payload, payload_len);
|
||||
term->kitty_clipboard.data_len = new_len;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case WALIAS:
|
||||
if (!term->kitty_clipboard.has_error) {
|
||||
if (unlikely(mime_type == NULL || payload == NULL)) {
|
||||
term->kitty_clipboard.has_error = true;
|
||||
term->kitty_clipboard.error = "EINVAL";
|
||||
} else {
|
||||
/* Aliases is a space separated list of mime-types */
|
||||
char *save = NULL;
|
||||
for (const char *alias = strtok_r(payload, " ", &save);
|
||||
alias != NULL;
|
||||
alias = strtok_r(NULL, " ", &save))
|
||||
{
|
||||
tll_push_back(
|
||||
term->kitty_clipboard.mime_aliases,
|
||||
((struct kitty_mime_alias){
|
||||
.target = xstrdup(mime_type),
|
||||
.alias = xstrdup(alias)}));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case UNSPECIFIED:
|
||||
BUG("'type' is unspecified");
|
||||
break;
|
||||
}
|
||||
|
||||
free(mime_type);
|
||||
free(payload);
|
||||
}
|
||||
|
||||
static void
|
||||
osc_flash(struct terminal *term)
|
||||
{
|
||||
|
|
@ -1501,6 +1969,9 @@ osc_dispatch(struct terminal *term)
|
|||
case 52: /* Copy to/from clipboard/primary */
|
||||
osc_selection(term, string);
|
||||
break;
|
||||
case 5522:
|
||||
kitty_clipboard(term, string);
|
||||
break;
|
||||
|
||||
case 66: /* text-size protocol (kitty) */
|
||||
kitty_text_size(term, string);
|
||||
|
|
|
|||
3
osc.h
3
osc.h
|
|
@ -5,3 +5,6 @@
|
|||
|
||||
bool osc_ensure_size(struct terminal *term, size_t required_size);
|
||||
void osc_dispatch(struct terminal *term);
|
||||
|
||||
void kitty_clipboard_reset(struct terminal *term);
|
||||
void kitty_clipboard_query(struct terminal *term, bool primary);
|
||||
|
|
|
|||
8
render.c
8
render.c
|
|
@ -3734,13 +3734,7 @@ render_search_box(struct terminal *term)
|
|||
|
||||
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||
/* TODO: do we want to/need to handle multi-seat? */
|
||||
struct seat *ime_seat = NULL;
|
||||
tll_foreach(term->wl->seats, it) {
|
||||
if (it->item.kbd_focus == term) {
|
||||
ime_seat = &it->item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
struct seat *ime_seat = term_first_focused_seat(term);
|
||||
|
||||
size_t text_len = term->search.len;
|
||||
if (ime_seat != NULL && ime_seat->ime.preedit.text != NULL)
|
||||
|
|
|
|||
4
search.c
4
search.c
|
|
@ -1375,13 +1375,13 @@ execute_binding(struct seat *seat, struct terminal *term,
|
|||
|
||||
case BIND_ACTION_SEARCH_CLIPBOARD_PASTE:
|
||||
text_from_clipboard(
|
||||
seat, term, false, &from_clipboard_cb, &from_clipboard_done, term);
|
||||
seat, term, false, &from_clipboard_cb, &from_clipboard_done, term, NULL);
|
||||
*update_search_result = *redraw = true;
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SEARCH_PRIMARY_PASTE:
|
||||
text_from_primary(
|
||||
seat, term, false, &from_clipboard_cb, &from_clipboard_done, term);
|
||||
seat, term, false, &from_clipboard_cb, &from_clipboard_done, term, NULL);
|
||||
*update_search_result = *redraw = true;
|
||||
return true;
|
||||
|
||||
|
|
|
|||
233
selection.c
233
selection.c
|
|
@ -1631,6 +1631,29 @@ selection_primary_has_data(const struct seat *seat)
|
|||
return seat->primary.data_offer != NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
clipboard_reset(struct wl_clipboard *clipboard)
|
||||
{
|
||||
|
||||
for (size_t i = 0; i < clipboard->kitty.data_count; i++)
|
||||
free(clipboard->kitty.data[i]);
|
||||
for (size_t i = 0; i < clipboard->kitty.mime_data_map_count; i++)
|
||||
free(clipboard->kitty.mime_data_map[i].mime_type);
|
||||
|
||||
free(clipboard->text);
|
||||
free(clipboard->kitty.data);
|
||||
free(clipboard->kitty.data_len);
|
||||
free(clipboard->kitty.mime_data_map);
|
||||
|
||||
clipboard->data_source = NULL;
|
||||
clipboard->serial = 0;
|
||||
clipboard->text = NULL;
|
||||
clipboard->kitty.data = NULL;
|
||||
clipboard->kitty.mime_data_map = NULL;
|
||||
clipboard->kitty.data_count = 0;
|
||||
clipboard->kitty.mime_data_map_count = 0;
|
||||
}
|
||||
|
||||
void
|
||||
selection_clipboard_unset(struct seat *seat)
|
||||
{
|
||||
|
|
@ -1643,12 +1666,30 @@ selection_clipboard_unset(struct seat *seat)
|
|||
xassert(clipboard->serial != 0);
|
||||
wl_data_device_set_selection(seat->data_device, NULL, clipboard->serial);
|
||||
wl_data_source_destroy(clipboard->data_source);
|
||||
clipboard_reset(clipboard);
|
||||
}
|
||||
|
||||
clipboard->data_source = NULL;
|
||||
clipboard->serial = 0;
|
||||
static void
|
||||
primary_reset(struct wl_primary *primary)
|
||||
{
|
||||
|
||||
free(clipboard->text);
|
||||
clipboard->text = NULL;
|
||||
for (size_t i = 0; i < primary->kitty.data_count; i++)
|
||||
free(primary->kitty.data[i]);
|
||||
for (size_t i = 0; i < primary->kitty.mime_data_map_count; i++)
|
||||
free(primary->kitty.mime_data_map[i].mime_type);
|
||||
|
||||
free(primary->text);
|
||||
free(primary->kitty.data);
|
||||
free(primary->kitty.data_len);
|
||||
free(primary->kitty.mime_data_map);
|
||||
|
||||
primary->data_source = NULL;
|
||||
primary->serial = 0;
|
||||
primary->text = NULL;
|
||||
primary->kitty.data = NULL;
|
||||
primary->kitty.mime_data_map = NULL;
|
||||
primary->kitty.data_count = 0;
|
||||
primary->kitty.mime_data_map_count = 0;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -1663,12 +1704,7 @@ selection_primary_unset(struct seat *seat)
|
|||
zwp_primary_selection_device_v1_set_selection(
|
||||
seat->primary_selection_device, NULL, primary->serial);
|
||||
zwp_primary_selection_source_v1_destroy(primary->data_source);
|
||||
|
||||
primary->data_source = NULL;
|
||||
primary->serial = 0;
|
||||
|
||||
free(primary->text);
|
||||
primary->text = NULL;
|
||||
primary_reset(primary);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
@ -1816,8 +1852,8 @@ done:
|
|||
}
|
||||
|
||||
static void
|
||||
send_clipboard_or_primary(struct seat *seat, int fd, const char *selection,
|
||||
const char *source_name)
|
||||
send_clipboard_or_primary(struct seat *seat, int fd, const uint8_t *selection,
|
||||
size_t len, const char *source_name)
|
||||
{
|
||||
/* Make it NONBLOCK:ing right away - we don't want to block if the
|
||||
* initial attempt to send the data synchronously fails */
|
||||
|
|
@ -1829,17 +1865,18 @@ send_clipboard_or_primary(struct seat *seat, int fd, const char *selection,
|
|||
return;
|
||||
}
|
||||
|
||||
size_t len = selection != NULL ? strlen(selection) : 0;
|
||||
size_t async_idx = 0;
|
||||
size_t async_idx = 0;
|
||||
|
||||
switch (async_write(fd, selection, len, &async_idx)) {
|
||||
case ASYNC_WRITE_REMAIN: {
|
||||
struct clipboard_send *ctx = xmalloc(sizeof(*ctx));
|
||||
*ctx = (struct clipboard_send) {
|
||||
.data = xstrdup(&selection[async_idx]),
|
||||
//.data = xstrdup(&selection[async_idx]),
|
||||
.data = xmalloc(len - async_idx),
|
||||
.len = len - async_idx,
|
||||
.idx = 0,
|
||||
};
|
||||
memcpy(ctx->data, &selection[async_idx], ctx->len);
|
||||
|
||||
if (fdm_add(seat->wayl->fdm, fd, EPOLLOUT, &fdm_send, ctx))
|
||||
return;
|
||||
|
|
@ -1868,7 +1905,21 @@ send(void *data, struct wl_data_source *wl_data_source, const char *mime_type,
|
|||
struct seat *seat = data;
|
||||
const struct wl_clipboard *clipboard = &seat->clipboard;
|
||||
|
||||
send_clipboard_or_primary(seat, fd, clipboard->text, "clipboard");
|
||||
if (clipboard->text != NULL) {
|
||||
send_clipboard_or_primary(
|
||||
seat, fd, (const uint8_t *)clipboard->text, strlen(clipboard->text), "clipboard");
|
||||
} else {
|
||||
for (size_t i = 0; i < clipboard->kitty.mime_data_map_count; i++) {
|
||||
const struct mime_data_map *map = &clipboard->kitty.mime_data_map[i];
|
||||
|
||||
if (streq(map->mime_type, mime_type)) {
|
||||
send_clipboard_or_primary(
|
||||
seat, fd, clipboard->kitty.data[map->data_idx],
|
||||
clipboard->kitty.data_len[map->data_idx], "clipboard");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1879,11 +1930,7 @@ cancelled(void *data, struct wl_data_source *wl_data_source)
|
|||
xassert(clipboard->data_source == wl_data_source);
|
||||
|
||||
wl_data_source_destroy(clipboard->data_source);
|
||||
clipboard->data_source = NULL;
|
||||
clipboard->serial = 0;
|
||||
|
||||
free(clipboard->text);
|
||||
clipboard->text = NULL;
|
||||
clipboard_reset(clipboard);
|
||||
}
|
||||
|
||||
/* We don't support dragging *from* */
|
||||
|
|
@ -1922,7 +1969,21 @@ primary_send(void *data,
|
|||
struct seat *seat = data;
|
||||
const struct wl_primary *primary = &seat->primary;
|
||||
|
||||
send_clipboard_or_primary(seat, fd, primary->text, "primary");
|
||||
if (primary->text != NULL) {
|
||||
send_clipboard_or_primary(
|
||||
seat, fd, (const uint8_t *)primary->text, strlen(primary->text), "primary");
|
||||
} else {
|
||||
for (size_t i = 0; i < primary->kitty.mime_data_map_count; i++) {
|
||||
const struct mime_data_map *map = &primary->kitty.mime_data_map[i];
|
||||
|
||||
if (streq(map->mime_type, mime_type)) {
|
||||
send_clipboard_or_primary(
|
||||
seat, fd, primary->kitty.data[map->data_idx],
|
||||
primary->kitty.data_len[map->data_idx], "primary");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1933,11 +1994,7 @@ primary_cancelled(void *data,
|
|||
struct wl_primary *primary = &seat->primary;
|
||||
|
||||
zwp_primary_selection_source_v1_destroy(primary->data_source);
|
||||
primary->data_source = NULL;
|
||||
primary->serial = 0;
|
||||
|
||||
free(primary->text);
|
||||
primary->text = NULL;
|
||||
primary_reset(primary);
|
||||
}
|
||||
|
||||
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
|
||||
|
|
@ -1946,12 +2003,15 @@ static const struct zwp_primary_selection_source_v1_listener primary_selection_s
|
|||
};
|
||||
|
||||
bool
|
||||
text_to_clipboard(struct seat *seat, struct terminal *term, char *text, uint32_t serial)
|
||||
text_to_clipboard(struct seat *seat, struct terminal *term, char *text,
|
||||
struct kitty_clipboard_offer *kitty_data, uint32_t serial)
|
||||
{
|
||||
if (text == NULL || text[0] == '\0')
|
||||
return false;
|
||||
|
||||
xassert(serial != 0);
|
||||
xassert((text != NULL && kitty_data == NULL) ||
|
||||
(text == NULL && kitty_data != NULL));
|
||||
|
||||
struct wl_clipboard *clipboard = &seat->clipboard;
|
||||
|
||||
|
|
@ -1960,11 +2020,7 @@ text_to_clipboard(struct seat *seat, struct terminal *term, char *text, uint32_t
|
|||
xassert(clipboard->serial != 0);
|
||||
wl_data_device_set_selection(seat->data_device, NULL, clipboard->serial);
|
||||
wl_data_source_destroy(clipboard->data_source);
|
||||
free(clipboard->text);
|
||||
|
||||
clipboard->data_source = NULL;
|
||||
clipboard->serial = 0;
|
||||
clipboard->text = NULL;
|
||||
clipboard_reset(clipboard);
|
||||
}
|
||||
|
||||
clipboard->data_source
|
||||
|
|
@ -1978,11 +2034,23 @@ text_to_clipboard(struct seat *seat, struct terminal *term, char *text, uint32_t
|
|||
clipboard->text = text;
|
||||
|
||||
/* Configure source */
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8]);
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_PLAIN]);
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_TEXT]);;
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_STRING]);
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8_STRING]);
|
||||
if (text != NULL) {
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8]);
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_PLAIN]);
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_TEXT]);;
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_STRING]);
|
||||
wl_data_source_offer(clipboard->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8_STRING]);
|
||||
} else {
|
||||
xassert(clipboard->kitty.data_count == 0);
|
||||
xassert(clipboard->kitty.mime_data_map_count == 0);
|
||||
|
||||
clipboard->kitty = *kitty_data;
|
||||
|
||||
for (size_t i = 0; i < clipboard->kitty.mime_data_map_count; i++) {
|
||||
wl_data_source_offer(
|
||||
clipboard->data_source, clipboard->kitty.mime_data_map[i].mime_type);
|
||||
}
|
||||
}
|
||||
|
||||
wl_data_source_add_listener(clipboard->data_source, &data_source_listener, seat);
|
||||
wl_data_device_set_selection(seat->data_device, clipboard->data_source, serial);
|
||||
|
|
@ -2000,7 +2068,7 @@ selection_to_clipboard(struct seat *seat, struct terminal *term, uint32_t serial
|
|||
|
||||
/* Get selection as a string */
|
||||
char *text = selection_to_text(term);
|
||||
if (!text_to_clipboard(seat, term, text, serial))
|
||||
if (!text_to_clipboard(seat, term, text, NULL, serial))
|
||||
free(text);
|
||||
}
|
||||
|
||||
|
|
@ -2339,11 +2407,11 @@ void
|
|||
text_from_clipboard(struct seat *seat, struct terminal *term,
|
||||
bool no_strip,
|
||||
void (*cb)(char *data, size_t size, void *user),
|
||||
void (*done)(void *user), void *user)
|
||||
void (*done)(void *user), void *user, const char *custom_mime_type)
|
||||
{
|
||||
struct wl_clipboard *clipboard = &seat->clipboard;
|
||||
if (clipboard->data_offer == NULL ||
|
||||
clipboard->mime_type == DATA_OFFER_MIME_UNSET)
|
||||
(clipboard->mime_type == DATA_OFFER_MIME_UNSET && custom_mime_type == NULL))
|
||||
{
|
||||
done(user);
|
||||
return;
|
||||
|
|
@ -2357,21 +2425,25 @@ text_from_clipboard(struct seat *seat, struct terminal *term,
|
|||
return;
|
||||
}
|
||||
|
||||
LOG_DBG("receive from clipboard: mime-type=%s",
|
||||
mime_type_map[clipboard->mime_type]);
|
||||
const char *mime_type = custom_mime_type == NULL
|
||||
? mime_type_map[clipboard->mime_type]
|
||||
: custom_mime_type;
|
||||
|
||||
LOG_DBG("receive from clipboard: mime-type=%s", mime_type);
|
||||
|
||||
int read_fd = fds[0];
|
||||
int write_fd = fds[1];
|
||||
|
||||
/* Give write-end of pipe to other client */
|
||||
wl_data_offer_receive(
|
||||
clipboard->data_offer, mime_type_map[clipboard->mime_type], write_fd);
|
||||
wl_data_offer_receive(clipboard->data_offer, mime_type, write_fd);
|
||||
|
||||
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
||||
close(write_fd);
|
||||
|
||||
begin_receive_clipboard(
|
||||
term, no_strip, read_fd, clipboard->mime_type, cb, done, user);
|
||||
term, no_strip, read_fd,
|
||||
custom_mime_type == NULL ? clipboard->mime_type : DATA_OFFER_MIME_TYPE_CUSTOM,
|
||||
cb, done, user);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2415,11 +2487,12 @@ selection_from_clipboard(struct seat *seat, struct terminal *term, uint32_t seri
|
|||
term_paste_data_to_slave(term, "\033[200~", 6);
|
||||
|
||||
text_from_clipboard(
|
||||
seat, term, false, &receive_offer, &receive_offer_done, term);
|
||||
seat, term, false, &receive_offer, &receive_offer_done, term, NULL);
|
||||
}
|
||||
|
||||
bool
|
||||
text_to_primary(struct seat *seat, struct terminal *term, char *text, uint32_t serial)
|
||||
text_to_primary(struct seat *seat, struct terminal *term, char *text,
|
||||
struct kitty_clipboard_offer *kitty_data, uint32_t serial)
|
||||
{
|
||||
if (text == NULL || text[0] == '\0')
|
||||
return false;
|
||||
|
|
@ -2428,6 +2501,8 @@ text_to_primary(struct seat *seat, struct terminal *term, char *text, uint32_t s
|
|||
return false;
|
||||
|
||||
xassert(serial != 0);
|
||||
xassert((text != NULL && kitty_data == NULL) ||
|
||||
(text == NULL && kitty_data != NULL));
|
||||
|
||||
struct wl_primary *primary = &seat->primary;
|
||||
|
||||
|
|
@ -2439,11 +2514,7 @@ text_to_primary(struct seat *seat, struct terminal *term, char *text, uint32_t s
|
|||
zwp_primary_selection_device_v1_set_selection(
|
||||
seat->primary_selection_device, NULL, primary->serial);
|
||||
zwp_primary_selection_source_v1_destroy(primary->data_source);
|
||||
free(primary->text);
|
||||
|
||||
primary->data_source = NULL;
|
||||
primary->serial = 0;
|
||||
primary->text = NULL;
|
||||
primary_reset(primary);
|
||||
}
|
||||
|
||||
primary->data_source
|
||||
|
|
@ -2459,11 +2530,26 @@ text_to_primary(struct seat *seat, struct terminal *term, char *text, uint32_t s
|
|||
primary->text = text;
|
||||
|
||||
/* Configure source */
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8]);
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_PLAIN]);
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_TEXT]);
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_STRING]);
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8_STRING]);
|
||||
if (text != NULL) {
|
||||
xassert(primary->kitty.data_count == 0);
|
||||
xassert(primary->kitty.mime_data_map_count == 0);
|
||||
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8]);
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_PLAIN]);
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_TEXT]);
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_STRING]);
|
||||
zwp_primary_selection_source_v1_offer(primary->data_source, mime_type_map[DATA_OFFER_MIME_TEXT_UTF8_STRING]);
|
||||
} else {
|
||||
xassert(primary->kitty.data_count == 0);
|
||||
xassert(primary->kitty.mime_data_map_count == 0);
|
||||
|
||||
primary->kitty = *kitty_data;
|
||||
|
||||
for (size_t i = 0; i < primary->kitty.mime_data_map_count; i++) {
|
||||
zwp_primary_selection_source_v1_offer(
|
||||
primary->data_source, primary->kitty.mime_data_map[i].mime_type);
|
||||
}
|
||||
}
|
||||
|
||||
zwp_primary_selection_source_v1_add_listener(primary->data_source, &primary_selection_source_listener, seat);
|
||||
zwp_primary_selection_device_v1_set_selection(seat->primary_selection_device, primary->data_source, serial);
|
||||
|
|
@ -2481,7 +2567,7 @@ selection_to_primary(struct seat *seat, struct terminal *term, uint32_t serial)
|
|||
|
||||
/* Get selection as a string */
|
||||
char *text = selection_to_text(term);
|
||||
if (!text_to_primary(seat, term, text, serial))
|
||||
if (!text_to_primary(seat, term, text, NULL, serial))
|
||||
free(text);
|
||||
}
|
||||
|
||||
|
|
@ -2489,7 +2575,7 @@ void
|
|||
text_from_primary(
|
||||
struct seat *seat, struct terminal *term, bool no_strip,
|
||||
void (*cb)(char *data, size_t size, void *user),
|
||||
void (*done)(void *user), void *user)
|
||||
void (*done)(void *user), void *user, const char *custom_mime_type)
|
||||
{
|
||||
if (term->wl->primary_selection_device_manager == NULL) {
|
||||
done(user);
|
||||
|
|
@ -2498,7 +2584,7 @@ text_from_primary(
|
|||
|
||||
struct wl_primary *primary = &seat->primary;
|
||||
if (primary->data_offer == NULL ||
|
||||
primary->mime_type == DATA_OFFER_MIME_UNSET)
|
||||
(primary->mime_type == DATA_OFFER_MIME_UNSET && custom_mime_type == NULL))
|
||||
{
|
||||
done(user);
|
||||
return;
|
||||
|
|
@ -2512,21 +2598,25 @@ text_from_primary(
|
|||
return;
|
||||
}
|
||||
|
||||
LOG_DBG("receive from primary: mime-type=%s",
|
||||
mime_type_map[primary->mime_type]);
|
||||
const char *mime_type = custom_mime_type == NULL
|
||||
? mime_type_map[primary->mime_type]
|
||||
: custom_mime_type;
|
||||
|
||||
LOG_DBG("receive from primary: mime-type=%s", mime_type);
|
||||
|
||||
int read_fd = fds[0];
|
||||
int write_fd = fds[1];
|
||||
|
||||
/* Give write-end of pipe to other client */
|
||||
zwp_primary_selection_offer_v1_receive(
|
||||
primary->data_offer, mime_type_map[primary->mime_type], write_fd);
|
||||
zwp_primary_selection_offer_v1_receive(primary->data_offer, mime_type, write_fd);
|
||||
|
||||
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
||||
close(write_fd);
|
||||
|
||||
begin_receive_clipboard(
|
||||
term, no_strip, read_fd, primary->mime_type, cb, done, user);
|
||||
term, no_strip, read_fd,
|
||||
custom_mime_type == NULL ? primary->mime_type : DATA_OFFER_MIME_TYPE_CUSTOM,
|
||||
cb, done, user);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -2549,7 +2639,7 @@ selection_from_primary(struct seat *seat, struct terminal *term)
|
|||
term_paste_data_to_slave(term, "\033[200~", 6);
|
||||
|
||||
text_from_primary(
|
||||
seat, term, false, &receive_offer, &receive_offer_done, term);
|
||||
seat, term, false, &receive_offer, &receive_offer_done, term, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2614,6 +2704,9 @@ select_mime_type_for_offer(const char *_mime_type,
|
|||
|
||||
case DATA_OFFER_MIME_UNSET:
|
||||
break;
|
||||
|
||||
case DATA_OFFER_MIME_TYPE_CUSTOM:
|
||||
BUG("the custom mime-type should never have been selected");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2627,6 +2720,7 @@ data_offer_reset(struct wl_clipboard *clipboard)
|
|||
|
||||
clipboard->window = NULL;
|
||||
clipboard->mime_type = DATA_OFFER_MIME_UNSET;
|
||||
tll_free_and_free(clipboard->all_mime_types, free);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2634,6 +2728,7 @@ offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type)
|
|||
{
|
||||
struct seat *seat = data;
|
||||
select_mime_type_for_offer(mime_type, &seat->clipboard.mime_type);
|
||||
tll_push_back(seat->clipboard.all_mime_types, xstrdup(mime_type));
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2844,6 +2939,7 @@ drop(void *data, struct wl_data_device *wl_data_device)
|
|||
/* data offer is now "owned" by the receive context */
|
||||
clipboard->data_offer = NULL;
|
||||
clipboard->mime_type = DATA_OFFER_MIME_UNSET;
|
||||
tll_free_and_free(clipboard->all_mime_types, free);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2875,6 +2971,7 @@ primary_offer(void *data,
|
|||
LOG_DBG("primary offer: %s", mime_type);
|
||||
struct seat *seat = data;
|
||||
select_mime_type_for_offer(mime_type, &seat->primary.mime_type);
|
||||
tll_push_back(seat->primary.all_mime_types, xstrdup(mime_type));
|
||||
}
|
||||
|
||||
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
|
||||
|
|
@ -2890,6 +2987,7 @@ primary_offer_reset(struct wl_primary *primary)
|
|||
}
|
||||
|
||||
primary->mime_type = DATA_OFFER_MIME_UNSET;
|
||||
tll_free_and_free(primary->all_mime_types, free);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2922,4 +3020,3 @@ const struct zwp_primary_selection_device_v1_listener primary_selection_device_l
|
|||
.data_offer = &primary_data_offer,
|
||||
.selection = &primary_selection,
|
||||
};
|
||||
|
||||
|
|
|
|||
10
selection.h
10
selection.h
|
|
@ -44,9 +44,11 @@ void selection_from_primary(struct seat *seat, struct terminal *term);
|
|||
|
||||
/* Copy text *to* primary/clipboard */
|
||||
bool text_to_clipboard(
|
||||
struct seat *seat, struct terminal *term, char *text, uint32_t serial);
|
||||
struct seat *seat, struct terminal *term, char *text,
|
||||
struct kitty_clipboard_offer *kitty_data, uint32_t serial);
|
||||
bool text_to_primary(
|
||||
struct seat *seat, struct terminal *term, char *text, uint32_t serial);
|
||||
struct seat *seat, struct terminal *term, char *text,
|
||||
struct kitty_clipboard_offer *kitty_data, uint32_t serial);
|
||||
|
||||
/*
|
||||
* Copy text *from* primary/clipboard
|
||||
|
|
@ -65,12 +67,12 @@ bool text_to_primary(
|
|||
void text_from_clipboard(
|
||||
struct seat *seat, struct terminal *term, bool no_strip,
|
||||
void (*cb)(char *data, size_t size, void *user),
|
||||
void (*done)(void *user), void *user);
|
||||
void (*done)(void *user), void *user, const char *custom_mime_type);
|
||||
|
||||
void text_from_primary(
|
||||
struct seat *seat, struct terminal *term, bool no_strip,
|
||||
void (*cb)(char *data, size_t size, void *user),
|
||||
void (*dont)(void *user), void *user);
|
||||
void (*dont)(void *user), void *user, const char *custom_mime_type);
|
||||
|
||||
void selection_start_scroll_timer(
|
||||
struct terminal *term, int interval_ns,
|
||||
|
|
|
|||
31
terminal.c
31
terminal.c
|
|
@ -33,7 +33,7 @@
|
|||
#include "ime.h"
|
||||
#include "input.h"
|
||||
#include "notify.h"
|
||||
#include "quirks.h"
|
||||
#include "osc.h"
|
||||
#include "reaper.h"
|
||||
#include "render.h"
|
||||
#include "selection.h"
|
||||
|
|
@ -1939,6 +1939,8 @@ term_destroy(struct terminal *term)
|
|||
for (size_t i = 0; i < ALEN(term->notification_icons); i++)
|
||||
notify_icon_free(&term->notification_icons[i]);
|
||||
|
||||
kitty_clipboard_reset(term);
|
||||
|
||||
sixel_fini(term);
|
||||
|
||||
term_ime_reset(term);
|
||||
|
|
@ -2167,6 +2169,8 @@ term_reset(struct terminal *term, bool hard)
|
|||
for (size_t i = 0; i < ALEN(term->notification_icons); i++)
|
||||
notify_icon_free(&term->notification_icons[i]);
|
||||
|
||||
kitty_clipboard_reset(term);
|
||||
|
||||
term->grapheme_shaping = term->conf->tweak.grapheme_shaping;
|
||||
|
||||
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||
|
|
@ -4830,3 +4834,28 @@ term_theme_get(const struct terminal *term)
|
|||
? &term->conf->colors_dark
|
||||
: &term->conf->colors_light;
|
||||
}
|
||||
|
||||
bool
|
||||
term_osc_paste_allowed(const struct terminal *term)
|
||||
{
|
||||
return term->conf->security.osc52 == OSC52_ENABLED
|
||||
|| term->conf->security.osc52 == OSC52_PASTE_ENABLED;
|
||||
}
|
||||
|
||||
bool
|
||||
term_osc_copy_allowed(const struct terminal *term)
|
||||
{
|
||||
return term->conf->security.osc52 == OSC52_ENABLED
|
||||
|| term->conf->security.osc52 == OSC52_COPY_ENABLED;
|
||||
}
|
||||
|
||||
struct seat *
|
||||
term_first_focused_seat(struct terminal *term)
|
||||
{
|
||||
tll_foreach(term->wl->seats, it) {
|
||||
if (it->item.kbd_focus == term)
|
||||
return &it->item;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
44
terminal.h
44
terminal.h
|
|
@ -398,6 +398,19 @@ struct colors {
|
|||
enum which_color_theme active_theme;
|
||||
};
|
||||
|
||||
struct kitty_mime_data {
|
||||
char *mime_type;
|
||||
uint8_t *data;
|
||||
size_t data_len;
|
||||
};
|
||||
typedef tll(struct kitty_mime_data) kitty_mime_data_list_t;
|
||||
|
||||
struct kitty_mime_alias {
|
||||
char *target;
|
||||
char *alias;
|
||||
};
|
||||
typedef tll(struct kitty_mime_alias) kitty_mime_alias_list_t;
|
||||
|
||||
struct terminal {
|
||||
struct fdm *fdm;
|
||||
struct reaper *reaper;
|
||||
|
|
@ -542,6 +555,7 @@ struct terminal {
|
|||
bool report_theme_changes:1;
|
||||
|
||||
bool size_notifications:1;
|
||||
bool kitty_clipboard:1;
|
||||
|
||||
bool sixel_display_mode:1;
|
||||
bool sixel_private_palette:1;
|
||||
|
|
@ -822,6 +836,30 @@ struct terminal {
|
|||
/* State, to handle chunked notifications */
|
||||
struct notification kitty_notification;
|
||||
|
||||
/* OSC-5522 */
|
||||
struct {
|
||||
bool emit_events; /* Enabled/disabled via private mode 5522 */
|
||||
|
||||
/*
|
||||
* State for setting the clipboard (OSC-5522;type=write|wdata|walias)
|
||||
*/
|
||||
bool for_primary;
|
||||
bool has_error; /* When set, all subsequent wdata|walias packets are ignored */
|
||||
const char *error; /* EIO|EINVAL|ENOSYS|EPERM|EBUSY */
|
||||
|
||||
/* Current mime-type being collected (via multiple wdata packets) */
|
||||
char *active_mime_type;
|
||||
uint8_t *data;
|
||||
size_t data_len;
|
||||
|
||||
/* mime-type aliases, collected via one or more walias packets */
|
||||
kitty_mime_alias_list_t mime_aliases;
|
||||
|
||||
/* Finished mime-types (will be written to the clipboard when
|
||||
* the final wdata packet is received) */
|
||||
kitty_mime_data_list_t committed_mime_data;
|
||||
} kitty_clipboard;
|
||||
|
||||
/* Currently active notifications, from foot's perspective (their
|
||||
notification helper processes are still running) */
|
||||
tll(struct notification) active_notifications;
|
||||
|
|
@ -990,6 +1028,12 @@ void term_theme_switch_to_light(struct terminal *term);
|
|||
void term_theme_toggle(struct terminal *term);
|
||||
const struct color_theme *term_theme_get(const struct terminal *term);
|
||||
|
||||
bool term_osc_paste_allowed(const struct terminal *term);
|
||||
bool term_osc_copy_allowed(const struct terminal *term);
|
||||
|
||||
/* Get (first) seat that is focusing this terminal instance */
|
||||
struct seat *term_first_focused_seat(struct terminal *term);
|
||||
|
||||
static inline void term_reset_grapheme_state(struct terminal *term)
|
||||
{
|
||||
#if defined(FOOT_GRAPHEME_CLUSTERING)
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ activate_url(struct seat *seat, struct terminal *term, const struct url *url,
|
|||
if (term->bracketed_paste)
|
||||
term_to_slave(term, "\033[201~", 6);
|
||||
}
|
||||
if (text_to_clipboard(seat, term, url_string, seat->kbd.serial)) {
|
||||
if (text_to_clipboard(seat, term, url_string, NULL, seat->kbd.serial)) {
|
||||
/* Now owned by our clipboard “manager” */
|
||||
url_string = NULL;
|
||||
}
|
||||
|
|
|
|||
18
wayland.c
18
wayland.c
|
|
@ -233,6 +233,24 @@ seat_destroy(struct seat *seat)
|
|||
free(seat->primary.text);
|
||||
free(seat->pointer.last_custom_xcursor);
|
||||
free(seat->name);
|
||||
|
||||
for (size_t i = 0; i < seat->clipboard.kitty.data_count; i++)
|
||||
free(seat->clipboard.kitty.data[i]);
|
||||
free(seat->clipboard.kitty.data);
|
||||
free(seat->clipboard.kitty.data_len);
|
||||
for (size_t i = 0; i < seat->clipboard.kitty.mime_data_map_count; i++)
|
||||
free(seat->clipboard.kitty.mime_data_map[i].mime_type);
|
||||
free(seat->clipboard.kitty.mime_data_map);
|
||||
tll_free_and_free(seat->clipboard.all_mime_types, free);
|
||||
|
||||
for (size_t i = 0; i < seat->primary.kitty.data_count; i++)
|
||||
free(seat->primary.kitty.data[i]);
|
||||
free(seat->primary.kitty.data);
|
||||
free(seat->primary.kitty.data_len);
|
||||
for (size_t i = 0; i < seat->primary.kitty.mime_data_map_count; i++)
|
||||
free(seat->primary.kitty.mime_data_map[i].mime_type);
|
||||
free(seat->primary.kitty.mime_data_map);
|
||||
tll_free_and_free(seat->primary.all_mime_types, free);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
36
wayland.h
36
wayland.h
|
|
@ -51,6 +51,8 @@ enum data_offer_mime_type {
|
|||
DATA_OFFER_MIME_TEXT_TEXT,
|
||||
DATA_OFFER_MIME_TEXT_STRING,
|
||||
DATA_OFFER_MIME_TEXT_UTF8_STRING,
|
||||
|
||||
DATA_OFFER_MIME_TYPE_CUSTOM,
|
||||
};
|
||||
|
||||
enum touch_state {
|
||||
|
|
@ -76,22 +78,52 @@ struct wayl_sub_surface {
|
|||
struct wl_subsurface *sub;
|
||||
};
|
||||
|
||||
typedef tll(char *) mime_list_t;
|
||||
struct mime_data_map {
|
||||
char *mime_type;
|
||||
size_t data_idx;
|
||||
};
|
||||
|
||||
/* OSC-5522: kitty's extended OSC-52, with explicit mime-type support */
|
||||
struct kitty_clipboard_offer {
|
||||
/*
|
||||
* - data[] is an array of contents
|
||||
* - data_len[] is an array of the same size as data[], each element
|
||||
* denoting the size of the corresponding data[] element
|
||||
* - mime_data_map[] is an array mapping mime-types to an entry in data[]
|
||||
*/
|
||||
uint8_t **data;
|
||||
size_t *data_len;
|
||||
size_t data_count;
|
||||
|
||||
struct mime_data_map *mime_data_map;
|
||||
size_t mime_data_map_count;
|
||||
};
|
||||
|
||||
|
||||
struct wl_window;
|
||||
struct wl_clipboard {
|
||||
struct wl_window *window; /* For DnD */
|
||||
struct wl_data_source *data_source;
|
||||
struct wl_data_offer *data_offer;
|
||||
enum data_offer_mime_type mime_type;
|
||||
enum data_offer_mime_type mime_type; /* Preferred mime-type */
|
||||
mime_list_t all_mime_types; /* List of all offered mime-types (for OSC-5522) */
|
||||
char *text;
|
||||
uint32_t serial;
|
||||
|
||||
struct kitty_clipboard_offer kitty;
|
||||
};
|
||||
|
||||
struct wl_primary {
|
||||
struct zwp_primary_selection_source_v1 *data_source;
|
||||
struct zwp_primary_selection_offer_v1 *data_offer;
|
||||
enum data_offer_mime_type mime_type;
|
||||
enum data_offer_mime_type mime_type; /* Preferred mime-type */
|
||||
mime_list_t all_mime_types; /* List of all offered mime-types (for OSC-5522) */
|
||||
|
||||
char *text;
|
||||
uint32_t serial;
|
||||
|
||||
struct kitty_clipboard_offer kitty;
|
||||
};
|
||||
|
||||
/* Maps a mouse button to its "owning" surface */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue