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>
diff --git a/src/clipboard.c b/src/clipboard.c
index fb967dc..4d9455a 100644
--- a/src/clipboard.c
+++ b/src/clipboard.c
@@ -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 @@
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 @@
cb->end.col = 0;
cb->state = SELECT_CLEARED;
+skip:
if (cb == &clip_plus)
break;
cb = &clip_plus;
@@ -109,13 +140,27 @@
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 @@
* 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 @@
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 @@
// 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 @@
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 @@
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 @@
}
#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 @@
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 @@
}
}
+#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