0
0
mirror of https://github.com/vim/vim.git synced 2025-10-28 09:27:14 -04:00

patch 9.1.1485: missing Wayland clipboard support

Problem:  missing Wayland clipboard support
Solution: make it work (Foxe Chen)

fixes: #5157
closes: #17097

Signed-off-by: Foxe Chen <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Foxe Chen
2025-06-27 21:10:35 +02:00
committed by Christian Brabandt
parent 03125277e9
commit b90c2395b2
68 changed files with 7520 additions and 226 deletions

View File

@@ -31,6 +31,32 @@
#if defined(FEAT_CLIPBOARD) || defined(PROTO)
#if defined(FEAT_WAYLAND_CLIPBOARD)
// Mime types we support sending and receiving
// Mimes with a lower index in the array are prioritized first when we are
// receiving data.
static const char *supported_mimes[] = {
VIMENC_ATOM_NAME,
VIM_ATOM_NAME,
"text/plain;charset=utf-8",
"text/plain",
"UTF8_STRING",
"STRING",
"TEXT"
};
static void clip_wl_receive_data(Clipboard_T *cbd,
const char *mime_type, int fd);
static void clip_wl_send_data(const char *mime_type, int fd,
wayland_selection_T);
static void clip_wl_selection_cancelled(wayland_selection_T selection);
#if defined(USE_SYSTEM) && defined(PROTO)
static int clip_wl_owner_exists(Clipboard_T *cbd);
#endif
#endif
/*
* Selection stuff using Visual mode, for cutting and pasting text to other
* windows.
@@ -50,6 +76,10 @@ clip_init(int can_use)
cb = &clip_star;
for (;;)
{
// No need to init again if cbd is already available
if (can_use && cb->available)
goto skip;
cb->available = can_use;
cb->owned = FALSE;
cb->start.lnum = 0;
@@ -58,6 +88,7 @@ clip_init(int can_use)
cb->end.col = 0;
cb->state = SELECT_CLEARED;
skip:
if (cb == &clip_plus)
break;
cb = &clip_plus;
@@ -109,13 +140,27 @@ clip_update_selection(Clipboard_T *clip)
static int
clip_gen_own_selection(Clipboard_T *cbd)
{
#ifdef FEAT_XCLIPBOARD
#if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD)
# ifdef FEAT_GUI
if (gui.in_use)
return clip_mch_own_selection(cbd);
else
# endif
return clip_xterm_own_selection(cbd);
{
if (clipmethod == CLIPMETHOD_WAYLAND)
{
#ifdef FEAT_WAYLAND_CLIPBOARD
return clip_wl_own_selection(cbd);
#endif
}
else if (clipmethod == CLIPMETHOD_X11)
{
#ifdef FEAT_XCLIPBOARD
return clip_xterm_own_selection(cbd);
#endif
}
}
return FAIL;
#else
return clip_mch_own_selection(cbd);
#endif
@@ -128,7 +173,7 @@ clip_own_selection(Clipboard_T *cbd)
* Also want to check somehow that we are reading from the keyboard rather
* than a mapping etc.
*/
#ifdef FEAT_X11
#if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD)
// Always own the selection, we might have lost it without being
// notified, e.g. during a ":sh" command.
if (cbd->available)
@@ -160,13 +205,26 @@ clip_own_selection(Clipboard_T *cbd)
static void
clip_gen_lose_selection(Clipboard_T *cbd)
{
#ifdef FEAT_XCLIPBOARD
#if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD)
# ifdef FEAT_GUI
if (gui.in_use)
clip_mch_lose_selection(cbd);
else
# endif
clip_xterm_lose_selection(cbd);
{
if (clipmethod == CLIPMETHOD_WAYLAND)
{
#ifdef FEAT_WAYLAND_CLIPBOARD
clip_wl_lose_selection(cbd);
#endif
}
else if (clipmethod == CLIPMETHOD_X11)
{
#ifdef FEAT_XCLIPBOARD
clip_xterm_lose_selection(cbd);
#endif
}
}
#else
clip_mch_lose_selection(cbd);
#endif
@@ -196,9 +254,9 @@ clip_lose_selection(Clipboard_T *cbd)
// windows on the current buffer.
if (was_owned
&& (get_real_state() == MODE_VISUAL
|| get_real_state() == MODE_SELECT)
|| get_real_state() == MODE_SELECT)
&& (cbd == &clip_star ?
clip_isautosel_star() : clip_isautosel_plus())
clip_isautosel_star() : clip_isautosel_plus())
&& HL_ATTR(HLF_V) != HL_ATTR(HLF_VNC)
&& !exiting)
{
@@ -1195,13 +1253,26 @@ clip_gen_set_selection(Clipboard_T *cbd)
return;
}
}
#ifdef FEAT_XCLIPBOARD
#if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD)
# ifdef FEAT_GUI
if (gui.in_use)
clip_mch_set_selection(cbd);
else
# endif
clip_xterm_set_selection(cbd);
{
if (clipmethod == CLIPMETHOD_WAYLAND)
{
#ifdef FEAT_WAYLAND_CLIPBOARD
clip_wl_set_selection(cbd);
#endif
}
else if (clipmethod == CLIPMETHOD_X11)
{
#ifdef FEAT_XCLIPBOARD
clip_xterm_set_selection(cbd);
#endif
}
}
#else
clip_mch_set_selection(cbd);
#endif
@@ -1210,13 +1281,26 @@ clip_gen_set_selection(Clipboard_T *cbd)
static void
clip_gen_request_selection(Clipboard_T *cbd)
{
#ifdef FEAT_XCLIPBOARD
#if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD)
# ifdef FEAT_GUI
if (gui.in_use)
clip_mch_request_selection(cbd);
else
# endif
clip_xterm_request_selection(cbd);
{
if (clipmethod == CLIPMETHOD_WAYLAND)
{
#ifdef FEAT_WAYLAND_CLIPBOARD
clip_wl_request_selection(cbd);
#endif
}
else if (clipmethod == CLIPMETHOD_X11)
{
#ifdef FEAT_XCLIPBOARD
clip_xterm_request_selection(cbd);
#endif
}
}
#else
clip_mch_request_selection(cbd);
#endif
@@ -1231,7 +1315,8 @@ clip_x11_owner_exists(Clipboard_T *cbd)
}
#endif
#if (defined(FEAT_X11) && defined(USE_SYSTEM)) || defined(PROTO)
#if ((defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD)) \
&& defined(USE_SYSTEM)) || defined(PROTO)
int
clip_gen_owner_exists(Clipboard_T *cbd UNUSED)
{
@@ -1241,7 +1326,22 @@ clip_gen_owner_exists(Clipboard_T *cbd UNUSED)
return clip_gtk_owner_exists(cbd);
else
# endif
return clip_x11_owner_exists(cbd);
{
if (clipmethod == CLIPMETHOD_WAYLAND)
{
#ifdef FEAT_WAYLAND_CLIPBOARD
return clip_wl_owner_exists(cbd);
#endif
}
else if (clipmethod == CLIPMETHOD_X11)
{
#ifdef FEAT_XCLIPBOARD
return clip_x11_owner_exists(cbd);
#endif
}
else
return FALSE;
}
#else
return TRUE;
#endif
@@ -2228,4 +2328,550 @@ adjust_clip_reg(int *rp)
}
}
#if defined(FEAT_WAYLAND_CLIPBOARD) || defined(PROTO)
/*
* Read data from a file descriptor and write it to the given clipboard.
*/
static void
clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd)
{
char_u *start, *buf, *tmp, *final, *enc;
int motion_type = MAUTO;
ssize_t r = 0;
size_t total = 0, max_total = 4096; // Initial buffer size, 4096
// bytes seems reasonable.
#ifndef HAVE_SELECT
struct pollfd pfd
pfd.fd = fd,
pfd.events = POLLIN
#else
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
tv.tv_sec = 0;
tv.tv_usec = p_wtm * 1000;
#endif
// Make pipe (read end) non-blocking
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1)
return;
if ((buf = alloc_clear(max_total)) == NULL)
return;
start = buf;
// Only poll before reading when we first start, then we do non-blocking
// reads and check for EAGAIN or EINTR to signal to poll again.
goto poll_data;
while (errno = 0, TRUE)
{
r = read(fd, start, max_total - 1 - total);
if (r == 0)
break;
else if (r < 0)
{
if (errno == EAGAIN || errno == EINTR)
{
poll_data:
#ifndef HAVE_SELECT
if (poll(&pfd, 1, p_wtm) > 0)
#else
if (select(fd + 1, &rfds, NULL, NULL, &tv) > 0)
#endif
continue;
}
break;
}
start += r;
total += (size_t)r;
// Realloc if we are at the end of the buffer
if (total >= max_total - 1)
{
tmp = vim_realloc(buf, max_total * 2);
if (tmp == NULL)
break;
max_total *= 2; // Double buffer size each time
buf = tmp;
start = buf + total;
// Zero out the newly allocated memory part
vim_memset(buf + total, 0, max_total - total);
}
}
if (total == 0)
{
clip_free_selection(cbd); // Nothing received, clear register
vim_free(buf);
return;
}
final = buf;
if (STRCMP(mime_type, VIM_ATOM_NAME) == 0 && total >= 2)
{
motion_type = *final++;;
total--;
}
else if (STRCMP(mime_type, VIMENC_ATOM_NAME) == 0 && total >= 3)
{
vimconv_T conv;
int convlen;
// first byte is motion type
motion_type = *final++;
total--;
// Get encoding of selection
enc = final;
// Skip the encoding type including null terminator in final text
final += STRLEN(final) + 1;
// Subtract pointers to get length of encoding;
total -= final - enc;
conv.vc_type = CONV_NONE;
convert_setup(&conv, enc, p_enc);
if (conv.vc_type != CONV_NONE)
{
convlen = total;
tmp = string_convert(&conv, final, &convlen);
total = convlen;
if (tmp != NULL)
final = tmp;
convert_setup(&conv, NULL, NULL);
}
}
clip_yank_selection(motion_type, final, (long)total, cbd);
vim_free(buf);
}
/*
* Get the current selection and fill the respective register for cbd with the
* data.
*/
void
clip_wl_request_selection(Clipboard_T *cbd)
{
wayland_selection_T selection;
garray_T *mime_types;
int len;
int fd;
const char *chosen_mime = NULL;
if (cbd == &clip_star)
selection = WAYLAND_SELECTION_PRIMARY;
else if (cbd == &clip_plus)
selection = WAYLAND_SELECTION_REGULAR;
else
return;
// Get mime types that the source client offers
mime_types = wayland_cb_get_mime_types(selection);
if (mime_types == NULL || mime_types->ga_len == 0)
{
// Selection is empty/cleared
clip_free_selection(cbd);
return;
}
len = ARRAY_LENGTH(supported_mimes);
// Loop through and pick the one we want to receive from
for (int i = 0; i < len && chosen_mime == NULL; i++)
{
for (int k = 0; k < mime_types->ga_len && chosen_mime == NULL; k++)
{
char *mime_type = ((char**)mime_types->ga_data)[k];
if (STRCMP(mime_type, supported_mimes[i]) == 0)
chosen_mime = supported_mimes[i];
}
}
if (chosen_mime == NULL)
return;
fd = wayland_cb_receive_data(chosen_mime, selection);
if (fd == -1)
return;
// Start reading the file descriptor returned
clip_wl_receive_data(cbd, chosen_mime, fd);
close(fd);
}
/*
* Write data from either the clip or plus register, depending on the given
* selection, to the file descriptor that the receiving client will read from.
*/
static void
clip_wl_send_data(
const char *mime_type,
int fd,
wayland_selection_T selection)
{
Clipboard_T *cbd;
long_u length;
char_u *string;
ssize_t written = 0;
size_t total = 0;
int did_vimenc = TRUE;
int did_motion_type = TRUE;
int motion_type;
int skip_len_check = FALSE;
#ifndef HAVE_SELECT
struct pollfd pfd
pfd.fd = fd,
pfd.events = POLLOUT
#else
fd_set wfds;
struct timeval tv;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
tv.tv_sec = 0;
tv.tv_usec = p_wtm * 1000;
#endif
if (selection == WAYLAND_SELECTION_REGULAR)
cbd = &clip_plus;
else if (selection == WAYLAND_SELECTION_PRIMARY)
cbd = &clip_star;
else
return;
// Shouldn't happen unless there is a bug.
if (!cbd->owned)
return;
// Get the current selection
clip_get_selection(cbd);
motion_type = clip_convert_selection(&string, &length, cbd);
if (motion_type < 0)
goto exit;
if (STRCMP(mime_type, VIMENC_ATOM_NAME) == 0)
{
did_vimenc = FALSE;
did_motion_type = FALSE;
}
else if (STRCMP(mime_type, VIM_ATOM_NAME) == 0)
did_motion_type = FALSE;
while ((total < (size_t)length || skip_len_check) &&
#ifndef HAVE_SELECT
poll(&pfd, 1, p_wtm) > 0)
#else
select(fd + 1, NULL, &wfds, NULL, &tv) > 0)
#endif
{
// First byte sent is motion type for vim specific formats
if (!did_motion_type)
{
if (total == 1)
{
total = 0;
did_motion_type = TRUE;
continue;
}
// We cast to char so that we only send one byte
written = write( fd, (char_u*)&motion_type, 1);
skip_len_check = TRUE;
}
else if (!did_vimenc)
{
// For the vimenc format, after the first byte is the encoding type,
// which is null terminated. Make sure we write that before writing
// the actual selection.
if (total == STRLEN(p_enc) + 1)
{
total = 0;
did_vimenc = TRUE;
continue;
}
// Include null terminator
written = write(fd, p_enc + total, STRLEN(p_enc) + 1 - total);
skip_len_check = TRUE;
}
else
{
// write the actual selection to the fd
written = write(fd, string + total, length - total);
if (skip_len_check)
skip_len_check = FALSE;
}
if (written == -1)
break;
total += written;
}
exit:
vim_free(string);
}
/*
* Called if another client gains ownership of the given selection. If so then
* lose the selection internally.
*/
static void
clip_wl_selection_cancelled(wayland_selection_T selection)
{
if (selection == WAYLAND_SELECTION_REGULAR)
clip_lose_selection(&clip_plus);
else if (selection == WAYLAND_SELECTION_PRIMARY)
clip_lose_selection(&clip_star);
}
/*
* Own the selection that cbd corresponds to. Start listening for requests from
* other Wayland clients so they can receive data from us. Returns OK on success
* and FAIL on failure.
*/
int
clip_wl_own_selection(Clipboard_T *cbd)
{
wayland_selection_T selection;
if (cbd == &clip_star)
selection = WAYLAND_SELECTION_PRIMARY;
else if (cbd == &clip_plus)
selection = WAYLAND_SELECTION_REGULAR;
else
return FAIL;
return wayland_cb_own_selection(
clip_wl_send_data,
clip_wl_selection_cancelled,
supported_mimes,
sizeof(supported_mimes)/sizeof(*supported_mimes),
selection);
}
/*
* Disown the selection that cbd corresponds to. Note that the the cancelled
* event is not sent when the data source is destroyed.
*/
void
clip_wl_lose_selection(Clipboard_T *cbd)
{
if (cbd == &clip_plus)
wayland_cb_lose_selection(WAYLAND_SELECTION_REGULAR);
else if (cbd == &clip_star)
wayland_cb_lose_selection(WAYLAND_SELECTION_PRIMARY);
/* wayland_cb_lose_selection(selection); */
}
/*
* Send the current selection to the clipboard. Do nothing for wayland because
* we will fill in the selection only when requested by another client.
*/
void
clip_wl_set_selection(Clipboard_T *cbd UNUSED)
{
}
#if defined(USE_SYSTEM) && defined(PROTO)
/*
* Return TRUE if we own the selection corresponding to cbd
*/
static int
clip_wl_owner_exists(Clipboard_T *cbd)
{
if (cbd == &clip_plus)
return wayland_cb_selection_is_owned(WAYLAND_SELECTION_REGULAR);
else if (cbd == &clip_star)
return wayland_cb_selection_is_owned(WAYLAND_SELECTION_PRIMARY);
}
#endif
#endif // FEAT_WAYLAND_CLIPBOARD
/*
* Returns the first method for accessing the clipboard that is available/works,
* depending on the order of values in str.
*/
static clipmethod_T
get_clipmethod(char_u *str)
{
int len = (int)STRLEN(str) + 1;
char_u *buf = alloc(len);
if (buf == NULL)
return CLIPMETHOD_FAIL;
clipmethod_T ret = CLIPMETHOD_FAIL;
char_u *p = str;
while (*p != NUL)
{
clipmethod_T method = CLIPMETHOD_NONE;
(void)copy_option_part(&p, buf, len, ",");
if (STRCMP(buf, "wayland") == 0)
{
#ifdef FEAT_WAYLAND_CLIPBOARD
if (wayland_cb_is_ready())
method = CLIPMETHOD_WAYLAND;
#endif
}
else if (STRCMP(buf, "x11") == 0)
{
#ifdef FEAT_XCLIPBOARD
// x_IOerror_handler() in os_unix.c should set xterm_dpy to NULL if
// we lost connection to the X server.
if (xterm_dpy != NULL)
{
// If the X connection is lost then that handler will longjmp
// somewhere else, in that case we will call choose_clipmethod()
// again from there, and this if block won't be executed since
// xterm_dpy will be set to NULL.
xterm_update();
method = CLIPMETHOD_X11;
}
#endif
}
else
{
ret = CLIPMETHOD_FAIL;
goto exit;
}
// Keep on going in order to catch errors
if (method != CLIPMETHOD_NONE && ret == CLIPMETHOD_FAIL)
ret = method;
}
// No match found, use "none".
ret = (ret == CLIPMETHOD_FAIL) ? CLIPMETHOD_NONE : ret;
exit:
vim_free(buf);
return ret;
}
/*
* Returns name of clipmethod in a statically allocated string.
*/
static char *
clipmethod_to_str(clipmethod_T method)
{
switch(method)
{
case CLIPMETHOD_WAYLAND:
return "wayland";
case CLIPMETHOD_X11:
return "x11";
default:
return "none";
}
}
/*
* Sets the current clipmethod to use given by `get_clipmethod()`. Returns an
* error message on failure else NULL.
*/
char *
choose_clipmethod(void)
{
// We call get_clipmethod first so that we can catch any errors, even if
// clipmethod is useless
clipmethod_T method = get_clipmethod(p_cpm);
if (method == CLIPMETHOD_FAIL)
return e_invalid_argument;
// If GUI is running or we are not on a system with wayland or x11, then always
// return CLIPMETHOD_NONE. System or GUI clipboard handling always overrides.
#if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD)
#if defined(FEAT_GUI)
if (gui.in_use)
{
#ifdef FEAT_WAYLAND
// We only interact with wayland for the clipboard, we can just deinit
// everything.
wayland_uninit_client();
#endif
method = CLIPMETHOD_NONE;
goto lose_sel_exit;
}
#endif
#else
// If on a system like windows or macos, then clipmethod is irrelevant, we
// use their way of accessing the clipboard.
method = CLIPMETHOD_NONE;
goto exit;
#endif
// Deinitialize clipboard if there is no way to access clipboard
if (method == CLIPMETHOD_NONE)
clip_init(FALSE);
// If we have a clipmethod that works now, then initialize clipboard
else if (clipmethod == CLIPMETHOD_NONE
&& method != CLIPMETHOD_NONE)
{
clip_init(TRUE);
did_warn_clipboard = FALSE;
}
// Disown clipboard if we are switching to a new method
if (clipmethod != CLIPMETHOD_NONE && method != clipmethod)
{
#if (defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD)) \
&& defined(FEAT_GUI)
lose_sel_exit:
#endif
if (clip_star.owned)
clip_lose_selection(&clip_star);
if (clip_plus.owned)
clip_lose_selection(&clip_plus);
}
#if !defined(FEAT_XCLIPBOARD) && !defined(FEAT_WAYLAND_CLIPBOARD)
exit:
#endif
clipmethod = method;
#ifdef FEAT_EVAL
set_vim_var_string(VV_CLIPMETHOD, (char_u*)clipmethod_to_str(method), -1);
#endif
return NULL;
}
/*
* Call choose_clipmethod().
*/
void
ex_clipreset(exarg_T *eap UNUSED)
{
clipmethod_T prev = clipmethod;
choose_clipmethod();
if (clipmethod == CLIPMETHOD_NONE)
smsg(_("Could not find a way to access the clipboard."));
else if (clipmethod != prev)
smsg(_("Switched to clipboard method '%s'."),
clipmethod_to_str(clipmethod));
}
#endif // FEAT_CLIPBOARD