Commit: patch 9.1.1784: Wayland code can be improved

57 views
Skip to first unread message

Christian Brabandt

unread,
Sep 22, 2025, 3:15:20 PMSep 22
to vim...@googlegroups.com
patch 9.1.1784: Wayland code can be improved

Commit: https://github.com/vim/vim/commit/368710abcfaadc8370a07cdd58303f2704f10282
Author: Foxe Chen <chen...@gmail.com>
Date: Mon Sep 22 19:06:58 2025 +0000

patch 9.1.1784: Wayland code can be improved

Problem: Wayland code can be improved
Solution: Refactor Wayland Clipboard code (Foxe Chen).

This the second attempt to refactor the Wayland code base:
- Move clipboard code from wayland.c to clipboard.c
- Use C99 bool type
- Properly poll the Wayland display file descriptor
- Instead of checking if the data source is not NULL in order to
determine if a selection event comes from us, use a special mime type
to identify selection events coming from ourselves. The problem with
the previous approach is that race conditions may occur.
- Put the focus stealing code under a new feature "wayland_focus_steal"
- Use ELAPSED_* macros instead of gettimeofday()
- Pass tests
- Reimplement commented out code
- Update docs
- Make Wayland clipboard behaviour more in line with X11 when connection
is lost
- add missing malloc checks and possible memory leaks + refactored some
tests.

closes: #18324

Signed-off-by: Foxe Chen <chen...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/Filelist b/Filelist
index e97e17ec0..3a2e066e3 100644
--- a/Filelist
+++ b/Filelist
@@ -537,6 +537,7 @@ SRC_UNIX = \
src/vimtutor \
src/gvimtutor \
src/wayland.c \
+ src/wayland.h \
src/which.sh \
src/gen-wayland-protocols.sh \
src/xxd/Makefile \
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 2e5edbbdc..2e9535e65 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt* For Vim version 9.1. Last change: 2025 Sep 21
+*builtin.txt* For Vim version 9.1. Last change: 2025 Sep 22


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -13194,7 +13194,9 @@ vreplace Compiled with |gR| and |gr| commands. (always true)
vtp Compiled for vcon support |+vtp| (check vcon to find
out if it works in the current console).
wayland Compiled with Wayland protocol support.
-wayland_clipboard Compiled with support for Wayland selections/clipboard
+wayland_clipboard Compiled with support for Wayland clipboard.
+wayland_focus_steal Compiled with support for Wayland clipboard focus
+ stealing.
wildignore Compiled with 'wildignore' option.
wildmenu Compiled with 'wildmenu' option.
win16 old version for MS-Windows 3.1 (always false)
diff --git a/runtime/doc/gui_x11.txt b/runtime/doc/gui_x11.txt
index 89ba7c2ee..3b618aa5e 100644
--- a/runtime/doc/gui_x11.txt
+++ b/runtime/doc/gui_x11.txt
@@ -1,4 +1,4 @@
-*gui_x11.txt* For Vim version 9.1. Last change: 2025 Sep 02
+*gui_x11.txt* For Vim version 9.1. Last change: 2025 Sep 22


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -714,6 +714,8 @@ output a warning:

Warning: Clipboard register not available, using register 0 ~

+Note: This also applies to the Wayland clipboard feature as well.
+
*W24*
Vim comes in different flavors, from a tiny build, that just tries to be
compatible to original Vi, to enhanced builds which include many improvements
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 7dad0c4ae..e1361d34d 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt* For Vim version 9.1. Last change: 2025 Sep 20
+*options.txt* For Vim version 9.1. Last change: 2025 Sep 22


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -10204,7 +10204,8 @@ A jump table for the options with a short description can be found at |Q_op|.
*'wlsteal'* *'wst'* *'nowlsteal'* *'nowst'*
'wlsteal' 'wst' boolean (default off)
global
- {only when the |+wayland_clipboard| feature is included}
+ {only when the |+wayland_focus_steal| feature is
+ included}
When enabled, then allow Vim to steal focus by creating a temporary
surface, in order to access the clipboard. For more information see
|wayland-focus-steal|.
diff --git a/runtime/doc/tags b/runtime/doc/tags
index b0662994e..7890b417e 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -1541,6 +1541,7 @@ $quote eval.txt /*$quote*
+vtp various.txt /*+vtp*
+wayland various.txt /*+wayland*
+wayland_clipboard various.txt /*+wayland_clipboard*
++wayland_focus_steal various.txt /*+wayland_focus_steal*
+wildignore various.txt /*+wildignore*
+wildmenu various.txt /*+wildmenu*
+windows various.txt /*+windows*
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index e29fafdf7..b91e3ef23 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -1,4 +1,4 @@
-*various.txt* For Vim version 9.1. Last change: 2025 Sep 02
+*various.txt* For Vim version 9.1. Last change: 2025 Sep 22


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -528,6 +528,9 @@ T *+vreplace* |gR| and |gr|
*+vtp* on MS-Windows console: support for 'termguicolors'
N *+wayland* Unix only: support for the Wayland protocol.
N *+wayland_clipboard* Unix only: support for Wayland selections/clipboard.
+N *+wayland_focus_steal*
+ Unix only: support for Wayland clipboard on
+ compositors without a data control protocol
T *+wildignore* 'wildignore' Always enabled since 9.0.0278
T *+wildmenu* 'wildmenu' Always enabled since 9.0.0279
T *+windows* more than one window; Always enabled since 8.0.1118.
diff --git a/runtime/doc/wayland.txt b/runtime/doc/wayland.txt
index adb7e694f..cbcdc0dc9 100644
--- a/runtime/doc/wayland.txt
+++ b/runtime/doc/wayland.txt
@@ -1,4 +1,4 @@
-*wayland.txt* For Vim version 9.1. Last change: 2025 Sep 15
+*wayland.txt* For Vim version 9.1. Last change: 2025 Sep 22


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -105,7 +105,8 @@ To solve this problem, Vim implements a way of gaining focus in order to
access the clipboard, by creating a temporary transparent top-level surface.
This is by default disabled and can be enabled via the 'wlsteal' option.
Moreover, a seat that has a keyboard is also required, see 'wlseat', and the
-xdg-shell protocol must be available.
+xdg-shell protocol must be available. Additionally, Vim must be compiled with
+the |+wayland_focus_steal| feature.

Note that this method can have several side effects from the result of focus
stealing. For example, if you have a taskbar that shows currently opened apps
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 5b5727aa6..538b44dad 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -1,7 +1,7 @@
" These commands create the option window.
"
" Maintainer: The Vim Project <https://github.com/vim/vim>
-" Last Change: 2025 Sep 20
+" Last Change: 2025 Sep 22
" Former Maintainer: Bram Moolenaar <Br...@vim.org>

" If there already is an option window, jump to that one.
@@ -822,7 +822,7 @@ if has('wayland')
call <SID>AddOption("wlseat", gettext("Wayland seat to use"))
call <SID>OptionG("wse", &wse)
endif
-if has("wayland_clipboard")
+if has("wayland_focus_steal")
call <SID>AddOption("wlsteal", gettext("Enable wayland focus stealing functionality in order to access the clipboard"))
call <SID>BinOptionG("wst", &wst)
endif
diff --git a/src/Makefile b/src/Makefile
index 333a9121c..b2b7a83b8 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1646,7 +1646,9 @@ MESSAGE_TEST_TARGET = message_test$(EXEEXT)

UNITTEST_SRC = $(JSON_TEST_SRC) $(KWORD_TEST_SRC) $(MEMFILE_TEST_SRC) $(MESSAGE_TEST_SRC)
UNITTEST_TARGETS = $(JSON_TEST_TARGET) $(KWORD_TEST_TARGET) $(MEMFILE_TEST_TARGET) $(MESSAGE_TEST_TARGET)
-RUN_UNITTESTS = run_json_test run_kword_test run_memfile_test run_message_test
+# We need to put WAYLAND_SRC because the protocol files need to be generated
+# else wayland.h will error
+RUN_UNITTESTS = $(WAYLAND_SRC) run_json_test run_kword_test run_memfile_test run_message_test

# All sources, also the ones that are not configured
ALL_LOCAL_SRC = $(BASIC_SRC) $(ALL_GUI_SRC) $(UNITTEST_SRC) $(EXTRA_SRC) \
@@ -3862,9 +3864,9 @@ objects/clientserver.o: clientserver.c vim.h protodef.h auto/config.h feature.h
ex_cmds.h spell.h proto.h globals.h errors.h
objects/clipboard.o: clipboard.c vim.h protodef.h auto/config.h feature.h \
os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h \
- beval.h proto/gui_beval.pro structs.h regexp.h gui.h \
- libvterm/include/vterm.h libvterm/include/vterm_keycodes.h alloc.h \
- ex_cmds.h spell.h proto.h globals.h errors.h
+ beval.h structs.h regexp.h gui.h libvterm/include/vterm.h \
+ libvterm/include/vterm_keycodes.h xdiff/xdiff.h xdiff/../vim.h alloc.h \
+ ex_cmds.h spell.h proto.h globals.h errors.h wayland.h
objects/cmdexpand.o: cmdexpand.c vim.h protodef.h auto/config.h feature.h \
os_unix.h auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h \
beval.h proto/gui_beval.pro structs.h regexp.h gui.h \
@@ -4565,7 +4567,7 @@ objects/wayland.o: wayland.c vim.h protodef.h auto/config.h feature.h os_unix.h
auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \
structs.h regexp.h gui.h libvterm/include/vterm.h \
libvterm/include/vterm_keycodes.h xdiff/xdiff.h xdiff/../vim.h alloc.h \
- ex_cmds.h spell.h proto.h globals.h errors.h \
+ ex_cmds.h spell.h proto.h globals.h errors.h wayland.h \
auto/wayland/wlr-data-control-unstable-v1.h \
auto/wayland/ext-data-control-v1.h auto/wayland/xdg-shell.h \
auto/wayland/primary-selection-unstable-v1.h
diff --git a/src/auto/configure b/src/auto/configure
index 5c0d3d614..c99feb050 100755
--- a/src/auto/configure
+++ b/src/auto/configure
@@ -862,6 +862,7 @@ enable_farsi
enable_xim
enable_fontset
with_wayland
+enable_wayland_focus_steal
with_x
enable_gui
enable_gtk2_check
@@ -1542,6 +1543,9 @@ Optional Features:
--disable-farsi Deprecated.
--enable-xim Include XIM input support.
--enable-fontset Include X fontset output support.
+ --enable-wayland-focus-steal
+ Include focus stealing support for Wayland
+ clipboard.
--enable-gui=OPTS X11 GUI. default=auto OPTS=auto/no/gtk2/gnome2/gtk3/motif/haiku/photon/carbon
--enable-gtk2-check If auto-select GUI, check for GTK+ 2 default=yes
--enable-gnome-check If GTK GUI, check for GNOME default=no
@@ -9271,13 +9275,39 @@ fi


if test "$with_wayland" = yes; then
-cppflags_save=$CPPFLAGS
-cflags_save=$CFLAGS
+ cppflags_save=$CPPFLAGS
+ cflags_save=$CFLAGS
+
{ printf "%s
" "$as_me:${as_lineno-$LINENO}: checking for wayland" >&5
printf %s "checking for wayland... " >&6; }
if "$PKG_CONFIG" --exists 'wayland-client'; then
{ printf "%s
" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s
" "yes" >&6; }
+
+ { printf "%s
" "$as_me:${as_lineno-$LINENO}: checking --enable-wayland-focus-steal argument" >&5
+printf %s "checking --enable-wayland-focus-steal argument... " >&6; }
+ # Check whether --enable-wayland-focus-steal was given.
+if test ${enable_wayland_focus_steal+y}
+then :
+ enableval=$enable_wayland_focus_steal; enable_wayland_fs=$enableval
+else case e in #(
+ e) enable_wayland_fs="yes" ;;
+esac
+fi
+
+
+ if test "$enable_wayland_fs" = "yes"
+then :
+ { printf "%s
" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s
" "yes" >&6; }
+ printf "%s
" "#define FEAT_WAYLAND_CLIPBOARD_FS 1" >>confdefs.h
+
+else case e in #(
+ e) { printf "%s
" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s
" "no" >&6; } ;;
+esac
+fi
+
printf "%s
" "#define HAVE_WAYLAND 1" >>confdefs.h

WAYLAND_CPPFLAGS=`$PKG_CONFIG --cflags-only-I wayland-client`
@@ -9288,16 +9318,23 @@ printf "%s
" "yes" >&6; }
WAYLAND_SRC=" \
auto/wayland/wlr-data-control-unstable-v1.c \
auto/wayland/ext-data-control-v1.c \
- auto/wayland/xdg-shell.c \
- auto/wayland/primary-selection-unstable-v1.c \
wayland.c"
WAYLAND_OBJ=" \
objects/wlr-data-control-unstable-v1.o \
objects/ext-data-control-v1.o \
- objects/xdg-shell.o \
- objects/primary-selection-unstable-v1.o \
objects/wayland.o"

+ if test "$enable_wayland_fs" = "yes"
+then :
+ as_fn_append WAYLAND_SRC " \
+ auto/wayland/xdg-shell.c \
+ auto/wayland/primary-selection-unstable-v1.c"
+ as_fn_append WAYLAND_OBJ " \
+ objects/xdg-shell.o \
+ objects/primary-selection-unstable-v1.o"
+fi
+
+



diff --git a/src/clipboard.c b/src/clipboard.c
index 4b2e6874a..66f3e321a 100644
--- a/src/clipboard.c
+++ b/src/clipboard.c
@@ -32,6 +32,80 @@
#if defined(FEAT_CLIPBOARD) || defined(PROTO)

#if defined(FEAT_WAYLAND_CLIPBOARD)
+
+# include "wayland.h"
+
+# ifdef FEAT_WAYLAND_CLIPBOARD_FS
+
+// Structures used for focus stealing
+typedef struct {
+ struct wl_shm_pool *pool;
+ int fd;
+
+ struct wl_buffer *buffer;
+ bool available;
+
+ int width;
+ int height;
+ int stride;
+ int size;
+} clip_wl_buffer_store_T;
+
+typedef struct {
+ void *user_data;
+ void (*on_focus)(void *data, uint32_t serial);
+
+ struct wl_surface *surface;
+ struct wl_keyboard *keyboard;
+
+ struct {
+ struct xdg_surface *surface;
+ struct xdg_toplevel *toplevel;
+ } shell;
+
+ bool got_focus;
+} clip_wl_fs_surface_T; // fs = focus steal
+
+# endif // FEAT_WAYLAND_CLIPBOARD_FS
+
+// Represents either the regular or primary selection
+typedef struct {
+ char_u *contents; // Non-null if we own selection,
+ // contains the data to send to other
+ // clients.
+ vwl_data_source_T *source; // Non-NULL if we own the selection,
+ // else NULL if we don't.
+ vwl_data_offer_T *offer; // Current offer for the selection
+
+# ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ bool requires_focus; // If focus needs to be given to us to
+ // work
+# endif
+ bool own_success; // Used by clip_wl_own_selection()
+ bool available; // If selection is ready to serve/use
+
+ // These may point to the same proxy as the other selection
+ vwl_data_device_manager_T *manager;
+ vwl_data_device_T *device;
+} clip_wl_selection_T;
+
+// Represents the clipboard for the global Wayland connection, for the chosen
+// seat (using the 'wl_seat' option)
+typedef struct {
+ vwl_seat_T *seat;
+
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ clip_wl_buffer_store_T *fs_buffer;
+#endif
+
+ clip_wl_selection_T regular;
+ clip_wl_selection_T primary;
+
+ // Array of file descriptors of clients we are sending data to. These should
+ // be polled for POLLOUT and have the respective callback called for each.
+ garray_T write_fds;
+} clip_wl_T;
+
// Mime types we support sending and receiving
// Mimes with a lower index in the array are prioritized first when we are
// receiving data.
@@ -45,21 +119,20 @@ static const char *supported_mimes[] = {
"TEXT"
};

-static void clip_wl_receive_data(Clipboard_T *cbd,
- const char *mime_type, int fd);
+clip_wl_T clip_wl;
+
+static void
+clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd);
static void clip_wl_request_selection(Clipboard_T *cbd);
-static void clip_wl_send_data(const char *mime_type, int fd,
- wayland_selection_T);
static int clip_wl_own_selection(Clipboard_T *cbd);
static void clip_wl_lose_selection(Clipboard_T *cbd);
static void clip_wl_set_selection(Clipboard_T *cbd);
-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
+# if defined(USE_SYSTEM) || defined(PROTO)
+static bool clip_wl_owner_exists(Clipboard_T *cbd);
+# endif

-#endif
+#endif // FEAT_WAYLAND_CLIPBOARD

/*
* Selection stuff using Visual mode, for cutting and pasting text to other
@@ -99,6 +172,22 @@ skip:
}
}

+ static void
+clip_init_single(Clipboard_T *cb, int can_use)
+{
+ // No need to init again if cbd is already available
+ if (can_use && cb->available)
+ return;
+
+ cb->available = can_use;
+ cb->owned = FALSE;
+ cb->start.lnum = 0;
+ cb->start.col = 0;
+ cb->end.lnum = 0;
+ cb->end.col = 0;
+ cb->state = SELECT_CLEARED;
+}
+
/*
* Check whether the VIsual area has changed, and if so try to become the owner
* of the selection, and free any old converted selection we may still have
@@ -2207,13 +2296,13 @@ clip_yank_selection(
str_to_reg(y_ptr, type, str, len, -1, FALSE);
}

-/*
- * Convert the '*'/'+' register into a GUI selection string returned in *str
- * with length *len.
- * Returns the motion type, or -1 for failure.
- */
- int
-clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd)
+ static int
+clip_convert_selection_offset(
+ char_u **str,
+ long_u *len,
+ int offset, // Extra space to add in *str and the offset to
+ // place the actual string in *str.
+ Clipboard_T *cbd)
{
char_u *p;
int lnum;
@@ -2244,11 +2333,13 @@ clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd)
if (y_ptr->y_type == MCHAR && *len >= eolsize)
*len -= eolsize;

+ *len += offset;
p = *str = alloc(*len + 1); // add one to avoid zero
if (p == NULL)
return -1;
+ p += offset;
lnum = 0;
- for (i = 0, j = 0; i < (int)*len; i++, j++)
+ for (i = 0, j = 0; i < (int)*len - offset; i++, j++)
{
if (y_ptr->y_array[lnum].string[j] == '
')
p[i] = NUL;
@@ -2267,6 +2358,17 @@ clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd)
return y_ptr->y_type;
}

+/*
+ * Convert the '*'/'+' register into a GUI selection string returned in *str
+ * with length *len.
+ * Returns the motion type, or -1 for failure.
+ */
+ int
+clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd)
+{
+ return clip_convert_selection_offset(str, len, 0, cbd);
+}
+
/*
* When "regname" is a clipboard register, obtain the selection. If it's not
* available return zero, otherwise return "regname".
@@ -2332,12 +2434,591 @@ adjust_clip_reg(int *rp)
if ((!clip_star.available && *rp == '*') ||
(!clip_plus.available && *rp == '+'))
{
- msg_warn_missing_clipboard();
+ msg_warn_missing_clipboard(!clip_plus.available, !clip_star.available);
*rp = 0;
}
}

-#if defined(FEAT_WAYLAND_CLIPBOARD) || defined(PROTO)
+#if defined(FEAT_WAYLAND_CLIPBOARD)
+
+ static clip_wl_selection_T *
+clip_wl_get_selection(wayland_selection_T sel)
+{
+ switch (sel)
+ {
+ case WAYLAND_SELECTION_REGULAR:
+ return &clip_wl.regular;
+ case WAYLAND_SELECTION_PRIMARY:
+ return &clip_wl.primary;
+ default:
+ return NULL;
+ }
+}
+
+ static clip_wl_selection_T *
+clip_wl_get_selection_from_cbd(Clipboard_T *cbd)
+{
+ if (cbd == &clip_plus)
+ return &clip_wl.regular;
+ else if (cbd == &clip_star)
+ return &clip_wl.primary;
+ else
+ return NULL;
+}
+
+ static Clipboard_T *
+clip_wl_get_cbd_from_selection(clip_wl_selection_T *sel)
+{
+ if (sel == &clip_wl.regular)
+ return &clip_plus;
+ else if (sel == &clip_wl.primary)
+ return &clip_star;
+ else
+ return NULL;
+}
+
+ static wayland_selection_T
+clip_wl_get_selection_type(clip_wl_selection_T *sel)
+{
+ if (sel == &clip_wl.regular)
+ return WAYLAND_SELECTION_REGULAR;
+ else if (sel == &clip_wl.primary)
+ return WAYLAND_SELECTION_PRIMARY;
+ else
+ return WAYLAND_SELECTION_NONE;
+}
+
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+/*
+ * If globals required for focus stealing method are available.
+ */
+ static bool
+clip_wl_focus_stealing_available(void)
+{
+ return wayland_ct->gobjects.wl_compositor != NULL &&
+ wayland_ct->gobjects.wl_shm != NULL &&
+ wayland_ct->gobjects.xdg_wm_base != NULL;
+}
+
+/*
+ * Called when compositor isn't using the buffer anymore, we can reuse it
+ * again.
+ */
+ static void
+wl_buffer_listener_release(
+ void *data,
+ struct wl_buffer *buffer UNUSED)
+{
+ clip_wl_buffer_store_T *store = data;
+
+ store->available = true;
+}
+
+static struct wl_buffer_listener wl_buffer_listener = {
+ .release = wl_buffer_listener_release
+};
+
+/*
+ * Destroy a buffer store structure.
+ */
+ static void
+clip_wl_destroy_buffer_store(clip_wl_buffer_store_T *store)
+{
+ if (store == NULL)
+ return;
+ if (store->buffer != NULL)
+ wl_buffer_destroy(store->buffer);
+ if (store->pool != NULL)
+ wl_shm_pool_destroy(store->pool);
+
+ close(store->fd);
+
+ vim_free(store);
+}
+
+/*
+ * Initialize a buffer and its backing memory pool.
+ */
+ static clip_wl_buffer_store_T *
+clip_wl_init_buffer_store(int width, int height)
+{
+ int fd, r;
+ clip_wl_buffer_store_T *store;
+
+ store = alloc(sizeof(*store));
+
+ if (store == NULL)
+ return NULL;
+
+ store->available = false;
+
+ store->width = width;
+ store->height = height;
+ store->stride = store->width * 4;
+ store->size = store->stride * store->height;
+
+ fd = mch_create_anon_file();
+ r = ftruncate(fd, store->size);
+
+ if (r == -1)
+ {
+ if (fd >= 0)
+ close(fd);
+ return NULL;
+ }
+
+ store->pool = wl_shm_create_pool(
+ wayland_ct->gobjects.wl_shm,
+ fd,
+ store->size);
+ store->buffer = wl_shm_pool_create_buffer(
+ store->pool,
+ 0,
+ store->width,
+ store->height,
+ store->stride,
+ WL_SHM_FORMAT_ARGB8888);
+
+ store->fd = fd;
+
+ wl_buffer_add_listener(store->buffer, &wl_buffer_listener, store);
+
+ if (vwl_connection_roundtrip(wayland_ct) == FAIL)
+ {
+ clip_wl_destroy_buffer_store(store);
+ return NULL;
+ }
+
+ store->available = true;
+
+ return store;
+}
+
+/*
+ * Configure xdg_surface
+ */
+ static void
+xdg_surface_listener_configure(
+ void *data UNUSED,
+ struct xdg_surface *surface,
+ uint32_t serial)
+{
+ xdg_surface_ack_configure(surface, serial);
+}
+
+
+static struct xdg_surface_listener xdg_surface_listener = {
+ .configure = xdg_surface_listener_configure
+};
+
+/*
+ * Destroy a focus stealing structure.
+ */
+ static void
+clip_wl_destroy_fs_surface(clip_wl_fs_surface_T *store)
+{
+ if (store == NULL)
+ return;
+ if (store->shell.toplevel != NULL)
+ xdg_toplevel_destroy(store->shell.toplevel);
+ if (store->shell.surface != NULL)
+ xdg_surface_destroy(store->shell.surface);
+ if (store->surface != NULL)
+ wl_surface_destroy(store->surface);
+ if (store->keyboard != NULL)
+ {
+ if (wl_keyboard_get_version(store->keyboard) >= 3)
+ wl_keyboard_release(store->keyboard);
+ else
+ wl_keyboard_destroy(store->keyboard);
+ }
+ vim_free(store);
+}
+
+VWL_FUNCS_DUMMY_KEYBOARD_EVENTS()
+
+/*
+ * Called when the keyboard focus is on our surface
+ */
+ static void
+clip_wl_fs_keyboard_listener_enter(
+ void *data,
+ struct wl_keyboard *keyboard UNUSED,
+ uint32_t serial,
+ struct wl_surface *surface UNUSED,
+ struct wl_array *keys UNUSED)
+{
+ clip_wl_fs_surface_T *store = data;
+
+ store->got_focus = true;
+
+ if (store->on_focus != NULL)
+ store->on_focus(store->user_data, serial);
+}
+
+
+static struct wl_keyboard_listener vwl_fs_keyboard_listener = {
+ .enter = clip_wl_fs_keyboard_listener_enter,
+ .key = clip_wl_fs_keyboard_listener_key,
+ .keymap = clip_wl_fs_keyboard_listener_keymap,
+ .leave = clip_wl_fs_keyboard_listener_leave,
+ .modifiers = clip_wl_fs_keyboard_listener_modifiers,
+ .repeat_info = clip_wl_fs_keyboard_listener_repeat_info
+};
+
+/*
+ * Create an invisible surface in order to gain focus and call on_focus() with
+ * serial that was given.
+ */
+ static int
+clip_wl_init_fs_surface(
+ vwl_seat_T *seat,
+ clip_wl_buffer_store_T *buffer_store,
+ void (*on_focus)(void *, uint32_t),
+ void *user_data)
+{
+ clip_wl_fs_surface_T *store;
+#ifdef ELAPSED_FUNC
+ elapsed_T start_tv;
+#endif
+
+ if (wayland_ct->gobjects.wl_compositor == NULL
+ || wayland_ct->gobjects.xdg_wm_base == NULL
+ || buffer_store == NULL
+ || seat == NULL)
+ return FAIL;
+
+ store = ALLOC_CLEAR_ONE(clip_wl_fs_surface_T);
+
+ if (store == NULL)
+ return FAIL;
+
+ // Get keyboard
+ store->keyboard = vwl_seat_get_keyboard(seat);
+
+ if (store->keyboard == NULL)
+ goto fail;
+
+ wl_keyboard_add_listener(store->keyboard, &vwl_fs_keyboard_listener, store);
+
+ if (vwl_connection_dispatch(wayland_ct) < 0)
+ goto fail;
+
+ store->surface = wl_compositor_create_surface(
+ wayland_ct->gobjects.wl_compositor);
+ store->shell.surface = xdg_wm_base_get_xdg_surface(
+ wayland_ct->gobjects.xdg_wm_base, store->surface);
+ store->shell.toplevel = xdg_surface_get_toplevel(store->shell.surface);
+
+ xdg_toplevel_set_title(store->shell.toplevel, "Vim clipboard");
+
+ xdg_surface_add_listener(store->shell.surface,
+ &xdg_surface_listener, NULL);
+
+ wl_surface_commit(store->surface);
+
+ store->on_focus = on_focus;
+ store->user_data = user_data;
+ store->got_focus = FALSE;
+
+ if (vwl_connection_roundtrip(wayland_ct) == FAIL)
+ goto fail;
+
+ // We may get the enter event early, if we do then we will set `got_focus`
+ // to TRUE.
+ if (store->got_focus)
+ goto early_exit;
+
+ // Buffer hasn't been released yet, abort. This shouldn't happen but still
+ // check for it.
+ if (!buffer_store->available)
+ goto fail;
+
+ buffer_store->available = false;
+
+ wl_surface_attach(store->surface, buffer_store->buffer, 0, 0);
+ wl_surface_damage(store->surface, 0, 0,
+ buffer_store->width, buffer_store->height);
+ wl_surface_commit(store->surface);
+
+ // Dispatch events until we receive the enter event. Add a max delay of
+ // 'p_wtm' when waiting for it (may be longer depending on how long we poll
+ // when dispatching events)
+#ifdef ELAPSED_FUNC
+ ELAPSED_INIT(start_tv);
+#endif
+
+ while (vwl_connection_dispatch(wayland_ct) >= 0)
+ {
+ if (store->got_focus)
+ break;
+
+#ifdef ELAPSED_FUNC
+ if (ELAPSED_FUNC(start_tv) >= p_wtm)
+ goto fail;
+#endif
+ }
+early_exit:
+ clip_wl_destroy_fs_surface(store);
+ vwl_connection_flush(wayland_ct);
+
+ return OK;
+fail:
+ clip_wl_destroy_fs_surface(store);
+ vwl_connection_flush(wayland_ct);
+
+ return FAIL;
+}
+
+#endif // FEAT_WAYLAND_CLIPBOARD_FS
+
+ static bool
+wl_data_offer_listener_event_offer(
+ void *data UNUSED,
+ vwl_data_offer_T *offer UNUSED,
+ const char *mime_type
+)
+{
+ // Only accept mime type if we support it
+ for (int i = 0; i < (int)ARRAY_LENGTH(supported_mimes); i++)
+ if (STRCMP(mime_type, supported_mimes[i]) == 0)
+ return true;
+ return FALSE;
+}
+
+static const vwl_data_offer_listener_T vwl_data_offer_listener = {
+ .offer = wl_data_offer_listener_event_offer
+};
+
+ static void
+vwl_data_device_listener_event_data_offer(
+ void *data UNUSED,
+ vwl_data_device_T *device UNUSED,
+ vwl_data_offer_T *offer)
+{
+ // Immediately start listening for offer events from the data offer
+ vwl_data_offer_add_listener(offer, &vwl_data_offer_listener, NULL);
+}
+
+ static void
+vwl_data_device_listener_event_selection(
+ void *data UNUSED,
+ vwl_data_device_T *device UNUSED,
+ vwl_data_offer_T *offer,
+ wayland_selection_T selection)
+{
+ clip_wl_selection_T *sel = clip_wl_get_selection(selection);
+
+ // Destroy previous offer if any, it is now invalid
+ vwl_data_offer_destroy(sel->offer);
+
+ // There are two cases when sel->offer is NULL
+ // 1. No one owns the selection
+ // 2. We own the selection (we'll just access the register directly)
+ if (offer == NULL || offer->from_vim)
+ {
+ // Selection event is from us, so we are the source client. Therefore
+ // ignore it. Or the selection is cleared, so set sel->offer to NULL
+ vwl_data_offer_destroy(offer);
+ sel->offer = NULL;
+ return;
+ }
+
+ // Save offer. When we want to request data, then we'll actually call the
+ // receive method.
+ sel->offer = offer;
+
+}
+
+ static void
+vwl_data_device_listener_event_finished(
+ void *data UNUSED,
+ vwl_data_device_T *device)
+{
+ clip_wl_selection_T *sel;
+ // Device finished, guessing this can happen is when the seat becomes
+ // invalid? If so, let the user call :wlrestore! to reset. There wouldn't be
+ // any point in trying to create another data device for the same seat,
+ // since the seat is in an invalid state.
+ if (device == clip_wl.regular.device)
+ {
+ sel = &clip_wl.regular;
+ clip_wl.regular.device = NULL;
+ }
+ else if (device == clip_wl.primary.device)
+ {
+ sel = &clip_wl.primary;
+ clip_wl.primary.device = NULL;
+ }
+ else
+ // Shouldn't happen
+ return;
+
+ vim_free(sel->contents);
+ vwl_data_source_destroy(sel->source);
+ vwl_data_offer_destroy(sel->offer);
+ sel->available = FALSE;
+
+ vwl_data_device_destroy(device);
+}
+
+static const vwl_data_device_listener_T vwl_data_device_listener = {
+ .data_offer = vwl_data_device_listener_event_data_offer,
+ .selection = vwl_data_device_listener_event_selection,
+ .finished = vwl_data_device_listener_event_finished
+};
+
+/*
+ * Initialize the clipboard for Wayland using the global Wayland connection.
+ * Returns OK on success and FAIL on failure.
+ */
+ int
+clip_init_wayland(void)
+{
+ int_u supported = WAYLAND_SELECTION_NONE;
+
+ if (wayland_ct == NULL)
+ return FAIL;
+
+ clip_wl.seat = vwl_connection_get_seat(wayland_ct, (char *)p_wse);
+
+ if (clip_wl.seat == NULL)
+ return FAIL;
+
+ clip_wl.regular.manager = vwl_connection_get_data_device_manager(
+ wayland_ct, WAYLAND_SELECTION_REGULAR, &supported);
+
+ if (clip_wl.regular.manager != NULL)
+ {
+ clip_wl.regular.device = vwl_data_device_manager_get_data_device(
+ clip_wl.regular.manager, clip_wl.seat);
+
+ if (clip_wl.regular.device != NULL)
+ clip_wl.regular.available = true;
+ else
+ {
+ vwl_data_device_manager_discard(clip_wl.regular.manager);
+ clip_wl.regular.manager = NULL;
+ }
+ }
+
+ // If we still don't support the primary selection, find one for it
+ // specifically.
+ if (!(supported & WAYLAND_SELECTION_PRIMARY))
+ {
+ clip_wl.primary.manager = vwl_connection_get_data_device_manager(
+ wayland_ct, WAYLAND_SELECTION_PRIMARY, &supported);
+
+ if (clip_wl.primary.manager != NULL)
+ {
+ clip_wl.primary.device = vwl_data_device_manager_get_data_device(
+ clip_wl.primary.manager, clip_wl.seat);
+
+ if (clip_wl.primary.device != NULL)
+ clip_wl.primary.available = true;
+ else
+ {
+ vwl_data_device_manager_discard(clip_wl.primary.manager);
+ clip_wl.primary.manager = NULL;
+ }
+ }
+ }
+ else if (clip_wl.regular.available)
+ {
+ // The protocol supports both regular and primary selections, just use
+ // one data device manager and one data device.
+ clip_wl.primary.available = true;
+ clip_wl.primary.manager = clip_wl.regular.manager;
+ clip_wl.primary.device = clip_wl.regular.device;
+ }
+
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ if (clip_wl.regular.available
+ && clip_wl.regular.manager->protocol == VWL_DATA_PROTOCOL_CORE
+ && clip_wl_focus_stealing_available())
+ clip_wl.regular.requires_focus = true;
+ if (clip_wl.primary.available
+ && clip_wl.primary.manager->protocol == VWL_DATA_PROTOCOL_PRIMARY
+ && clip_wl_focus_stealing_available())
+ clip_wl.primary.requires_focus = true;
+
+ if (clip_wl.regular.requires_focus || clip_wl.primary.requires_focus)
+ {
+ // Initialize buffer to use for focus stealing
+ clip_wl.fs_buffer = clip_wl_init_buffer_store(1, 1);
+ }
+#endif
+
+ if (!clip_wl.regular.available && !clip_wl.primary.available)
+ return FAIL;
+
+ // Start listening for selection updates
+ if (clip_wl.regular.device != NULL)
+ vwl_data_device_add_listener(clip_wl.regular.device,
+ &vwl_data_device_listener, NULL);
+ // Don't want to listen to the same data device twice
+ if (clip_wl.primary.device != NULL
+ && clip_wl.primary.device != clip_wl.regular.device)
+ vwl_data_device_add_listener(clip_wl.primary.device,
+ &vwl_data_device_listener, NULL);
+
+ return OK;
+}
+
+ void
+clip_uninit_wayland(void)
+{
+ clip_wl_selection_T *sel;
+
+ if (clipmethod == CLIPMETHOD_WAYLAND)
+ {
+ if (clip_star.owned)
+ clip_lose_selection(&clip_star);
+ if (clip_plus.owned)
+ clip_lose_selection(&clip_plus);
+ }
+
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ clip_wl_destroy_buffer_store(clip_wl.fs_buffer);
+#endif
+
+ // Don't want to double free
+ if (clip_wl.regular.manager != clip_wl.primary.manager)
+ vwl_data_device_manager_discard(clip_wl.primary.manager);
+ vwl_data_device_manager_discard(clip_wl.regular.manager);
+
+ if (clip_wl.regular.device != clip_wl.primary.device)
+ vwl_data_device_destroy(clip_wl.primary.device);
+ vwl_data_device_destroy(clip_wl.regular.device);
+
+ sel = &clip_wl.regular;
+ while (true)
+ {
+ vim_free(sel->contents);
+ vwl_data_source_destroy(sel->source);
+ vwl_data_offer_destroy(sel->offer);
+ sel->available = false;
+
+ if (sel == &clip_wl.primary)
+ break;
+ sel = &clip_wl.primary;
+ }
+
+ vim_memset(&clip_wl, 0, sizeof(clip_wl));
+}
+
+ int
+clip_reset_wayland(void)
+{
+ wayland_uninit_connection();
+
+ if (wayland_init_connection(wayland_display_name) == FAIL
+ || clip_init_wayland() == FAIL)
+ return FAIL;
+
+ choose_clipmethod();
+ return OK;
+}

/*
* Read data from a file descriptor and write it to the given clipboard.
@@ -2350,10 +3031,10 @@ clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd)
int motion_type = MAUTO;
ssize_t r = 0;
#ifndef HAVE_SELECT
- struct pollfd pfd
+ struct pollfd pfd;

- pfd.fd = fd,
- pfd.events = POLLIN
+ pfd.fd = fd;
+ pfd.events = POLLIN;
#else
fd_set rfds;
struct timeval tv;
@@ -2368,17 +3049,19 @@ clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd)

ga_init2(&buf, 1, 4096);

- // 4096 bytes seems reasonable for initial buffer size
+ // 4096 bytes seems reasonable for initial buffer size, memory is cheap
+ // anyways.
if (ga_grow(&buf, 4096) == FAIL)
return;

start = buf.ga_data;

- // 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)
+#ifndef HAVE_SELECT
+ while (poll(&pfd, 1, p_wtm) > 0)
+#else
+ while (tv.tv_sec = p_wtm / 1000, tv.tv_usec = (p_wtm % 1000) * 1000,
+ select(fd + 1, &rfds, NULL, NULL, &tv) > 0)
+#endif
{
r = read(fd, start, buf.ga_maxlen - 1 - buf.ga_len);

@@ -2387,18 +3070,7 @@ clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd)
else if (r < 0)
{
if (errno == EAGAIN || errno == EINTR)
- {
-poll_data:
-#ifndef HAVE_SELECT
- if (poll(&pfd, 1, p_wtm) > 0)
- continue;
-#else
- tv.tv_sec = 0;
- tv.tv_usec = p_wtm * 1000;
- if (select(fd + 1, &rfds, NULL, NULL, &tv) > 0)
- continue;
-#endif
- }
+ continue;
break;
}

@@ -2472,182 +3144,182 @@ poll_data:
static 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;
+ clip_wl_selection_T *sel = clip_wl_get_selection_from_cbd(cbd);
+ int fds[2];
+ int mime_types_len;
+ const char **mime_types;
+ const char *chosen_mime = NULL;

- // Get mime types that the source client offers
- mime_types = wayland_cb_get_mime_types(selection);
+ if (!sel->available)
+ goto clear;

- if (mime_types == NULL || mime_types->ga_len == 0)
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ if (sel->requires_focus)
{
- // Selection is empty/cleared
- clip_free_selection(cbd);
- return;
+ // We don't care about the on_focus callback since once we gain
+ // focus the data offer events will come immediately.
+ if (clip_wl_init_fs_surface(clip_wl.seat,
+ clip_wl.fs_buffer, NULL, NULL) == FAIL)
+ goto clear;
+ }
+ else
+#endif
+ {
+ // Dispatch any events that still queued up before checking for a data
+ // offer.
+ if (vwl_connection_roundtrip(wayland_ct) == FAIL)
+ goto clear;
}

- len = ARRAY_LENGTH(supported_mimes);
+ if (sel->offer == NULL)
+ goto clear;

- // 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];
+ mime_types_len = sel->offer->mime_types.ga_len;
+ mime_types = sel->offer->mime_types.ga_data;

- if (STRCMP(mime_type, supported_mimes[i]) == 0)
+ // Choose mime type to receive from. Mime types with a lower index in the
+ // "supported_mimes" array are prioritized over ones after it.
+ for (int i = 0; i < (int)ARRAY_LENGTH(supported_mimes)
+ && chosen_mime == NULL; i++)
+ {
+ for (int k = 0; k < mime_types_len && chosen_mime == NULL; k++)
+ if (STRCMP(mime_types[k], supported_mimes[i]) == 0)
chosen_mime = supported_mimes[i];
- }
}
- if (chosen_mime == NULL)
- return;

- fd = wayland_cb_receive_data(chosen_mime, selection);
+ if (chosen_mime == NULL || pipe(fds) == -1)
+ goto clear;

- if (fd == -1)
- return;
+ vwl_data_offer_receive(sel->offer, chosen_mime, fds[1]);

- // Start reading the file descriptor returned
- clip_wl_receive_data(cbd, chosen_mime, fd);
+ close(fds[1]); // Close before we read data so that when the source client
+ // closes their end we receive an EOF.

- close(fd);
+ if (vwl_connection_flush(wayland_ct) >= 0)
+ clip_wl_receive_data(cbd, chosen_mime, fds[0]);
+
+ close(fds[0]);
+
+ return;
+clear:
+ clip_free_selection(cbd);
}

-/*
- * 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;
+vwl_data_source_listener_event_send(
+ void *data,
+ vwl_data_source_T *source UNUSED,
+ const char *mime_type,
+ int32_t fd
+)
+{
+ clip_wl_selection_T *sel = data;
+ Clipboard_T *cbd = clip_wl_get_cbd_from_selection(sel);
+ bool have_mime = false;
+ int motion_type;
+ long_u length;
+ char_u *string; // Will be reallocated to a bigger size if
+ // needed.
+ int offset = 0;
+ bool is_vim, is_vimenc;
+ size_t total = 0;
#ifndef HAVE_SELECT
- struct pollfd pfd
+ struct pollfd pfd;

- pfd.fd = fd,
- pfd.events = POLLOUT
+ 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;
+ // Check if we actually have mime type
+ for (int i = 0; i < (int)ARRAY_LENGTH(supported_mimes); i++)
+ if (STRCMP(supported_mimes[i], mime_type) == 0)
+ {
+ have_mime = true;
+ break;
+ }
+
+ if (!have_mime)
+ goto exit;
+
+ // First byte sent is motion type for vim specific formats. For the vimenc
+ // format, after the first byte is the encoding type, which is null
+ // terminated.
+
+ is_vimenc = STRCMP(mime_type, VIMENC_ATOM_NAME) == 0;
+ is_vim = STRCMP(mime_type, VIM_ATOM_NAME) == 0;
+
+ if (is_vimenc)
+ offset += 2 + STRLEN(p_enc);
+ else if (is_vim)
+ offset += 1;

- // Get the current selection
clip_get_selection(cbd);
- motion_type = clip_convert_selection(&string, &length, cbd);
+ motion_type = clip_convert_selection_offset(&string, &length, offset, cbd);

if (motion_type < 0)
goto exit;

- if (STRCMP(mime_type, VIMENC_ATOM_NAME) == 0)
+ if (is_vimenc)
{
- did_vimenc = FALSE;
- did_motion_type = FALSE;
+ string[0] = (char_u)motion_type;
+ // strcpy copies the NUL terminator too
+ strcpy((char *)string + 1, (char *)p_enc);
}
- else if (STRCMP(mime_type, VIM_ATOM_NAME) == 0)
- did_motion_type = FALSE;
+ else if (is_vim)
+ string[0] = (char_u)motion_type;
+

- while ((total < (size_t)length || skip_len_check) &&
+ while (total < (size_t)length &&
#ifndef HAVE_SELECT
- poll(&pfd, 1, p_wtm) > 0)
+ poll(&pfd, 1, p_wtm) > 0)
#else
- select(fd + 1, NULL, &wfds, NULL, &tv) > 0)
+ ((tv.tv_sec = p_wtm / 1000, tv.tv_usec = (p_wtm % 1000) * 1000),
+ 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;
+ ssize_t w = write(fd, string + total, length - total);

-#ifdef HAVE_SELECT
- tv.tv_sec = 0;
- tv.tv_usec = p_wtm * 1000;
-#endif
+ if (w == -1)
+ break;
+ total += w;
}
-exit:
+
vim_free(string);
+exit:
+ close(fd);
}

-/*
- * 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)
+vwl_data_source_listener_event_cancelled(
+ void *data,
+ vwl_data_source_T *source UNUSED)
{
- if (selection == WAYLAND_SELECTION_REGULAR)
- clip_lose_selection(&clip_plus);
- else if (selection == WAYLAND_SELECTION_PRIMARY)
- clip_lose_selection(&clip_star);
+ clip_wl_selection_T *sel = data;
+ Clipboard_T *cbd = clip_wl_get_cbd_from_selection(sel);
+
+ clip_lose_selection(cbd);
+}
+
+static const vwl_data_source_listener_T vwl_data_source_listener = {
+ .send = vwl_data_source_listener_event_send,
+ .cancelled = vwl_data_source_listener_event_cancelled
+};
+
+ static void
+clip_wl_do_set_selection(void *data, uint32_t serial)
+{
+ clip_wl_selection_T *sel = data;
+ wayland_selection_T sel_type = clip_wl_get_selection_type(sel);
+
+ vwl_data_device_set_selection(sel->device, sel->source, serial, sel_type);
+
+ sel->own_success = (vwl_connection_roundtrip(wayland_ct) == OK);
}

/*
@@ -2658,36 +3330,81 @@ clip_wl_selection_cancelled(wayland_selection_T selection)
static int
clip_wl_own_selection(Clipboard_T *cbd)
{
- wayland_selection_T selection;
+ clip_wl_selection_T *sel = clip_wl_get_selection_from_cbd(cbd);
+ wayland_selection_T sel_type = clip_wl_get_selection_type(sel);

- if (cbd == &clip_star)
- selection = WAYLAND_SELECTION_PRIMARY;
- else if (cbd == &clip_plus)
- selection = WAYLAND_SELECTION_REGULAR;
- else
+ if (!sel->available || vwl_connection_roundtrip(wayland_ct) == FAIL)
return FAIL;

- return wayland_cb_own_selection(
- clip_wl_send_data,
- clip_wl_selection_cancelled,
- supported_mimes,
- sizeof(supported_mimes)/sizeof(*supported_mimes),
- selection);
+ if (sel->source != NULL)
+ {
+ if (sel_type == WAYLAND_SELECTION_PRIMARY)
+ // We already own the selection, ignore (only do this for primary
+ // selection). We don't re set the selection because then we would
+ // be setting the selection every time the user moves the visual
+ // selection cursor, which is messy and inefficient. Some
+ // applications like Google Chrome do it this way however.
+ return OK;
+ else if (sel_type == WAYLAND_SELECTION_REGULAR)
+ {
+ // Technically we don't need to do this as we already own the
+ // selection, however if a user yanks text a second time, the
+ // text yanked won't appear in their clipboard manager if they are
+ // using one.
+ //
+ // This can be unexpected behaviour for the user so its probably
+ // better to do it this way. Additionally other Wayland applications
+ // seem to set the selection every time.
+ vwl_data_source_destroy(sel->source);
+ }
+ else
+ // Shouldn't happen
+ return FAIL;
+ }
+
+ sel->source = vwl_data_device_manager_create_data_source(sel->manager);
+ vwl_data_source_add_listener(sel->source, &vwl_data_source_listener, sel);
+
+ // Advertise mime types
+ vwl_data_source_offer(sel->source, wayland_vim_special_mime);
+ for (int i = 0; i < (int)ARRAY_LENGTH(supported_mimes); i++)
+ vwl_data_source_offer(sel->source, supported_mimes[i]);
+
+ sel->own_success = false;
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ if (sel->requires_focus)
+ {
+ if (clip_wl_init_fs_surface(clip_wl.seat, clip_wl.fs_buffer,
+ clip_wl_do_set_selection, sel) == FAIL)
+ goto fail;
+ }
+ else
+#endif
+ clip_wl_do_set_selection(sel, 0);
+
+ if (!sel->own_success)
+ goto fail;
+
+ return OK;
+fail:
+ vwl_data_source_destroy(sel->source);
+ sel->source = NULL;
+ return FAIL;
}

/*
- * Disown the selection that cbd corresponds to. Note that the the cancelled
- * event is not sent when the data source is destroyed.
+ * Disown the selection that cbd corresponds to.
*/
static 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);
+ clip_wl_selection_T *sel = clip_wl_get_selection_from_cbd(cbd);

- /* wayland_cb_lose_selection(selection); */
+ if (!sel->available)
+ return;
+
+ vwl_data_source_destroy(sel->source);
+ sel->source = NULL;
}

/*
@@ -2699,17 +3416,20 @@ clip_wl_set_selection(Clipboard_T *cbd UNUSED)
{
}

-#if defined(USE_SYSTEM) && defined(PROTO)
+#if defined(USE_SYSTEM) || defined(PROTO)
/*
- * Return TRUE if we own the selection corresponding to cbd
+ * Return true if we own the selection corresponding to cbd or another client
+ * does.
*/
- static int
+ static bool
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);
+ clip_wl_selection_T *sel = clip_wl_get_selection_from_cbd(cbd);
+
+ if (vwl_connection_roundtrip(wayland_ct) == FAIL)
+ return false;
+
+ return sel->available && (sel->source != NULL || sel->offer != NULL);
}
#endif

@@ -2721,7 +3441,7 @@ clip_wl_owner_exists(Clipboard_T *cbd)
* depending on the order of values in str.
*/
static clipmethod_T
-get_clipmethod(char_u *str)
+get_clipmethod(char_u *str, bool *regular, bool *primary)
{
int len = (int)STRLEN(str) + 1;
char_u *buf = alloc(len);
@@ -2745,8 +3465,12 @@ get_clipmethod(char_u *str)
#endif
{
#ifdef FEAT_WAYLAND_CLIPBOARD
- if (wayland_cb_is_ready())
+ if (clip_wl.regular.available || clip_wl.primary.available)
+ {
method = CLIPMETHOD_WAYLAND;
+ *regular = clip_wl.regular.available;
+ *primary = clip_wl.primary.available;
+ }
#endif
}
}
@@ -2767,6 +3491,7 @@ get_clipmethod(char_u *str)
// won't be executed since xterm_dpy will be set to NULL.
xterm_update();
method = CLIPMETHOD_X11;
+ *regular = *primary = true;
}
#endif
}
@@ -2775,13 +3500,17 @@ get_clipmethod(char_u *str)
{
#ifdef FEAT_GUI
if (gui.in_use)
+ {
method = CLIPMETHOD_GUI;
+ *regular = *primary = true;
+ }
#endif
}
else if (STRCMP(buf, "other") == 0)
{
#if !defined(FEAT_XCLIPBOARD) && !defined(FEAT_WAYLAND_CLIPBOARD)
method = CLIPMETHOD_OTHER;
+ *regular = *primary = true;
#endif
}
else
@@ -2832,9 +3561,8 @@ clipmethod_to_str(clipmethod_T method)
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);
+ bool regular = false, primary = false;
+ clipmethod_T method = get_clipmethod(p_cpm, &regular, &primary);

if (method == CLIPMETHOD_FAIL)
return e_invalid_argument;
@@ -2843,27 +3571,29 @@ choose_clipmethod(void)
if (method == CLIPMETHOD_GUI)
// We only interact with Wayland for the clipboard, we can just deinit
// everything.
- wayland_uninit_client();
+ wayland_uninit_connection();
#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)
+ else if (clipmethod == CLIPMETHOD_NONE && method != CLIPMETHOD_NONE)
{
- clip_init(TRUE);
- did_warn_clipboard = FALSE;
+ clip_init_single(&clip_plus, regular);
+ clip_init_single(&clip_star, primary);
+ clip_plus.did_warn = false;
+ clip_star.did_warn = false;
}
-
// Disown clipboard if we are switching to a new method
- if (clipmethod != CLIPMETHOD_NONE && method != clipmethod)
+ else if (clipmethod != CLIPMETHOD_NONE && method != clipmethod)
{
if (clip_star.owned)
clip_lose_selection(&clip_star);
if (clip_plus.owned)
clip_lose_selection(&clip_plus);
+ clip_init_single(&clip_plus, regular);
+ clip_init_single(&clip_star, primary);
}

clipmethod = method;
diff --git a/src/config.h.in b/src/config.h.in
index e8775d98c..983f186b8 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -12,6 +12,9 @@
/* Define unless no Wayland support found */
#undef HAVE_WAYLAND

+/* Define if you want focus stealing support with Wayland clipboard */
+#undef FEAT_WAYLAND_CLIPBOARD_FS
+
/* Define when terminfo support found */
#undef TERMINFO

diff --git a/src/configure.ac b/src/configure.ac
index 983c58224..6a641c2f2 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -2464,11 +2464,25 @@ AC_ARG_WITH(wayland,
AC_MSG_RESULT([yes])]))

if test "$with_wayland" = yes; then
-cppflags_save=$CPPFLAGS
-cflags_save=$CFLAGS
+ cppflags_save=$CPPFLAGS
+ cflags_save=$CFLAGS
+
AC_MSG_CHECKING(for wayland)
if "$PKG_CONFIG" --exists 'wayland-client'; then
AC_MSG_RESULT([yes])
+
+ AC_MSG_CHECKING(--enable-wayland-focus-steal argument)
+ AC_ARG_ENABLE(wayland-focus-steal,
+ [AS_HELP_STRING([--enable-wayland-focus-steal],
+ [Include focus stealing support for Wayland clipboard.])],
+ [enable_wayland_fs=$enableval],
+ enable_wayland_fs="yes")
+
+ AS_IF([test "$enable_wayland_fs" = "yes"],
+ [AC_MSG_RESULT([yes])
+ AC_DEFINE(FEAT_WAYLAND_CLIPBOARD_FS)],
+ [AC_MSG_RESULT([no])])
+
AC_DEFINE(HAVE_WAYLAND)
WAYLAND_CPPFLAGS=`$PKG_CONFIG --cflags-only-I wayland-client`
WAYLAND_CFLAGS=`$PKG_CONFIG --cflags-only-other wayland-client`
@@ -2478,15 +2492,20 @@ cflags_save=$CFLAGS
WAYLAND_SRC=" \
auto/wayland/wlr-data-control-unstable-v1.c \
auto/wayland/ext-data-control-v1.c \
- auto/wayland/xdg-shell.c \
- auto/wayland/primary-selection-unstable-v1.c \
wayland.c"
WAYLAND_OBJ=" \
objects/wlr-data-control-unstable-v1.o \
objects/ext-data-control-v1.o \
- objects/xdg-shell.o \
- objects/primary-selection-unstable-v1.o \
objects/wayland.o"
+
+ AS_IF([test "$enable_wayland_fs" = "yes"],
+ [AS_VAR_APPEND([WAYLAND_SRC], " \
+ auto/wayland/xdg-shell.c \
+ auto/wayland/primary-selection-unstable-v1.c")
+ AS_VAR_APPEND([WAYLAND_OBJ], " \
+ objects/xdg-shell.o \
+ objects/primary-selection-unstable-v1.o")])
+
AC_SUBST(WAYLAND_CPPFLAGS)
AC_SUBST(WAYLAND_CFLAGS)
AC_SUBST(WAYLAND_LIBS)
diff --git a/src/evalfunc.c b/src/evalfunc.c
index da5377799..a8d190638 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -7616,6 +7616,13 @@ f_has(typval_T *argvars, typval_T *rettv)
1
#else
0
+#endif
+ },
+ {"wayland_focus_steal",
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ 1
+#else
+ 0
#endif
},
{"wildignore", 1},
diff --git a/src/feature.h b/src/feature.h
index 826adbe2b..d7db96cd5 100644
--- a/src/feature.h
+++ b/src/feature.h
@@ -928,6 +928,13 @@
# endif
#endif

+/*
+ * +wayland_focus_steal Focus stealing support for Wayland clipboard
+ */
+#if !defined(FEAT_WAYLAND_CLIPBOARD) && defined(FEAT_WAYLAND_CLIPBOARD_FS)
+# undef FEAT_WAYLAND_CLIPBOARD_FS
+#endif
+
/*
* +dnd Drag'n'drop support. Always used for the GTK+ GUI.
*/
diff --git a/src/globals.h b/src/globals.h
index 8d031028e..653690b00 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -2071,23 +2071,23 @@ EXTERN char_u showcmd_buf[SHOWCMD_BUFLEN];
EXTERN int p_tgc_set INIT(= FALSE);
#endif

-// If we've already warned about missing/unavailable clipboard
-EXTERN int did_warn_clipboard INIT(= FALSE);
-
#ifdef FEAT_CLIPBOARD
EXTERN clipmethod_T clipmethod INIT(= CLIPMETHOD_NONE);
#endif

#ifdef FEAT_WAYLAND

-// Don't connect to Wayland compositor if TRUE
-EXTERN int wayland_no_connect INIT(= FALSE);
-
-// Wayland display name (ex. wayland-0). Can be NULL
+// Wayland display name for global connection (ex. wayland-0). Can be NULL
EXTERN char *wayland_display_name INIT(= NULL);

-// Wayland display file descriptor; set by wayland_init_client()
-EXTERN int wayland_display_fd;
+// Special mime type used to identify selection events that came from us setting
+// the selection. Is in format of "application/x-vim-instance-<pid>" where <pid>
+// is the PID of the Vim process. Set in main.c
+EXTERN char wayland_vim_special_mime[
+ sizeof("application/x-vim-instance-") + NUMBUFLEN - 1]; // Includes NUL
+
+// Don't connect to Wayland compositor if TRUE
+EXTERN int wayland_no_connect INIT(= FALSE);

#endif

diff --git a/src/main.c b/src/main.c
index c77454a26..50bc6f4f7 100644
--- a/src/main.c
+++ b/src/main.c
@@ -682,15 +682,18 @@ vim_main2(void)
if (!gui.in_use)
# endif
{
- if (wayland_init_client(wayland_display_name) == OK)
+ sprintf(wayland_vim_special_mime, "application/x-vim-instance-%ld",
+ mch_get_pid());
+
+ if (wayland_init_connection(wayland_display_name) == OK)
{
TIME_MSG("connected to Wayland display");

# ifdef FEAT_WAYLAND_CLIPBOARD
- if (wayland_cb_init((char*)p_wse) == OK)
+ if (clip_init_wayland() == OK)
TIME_MSG("setup Wayland clipboard");
- }
# endif
+ }
}
#endif

diff --git a/src/message.c b/src/message.c
index 35f57e8db..d5fc6b624 100644
--- a/src/message.c
+++ b/src/message.c
@@ -4166,17 +4166,37 @@ msg_advance(int col)
* Warn about missing Clipboard Support
*/
void
-msg_warn_missing_clipboard(void)
+msg_warn_missing_clipboard(bool plus UNUSED, bool star UNUSED)
{
- if (!global_busy && !did_warn_clipboard)
+#ifndef FEAT_CLIPBOARD
+ static bool did_warn;
+
+ if (!global_busy && !did_warn)
{
-#ifdef FEAT_CLIPBOARD
- msg(_("W23: Clipboard register not available, using register 0"));
-#else
msg(_("W24: Clipboard register not available. See :h W24"));
-#endif
- did_warn_clipboard = TRUE;
+ did_warn = true;
+ }
+#else
+ if (!global_busy)
+ {
+ if (plus && star && !clip_plus.did_warn && !clip_star.did_warn)
+ {
+ msg(_("W23: Clipboard register not available, using register 0"));
+ clip_plus.did_warn = true;
+ clip_star.did_warn = true;
+ }
+ else if (plus && !clip_plus.did_warn)
+ {
+ msg(_("W23: Clipboard register + not available, using register 0"));
+ clip_plus.did_warn = true;
+ }
+ else if (star && !clip_star.did_warn)
+ {
+ msg(_("W23: Clipboard register * not available, using register 0"));
+ clip_star.did_warn = true;
+ }
}
+#endif
}

#if defined(FEAT_CON_DIALOG) || defined(PROTO)
diff --git a/src/option.c b/src/option.c
index 7c5f4b99d..88da9c099 100644
--- a/src/option.c
+++ b/src/option.c
@@ -4774,7 +4774,7 @@ did_set_winwidth(optset_T *args UNUSED)
char *
did_set_wlsteal(optset_T *args UNUSED)
{
- wayland_cb_reload();
+ clip_reset_wayland();

return NULL;
}
diff --git a/src/option.h b/src/option.h
index 9e6aed355..77f3ceb3c 100644
--- a/src/option.h
+++ b/src/option.h
@@ -1144,7 +1144,7 @@ EXTERN long p_wmw; // 'winminwidth'
EXTERN long p_wiw; // 'winwidth'
#ifdef FEAT_WAYLAND
EXTERN char_u *p_wse; // 'wlseat'
-# ifdef FEAT_WAYLAND_CLIPBOARD
+# ifdef FEAT_WAYLAND_CLIPBOARD_FS
EXTERN int p_wst; // 'wlsteal'
# endif
EXTERN long p_wtm; // 'wltimeoutlen'
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 572b7aff1..626823c16 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -3010,7 +3010,7 @@ static struct vimoption options[] =
#endif
SCTX_INIT},
{"wlsteal", "wst", P_BOOL|P_VI_DEF,
-#ifdef FEAT_WAYLAND_CLIPBOARD
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
(char_u *)&p_wst, PV_NONE, did_set_wlsteal, NULL,
{(char_u *)FALSE, (char_u *)0L}
#else
diff --git a/src/optionstr.c b/src/optionstr.c
index 628842ef1..55cde5716 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -3689,7 +3689,7 @@ did_set_wlseat(optset_T *args UNUSED)
#ifdef FEAT_WAYLAND_CLIPBOARD
// If there isn't any seat named 'wlseat', then let the Wayland clipboard be
// unavailable. Ignore errors returned.
- wayland_cb_reload();
+ clip_reset_wayland();
#endif

return NULL;
diff --git a/src/os_unix.c b/src/os_unix.c
index b2bd8adb3..02116ee8c 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -5740,7 +5740,7 @@ mch_call_shell_fork(
#ifdef FEAT_WAYLAND
// Handle Wayland events such as sending data as the source
// client.
- wayland_client_update();
+ wayland_update();
#endif
}
finished:
@@ -5814,7 +5814,7 @@ finished:
#ifdef FEAT_WAYLAND
// Handle Wayland events such as sending data as the source
// client.
- wayland_client_update();
+ wayland_update();
#endif

// Wait for 1 to 10 msec. 1 is faster but gives the child
@@ -6666,6 +6666,9 @@ RealWaitForChar(int fd, long msec, int *check_for_gpm UNUSED, int *interrupted)
int mzquantum_used = FALSE;
# endif
#endif
+#ifdef FEAT_WAYLAND
+ int wayland_fd = -1;
+#endif
#ifndef HAVE_SELECT
// each channel may use in, out and err
struct pollfd fds[7 + 3 * MAX_OPEN_CHANNELS];
@@ -6709,11 +6712,11 @@ RealWaitForChar(int fd, long msec, int *check_for_gpm UNUSED, int *interrupted)
}
# endif

-# ifdef FEAT_WAYLAND_CLIPBOARD
- if (wayland_may_restore_connection())
+# ifdef FEAT_WAYLAND
+ if ((wayland_fd = wayland_prepare_read()) >= 0)
{
wayland_idx = nfd;
- fds[nfd].fd = wayland_display_fd;
+ fds[nfd].fd = wayland_fd;
fds[nfd].events = POLLIN;
nfd++;
}
@@ -6777,13 +6780,9 @@ RealWaitForChar(int fd, long msec, int *check_for_gpm UNUSED, int *interrupted)
}
# endif

-# ifdef FEAT_WAYLAND_CLIPBOARD
- // Technically we should first call wl_display_prepare_read() before
- // polling the fd, then read and dispatch after we poll. However that is
- // only needed for multi threaded environments to prevent deadlocks so
- // we are fine.
- if (fds[wayland_idx].revents & POLLIN)
- wayland_client_update();
+# ifdef FEAT_WAYLAND
+ if (wayland_idx >= 0)
+ wayland_poll_check(fds[wayland_idx].revents);
# endif

# ifdef FEAT_XCLIPBOARD
@@ -6876,14 +6875,13 @@ select_eintr:
}
# endif

-# ifdef FEAT_WAYLAND_CLIPBOARD
-
- if (wayland_may_restore_connection())
+# ifdef FEAT_WAYLAND
+ if ((wayland_fd = wayland_prepare_read()) >= 0)
{
- FD_SET(wayland_display_fd, &rfds);
+ FD_SET(wayland_fd, &rfds);

- if (maxfd < wayland_display_fd)
- maxfd = wayland_display_fd;
+ if (maxfd < wayland_fd)
+ maxfd = wayland_fd;
}
# endif

@@ -6983,13 +6981,9 @@ select_eintr:
socket_server_uninit();
# endif

-# ifdef FEAT_WAYLAND_CLIPBOARD
- // Technically we should first call wl_display_prepare_read() before
- // polling the fd, then read and dispatch after we poll. However that is
- // only needed for multi threaded environments to prevent deadlocks so
- // we are fine.
- if (ret > 0 && FD_ISSET(wayland_display_fd, &rfds))
- wayland_client_update();
+# ifdef FEAT_WAYLAND
+ if (wayland_fd != -1)
+ wayland_select_check(ret > 0 && FD_ISSET(wayland_fd, &rfds));
# endif

# ifdef FEAT_XCLIPBOARD
diff --git a/src/po/vim.pot b/src/po/vim.pot
index d5cb0795d..20571200d 100644
--- a/src/po/vim.pot
+++ b/src/po/vim.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Vim
"
"Report-Msgid-Bugs-To: vim...@vim.org
"
-"POT-Creation-Date: 2025-09-21 18:48+0000
"
+"POT-Creation-Date: 2025-09-22 19:04+0000
"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>
"
"Language-Team: LANGUAGE <L...@li.org>
"
@@ -2220,10 +2220,16 @@ msgstr ""
msgid " SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "
msgstr ""

+msgid "W24: Clipboard register not available. See :h W24"
+msgstr ""
+
msgid "W23: Clipboard register not available, using register 0"
msgstr ""

-msgid "W24: Clipboard register not available. See :h W24"
+msgid "W23: Clipboard register + not available, using register 0"
+msgstr ""
+
+msgid "W23: Clipboard register * not available, using register 0"
msgstr ""

msgid "Question"
diff --git a/src/proto/clipboard.pro b/src/proto/clipboard.pro
index 9ccfea90a..5727aa8b7 100644
--- a/src/proto/clipboard.pro
+++ b/src/proto/clipboard.pro
@@ -35,6 +35,9 @@ int clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd);
int may_get_selection(int regname);
void may_set_selection(void);
void adjust_clip_reg(int *rp);
+int clip_init_wayland(void);
+void clip_uninit_wayland(void);
+int clip_reset_wayland(void);
char *choose_clipmethod(void);
void ex_clipreset(exarg_T *eap);
/* vim: set ft=c : */
diff --git a/src/proto/message.pro b/src/proto/message.pro
index 34b381866..5b6c80b4d 100644
--- a/src/proto/message.pro
+++ b/src/proto/message.pro
@@ -74,7 +74,7 @@ void give_warning(char_u *message, int hl);
void give_warning_with_source(char_u *message, int hl, int with_source);
void give_warning2(char_u *message, char_u *a1, int hl);
void msg_advance(int col);
-void msg_warn_missing_clipboard(void);
+void msg_warn_missing_clipboard(bool plus, bool star);
int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton, char_u *textfield, int ex_cmd);
int vim_dialog_yesno(int type, char_u *title, char_u *message, int dflt);
int vim_dialog_yesnocancel(int type, char_u *title, char_u *message, int dflt);
diff --git a/src/proto/wayland.pro b/src/proto/wayland.pro
index a1f327dbc..113bf82e4 100644
--- a/src/proto/wayland.pro
+++ b/src/proto/wayland.pro
@@ -1,16 +1,27 @@
/* wayland.c */
-int wayland_init_client(const char *display);
-void wayland_uninit_client(void);
-int wayland_client_update(void);
-int wayland_cb_init(const char *seat);
-void wayland_cb_uninit(void);
-garray_T * wayland_cb_get_mime_types(wayland_selection_T selection);
-int wayland_cb_receive_data(const char *mime_type, wayland_selection_T selection);
-int wayland_cb_own_selection( wayland_cb_send_data_func_T send_cb, wayland_cb_selection_cancelled_func_T cancelled_cb, const char **mime_types, int len, wayland_selection_T selection);
-void wayland_cb_lose_selection(wayland_selection_T selection);
-int wayland_cb_selection_is_owned(wayland_selection_T selection);
-int wayland_cb_is_ready(void);
-int wayland_cb_reload(void);
-int wayland_may_restore_connection(void);
+int vwl_connection_flush(vwl_connection_T *self);
+int vwl_connection_dispatch(vwl_connection_T *self);
+int vwl_connection_roundtrip(vwl_connection_T *self);
+int wayland_init_connection(const char *display);
+void wayland_uninit_connection(void);
+int wayland_prepare_read(void);
+int wayland_update(void);
+void wayland_poll_check(int revents);
+void wayland_select_check(bool is_set);
void ex_wlrestore(exarg_T *eap);
+vwl_seat_T *vwl_connection_get_seat(vwl_connection_T *ct, const char *label);
+struct wl_keyboard *vwl_seat_get_keyboard(vwl_seat_T *seat);
+vwl_data_device_manager_T *vwl_connection_get_data_device_manager(vwl_connection_T *self,wayland_selection_T req_sel, unsigned int *supported);
+vwl_data_device_T *vwl_data_device_manager_get_data_device(vwl_data_device_manager_T *self,vwl_seat_T *seat);
+vwl_data_source_T *vwl_data_device_manager_create_data_source(vwl_data_device_manager_T *self);
+void vwl_data_device_destroy(vwl_data_device_T *self);
+void vwl_data_source_destroy(vwl_data_source_T *self);
+void vwl_data_offer_destroy(vwl_data_offer_T *self);
+void vwl_data_device_manager_discard(vwl_data_device_manager_T *self);
+void vwl_data_device_add_listener(vwl_data_device_T *self,const vwl_data_device_listener_T *listener, void *data);
+void vwl_data_source_add_listener(vwl_data_source_T *self,const vwl_data_source_listener_T *listener, void *data);
+void vwl_data_offer_add_listener(vwl_data_offer_T *self,const vwl_data_offer_listener_T *listener, void *data);
+void vwl_data_device_set_selection(vwl_data_device_T *self, vwl_data_source_T *source, uint32_t serial, wayland_selection_T selection);
+void vwl_data_source_offer(vwl_data_source_T *self, const char *mime_type);
+void vwl_data_offer_receive(vwl_data_offer_T *self, const char *mime_type, int32_t fd);
/* vim: set ft=c : */
diff --git a/src/register.c b/src/register.c
index 818166df5..a866321a3 100644
--- a/src/register.c
+++ b/src/register.c
@@ -204,7 +204,7 @@ valid_yank_reg(
else if (regname == '*' || regname == '+')
{
// Warn about missing clipboard support once
- msg_warn_missing_clipboard();
+ msg_warn_missing_clipboard(true, true);
return FALSE;
}
#endif
@@ -1189,7 +1189,7 @@ op_yank(oparg_T *oap, int deleting, int mess)
(!clip_plus.available && oap->regname == '+'))
{
oap->regname = 0;
- msg_warn_missing_clipboard();
+ msg_warn_missing_clipboard(!clip_plus.available, !clip_star.available);
}
#endif

diff --git a/src/structs.h b/src/structs.h
index e28c38340..981db0e7c 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -5239,20 +5239,26 @@ struct cellsize {

#ifdef FEAT_WAYLAND

+typedef struct vwl_connection_S vwl_connection_T;
+typedef struct vwl_seat_S vwl_seat_T;
+
+# ifdef FEAT_WAYLAND_CLIPBOARD
+
+typedef struct vwl_data_offer_S vwl_data_offer_T;
+typedef struct vwl_data_source_S vwl_data_source_T;
+typedef struct vwl_data_device_S vwl_data_device_T;
+typedef struct vwl_data_device_manager_S vwl_data_device_manager_T;
+
+typedef struct vwl_data_device_listener_S vwl_data_device_listener_T;
+typedef struct vwl_data_source_listener_S vwl_data_source_listener_T;
+typedef struct vwl_data_offer_listener_S vwl_data_offer_listener_T;
+
// Wayland selections
typedef enum {
- WAYLAND_SELECTION_NONE = 0x0,
- WAYLAND_SELECTION_REGULAR = 0x1,
- WAYLAND_SELECTION_PRIMARY = 0x2,
+ WAYLAND_SELECTION_NONE = 0,
+ WAYLAND_SELECTION_REGULAR = 1 << 0,
+ WAYLAND_SELECTION_PRIMARY = 1 << 1,
} wayland_selection_T;

-// Callback when another client wants us to send data to them
-typedef void (*wayland_cb_send_data_func_T)(
- const char *mime_type,
- int fd,
- wayland_selection_T type);
-
-// Callback when the selection is lost (data source object overwritten)
-typedef void (*wayland_cb_selection_cancelled_func_T)(wayland_selection_T type);
-
-#endif // FEAT_WAYLAND
+# endif
+#endif
diff --git a/src/testdir/test_wayland.vim b/src/testdir/test_wayland.vim
index 818574964..155172a0f 100644
--- a/src/testdir/test_wayland.vim
+++ b/src/testdir/test_wayland.vim
@@ -21,12 +21,17 @@ let s:old_wayland_display = $WAYLAND_DISPLAY
" every test function
func s:PreTest()
let $WAYLAND_DISPLAY=s:global_wayland_display
- exe 'wlrestore ' .. $WAYLAND_DISPLAY
+ " Always reconnect so we have a clean state each time and clear both
+ " selections.
+ call system('wl-copy -c')
+ call system('wl-copy -p -c')
+ exe 'wlrestore! ' .. $WAYLAND_DISPLAY

set cpm=wayland
endfunc

func s:SetupFocusStealing()
+ CheckFeature wayland_focus_steal
if !executable('wayland-info')
throw "Skipped: wayland-info program not available"
endif
@@ -35,7 +40,7 @@ func s:SetupFocusStealing()
" seat, so we must use the user's existing Wayland session if they are in one.
let $WAYLAND_DISPLAY = s:old_wayland_display

- exe 'wlrestore ' .. $WAYLAND_DISPLAY
+ exe 'wlrestore! ' .. $WAYLAND_DISPLAY

" Check if we have keyboard capability for seat
if system("wayland-info -i wl_seat | grep capabilities") !~? "keyboard"
@@ -50,16 +55,14 @@ func s:UnsetupFocusStealing()
unlet $VIM_WAYLAND_FORCE_FS
endfunc

-" Need X connection for tests that use client server communication
-func s:CheckXConnection()
- CheckFeature x11
- try
- call remote_send('xxx', '')
- catch /^Vim\%(( \+)\)\=:E240:/ " not possible to start a remote server
- throw 'Skipped: No connection to the X server possible'
- catch
- " ignore other errors
- endtry
+func s:CheckClientserver()
+ CheckFeature clientserver
+
+ if has('socketserver') && !has('x11')
+ if v:servername == ""
+ call remote_startserver('VIMSOCKETSERVER')
+ endif
+ endif
endfunc

func s:EndRemoteVim(name, job)
@@ -76,11 +79,7 @@ endfunc

func Test_wayland_startup()
call s:PreTest()
- call s:CheckXConnection()
-
- if v:servername == ""
- call remote_startserver('VIMSOCKETSERVER')
- endif
+ call s:CheckClientserver()

let l:name = 'WLVIMTEST'
let l:cmd = GetVimCommand() .. ' --servername ' .. l:name
@@ -168,18 +167,12 @@ func Test_wayland_connection_lost()
call EndWaylandCompositor(l:wayland_display)

call assert_equal('', getreg('+'))
- call assert_fails('put +', 'E353:')
- call assert_fails('yank +', 'E1548:')
endfunc

" Basic paste tests
func Test_wayland_paste()
call s:PreTest()

- " Prevent 'Register changed while using it' error, guessing this works because
- " it makes Vim lose the selection?
- wlrestore!
-
" Regular selection
new

@@ -218,8 +211,6 @@ endfunc
func Test_wayland_yank()
call s:PreTest()

- wlrestore!
-
new

call setline(1, 'testing')
@@ -264,7 +255,8 @@ func Test_wayland_mime_types_correct()
\ 'text/plain',
\ 'UTF8_STRING',
\ 'STRING',
- \ 'TEXT'
+ \ 'TEXT',
+ \ 'application/x-vim-instance-' .. getpid()
\ ]

call setreg('+', 'text', 'c')
@@ -359,7 +351,7 @@ endfunc
" Test if autoselect option in 'clipboard' works properly for Wayland
func Test_wayland_autoselect_works()
call s:PreTest()
- call s:CheckXConnection()
+ call s:CheckClientserver()

let l:lines =<< trim END
set cpm=wayland
@@ -375,10 +367,6 @@ func Test_wayland_autoselect_works()

call writefile(l:lines, 'Wltester', 'D')

- if v:servername == ""
- call remote_startserver('VIMSOCKETSERVER')
- endif
-
let l:name = 'WLVIMTEST'
let l:cmd = GetVimCommand() .. ' -S Wltester --servername ' .. l:name
let l:job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
@@ -407,24 +395,13 @@ func Test_wayland_autoselect_works()

eval remote_send(l:name, "\<Esc>:qa!\<CR>")

- try
- call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
- finally
- if job_status(l:job) != 'dead'
- call assert_report('Vim instance did not exit')
- call job_stop(l:job, 'kill')
- endif
- endtry
+ call s:EndRemoteVim(l:name, l:job)
endfunc

" Check if the -Y flag works properly
func Test_no_wayland_connect_cmd_flag()
call s:PreTest()
- call s:CheckXConnection()
-
- if v:servername == ""
- call remote_startserver('VIMSOCKETSERVER')
- endif
+ call s:CheckClientserver()

let l:name = 'WLFLAGVIMTEST'
let l:cmd = GetVimCommand() .. ' -Y --servername ' .. l:name
@@ -448,25 +425,13 @@ func Test_no_wayland_connect_cmd_flag()
call WaitForAssert({-> assert_equal('',
\ remote_expr(l:name, 'v:wayland_display'))})

- eval remote_send(l:name, "\<Esc>:qa!\<CR>")
- try
- call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
- finally
- if job_status(l:job) != 'dead'
- call assert_report('Vim instance did not exit')
- call job_stop(l:job, 'kill')
- endif
- endtry
+ call s:EndRemoteVim(l:name, l:job)
endfunc

" Test behaviour when we do something like suspend Vim
func Test_wayland_become_inactive()
call s:PreTest()
- call s:CheckXConnection()
-
- if v:servername == ""
- call remote_startserver('VIMSOCKETSERVER')
- endif
+ call s:CheckClientserver()

let l:name = 'WLLOSEVIMTEST'
let l:cmd = GetVimCommand() .. ' --servername ' .. l:name
@@ -488,15 +453,7 @@ func Test_wayland_become_inactive()
call WaitForAssert({-> assert_equal("Nothing is copied
",
\ system('wl-paste -n'))})

- eval remote_send(l:name, "\<Esc>:qa!\<CR>")
- try
- call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
- finally
- if job_status(l:job) != 'dead'
- call assert_report('Vim instance did not exit')
- call job_stop(l:job, 'kill')
- endif
- endtry
+ call s:EndRemoteVim(l:name, l:job)
endfunc

" Test wlseat option
@@ -527,6 +484,7 @@ endfunc

" Test focus stealing
func Test_wayland_focus_steal()
+ CheckFeature wayland_focus_steal
call s:PreTest()
call s:SetupFocusStealing()

@@ -552,17 +510,13 @@ endfunc
" Test when environment is not suitable for Wayland
func Test_wayland_bad_environment()
call s:PreTest()
- call s:CheckXConnection()
+ call s:CheckClientserver()

unlet $WAYLAND_DISPLAY

let l:old = $XDG_RUNTIME_DIR
unlet $XDG_RUNTIME_DIR

- if v:servername == ""
- call remote_startserver('VIMSOCKETSERVER')
- endif
-
let l:name = 'WLVIMTEST'
let l:cmd = GetVimCommand() .. ' --servername ' .. l:name
let l:job = job_start(cmd, {
@@ -576,15 +530,7 @@ func Test_wayland_bad_environment()
call WaitForAssert({-> assert_equal('',
\ remote_expr(l:name, 'v:wayland_display'))})

- eval remote_send(l:name, "\<Esc>:qa!\<CR>")
- try
- call WaitForAssert({-> assert_equal("dead", job_status(l:job))})
- finally
- if job_status(l:job) != 'dead'
- call assert_report('Vim instance did not exit')
- call job_stop(l:job, 'kill')
- endif
- endtry
+ call s:EndRemoteVim(l:name, l:job)

let $XDG_RUNTIME_DIR = l:old
endfunc
@@ -611,7 +557,11 @@ func Test_wayland_lost_selection()
call assert_equal('regular', getreg('+'))
call assert_equal('primary', getreg('*'))

- " Test focus stealing
+endfunc
+
+" Same as above but for the focus stealing method
+func Test_wayland_lost_selection_focus_steal()
+ call s:PreTest()
call s:SetupFocusStealing()

call setreg('+', 'regular')
@@ -624,7 +574,7 @@ func Test_wayland_lost_selection()
call system('wl-copy -p overwrite')

call assert_equal('overwrite', getreg('+'))
- call assert_equal('overwrite', getreg('*'))
+ call assert_equal('overwrite-primary', getreg('*'))

call setreg('+', 'regular')
call setreg('*', 'primary')
@@ -635,14 +585,14 @@ func Test_wayland_lost_selection()
call s:UnsetupFocusStealing()
endfunc

-" Test when there are no supported mime types for the selecftion
+" Test when there are no supported mime types for the selection
func Test_wayland_no_mime_types_supported()
call s:PreTest()

- wlrestore!
+ call system('wl-copy tester')
+ call assert_equal('tester', getreg('+'))

call system('wl-copy -t image/png testing')
-
call assert_equal('', getreg('+'))
call assert_fails('put +', 'E353:')
endfunc
@@ -651,28 +601,17 @@ endfunc
func Test_wayland_handle_large_data()
call s:PreTest()

- call writefile([''], 'data_file', 'D')
- call writefile([''], 'data_file_cmp', 'D')
- call system('yes c | head -5000000 > data_file') " ~ 10 MB
- call system('wl-copy -t TEXT < data_file')
-
- edit data_file_cmp
+ let l:file = tempname()
+ let l:contents = repeat('c', 1000000) " ~ 1 MB

- put! +
+ call writefile([l:contents], l:file, 'b')
+ call system('cat ' .. l:file .. ' | wl-copy -t TEXT')

- write
+ call assert_equal(l:contents, getreg('+'))

- call system('truncate -s -1 data_file_cmp') " Remove newline at the end
- call system('cmp --silent data_file data_file_cmp')
- call assert_equal(0, v:shell_error)
+ call setreg('+', l:contents, 'c')

- call feedkeys('gg0v$G"+yy', 'x')
- call system('wl-paste -n -t TEXT > data_file')
-
- call system('cmp --silent data_file data_file_cmp')
- call assert_equal(0, v:shell_error)
-
- bw!
+ call assert_equal(l:contents, system('wl-paste -n -t TEXT'))
endfunc

" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index efea7d85f..911b91399 100644
--- a/src/version.c
+++ b/src/version.c
@@ -658,6 +658,11 @@ static char *(features[]) =
"+wayland_clipboard",
#else
"-wayland_clipboard",
+#endif
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ "+wayland_focus_steal",
+#else
+ "-wayland_focus_steal",
#endif
"+wildignore",
"+wildmenu",
@@ -724,6 +729,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 1784,
/**/
1783,
/**/
diff --git a/src/vim.h b/src/vim.h
index dd4e782b3..3c4dacb44 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2364,6 +2364,8 @@ typedef struct
# ifdef FEAT_GUI_HAIKU
// No clipboard at the moment. TODO?
# endif
+ // If we've already warned about missing/unavailable clipboard
+ bool did_warn;
} Clipboard_T;
#else
typedef int Clipboard_T; // This is required for the prototypes.
diff --git a/src/wayland.c b/src/wayland.c
index 5aa8ec17e..33c10987f 100644
--- a/src/wayland.c
+++ b/src/wayland.c
@@ -8,485 +8,205 @@
*/

/*
- * wayland.c: Stuff related to Wayland
+ * wayland.c: Stuff related to Wayland. Functions that prefixed with "vwl_"
+ * handle/provide abstractions and building blocks to create more
+ * complex things. The "wayland_" functions handle the global
+ * Wayland connection.
+ *
+ * At the end of this file, there are a bunch of macro definitions
+ * that abstract away all the different protocols for the clipboard
+ * we need to support under one single interface. This is then used
+ * by clipboard.c to implement the Wayland clipboard functionality.
+ *
+ * The clipboard functionality monitors the Wayland display at all
+ * times, and saves new selections/offers as events come in. When we
+ * want retrieve the selection, the currently saved data offer is
+ * used from the respective data device.
+ *
+ * The focus stealing code is implemented in clipboard.c, and is
+ * based off of wl-clipboard's implementation. The idea using of
+ * extensive macros to reduce boilerplate code also comes from
+ * wl-clipboard as well. The project page for wl-clipboard can be
+ * found here: https://github.com/bugaevc/wl-clipboard
*/

#include "vim.h"

#ifdef FEAT_WAYLAND

-#include <wayland-client.h>
-
-#ifdef FEAT_WAYLAND_CLIPBOARD
-# include "auto/wayland/wlr-data-control-unstable-v1.h"
-# include "auto/wayland/ext-data-control-v1.h"
-# include "auto/wayland/xdg-shell.h"
-# include "auto/wayland/primary-selection-unstable-v1.h"
-#endif
-
-// Struct that represents a seat. (Should be accessed via
-// vwl_get_seat()).
-typedef struct {
- struct wl_seat *proxy;
- char *label; // Name of seat as text (e.g. seat0,
- // seat1...).
- uint32_t capabilities; // Bitmask of the capabilities of the seat
- // (pointer, keyboard, touch).
-} vwl_seat_T;
-
-// Global objects
-typedef struct {
-#ifdef FEAT_WAYLAND_CLIPBOARD
- // Data control protocols
- struct zwlr_data_control_manager_v1 *zwlr_data_control_manager_v1;
- struct ext_data_control_manager_v1 *ext_data_control_manager_v1;
- struct wl_data_device_manager *wl_data_device_manager;
- struct wl_shm *wl_shm;
- struct wl_compositor *wl_compositor;
- struct xdg_wm_base *xdg_wm_base;
- struct zwp_primary_selection_device_manager_v1
- *zwp_primary_selection_device_manager_v1;
-#endif
-} vwl_global_objects_T;
-
-// Struct wrapper for Wayland display and registry
-typedef struct {
- struct wl_display *proxy;
- int fd; // File descriptor for display
-
- struct {
- struct wl_registry *proxy;
- } registry;
-} vwl_display_T;
-
-#ifdef FEAT_WAYLAND_CLIPBOARD
-
-typedef struct {
- struct wl_shm_pool *pool;
- int fd;
-
- struct wl_buffer *buffer;
- int available;
-
- int width;
- int height;
- int stride;
- int size;
-} vwl_buffer_store_T;
-
-typedef struct {
- void *user_data;
- void (*on_focus)(void *data, uint32_t serial);
-
- struct wl_surface *surface;
- struct wl_keyboard *keyboard;
-
- struct {
- struct xdg_surface *surface;
- struct xdg_toplevel *toplevel;
- } shell;
-
- int got_focus;
-} vwl_fs_surface_T; // fs = focus steal
-
-// Wayland protocols for accessing the selection
-typedef enum {
- VWL_DATA_PROTOCOL_NONE,
- VWL_DATA_PROTOCOL_EXT,
- VWL_DATA_PROTOCOL_WLR,
- VWL_DATA_PROTOCOL_CORE,
- VWL_DATA_PROTOCOL_PRIMARY
-} vwl_data_protocol_T;
-
-// DATA RELATED OBJECT WRAPPERS
-// These wrap around a proxy and act as a generic container.
-// The `data` member is used to pass other needed stuff around such as a
-// vwl_clipboard_selection_T pointer.
-
-typedef struct {
- void *proxy;
- void *data; // Is not set when a new offer is created on a
- // data_offer event. Only set when listening to a
- // data offer.
- vwl_data_protocol_T protocol;
-} vwl_data_offer_T;
-
-typedef struct {
- void *proxy;
- void *data;
- vwl_data_protocol_T protocol;
-} vwl_data_source_T;
-
-typedef struct {
- void *proxy;
- void *data;
- vwl_data_protocol_T protocol;
-} vwl_data_device_T;
-
-typedef struct {
- void *proxy;
- vwl_data_protocol_T protocol;
-} vwl_data_device_manager_T;
-
-// LISTENER WRAPPERS
-
-typedef struct {
- void (*data_offer)(vwl_data_device_T *device, vwl_data_offer_T *offer);
-
- // If the protocol that the data device uses doesn't support a specific
- // selection, then this callback will never be called with that selection.
- void (*selection)(
- vwl_data_device_T *device,
- vwl_data_offer_T *offer,
- wayland_selection_T selection);
-
- // This event is only relevant for data control protocols
- void (*finished)(vwl_data_device_T *device);
-} vwl_data_device_listener_T;
-
-typedef struct {
- void (*send)(vwl_data_source_T *source, const char *mime_type, int fd);
- void (*cancelled)(vwl_data_source_T *source);
-} vwl_data_source_listener_T;
-
-typedef struct {
- void (*offer)(vwl_data_offer_T *offer, const char *mime_type);
-} vwl_data_offer_listener_T;
-
-typedef struct
-{
- // What selection this refers to
- wayland_selection_T selection;
-
- // Do not destroy here
- vwl_data_device_manager_T manager;
-
- vwl_data_device_T device;
- vwl_data_source_T source;
- vwl_data_offer_T *offer; // Current offer for the selection
-
- garray_T mime_types; // Mime types supported by the
- // current offer
-
- garray_T tmp_mime_types; // Temporary array for mime
- // types when we are receiving
- // them. When the selection
- // event arrives and it is the
- // one we want, then copy it
- // over to mime_types
-
- // To be populated by callbacks from outside this file
- wayland_cb_send_data_func_T send_cb;
- wayland_cb_selection_cancelled_func_T cancelled_cb;
-
- int requires_focus; // If focus needs to be given to us to work
-} vwl_clipboard_selection_T;
-
-// Holds stuff related to the clipboard/selections
-typedef struct {
- // Do not destroy here, will be destroyed when vwl_disconnect_display() is
- // called.
- vwl_seat_T *seat;
-
- vwl_clipboard_selection_T regular;
- vwl_clipboard_selection_T primary;
-
- vwl_buffer_store_T *fs_buffer;
-} vwl_clipboard_T;
-
-#endif // FEAT_WAYLAND_CLIPBOARD
-
-static int vwl_display_flush(vwl_display_T *display);
-static void vwl_callback_done(void *data, struct wl_callback *callback,
- uint32_t cb_data);
-static int vwl_display_roundtrip(vwl_display_T *display);
-static int vwl_display_dispatch(vwl_display_T *display);
-static int vwl_display_dispatch_any(vwl_display_T *display);
-
-static void vwl_log_handler(const char *fmt, va_list args);
-static int vwl_connect_display(const char *display);
-static void vwl_disconnect_display(void);
-
-static void vwl_xdg_wm_base_listener_ping(void *data,
- struct xdg_wm_base *base, uint32_t serial);
-static int vwl_listen_to_registry(void);
-
-static void vwl_registry_listener_global(void *data,
- struct wl_registry *registry, uint32_t name,
- const char *interface, uint32_t version);
-static void vwl_registry_listener_global_remove(void *data,
- struct wl_registry *registry, uint32_t name);
-
-static void vwl_add_seat(struct wl_seat *seat);
-static void vwl_seat_listener_name(void *data, struct wl_seat *seat,
- const char *name);
-static void vwl_seat_listener_capabilities(void *data, struct wl_seat *seat,
- uint32_t capabilities);
-static void vwl_destroy_seat(vwl_seat_T *seat);
-
-static vwl_seat_T *vwl_get_seat(const char *label);
-static struct wl_keyboard *vwl_seat_get_keyboard(vwl_seat_T *seat);
-
-#ifdef FEAT_WAYLAND_CLIPBOARD
-
-static int vwl_focus_stealing_available(void);
-static void vwl_xdg_surface_listener_configure(void *data,
- struct xdg_surface *surface, uint32_t serial);
-
-static void vwl_bs_buffer_listener_release(void *data,
- struct wl_buffer *buffer);
-static void vwl_destroy_buffer_store(vwl_buffer_store_T *store);
-static vwl_buffer_store_T *vwl_init_buffer_store(int width, int height);
-
-static void vwl_destroy_fs_surface(vwl_fs_surface_T *store);
-static int vwl_init_fs_surface(vwl_seat_T *seat,
- vwl_buffer_store_T *buffer_store,
- void (*on_focus)(void *, uint32_t), void *user_data);
-
-static void vwl_fs_keyboard_listener_enter(void *data,
- struct wl_keyboard *keyboard, uint32_t serial,
- struct wl_surface *surface, struct wl_array *keys);
-static void vwl_fs_keyboard_listener_keymap(void *data,
- struct wl_keyboard *keyboard, uint32_t format,
- int fd, uint32_t size);
-static void vwl_fs_keyboard_listener_leave(void *data,
- struct wl_keyboard *keyboard, uint32_t serial,
- struct wl_surface *surface);
-static void vwl_fs_keyboard_listener_key(void *data,
- struct wl_keyboard *keyboard, uint32_t serial,
- uint32_t time, uint32_t key, uint32_t state);
-static void vwl_fs_keyboard_listener_modifiers(void *data,
- struct wl_keyboard *keyboard, uint32_t serial,
- uint32_t mods_depressed, uint32_t mods_latched,
- uint32_t mods_locked, uint32_t group);
-static void vwl_fs_keyboard_listener_repeat_info(void *data,
- struct wl_keyboard *keyboard, int32_t rate, int32_t delay);
-
-static void vwl_gen_data_device_listener_data_offer(void *data,
- void *offer_proxy);
-static void vwl_gen_data_device_listener_selection(void *data,
- void *offer_proxy, wayland_selection_T selection,
- vwl_data_protocol_T protocol);
-
-static void vwl_data_device_destroy(vwl_data_device_T *device, int alloced);
-static void vwl_data_offer_destroy(vwl_data_offer_T *offer, int alloced);
-static void vwl_data_source_destroy(vwl_data_source_T *source, int alloced);
-
-static void vwl_data_device_add_listener(vwl_data_device_T *device,
- void *data);
-static void vwl_data_source_add_listener(vwl_data_source_T *source,
- void *data);
-static void vwl_data_offer_add_listener(vwl_data_offer_T *offer,
- void *data);
-
-static void vwl_data_device_set_selection(vwl_data_device_T *device,
- vwl_data_source_T *source, uint32_t serial,
- wayland_selection_T selection);
-static void vwl_data_offer_receive(vwl_data_offer_T *offer,
- const char *mime_type, int fd);
-static int vwl_get_data_device_manager(vwl_data_device_manager_T *manager,
- wayland_selection_T selection);
-static void vwl_get_data_device(vwl_data_device_manager_T *manager,
- vwl_seat_T *seat, vwl_data_device_T *device);
-static void vwl_create_data_source(vwl_data_device_manager_T *manager,
- vwl_data_source_T *source);
-static void vwl_data_source_offer(vwl_data_source_T *source,
- const char *mime_type);
-
-static void vwl_clipboard_free_mime_types(
- vwl_clipboard_selection_T *clip_sel);
-static int vwl_clipboard_selection_is_ready(
- vwl_clipboard_selection_T *clip_sel);
-
-static void vwl_data_device_listener_data_offer(
- vwl_data_device_T *device, vwl_data_offer_T *offer);
-static void vwl_data_offer_listener_offer(vwl_data_offer_T *offer,
- const char *mime_type);
-static void vwl_data_device_listener_selection(vwl_data_device_T *device,
- vwl_data_offer_T *offer, wayland_selection_T selection);
-static void vwl_data_device_listener_finished(vwl_data_device_T *device);
-
-static void vwl_data_source_listener_send(vwl_data_source_T *source,
- const char *mime_type, int fd);
-static void vwl_data_source_listener_cancelled(vwl_data_source_T *source);
-
-static void vwl_on_focus_set_selection(void *data, uint32_t serial);
-
-static void wayland_set_display(const char *display);
-
-static vwl_data_device_listener_T vwl_data_device_listener = {
- .data_offer = vwl_data_device_listener_data_offer,
- .selection = vwl_data_device_listener_selection,
- .finished = vwl_data_device_listener_finished
-};
-
-static vwl_data_source_listener_T vwl_data_source_listener = {
- .send = vwl_data_source_listener_send,
- .cancelled = vwl_data_source_listener_cancelled
-};
-
-static vwl_data_offer_listener_T vwl_data_offer_listener = {
- .offer = vwl_data_offer_listener_offer
-};
-
-static struct xdg_wm_base_listener vwl_xdg_wm_base_listener = {
- .ping = vwl_xdg_wm_base_listener_ping
-};
-
-static struct xdg_surface_listener vwl_xdg_surface_listener = {
- .configure = vwl_xdg_surface_listener_configure
-};
-
-static struct wl_buffer_listener vwl_cb_buffer_listener = {
- .release = vwl_bs_buffer_listener_release
-};
-
-static struct wl_keyboard_listener vwl_fs_keyboard_listener = {
- .enter = vwl_fs_keyboard_listener_enter,
- .key = vwl_fs_keyboard_listener_key,
- .keymap = vwl_fs_keyboard_listener_keymap,
- .leave = vwl_fs_keyboard_listener_leave,
- .modifiers = vwl_fs_keyboard_listener_modifiers,
- .repeat_info = vwl_fs_keyboard_listener_repeat_info
-};
-
-#endif // FEAT_WAYLAND_CLIPBOARD
-
-static struct wl_callback_listener vwl_callback_listener = {
- .done = vwl_callback_done
-};
-
-static struct wl_registry_listener vwl_registry_listener = {
- .global = vwl_registry_listener_global,
- .global_remove = vwl_registry_listener_global_remove
-};
-
-static struct wl_seat_listener vwl_seat_listener = {
- .name = vwl_seat_listener_name,
- .capabilities = vwl_seat_listener_capabilities
-};
-
-static vwl_display_T vwl_display;
-static vwl_global_objects_T vwl_gobjects;
-static garray_T vwl_seats;
+#include "wayland.h"

-#ifdef FEAT_WAYLAND_CLIPBOARD
-// Make sure to sync this with vwl_cb_uninit since it memsets this to zero
-static vwl_clipboard_T vwl_clipboard = {
- .regular.selection = WAYLAND_SELECTION_REGULAR,
- .primary.selection = WAYLAND_SELECTION_PRIMARY,
-};
-
-// Only really used for debugging/testing purposes in order to force focus
-// stealing even when a data control protocol is available.
-static int force_fs = FALSE;
-#endif
+vwl_connection_T *wayland_ct;
+bool is_reading = false;

/*
* Like wl_display_flush but always writes all the data in the buffer to the
* display fd. Returns FAIL on failure and OK on success.
*/
- static int
-vwl_display_flush(vwl_display_T *display)
+ int
+vwl_connection_flush(vwl_connection_T *self)
{
- int ret;
-
#ifndef HAVE_SELECT
struct pollfd fds;

- fds.fd = display->fd;
- fds.events = POLLOUT;
+ fds.fd = self->display.fd;
+ fds.events = POLLOUT;
#else
fd_set wfds;
struct timeval tv;

FD_ZERO(&wfds);
- FD_SET(display->fd, &wfds);
-
- tv.tv_sec = p_wtm / 1000;
- tv.tv_usec = (p_wtm % 1000) * 1000;
+ FD_SET(self->display.fd, &wfds);
#endif

- if (display->proxy == NULL)
+ if (self->display.proxy == NULL)
return FAIL;

// Send the requests we have made to the compositor, until we have written
// all the data. Poll in order to check if the display fd is writable, if
// not, then wait until it is and continue writing or until we timeout.
- while (errno = 0, (ret = wl_display_flush(display->proxy)) == -1
- && errno == EAGAIN)
+ while (true)
{
+ int ret = wl_display_flush(self->display.proxy);
+
+ if (ret == -1 && errno == EAGAIN)
+ {
#ifndef HAVE_SELECT
- if (poll(&fds, 1, p_wtm) <= 0)
+ if (poll(&fds, 1, p_wtm) <= 0)
#else
- if (select(display->fd + 1, NULL, &wfds, NULL, &tv) <= 0)
-#endif
+ tv.tv_sec = p_wtm / 1000;
+ tv.tv_usec = (p_wtm % 1000) * 1000;
+ if (select(self->display.fd + 1, NULL, &wfds, NULL, &tv) <= 0)
return FAIL;
-#ifdef HAVE_SELECT
- tv.tv_sec = 0;
- tv.tv_usec = p_wtm * 1000;
#endif
+ }
+ else if (ret == -1)
+ return FAIL;
+ else
+ break;
}
- // Return FAIL on error or timeout
- if ((errno != 0 && errno != EAGAIN) || ret == -1)
- return FAIL;

return OK;
}

+/*
+ * Like wl_display_roundtrip but polls the display fd with a timeout. Returns
+ * number of events dispatched on success else -1 on failure.
+ */
+ int
+vwl_connection_dispatch(vwl_connection_T *self)
+{
+#ifndef HAVE_SELECT
+ struct pollfd fds;
+
+ fds.fd = self->display.fd;
+ fds.events = POLLIN;
+#else
+ fd_set rfds;
+ struct timeval tv;
+
+ FD_ZERO(&rfds);
+ FD_SET(self->display.fd, &rfds);
+#endif
+
+ if (self->display.proxy == NULL)
+ return -1;
+
+ while (wl_display_prepare_read(self->display.proxy) == -1)
+ // Dispatch any queued events so that we can start reading
+ if (wl_display_dispatch_pending(self->display.proxy) == -1)
+ return -1;
+
+ // Send any requests before we starting blocking to read display fd
+ if (vwl_connection_flush(self) == FAIL)
+ {
+ wl_display_cancel_read(self->display.proxy);
+ return -1;
+ }
+
+ // Poll until there is data to read from the display fd.
+#ifndef HAVE_SELECT
+ if (poll(&fds, 1, p_wtm) <= 0)
+#else
+ tv.tv_sec = p_wtm / 1000;
+ tv.tv_usec = (p_wtm % 1000) * 1000;
+ if (select(self->display.fd + 1, &rfds, NULL, NULL, &tv) <= 0)
+#endif
+ {
+ wl_display_cancel_read(self->display.proxy);
+ return -1;
+ }
+
+ // Read events into the queue
+ if (wl_display_read_events(self->display.proxy) == -1)
+ // No need to cancel
+ return -1;
+
+ // Dispatch those events (call the handlers associated for each event)
+ return wl_display_dispatch_pending(self->display.proxy);
+}
+
/*
* Called when compositor is done processing requests/events.
*/
static void
-vwl_callback_done(void *data, struct wl_callback *callback,
- uint32_t cb_data UNUSED)
+vwl_callback_event_done(void *data, struct wl_callback *callback,
+ uint32_t callback_data UNUSED)
{
- *((int*)data) = TRUE;
+ *((bool*)data) = true;
wl_callback_destroy(callback);
}

+static const struct wl_callback_listener vwl_callback_listener = {
+ .done = vwl_callback_event_done
+};
+
/*
* Like wl_display_roundtrip but polls the display fd with a timeout. Returns
* FAIL on failure and OK on success.
*/
- static int
-vwl_display_roundtrip(vwl_display_T *display)
+ int
+vwl_connection_roundtrip(vwl_connection_T *self)
{
struct wl_callback *callback;
- int ret, done = FALSE;
- struct timeval start, now;
+ int ret;
+ bool done = false;
+#ifdef ELAPSED_FUNC
+ elapsed_T start_tv;
+#endif

- if (display->proxy == NULL)
+ if (self->display.proxy == NULL)
return FAIL;

// Tell compositor to emit 'done' event after processing all requests we
// have sent and handling events.
- callback = wl_display_sync(display->proxy);
+ callback = wl_display_sync(self->display.proxy);

if (callback == NULL)
return FAIL;

wl_callback_add_listener(callback, &vwl_callback_listener, &done);

- gettimeofday(&start, NULL);
+#ifdef ELAPSED_FUNC
+ ELAPSED_INIT(start_tv);
+#endif

// Wait till we get the done event (which will set `done` to TRUE), unless
// we timeout
- while (TRUE)
+ while (true)
{
- ret = vwl_display_dispatch(display);
+ ret = vwl_connection_dispatch(self);

if (done || ret == -1)
break;

- gettimeofday(&now, NULL);
-
- if ((now.tv_sec * 1000000 + now.tv_usec) -
- (start.tv_sec * 1000000 + start.tv_usec) >= p_wtm * 1000)
+#ifdef ELAPSED_FUNC
+ if (ELAPSED_FUNC(start_tv) >= p_wtm)
{
ret = -1;
break;
}
+#endif
}

if (ret == -1)
@@ -499,94 +219,6 @@ vwl_display_roundtrip(vwl_display_T *display)
return OK;
}

-/*
- * Like wl_display_roundtrip but polls the display fd with a timeout. Returns
- * number of events dispatched on success else -1 on failure.
- */
- static int
-vwl_display_dispatch(vwl_display_T *display)
-{
-#ifndef HAVE_SELECT
- struct pollfd fds;
-
- fds.fd = display->fd;
- fds.events = POLLIN;
-#else
- fd_set rfds;
- struct timeval tv;
-
- FD_ZERO(&rfds);
- FD_SET(display->fd, &rfds);
-
- tv.tv_sec = p_wtm / 1000;
- tv.tv_usec = (p_wtm % 1000) * 1000;
-#endif
-
- if (display->proxy == NULL)
- return -1;
-
- while (wl_display_prepare_read(display->proxy) == -1)
- // Dispatch any queued events so that we can start reading
- if (wl_display_dispatch_pending(display->proxy) == -1)
- return -1;
-
- // Send any requests before we starting blocking to read display fd
- if (vwl_display_flush(display) == FAIL)
- {
- wl_display_cancel_read(display->proxy);
- return -1;
- }
-
- // Poll until there is data to read from the display fd.
-#ifndef HAVE_SELECT
- if (poll(&fds, 1, p_wtm) <= 0)
-#else
- if (select(display->fd + 1, &rfds, NULL, NULL, &tv) <= 0)
-#endif
- {
- wl_display_cancel_read(display->proxy);
- return -1;
- }
-
- // Read events into the queue
- if (wl_display_read_events(display->proxy) == -1)
- return -1;
-
- // Dispatch those events (call the handlers associated for each event)
- return wl_display_dispatch_pending(display->proxy);
-}
-
-/*
- * Same as vwl_display_dispatch but poll/select is never called. This is useful
- * is poll/select was already called before or if you just want to dispatch any
- * events that happen to be waiting to be dispatched on the display fd.
- */
- static int
-vwl_display_dispatch_any(vwl_display_T *display)
-{
- if (display->proxy == NULL)
- return -1;
-
- while (wl_display_prepare_read(display->proxy) == -1)
- // Dispatch any queued events so that we can start reading
- if (wl_display_dispatch_pending(display->proxy) == -1)
- return -1;
-
- // Send any requests before we starting blocking to read display fd
- if (vwl_display_flush(display) == FAIL)
- {
- wl_display_cancel_read(display->proxy);
- return -1;
- }
-
- // Read events into the queue
- if (wl_display_read_events(display->proxy) == -1)
- return -1;
-
- // Dispatch those events (call the handlers associated for each event)
- return wl_display_dispatch_pending(display->proxy);
-}
-
/*
* Redirect libwayland logging to use ch_log + emsg instead.
*/
@@ -616,210 +248,128 @@ vwl_log_handler(const char *fmt, va_list args)
}

/*
- * Connect to the display with name; passing NULL will use libwayland's way of
- * getting the display. Additionally get the registry object but will not
- * starting listening. Returns OK on success and FAIL on failure.
- */
- static int
-vwl_connect_display(const char *display)
-{
- if (wayland_no_connect)
- return FAIL;
-
- // We will get an error if XDG_RUNTIME_DIR is not set.
- if (mch_getenv("XDG_RUNTIME_DIR") == NULL)
- return FAIL;
-
- // Must set log handler before we connect display in order to work.
- wl_log_set_handler_client(vwl_log_handler);
-
- vwl_display.proxy = wl_display_connect(display);
-
- if (vwl_display.proxy == NULL)
- return FAIL;
-
- wayland_set_display(display);
- vwl_display.fd = wl_display_get_fd(vwl_display.proxy);
-
- vwl_display.registry.proxy = wl_display_get_registry(vwl_display.proxy);
-
- if (vwl_display.registry.proxy == NULL)
- {
- vwl_disconnect_display();
- return FAIL;
- }
-
- return OK;
-}
-
-#define destroy_gobject(object) \
- if (vwl_gobjects.object != NULL) \
- { \
- object##_destroy(vwl_gobjects.object); \
- vwl_gobjects.object = NULL; \
- }
-
-/*
- * Disconnects the display and frees up all resources, including all global
- * objects.
+ * Callback for seat text label/name
*/
static void
-vwl_disconnect_display(void)
+wl_seat_listener_event_name(
+ void *data,
+ struct wl_seat *seat_proxy UNUSED,
+ const char *name)
{
+ vwl_seat_T *seat = data;

- destroy_gobject(ext_data_control_manager_v1)
- destroy_gobject(zwlr_data_control_manager_v1)
- destroy_gobject(wl_data_device_manager)
- destroy_gobject(wl_shm)
- destroy_gobject(wl_compositor)
- destroy_gobject(xdg_wm_base)
- destroy_gobject(zwp_primary_selection_device_manager_v1)
-
- for (int i = 0; i < vwl_seats.ga_len; i++)
- vwl_destroy_seat(&((vwl_seat_T *)vwl_seats.ga_data)[i]);
- ga_clear(&vwl_seats);
- vwl_seats.ga_len = 0;
-
- if (vwl_display.registry.proxy != NULL)
- {
- wl_registry_destroy(vwl_display.registry.proxy);
- vwl_display.registry.proxy = NULL;
- }
- if (vwl_display.proxy != NULL)
- {
- wl_display_disconnect(vwl_display.proxy);
- vwl_display.proxy = NULL;
- }
+ seat->label = (char *)vim_strsave((char_u *)name);
}

/*
- * Tells the compositor we are still responsive.
+ * Callback for seat capabilities
*/
static void
-vwl_xdg_wm_base_listener_ping(
- void *data UNUSED,
- struct xdg_wm_base *base,
- uint32_t serial)
-{
- xdg_wm_base_pong(base, serial);
-}
-
-/*
- * Start listening to the registry and get initial set of global
- * objects/interfaces.
- */
- static int
-vwl_listen_to_registry(void)
+wl_seat_listener_event_capabilities(
+ void *data,
+ struct wl_seat *seat_proxy UNUSED,
+ uint32_t capabilities)
{
- // Only meant for debugging/testing purposes
- char_u *env = mch_getenv("VIM_WAYLAND_FORCE_FS");
-
- if (env != NULL && STRCMP(env, "1") == 0)
- force_fs = TRUE;
- else
- force_fs = FALSE;
-
- ga_init2(&vwl_seats, sizeof(vwl_seat_T), 1);
-
- wl_registry_add_listener(
- vwl_display.registry.proxy,
- &vwl_registry_listener,
- NULL);
-
- if (vwl_display_roundtrip(&vwl_display) == FAIL)
- return FAIL;
+ vwl_seat_T *seat = data;

-#ifdef FEAT_WAYLAND_CLIPBOARD
- // If we have a suitable data control protocol discard the rest. If we only
- // have wlr data control protocol but its version is 1, then don't discard
- // globals if we also have the primary selection protocol.
- if (!force_fs &&
- (vwl_gobjects.ext_data_control_manager_v1 != NULL ||
- (vwl_gobjects.zwlr_data_control_manager_v1 != NULL &&
- zwlr_data_control_manager_v1_get_version(
- vwl_gobjects.zwlr_data_control_manager_v1) > 1)))
- {
- destroy_gobject(wl_data_device_manager)
- destroy_gobject(wl_shm)
- destroy_gobject(wl_compositor)
- destroy_gobject(xdg_wm_base)
- }
- else
- // Be ready for ping events
- xdg_wm_base_add_listener(
- vwl_gobjects.xdg_wm_base,
- &vwl_xdg_wm_base_listener,
- NULL);
-#endif
- return OK;
+ seat->capabilities = capabilities;
}

-#define SET_GOBJECT(object, min_ver) \
- do { \
- chosen_interface = &object##_interface; \
- object_member = (void*)&vwl_gobjects.object; \
- min_version = min_ver; \
- } while (0)
+static const struct wl_seat_listener wl_seat_listener = {
+ .name = wl_seat_listener_event_name,
+ .capabilities = wl_seat_listener_event_capabilities
+};
+
+static void vwl_seat_destroy(vwl_seat_T *self);

/*
* Callback for global event, for each global interface the compositor supports.
* Keep in sync with vwl_disconnect_display().
*/
static void
-vwl_registry_listener_global(
- void *data UNUSED,
- struct wl_registry *registry UNUSED,
+wl_registry_listener_event_global(
+ void *data,
+ struct wl_registry *registry,
uint32_t name,
const char *interface,
uint32_t version)
{
-
- const struct wl_interface *chosen_interface = NULL;
- void *proxy;
- uint32_t min_version;
- void **object_member;
+ vwl_connection_T *ct = data;

if (STRCMP(interface, wl_seat_interface.name) == 0)
{
- chosen_interface = &wl_seat_interface;
- min_version = 2;
- }
-#ifdef FEAT_WAYLAND_CLIPBOARD
- else if (STRCMP(interface, zwlr_data_control_manager_v1_interface.name) == 0)
- SET_GOBJECT(zwlr_data_control_manager_v1, 1);
-
- else if (STRCMP(interface, ext_data_control_manager_v1_interface.name) == 0)
- SET_GOBJECT(ext_data_control_manager_v1, 1);
+ struct wl_seat *seat_proxy = wl_registry_bind(registry, name,
+ &wl_seat_interface, version > 5 ? 5 : version);
+ vwl_seat_T *seat;

- else if (STRCMP(interface, wl_data_device_manager_interface.name) == 0)
- SET_GOBJECT(wl_data_device_manager, 1);
+ if (seat_proxy == NULL)
+ return;

- else if (STRCMP(interface, wl_shm_interface.name) == 0)
- SET_GOBJECT(wl_shm, 1);
+ seat = ALLOC_CLEAR_ONE(vwl_seat_T);

- else if (STRCMP(interface, wl_compositor_interface.name) == 0)
- SET_GOBJECT(wl_compositor, 2);
+ if (seat == NULL || ga_grow(&ct->gobjects.seats, 1) == FAIL)
+ {
+ vwl_seat_destroy(seat);
+ return;
+ }

- else if (STRCMP(interface, xdg_wm_base_interface.name) == 0)
- SET_GOBJECT(xdg_wm_base, 1);
+ seat->proxy = seat_proxy;
+ wl_seat_add_listener(seat_proxy, &wl_seat_listener, seat);

- else if (STRCMP(interface,
- zwp_primary_selection_device_manager_v1_interface.name) == 0)
- SET_GOBJECT(zwp_primary_selection_device_manager_v1, 1);
-#endif
+ if (vwl_connection_roundtrip(ct) == FAIL || seat->label == NULL)
+ {
+ vwl_seat_destroy(seat);
+ return;
+ }

- if (chosen_interface == NULL || version < min_version)
- return;
+ ((vwl_seat_T **)ct->gobjects.seats.ga_data)[ct->gobjects.seats.ga_len++]
+ = seat;
+ }
+#ifdef FEAT_WAYLAND_CLIPBOARD
+ else if (STRCMP(interface, zwlr_data_control_manager_v1_interface.name) == 0)
+ ct->gobjects.zwlr_data_control_manager_v1 =
+ wl_registry_bind(registry, name,
+ &zwlr_data_control_manager_v1_interface,
+ version > 2 ? 2 : version);

- proxy = wl_registry_bind(vwl_display.registry.proxy, name, chosen_interface,
- version);
+ else if (STRCMP(interface, ext_data_control_manager_v1_interface.name) == 0)
+ ct->gobjects.ext_data_control_manager_v1 =
+ wl_registry_bind(registry, name,
+ &ext_data_control_manager_v1_interface, 1);
+
+# ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ else if (p_wst)
+ {
+ if (STRCMP(interface, wl_data_device_manager_interface.name) == 0)
+ ct->gobjects.wl_data_device_manager =
+ wl_registry_bind(registry, name,
+ &wl_data_device_manager_interface, 1);
+
+ else if (STRCMP(interface, wl_shm_interface.name) == 0)
+ ct->gobjects.wl_shm =
+ wl_registry_bind(registry, name,
+ &wl_shm_interface, 1);
+
+ else if (STRCMP(interface, wl_compositor_interface.name) == 0)
+ ct->gobjects.wl_compositor =
+ wl_registry_bind(registry, name,
+ &wl_compositor_interface, 1);
+
+ else if (STRCMP(interface, xdg_wm_base_interface.name) == 0)
+ ct->gobjects.xdg_wm_base =
+ wl_registry_bind(registry, name,
+ &xdg_wm_base_interface, 1);
+
+ else if (STRCMP(interface,
+ zwp_primary_selection_device_manager_v1_interface.name)
+ == 0)
+ ct->gobjects.zwp_primary_selection_device_manager_v1 =
+ wl_registry_bind(registry, name,
+ &zwp_primary_selection_device_manager_v1_interface, 1);
+ }
+# endif // FEAT_WAYLAND_CLIPBOARD_FS
+#endif // FEAT_WAYLAND_CLIPBOARD

- if (chosen_interface == &wl_seat_interface)
- // Add seat to vwl_seats array, as we can have multiple seats.
- vwl_add_seat(proxy);
- else
- // Hold proxy & name in the vwl_gobject struct
- *object_member = proxy;
}

/*
@@ -829,86 +379,196 @@ vwl_registry_listener_global(
* global will just be ignored on the compositor side.
*/
static void
-vwl_registry_listener_global_remove(
+wl_registry_listener_event_global_remove(
void *data UNUSED,
struct wl_registry *registry UNUSED,
uint32_t name UNUSED)
{
}

-/*
- * Add a new seat given its proxy to the global grow array
- */
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
static void
-vwl_add_seat(struct wl_seat *seat_proxy)
+xdg_wm_base_listener_event_ping(
+ void *data UNUSED,
+ struct xdg_wm_base *xdg_base,
+ uint32_t serial)
{
- vwl_seat_T *seat;
+ xdg_wm_base_pong(xdg_base, serial);
+}
+#endif

- if (ga_grow(&vwl_seats, 1) == FAIL)
- return;
+static const struct wl_registry_listener wl_registry_listener = {
+ .global = wl_registry_listener_event_global,
+ .global_remove = wl_registry_listener_event_global_remove
+};

- seat = &((vwl_seat_T *)vwl_seats.ga_data)[vwl_seats.ga_len];
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+ .ping = xdg_wm_base_listener_event_ping
+};
+#endif

- seat->proxy = seat_proxy;
+static void vwl_connection_destroy(vwl_connection_T *self);

- // Get label and capabilities
- wl_seat_add_listener(seat_proxy, &vwl_seat_listener, seat);
+#ifdef FEAT_WAYLAND_CLIPBOARD

- if (vwl_display_roundtrip(&vwl_display) == FAIL)
- return;
+# define VWL_DESTROY_GOBJECT(ct, object) \
+ if (ct->gobjects.object != NULL) \
+ { \
+ object##_destroy(ct->gobjects.object); \
+ ct->gobjects.object = NULL; \
+ }

- // Check if label has been allocated
- if (seat->label == NULL)
- return;
+# define VWL_GOBJECT_AVAIL(ct, object) (ct->gobjects.object != NULL)

- vwl_seats.ga_len++;
-}
+#endif

-/*
- * Callback for seat text label/name
- */
- static void
-vwl_seat_listener_name(
- void *data,
- struct wl_seat *seat_proxy UNUSED,
- const char *name)
+// Make sure to call wayland_set_display(display);
+ static vwl_connection_T *
+vwl_connection_new(const char *display)
{
- vwl_seat_T *seat = data;
+ vwl_connection_T *ct;
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ const char_u *env;
+ bool force_fs;
+#endif
+ if (wayland_no_connect)
+ return NULL;

- seat->label = (char *)vim_strsave((char_u *)name);
+ // We will get an error if XDG_RUNTIME_DIR is not set.
+ if (mch_getenv("XDG_RUNTIME_DIR") == NULL)
+ return NULL;
+
+ ct = ALLOC_CLEAR_ONE(vwl_connection_T);
+
+ if (ct == NULL)
+ return NULL;
+
+ // Must set log handler before we connect display in order to work.
+ wl_log_set_handler_client(vwl_log_handler);
+
+ ct->display.proxy = wl_display_connect(display);
+
+ if (ct->display.proxy == NULL)
+ {
+ vim_free(ct);
+ return NULL;
+ }
+
+ ct->display.fd = wl_display_get_fd(ct->display.proxy);
+ ct->registry.proxy = wl_display_get_registry(ct->display.proxy);
+
+ if (ct->registry.proxy == NULL)
+ {
+ wl_display_disconnect(ct->display.proxy);
+ vim_free(ct);
+ return NULL;
+ }
+
+ ga_init2(&ct->gobjects.seats, sizeof(vwl_seat_T *), 1);
+
+ wl_registry_add_listener(ct->registry.proxy, &wl_registry_listener, ct);
+
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ env = mch_getenv("VIM_WAYLAND_FORCE_FS");
+ force_fs = (env != NULL && STRCMP(env, "1") == 0);
+
+ if (force_fs)
+ p_wst = TRUE;
+#endif
+
+ if (vwl_connection_roundtrip(ct) == FAIL)
+ {
+ vwl_connection_destroy(ct);
+ return NULL;
+ }
+
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ if (force_fs)
+ {
+ // Force using focus stealing method
+ VWL_DESTROY_GOBJECT(ct, ext_data_control_manager_v1)
+ VWL_DESTROY_GOBJECT(ct, zwlr_data_control_manager_v1)
+ }
+
+ // If data control protocols are available, we don't need the other global
+ // objects.
+ else if (VWL_GOBJECT_AVAIL(ct, ext_data_control_manager_v1)
+ || VWL_GOBJECT_AVAIL(ct, zwlr_data_control_manager_v1))
+ {
+ VWL_DESTROY_GOBJECT(ct, wl_data_device_manager)
+ VWL_DESTROY_GOBJECT(ct, wl_shm)
+ VWL_DESTROY_GOBJECT(ct, wl_compositor)
+ VWL_DESTROY_GOBJECT(ct, xdg_wm_base)
+ VWL_DESTROY_GOBJECT(ct, zwp_primary_selection_device_manager_v1)
+ }
+
+ // Start responding to pings from the compositor if we have xdg_wm_base
+ if (VWL_GOBJECT_AVAIL(ct, xdg_wm_base))
+ xdg_wm_base_add_listener(ct->gobjects.xdg_wm_base,
+ &xdg_wm_base_listener, NULL);
+#endif
+
+ return ct;
}

+#ifdef FEAT_WAYLAND_CLIPBOARD
+
/*
- * Callback for seat capabilities
+ * Destroy/free seat.
*/
static void
-vwl_seat_listener_capabilities(
- void *data,
- struct wl_seat *seat_proxy UNUSED,
- uint32_t capabilities)
+vwl_seat_destroy(vwl_seat_T *self)
{
- vwl_seat_T *seat = data;
-
- seat->capabilities = capabilities;
+ if (self == NULL)
+ return;
+ if (self->proxy != NULL)
+ {
+ if (wl_seat_get_version(self->proxy) >= 5)
+ // Helpful for the compositor
+ wl_seat_release(self->proxy);
+ else
+ wl_seat_destroy(self->proxy);
+ }
+ vim_free(self->label);
+ vim_free(self);
}

/*
- * Destroy/free seat.
+ * Disconnects the display and frees up all resources, including all global
+ * objects.
*/
static void
-vwl_destroy_seat(vwl_seat_T *seat)
+vwl_connection_destroy(vwl_connection_T *self)
{
- if (seat->proxy != NULL)
+#ifdef FEAT_WAYLAND_CLIPBOARD
+ VWL_DESTROY_GOBJECT(self, ext_data_control_manager_v1)
+ VWL_DESTROY_GOBJECT(self, zwlr_data_control_manager_v1)
+# ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ VWL_DESTROY_GOBJECT(self, wl_data_device_manager)
+ VWL_DESTROY_GOBJECT(self, wl_shm)
+ VWL_DESTROY_GOBJECT(self, wl_compositor)
+ VWL_DESTROY_GOBJECT(self, xdg_wm_base)
+ VWL_DESTROY_GOBJECT(self, zwp_primary_selection_device_manager_v1)
+# endif
+#endif
+
+ for (int i = 0; i < self->gobjects.seats.ga_len; i++)
+ vwl_seat_destroy(((vwl_seat_T **)self->gobjects.seats.ga_data)[i]);
+ ga_clear(&self->gobjects.seats);
+ self->gobjects.seats.ga_len = 0;
+
+ if (self->registry.proxy != NULL)
{
- if (wl_seat_get_version(seat->proxy) >= 5)
- // Helpful for the compositor
- wl_seat_release(seat->proxy);
- else
- wl_seat_destroy(seat->proxy);
- seat->proxy = NULL;
+ wl_registry_destroy(self->registry.proxy);
+ self->registry.proxy = NULL;
+ }
+ if (self->display.proxy != NULL)
+ {
+ wl_display_disconnect(self->display.proxy);
+ self->display.proxy = NULL;
}
- vim_free(seat->label);
- seat->label = NULL;
+ vim_free(self);
}

/*
@@ -916,50 +576,87 @@ vwl_destroy_seat(vwl_seat_T *seat)
* If NULL or an empty string is passed as the label then the first available
* seat found is used.
*/
- static vwl_seat_T *
-vwl_get_seat(const char *label)
+ vwl_seat_T *
+vwl_connection_get_seat(vwl_connection_T *self, const char *label)
{
- if ((STRCMP(label, "") == 0 || label == NULL) && vwl_seats.ga_len > 0)
- return &((vwl_seat_T *)vwl_seats.ga_data)[0];
+ if ((STRCMP(label, "") == 0 || label == NULL)
+ && self->gobjects.seats.ga_len > 0)
+ return ((vwl_seat_T **)self->gobjects.seats.ga_data)[0];

- for (int i = 0; i < vwl_seats.ga_len; i++)
+ for (int i = 0; i < self->gobjects.seats.ga_len; i++)
{
- vwl_seat_T *seat = &((vwl_seat_T *)vwl_seats.ga_data)[i];
+ vwl_seat_T *seat = ((vwl_seat_T **)self->gobjects.seats.ga_data)[i];
if (STRCMP(seat->label, label) == 0)
return seat;
}
return NULL;
}

+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
/*
* Get keyboard object from seat and return it. NULL is returned on
* failure such as when a keyboard is not available for seat.
*/
- static struct wl_keyboard *
-vwl_seat_get_keyboard(vwl_seat_T *seat)
+ struct wl_keyboard *
+vwl_seat_get_keyboard(vwl_seat_T *self)
{
- if (!(seat->capabilities & WL_SEAT_CAPABILITY_KEYBOARD))
+ if (!(self->capabilities & WL_SEAT_CAPABILITY_KEYBOARD))
return NULL;

- return wl_seat_get_keyboard(seat->proxy);
+ return wl_seat_get_keyboard(self->proxy);
+}
+#endif
+
+#endif
+
+/*
+ * Set wayland_display_name to display. Note that this allocate a copy of the
+ * string, unless NULL is passed. If NULL is passed then v:wayland_display is
+ * set to $WAYLAND_DISPLAY, but wayland_display_name is set to NULL.
+ */
+ static void
+wayland_set_display(const char *display)
+{
+ if (display == NULL)
+ display = (char*)mch_getenv((char_u*)"WAYLAND_DISPLAY");
+ else if (display == wayland_display_name)
+ // Don't want to be freeing vwl_display_strname then trying to copy it
+ // after.
+ goto exit;
+
+ if (display == NULL)
+ // $WAYLAND_DISPLAY is not set
+ display = "";
+
+ // Leave unchanged if display is empty (but not NULL)
+ if (STRCMP(display, "") != 0)
+ {
+ vim_free(wayland_display_name);
+ wayland_display_name = (char*)vim_strsave((char_u*)display);
+ }
+
+exit:
+#ifdef FEAT_EVAL
+ set_vim_var_string(VV_WAYLAND_DISPLAY, (char_u*)display, -1);
+#endif
}

/*
- * Connects to the Wayland display with given name and binds to global objects
- * as needed. If display is NULL then the $WAYLAND_DISPLAY environment variable
- * will be used (handled by libwayland). Returns FAIL on failure and OK on
+ * Initializes the global Wayland connection. Connects to the Wayland display
+ * with given name and binds to global objects as needed. If display is NULL
+ * then the $WAYLAND_DISPLAY environment variable will be used (handled by
+ * libwayland). Returns FAIL on failure and OK on
* success
*/
int
-wayland_init_client(const char *display)
+wayland_init_connection(const char *display)
{
wayland_set_display(display);

- if (vwl_connect_display(display) == FAIL ||
- vwl_listen_to_registry() == FAIL)
- goto fail;
+ wayland_ct = vwl_connection_new(display);

- wayland_display_fd = vwl_display.fd;
+ if (wayland_ct == NULL)
+ goto fail;

return OK;
fail:
@@ -969,1545 +666,803 @@ fail:
}

/*
- * Disconnect Wayland client and free up all resources used.
+ * Disconnect global Wayland connection and free up all resources used.
*/
void
-wayland_uninit_client(void)
+wayland_uninit_connection(void)
{
+ if (wayland_ct == NULL)
+ return;
#ifdef FEAT_WAYLAND_CLIPBOARD
- wayland_cb_uninit();
+ clip_uninit_wayland();
#endif
- vwl_disconnect_display();
-
+ vwl_connection_destroy(wayland_ct);
+ wayland_ct = NULL;
+ is_reading = false;
wayland_set_display("");
}

+static int wayland_ct_restore_count = 0;
+
/*
- * Return TRUE if Wayland display connection is valid and ready.
+ * Attempts to restore the Wayland display connection.
*/
- static int
-wayland_client_is_connected(int quiet)
+ static void
+wayland_restore_connection(void)
{
- if (vwl_display.proxy == NULL)
- goto error;
-
- // Display errors are always fatal
- if (wl_display_get_error(vwl_display.proxy) != 0
- || vwl_display_flush(&vwl_display) == FAIL)
- goto error;
-
- return TRUE;
-error:
- if (!quiet)
- emsg(e_wayland_connection_unavailable);
- return FALSE;
+ // No point in restoring the connection if we are exiting or dying.
+ if (exiting || v_dying || wayland_ct_restore_count <= 0)
+ wayland_set_display("");
+
+ --wayland_ct_restore_count;
+ wayland_uninit_connection();
+
+ if (wayland_init_connection(wayland_display_name) == OK)
+ {
+#ifdef FEAT_WAYLAND_CLIPBOARD
+ clip_init_wayland();
+#endif
+ }
}

/*
- * Flush requests and process new Wayland events, does not poll the display file
- * descriptor.
+ * Should be called before polling (select or poll) the global Wayland
+ * connection display fd. Returns fd on success and -1 on failure.
*/
int
-wayland_client_update(void)
+wayland_prepare_read(void)
{
- return vwl_display_dispatch_any(&vwl_display) == -1 ? FAIL : OK;
-}
+ if (wayland_ct == NULL)
+ return -1;

-#ifdef FEAT_WAYLAND_CLIPBOARD
+ if (is_reading)
+ {
+ wl_display_cancel_read(wayland_ct->display.proxy);
+ is_reading = false;
+ }

-/*
- * If globals required for focus stealing method is available.
- */
- static int
-vwl_focus_stealing_available(void)
-{
- return (p_wst || force_fs) &&
- vwl_gobjects.wl_compositor != NULL &&
- vwl_gobjects.wl_shm != NULL &&
- vwl_gobjects.xdg_wm_base != NULL;
+ while (wl_display_prepare_read(wayland_ct->display.proxy) == -1)
+ // Event queue not empty, dispatch the events
+ if (wl_display_dispatch_pending(wayland_ct->display.proxy) == -1)
+ return -1;
+
+ if (vwl_connection_flush(wayland_ct) < 0)
+ {
+ wl_display_cancel_read(wayland_ct->display.proxy);
+ return -1;
+ }
+
+ is_reading = true;
+
+ return wayland_ct->display.fd;
}

/*
- * Configure xdg_surface
+ * Catch up on any qeueued events
*/
- static void
-vwl_xdg_surface_listener_configure(
- void *data UNUSED,
- struct xdg_surface *surface,
- uint32_t serial)
+ int
+wayland_update(void)
{
- xdg_surface_ack_configure(surface, serial);
+ if (wayland_ct == NULL)
+ return FAIL;
+ return vwl_connection_roundtrip(wayland_ct);
}

-/*
- * Called when compositor isn't using the buffer anymore, we can reuse it again.
- */
- static void
-vwl_bs_buffer_listener_release(
- void *data,
- struct wl_buffer *buffer UNUSED)
+#ifndef HAVE_SELECT
+
+ void
+wayland_poll_check(int revents)
{
- vwl_buffer_store_T *store = data;
+ if (wayland_ct == NULL)
+ return;

- store->available = TRUE;
+ is_reading = false;
+ if (revents & POLLIN)
+ if (wl_display_read_events(wayland_ct->display.proxy) != -1)
+ {
+ wl_display_dispatch_pending(wayland_ct->display.proxy);
+ return;
+ }
+ else if (revents & (POLLHUP | POLLERR))
+ wl_display_cancel_read(wayland_ct->display.proxy);
+ else
+ {
+ // Nothing happened
+ wl_display_cancel_read(wayland_ct->display.proxy);
+ return;
+ }
+ wayland_restore_connection();
}

-/*
- * Destroy a buffer store structure.
- */
- static void
-vwl_destroy_buffer_store(vwl_buffer_store_T *store)
-{
- if (store->buffer != NULL)
- wl_buffer_destroy(store->buffer);
- if (store->pool != NULL)
- wl_shm_pool_destroy(store->pool);
+#else // ifdef HAVE_SELECT

- close(store->fd);
+ void
+wayland_select_check(bool is_set)
+{
+ if (wayland_ct == NULL)
+ return;

- vim_free(store);
+ is_reading = false;
+ if (is_set)
+ {
+ if (wl_display_read_events(wayland_ct->display.proxy) != -1)
+ wl_display_dispatch_pending(wayland_ct->display.proxy);
+ else
+ {
+ wl_display_cancel_read(wayland_ct->display.proxy);
+ wayland_restore_connection();
+ }
+ }
+ else
+ wl_display_cancel_read(wayland_ct->display.proxy);
}

+#endif // !HAVE_SELECT
+
/*
- * Initialize a buffer and its backing memory pool.
+ * Disconnect then reconnect Wayland connection, and update clipmethod.
*/
- static vwl_buffer_store_T *
-vwl_init_buffer_store(int width, int height)
+ void
+ex_wlrestore(exarg_T *eap)
{
- int fd, r;
- vwl_buffer_store_T *store;
-
- if (vwl_gobjects.wl_shm == NULL)
- return NULL;
-
- store = alloc(sizeof(*store));
-
- if (store == NULL)
- return NULL;
-
- store->available = FALSE;
+ char *display;

- store->width = width;
- store->height = height;
- store->stride = store->width * 4;
- store->size = store->stride * store->height;
+ if (eap->arg == NULL || STRLEN(eap->arg) == 0)
+ // Use current display name if none given
+ display = wayland_display_name;
+ else
+ display = (char*)eap->arg;

- fd = mch_create_anon_file();
- r = ftruncate(fd, store->size);
+ // Return early if shebang is not passed, we are still connected, and if not
+ // changing to a new Wayland display.
+ if (!eap->forceit && wayland_ct != NULL &&
+ (display == wayland_display_name ||
+ (wayland_display_name != NULL &&
+ STRCMP(wayland_display_name, display) == 0)))
+ return;

- if (r == -1)
+#ifdef FEAT_WAYLAND_CLIPBOARD
+ if (clipmethod == CLIPMETHOD_WAYLAND)
{
- if (fd >= 0)
- close(fd);
- return NULL;
+ // Lose any selections we own
+ if (clip_star.owned)
+ clip_lose_selection(&clip_star);
+ if (clip_plus.owned)
+ clip_lose_selection(&clip_plus);
}
+#endif

- store->pool = wl_shm_create_pool(vwl_gobjects.wl_shm, fd, store->size);
- store->buffer = wl_shm_pool_create_buffer(
- store->pool,
- 0,
- store->width,
- store->height,
- store->stride,
- WL_SHM_FORMAT_ARGB8888);
+ if (display != NULL)
+ display = (char*)vim_strsave((char_u*)display);

- store->fd = fd;
+ // Will lose any selections we own
+ wayland_uninit_connection();

- wl_buffer_add_listener(store->buffer, &vwl_cb_buffer_listener, store);
+ // Reset amount of available tries to reconnect the display to 5
+ wayland_ct_restore_count = 5;

- if (vwl_display_roundtrip(&vwl_display) == -1)
+ if (wayland_init_connection(display) == OK)
{
- vwl_destroy_buffer_store(store);
- return NULL;
+ smsg(_("restoring Wayland display %s"), wayland_display_name);
+
+#ifdef FEAT_WAYLAND_CLIPBOARD
+ clip_plus.did_warn = false;
+ clip_star.did_warn = false;
+ clip_init_wayland();
+#endif
}
+ else
+ msg(_("failed restoring, lost connection to Wayland display"));

- store->available = TRUE;
+ vim_free(display);

- return store;
+ choose_clipmethod();
}

+#ifdef FEAT_WAYLAND_CLIPBOARD
+
/*
- * Destroy a focus stealing store structure.
+ * Get a suitable data device manager from connection. "supported" should be
+ * iniitialized to VWL_DATA_PROTOCOL_NONE beforehand. Returns NULL if there are
+ * no data device manager available with the required selection.
*/
- static void
-vwl_destroy_fs_surface(vwl_fs_surface_T *store)
-{
- if (store->shell.toplevel != NULL)
- xdg_toplevel_destroy(store->shell.toplevel);
- if (store->shell.surface != NULL)
- xdg_surface_destroy(store->shell.surface);
- if (store->surface != NULL)
- wl_surface_destroy(store->surface);
- if (store->keyboard != NULL)
+ vwl_data_device_manager_T *
+vwl_connection_get_data_device_manager(
+ vwl_connection_T *self,
+ wayland_selection_T req_sel,
+ int_u *supported)
+{
+ vwl_data_device_manager_T *manager =
+ ALLOC_CLEAR_ONE(vwl_data_device_manager_T);
+
+ // Prioritize ext-data-control-v1 over wlr-data-control-unstable-v1 because
+ // it is newer.
+ if (self->gobjects.ext_data_control_manager_v1 != NULL)
{
- if (wl_keyboard_get_version(store->keyboard) >= 3)
- wl_keyboard_release(store->keyboard);
- else
- wl_keyboard_destroy(store->keyboard);
- }
- vim_free(store);
-}
-
-/*
- * Create an invisible surface in order to gain focus and call on_focus() with
- * serial that was given.
- */
- static int
-vwl_init_fs_surface(
- vwl_seat_T *seat,
- vwl_buffer_store_T *buffer_store,
- void (*on_focus)(void *, uint32_t),
- void *user_data)
-{
- vwl_fs_surface_T *store;
-
- if (vwl_gobjects.wl_compositor == NULL || vwl_gobjects.xdg_wm_base == NULL)
- return FAIL;
- if (buffer_store == NULL || seat == NULL)
- return FAIL;
-
- store = alloc_clear(sizeof(*store));
-
- if (store == NULL)
- return FAIL;
-
- // Get keyboard
- store->keyboard = vwl_seat_get_keyboard(seat);
-
- if (store->keyboard == NULL)
- goto fail;
-
- wl_keyboard_add_listener(store->keyboard, &vwl_fs_keyboard_listener, store);
+ manager->proxy = self->gobjects.ext_data_control_manager_v1;
+ manager->protocol = VWL_DATA_PROTOCOL_EXT;

- if (vwl_display_dispatch(&vwl_display) == -1)
- goto fail;
-
- store->surface = wl_compositor_create_surface(vwl_gobjects.wl_compositor);
- store->shell.surface = xdg_wm_base_get_xdg_surface(
- vwl_gobjects.xdg_wm_base, store->surface);
- store->shell.toplevel = xdg_surface_get_toplevel(store->shell.surface);
-
- xdg_toplevel_set_title(store->shell.toplevel, "Vim clipboard");
-
- xdg_surface_add_listener(store->shell.surface,
- &vwl_xdg_surface_listener, NULL);
-
- wl_surface_commit(store->surface);
-
- store->on_focus = on_focus;
- store->user_data = user_data;
- store->got_focus = FALSE;
-
- if (vwl_display_roundtrip(&vwl_display) == FAIL)
- goto fail;
-
- // We may get the enter event early, if we do then we will set `got_focus`
- // to TRUE.
- if (store->got_focus)
- goto early_exit;
-
- // Buffer hasn't been released yet, abort. This shouldn't happen but still
- // check for it.
- if (!buffer_store->available)
- goto fail;
-
- buffer_store->available = FALSE;
+ *supported |= (WAYLAND_SELECTION_REGULAR | WAYLAND_SELECTION_PRIMARY);
+ }
+ else if (self->gobjects.zwlr_data_control_manager_v1 != NULL)
+ {
+ manager->proxy = self->gobjects.zwlr_data_control_manager_v1;
+ manager->protocol = VWL_DATA_PROTOCOL_WLR;

- wl_surface_attach(store->surface, buffer_store->buffer, 0, 0);
- wl_surface_damage(store->surface, 0, 0,
- buffer_store->width, buffer_store->height);
- wl_surface_commit(store->surface);
+ *supported |= WAYLAND_SELECTION_REGULAR;

+ // Only version 2 or greater supports the primary selection
+ if (zwlr_data_control_manager_v1_get_version(manager->proxy) >= 2)
+ *supported |= WAYLAND_SELECTION_PRIMARY;
+ }
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ else if (self->gobjects.wl_data_device_manager != NULL
+ && req_sel == WAYLAND_SELECTION_REGULAR)
{
- // Dispatch events until we receive the enter event. Add a max delay of
- // 'p_wtm' when waiting for it (may be longer depending on how long we
- // poll when dispatching events)
- struct timeval start, now;
+ manager->proxy = self->gobjects.wl_data_device_manager;
+ manager->protocol = VWL_DATA_PROTOCOL_CORE;

- gettimeofday(&start, NULL);
+ *supported |= WAYLAND_SELECTION_REGULAR;
+ }

- while (vwl_display_dispatch(&vwl_display) != -1)
+ if (req_sel == WAYLAND_SELECTION_PRIMARY
+ && !(*supported & WAYLAND_SELECTION_PRIMARY))
+ if (self->gobjects.zwp_primary_selection_device_manager_v1 != NULL)
{
- if (store->got_focus)
- break;
-
- gettimeofday(&now, NULL);
+ manager->proxy =
+ self->gobjects.zwp_primary_selection_device_manager_v1;
+ manager->protocol = VWL_DATA_PROTOCOL_PRIMARY;

- if ((now.tv_sec * 1000000 + now.tv_usec) -
- (start.tv_sec * 1000000 + start.tv_usec)
- >= p_wtm * 1000)
- goto fail;
+ *supported |= WAYLAND_SELECTION_PRIMARY;
}
- }
-early_exit:
- vwl_destroy_fs_surface(store);
- vwl_display_flush(&vwl_display);
+#endif

- return OK;
-fail:
- vwl_destroy_fs_surface(store);
- vwl_display_flush(&vwl_display);
+ if (!(*supported & req_sel))
+ {
+ vim_free(manager);
+ return NULL;
+ }

- return FAIL;
+ return manager;
}

-/*
- * Called when the keyboard focus is on our surface
- */
- static void
-vwl_fs_keyboard_listener_enter(
- void *data,
- struct wl_keyboard *keyboard UNUSED,
- uint32_t serial,
- struct wl_surface *surface UNUSED,
- struct wl_array *keys UNUSED)
+ vwl_data_device_T *
+vwl_data_device_manager_get_data_device(
+ vwl_data_device_manager_T *self,
+ vwl_seat_T *seat)
{
- vwl_fs_surface_T *store = data;
-
- store->got_focus = TRUE;
-
- if (store->on_focus != NULL)
- store->on_focus(store->user_data, serial);
-}
+ vwl_data_device_T *device = ALLOC_CLEAR_ONE(vwl_data_device_T);

-// Dummy functions to handle keyboard events we don't care about.
+ switch (self->protocol)
+ {
+ case VWL_DATA_PROTOCOL_EXT:
+ device->proxy = ext_data_control_manager_v1_get_data_device(
+ self->proxy, seat->proxy);
+ break;
+ case VWL_DATA_PROTOCOL_WLR:
+ device->proxy = zwlr_data_control_manager_v1_get_data_device(
+ self->proxy, seat->proxy);
+ break;
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ case VWL_DATA_PROTOCOL_CORE:
+ device->proxy = wl_data_device_manager_get_data_device(
+ self->proxy, seat->proxy);
+ break;
+ case VWL_DATA_PROTOCOL_PRIMARY:
+ device->proxy = zwp_primary_selection_device_manager_v1_get_device(
+ self->proxy, seat->proxy);
+ break;
+#endif
+ default:
+ vim_free(device);
+ return NULL;
+ }
+ device->protocol = self->protocol;

- static void
-vwl_fs_keyboard_listener_keymap(
- void *data UNUSED,
- struct wl_keyboard *keyboard UNUSED,
- uint32_t format UNUSED,
- int fd,
- uint32_t size UNUSED)
-{
- close(fd);
+ return device;
}

- static void
-vwl_fs_keyboard_listener_leave(
- void *data UNUSED,
- struct wl_keyboard *keyboard UNUSED,
- uint32_t serial UNUSED,
- struct wl_surface *surface UNUSED)
+ vwl_data_source_T *
+vwl_data_device_manager_create_data_source(vwl_data_device_manager_T *self)
{
-}
+ vwl_data_source_T *source = ALLOC_CLEAR_ONE(vwl_data_source_T);

- static void
-vwl_fs_keyboard_listener_key(
- void *data UNUSED,
- struct wl_keyboard *keyboard UNUSED,
- uint32_t serial UNUSED,
- uint32_t time UNUSED,
- uint32_t key UNUSED,
- uint32_t state UNUSED)
-{
-}
+ switch (self->protocol)
+ {
+ case VWL_DATA_PROTOCOL_EXT:
+ source->proxy = ext_data_control_manager_v1_create_data_source(
+ self->proxy);
+ break;
+ case VWL_DATA_PROTOCOL_WLR:
+ source->proxy = zwlr_data_control_manager_v1_create_data_source(
+ self->proxy);
+ break;
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ case VWL_DATA_PROTOCOL_CORE:
+ source->proxy = wl_data_device_manager_create_data_source(
+ self->proxy);
+ break;
+ case VWL_DATA_PROTOCOL_PRIMARY:
+ source->proxy =
+ zwp_primary_selection_device_manager_v1_create_source(
+ self->proxy);
+ break;
+#endif
+ default:
+ vim_free(source);
+ return NULL;
+ }
+ source->protocol = self->protocol;

- static void
-vwl_fs_keyboard_listener_modifiers(
- void *data UNUSED,
- struct wl_keyboard *keyboard UNUSED,
- uint32_t serial UNUSED,
- uint32_t mods_depressed UNUSED,
- uint32_t mods_latched UNUSED,
- uint32_t mods_locked UNUSED,
- uint32_t group UNUSED)
-{
+ return source;
}

- static void
-vwl_fs_keyboard_listener_repeat_info(
- void *data UNUSED,
- struct wl_keyboard *keyboard UNUSED,
- int32_t rate UNUSED,
- int32_t delay UNUSED)
+ static vwl_data_offer_T *
+vwl_data_device_wrap_offer_proxy(vwl_data_device_T *self, void *proxy)
{
-}
+ vwl_data_offer_T *offer = ALLOC_CLEAR_ONE(vwl_data_offer_T);

-#define VWL_CODE_DATA_OBJECT_DESTROY(type) \
-do { \
- if (type == NULL || type->proxy == NULL) \
- return; \
- switch (type->protocol) \
- { \
- case VWL_DATA_PROTOCOL_WLR: \
- zwlr_data_control_##type##_v1_destroy(type->proxy); \
- break; \
- case VWL_DATA_PROTOCOL_EXT: \
- ext_data_control_##type##_v1_destroy(type->proxy); \
- break; \
- case VWL_DATA_PROTOCOL_CORE: \
- wl_data_##type##_destroy(type->proxy); \
- break; \
- case VWL_DATA_PROTOCOL_PRIMARY: \
- zwp_primary_selection_##type##_v1_destroy(type->proxy); \
- break; \
- default: \
- break; \
- } \
- if (alloced) \
- vim_free(type); \
- else \
- type->proxy = NULL; \
-} while (0)
+ if (offer == NULL)
+ return NULL;

- static void
-vwl_data_device_destroy(vwl_data_device_T *device, int alloced)
-{
- VWL_CODE_DATA_OBJECT_DESTROY(device);
-}
+ offer->proxy = proxy;
+ offer->protocol = self->protocol;
+ offer->data = self->data;
+ ga_init2(&offer->mime_types, sizeof(char *), 10);

- static void
-vwl_data_offer_destroy(vwl_data_offer_T *offer, int alloced)
-{
- VWL_CODE_DATA_OBJECT_DESTROY(offer);
-}
+ // Try pre allocating the array, 10 mime types seems to usually be the
+ // maximum from experience.
+ ga_grow(&offer->mime_types, 10);

- static void
-vwl_data_source_destroy(vwl_data_source_T *source, int alloced)
-{
- VWL_CODE_DATA_OBJECT_DESTROY(source);
+ return offer;
}

+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+# define VWL_CODE_DATA_PROXY_FS_DESTROY(type) \
+ case VWL_DATA_PROTOCOL_CORE: \
+ wl_data_##type##_destroy(self->proxy); \
+ break; \
+ case VWL_DATA_PROTOCOL_PRIMARY: \
+ zwp_primary_selection_##type##_v1_destroy(self->proxy); \
+ break;
+#else
+# define VWL_CODE_DATA_PROXY_FS_DESTROY(type)
+#endif

-// Used to pass a vwl_data_offer_T struct from the data_offer event to the offer
-// event and to the selection event.
-static vwl_data_offer_T *tmp_vwl_offer;
+#define VWL_FUNC_DATA_PROXY_DESTROY(type) \
+ void \
+ vwl_data_##type##_destroy(vwl_data_##type##_T *self) \
+ { \
+ if (self == NULL) \
+ return; \
+ switch (self->protocol) \
+ { \
+ case VWL_DATA_PROTOCOL_EXT: \
+ ext_data_control_##type##_v1_destroy(self->proxy); \
+ break; \
+ case VWL_DATA_PROTOCOL_WLR: \
+ zwlr_data_control_##type##_v1_destroy(self->proxy); \
+ break; \
+ VWL_CODE_DATA_PROXY_FS_DESTROY(type) \
+ default: \
+ break; \
+ } \
+ vim_free(self); \
+ }

-// These functions handle the more complicated data_offer and selection events.
+VWL_FUNC_DATA_PROXY_DESTROY(device)
+VWL_FUNC_DATA_PROXY_DESTROY(source)

- static void
-vwl_gen_data_device_listener_data_offer(void *data, void *offer_proxy)
+ void
+vwl_data_offer_destroy(vwl_data_offer_T *self)
{
- vwl_data_device_T *device = data;
-
- tmp_vwl_offer = alloc(sizeof(*tmp_vwl_offer));
-
- if (tmp_vwl_offer != NULL)
+ if (self == NULL)
+ return;
+ switch (self->protocol)
{
- tmp_vwl_offer->proxy = offer_proxy;
- tmp_vwl_offer->protocol = device->protocol;
-
- vwl_data_device_listener.data_offer(device, tmp_vwl_offer);
+ case VWL_DATA_PROTOCOL_EXT:
+ ext_data_control_offer_v1_destroy(self->proxy);
+ break;
+ case VWL_DATA_PROTOCOL_WLR:
+ zwlr_data_control_offer_v1_destroy(self->proxy);
+ break;
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ case VWL_DATA_PROTOCOL_CORE:
+ wl_data_offer_destroy(self->proxy);
+ break;
+ case VWL_DATA_PROTOCOL_PRIMARY:
+ zwp_primary_selection_offer_v1_destroy(self->proxy);
+ break;
+#endif
+ default:
+ break;
}
+ ga_clear_strings(&self->mime_types);
+ vim_free(self);
}

- static void
-vwl_gen_data_device_listener_selection(
- void *data,
- void *offer_proxy,
- wayland_selection_T selection,
- vwl_data_protocol_T protocol)
+/*
+ * Doesn't destroy the actual global object proxy, only frees the structure.
+ */
+void
+vwl_data_device_manager_discard(vwl_data_device_manager_T *self)
{
- if (tmp_vwl_offer == NULL)
- {
- // Memory allocation failed or selection cleared (data_offer is never
- // sent when selection is cleared/empty).
- vwl_data_offer_T tmp = {
- .proxy = offer_proxy,
- .protocol = protocol
- };
-
- vwl_data_offer_destroy(&tmp, FALSE);
-
- // If offer proxy is NULL then we know the selection has been cleared.
- if (offer_proxy == NULL)
- vwl_data_device_listener.selection(data, NULL, selection);
+ if (self == NULL)
+ return;
+ vim_free(self);
+}
+
+#define VWL_FUNC_DATA_DEVICE_EVENT_DATA_OFFER(device_type, offer_type) \
+ static void \
+ device_type##_listener_event_data_offer( \
+ void *data, \
+ struct device_type *device UNUSED, \
+ struct offer_type *offer) \
+ { \
+ vwl_data_device_T *self = data; \
+ self->offer = vwl_data_device_wrap_offer_proxy(self, offer); \
+ self->listener->data_offer(self->data, self, self->offer); \
}
- else
- {
- vwl_data_device_listener.selection(data, tmp_vwl_offer, selection);
- tmp_vwl_offer = NULL;
+
+// We want to set the offer to NULL after the selection callback, because the
+// callback may free the offer, and we don't want a dangling pointer.
+#define VWL_FUNC_DATA_DEVICE_EVENT_SELECTION(device_type, offer_type) \
+ static void \
+ device_type##_listener_event_selection( \
+ void *data, \
+ struct device_type *device UNUSED, \
+ struct offer_type *offer UNUSED) \
+ { \
+ vwl_data_device_T *self = data; \
+ self->listener->selection(self->data, self, self->offer, \
+ WAYLAND_SELECTION_REGULAR); \
+ self->offer = NULL; \
+ } \
+
+#define VWL_FUNC_DATA_DEVICE_EVENT_PRIMARY_SELECTION(device_type, offer_type) \
+ static void \
+ device_type##_listener_event_primary_selection( \
+ void *data, \
+ struct device_type *device UNUSED, \
+ struct offer_type *offer UNUSED) \
+ { \
+ vwl_data_device_T *self = data; \
+ self->listener->selection(self->data, self, self->offer, \
+ WAYLAND_SELECTION_PRIMARY); \
+ self->offer = NULL; \
}
-}

-// Boilerplate macros. Each just calls its respective generic callback.
-//
-#define VWL_FUNC_DATA_DEVICE_DATA_OFFER(device_name, offer_name) \
- static void device_name##_listener_data_offer( \
- void *data, struct device_name *device_proxy UNUSED, \
- struct offer_name *offer_proxy) \
-{ \
- vwl_gen_data_device_listener_data_offer(data, offer_proxy); \
-}
-#define VWL_FUNC_DATA_DEVICE_SELECTION( \
- device_name, offer_name, type, selection_type, protocol) \
- static void device_name##_listener_##type( \
- void *data, struct device_name *device_proxy UNUSED, \
- struct offer_name *offer_proxy UNUSED) \
-{ \
- vwl_gen_data_device_listener_selection( \
- data, offer_proxy, selection_type, protocol); \
-}
-#define VWL_FUNC_DATA_DEVICE_FINISHED(device_name) \
- static void device_name##_listener_finished( \
- void *data, struct device_name *device_proxy UNUSED) \
-{ \
- vwl_data_device_listener.finished(data); \
-}
-#define VWL_FUNC_DATA_SOURCE_SEND(source_name) \
- static void source_name##_listener_send(void *data, \
- struct source_name *source_proxy UNUSED, \
- const char *mime_type, int fd) \
-{ \
- vwl_data_source_listener.send(data, mime_type, fd); \
-}
-#define VWL_FUNC_DATA_SOURCE_CANCELLED(source_name) \
- static void source_name##_listener_cancelled(void *data, \
- struct source_name *source_proxy UNUSED) \
-{ \
- vwl_data_source_listener.cancelled(data); \
-}
-#define VWL_FUNC_DATA_OFFER_OFFER(offer_name) \
- static void offer_name##_listener_offer(void *data, \
- struct offer_name *offer_proxy UNUSED, \
- const char *mime_type) \
-{ \
- vwl_data_offer_listener.offer(data, mime_type); \
-}
+#define VWL_FUNC_DATA_DEVICE_EVENT_FINISHED(device_type) \
+ static void \
+ device_type##_listener_event_finished( \
+ void *data, \
+ struct device_type *device UNUSED) \
+ { \
+ vwl_data_device_T *self = data; \
+ self->listener->finished(self->data, self); \
+ }

-VWL_FUNC_DATA_DEVICE_DATA_OFFER(
- ext_data_control_device_v1, ext_data_control_offer_v1)
-VWL_FUNC_DATA_DEVICE_DATA_OFFER(
- zwlr_data_control_device_v1, zwlr_data_control_offer_v1)
-VWL_FUNC_DATA_DEVICE_DATA_OFFER(wl_data_device, wl_data_offer)
-VWL_FUNC_DATA_DEVICE_DATA_OFFER(
+VWL_FUNC_DATA_DEVICE_EVENT_DATA_OFFER(
+ ext_data_control_device_v1, ext_data_control_offer_v1)
+VWL_FUNC_DATA_DEVICE_EVENT_DATA_OFFER(
+ zwlr_data_control_device_v1, zwlr_data_control_offer_v1)
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+VWL_FUNC_DATA_DEVICE_EVENT_DATA_OFFER(wl_data_device, wl_data_offer)
+VWL_FUNC_DATA_DEVICE_EVENT_DATA_OFFER(
zwp_primary_selection_device_v1, zwp_primary_selection_offer_v1)
+#endif

-VWL_FUNC_DATA_DEVICE_SELECTION(
- ext_data_control_device_v1, ext_data_control_offer_v1,
- selection, WAYLAND_SELECTION_REGULAR, VWL_DATA_PROTOCOL_EXT)
-VWL_FUNC_DATA_DEVICE_SELECTION(
- zwlr_data_control_device_v1, zwlr_data_control_offer_v1,
- selection, WAYLAND_SELECTION_REGULAR, VWL_DATA_PROTOCOL_WLR)
-VWL_FUNC_DATA_DEVICE_SELECTION(
- wl_data_device, wl_data_offer, selection,
- WAYLAND_SELECTION_REGULAR, VWL_DATA_PROTOCOL_CORE)
-
-VWL_FUNC_DATA_DEVICE_SELECTION(
- ext_data_control_device_v1, ext_data_control_offer_v1,
- primary_selection, WAYLAND_SELECTION_PRIMARY, VWL_DATA_PROTOCOL_EXT)
-VWL_FUNC_DATA_DEVICE_SELECTION(
- zwlr_data_control_device_v1, zwlr_data_control_offer_v1,
- primary_selection, WAYLAND_SELECTION_PRIMARY, VWL_DATA_PROTOCOL_WLR)
-VWL_FUNC_DATA_DEVICE_SELECTION(
- zwp_primary_selection_device_v1, zwp_primary_selection_offer_v1,
- primary_selection, WAYLAND_SELECTION_PRIMARY, VWL_DATA_PROTOCOL_PRIMARY)
-
-VWL_FUNC_DATA_DEVICE_FINISHED(ext_data_control_device_v1)
-VWL_FUNC_DATA_DEVICE_FINISHED(zwlr_data_control_device_v1)
-
-VWL_FUNC_DATA_SOURCE_SEND(ext_data_control_source_v1)
-VWL_FUNC_DATA_SOURCE_SEND(zwlr_data_control_source_v1)
-VWL_FUNC_DATA_SOURCE_SEND(wl_data_source)
-VWL_FUNC_DATA_SOURCE_SEND(zwp_primary_selection_source_v1)
-
-VWL_FUNC_DATA_SOURCE_CANCELLED(ext_data_control_source_v1)
-VWL_FUNC_DATA_SOURCE_CANCELLED(zwlr_data_control_source_v1)
-VWL_FUNC_DATA_SOURCE_CANCELLED(wl_data_source)
-VWL_FUNC_DATA_SOURCE_CANCELLED(zwp_primary_selection_source_v1)
-
-VWL_FUNC_DATA_OFFER_OFFER(ext_data_control_offer_v1)
-VWL_FUNC_DATA_OFFER_OFFER(zwlr_data_control_offer_v1)
-VWL_FUNC_DATA_OFFER_OFFER(wl_data_offer)
-VWL_FUNC_DATA_OFFER_OFFER(zwp_primary_selection_offer_v1)
-
-// Listener handlers
-
-// DATA DEVICES
-struct zwlr_data_control_device_v1_listener
-zwlr_data_control_device_v1_listener = {
- .data_offer = zwlr_data_control_device_v1_listener_data_offer,
- .selection = zwlr_data_control_device_v1_listener_selection,
- .primary_selection = zwlr_data_control_device_v1_listener_primary_selection,
- .finished = zwlr_data_control_device_v1_listener_finished
-};
+VWL_FUNC_DATA_DEVICE_EVENT_SELECTION(
+ ext_data_control_device_v1, ext_data_control_offer_v1
+)
+VWL_FUNC_DATA_DEVICE_EVENT_SELECTION(
+ zwlr_data_control_device_v1, zwlr_data_control_offer_v1
+)
+VWL_FUNC_DATA_DEVICE_EVENT_PRIMARY_SELECTION(
+ ext_data_control_device_v1, ext_data_control_offer_v1
+)
+VWL_FUNC_DATA_DEVICE_EVENT_PRIMARY_SELECTION(
+ zwlr_data_control_device_v1, zwlr_data_control_offer_v1
+)
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+VWL_FUNC_DATA_DEVICE_EVENT_SELECTION(
+ wl_data_device, wl_data_offer
+)
+VWL_FUNC_DATA_DEVICE_EVENT_PRIMARY_SELECTION(
+ zwp_primary_selection_device_v1, zwp_primary_selection_offer_v1)
+#endif

-struct ext_data_control_device_v1_listener
-ext_data_control_device_v1_listener = {
- .data_offer = ext_data_control_device_v1_listener_data_offer,
- .selection = ext_data_control_device_v1_listener_selection,
- .primary_selection = ext_data_control_device_v1_listener_primary_selection,
- .finished = ext_data_control_device_v1_listener_finished
-};
+VWL_FUNC_DATA_DEVICE_EVENT_FINISHED(ext_data_control_device_v1)
+VWL_FUNC_DATA_DEVICE_EVENT_FINISHED(zwlr_data_control_device_v1)

-struct wl_data_device_listener wl_data_device_listener = {
- .data_offer = wl_data_device_listener_data_offer,
- .selection = wl_data_device_listener_selection,
+static struct ext_data_control_device_v1_listener
+ ext_data_control_device_v1_listener = {
+ .data_offer = ext_data_control_device_v1_listener_event_data_offer,
+ .selection = ext_data_control_device_v1_listener_event_selection,
+ .primary_selection =
+ ext_data_control_device_v1_listener_event_primary_selection,
+ .finished = ext_data_control_device_v1_listener_event_finished
};
-
-struct zwp_primary_selection_device_v1_listener
-zwp_primary_selection_device_v1_listener = {
- .selection = zwp_primary_selection_device_v1_listener_primary_selection,
- .data_offer = zwp_primary_selection_device_v1_listener_data_offer
+static const struct zwlr_data_control_device_v1_listener
+ zwlr_data_control_device_v1_listener = {
+ .data_offer = zwlr_data_control_device_v1_listener_event_data_offer,
+ .selection = zwlr_data_control_device_v1_listener_event_selection,
+ .primary_selection =
+ zwlr_data_control_device_v1_listener_event_primary_selection,
+ .finished = zwlr_data_control_device_v1_listener_event_finished
};
-
-// DATA SOURCES
-struct zwlr_data_control_source_v1_listener
-zwlr_data_control_source_v1_listener = {
- .send = zwlr_data_control_source_v1_listener_send,
- .cancelled = zwlr_data_control_source_v1_listener_cancelled
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+static const struct wl_data_device_listener wl_data_device_listener = {
+ .data_offer = wl_data_device_listener_event_data_offer,
+ .selection = wl_data_device_listener_event_selection,
};
-
-struct ext_data_control_source_v1_listener
-ext_data_control_source_v1_listener = {
- .send = ext_data_control_source_v1_listener_send,
- .cancelled = ext_data_control_source_v1_listener_cancelled
+static const struct zwp_primary_selection_device_v1_listener
+ zwp_primary_selection_device_v1_listener = {
+ .data_offer = zwp_primary_selection_device_v1_listener_event_data_offer,
+ .selection =
+ zwp_primary_selection_device_v1_listener_event_primary_selection,
};
+# endif

-struct wl_data_source_listener wl_data_source_listener = {
- .send = wl_data_source_listener_send,
- .cancelled = wl_data_source_listener_cancelled
-};
+# define VWL_FUNC_DATA_SOURCE_EVENT_SEND(source_type) \
+ static void \
+ source_type##_listener_event_send( \
+ void *data, struct source_type *source UNUSED, \
+ const char *mime_type, int fd) \
+ { \
+ vwl_data_source_T *self = data; \
+ self->listener->send(self->data, self, mime_type, fd); \
+ }

-struct zwp_primary_selection_source_v1_listener
-zwp_primary_selection_source_v1_listener = {
- .send = zwp_primary_selection_source_v1_listener_send,
- .cancelled = zwp_primary_selection_source_v1_listener_cancelled,
-};
+# define VWL_FUNC_DATA_SOURCE_EVENT_CANCELLED(source_type) \
+ static void \
+ source_type##_listener_event_cancelled( \
+ void *data, struct source_type *source UNUSED) \
+ { \
+ vwl_data_source_T *self = data; \
+ self->listener->cancelled(self->data, self); \
+ }

-// OFFERS
-struct zwlr_data_control_offer_v1_listener
-zwlr_data_control_offer_v1_listener = {
- .offer = zwlr_data_control_offer_v1_listener_offer
-};
+VWL_FUNC_DATA_SOURCE_EVENT_SEND(ext_data_control_source_v1)
+VWL_FUNC_DATA_SOURCE_EVENT_SEND(zwlr_data_control_source_v1)
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+VWL_FUNC_DATA_SOURCE_EVENT_SEND(wl_data_source)
+VWL_FUNC_DATA_SOURCE_EVENT_SEND(zwp_primary_selection_source_v1)
+#endif

-struct ext_data_control_offer_v1_listener
-ext_data_control_offer_v1_listener = {
- .offer = ext_data_control_offer_v1_listener_offer
-};
+VWL_FUNC_DATA_SOURCE_EVENT_CANCELLED(ext_data_control_source_v1)
+VWL_FUNC_DATA_SOURCE_EVENT_CANCELLED(zwlr_data_control_source_v1)
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+VWL_FUNC_DATA_SOURCE_EVENT_CANCELLED(wl_data_source)
+VWL_FUNC_DATA_SOURCE_EVENT_CANCELLED(zwp_primary_selection_source_v1)
+#endif

-struct wl_data_offer_listener wl_data_offer_listener = {
- .offer = wl_data_offer_listener_offer
+static const struct ext_data_control_source_v1_listener
+ ext_data_control_source_v1_listener = {
+ .send = ext_data_control_source_v1_listener_event_send,
+ .cancelled = ext_data_control_source_v1_listener_event_cancelled
};
-
-struct zwp_primary_selection_offer_v1_listener
-zwp_primary_selection_offer_v1_listener = {
- .offer = zwp_primary_selection_offer_v1_listener_offer
+static const struct zwlr_data_control_source_v1_listener
+ zwlr_data_control_source_v1_listener = {
+ .send = zwlr_data_control_source_v1_listener_event_send,
+ .cancelled = zwlr_data_control_source_v1_listener_event_cancelled
+};
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+static const struct wl_data_source_listener wl_data_source_listener = {
+ .send = wl_data_source_listener_event_send,
+ .cancelled = wl_data_source_listener_event_cancelled
};
+static const struct zwp_primary_selection_source_v1_listener
+ zwp_primary_selection_source_v1_listener = {
+ .send = zwp_primary_selection_source_v1_listener_event_send,
+ .cancelled = zwp_primary_selection_source_v1_listener_event_cancelled
+};
+#endif

-// `type` is also used as the user data
-#define VWL_CODE_DATA_OBJECT_ADD_LISTENER(type) \
-do { \
- if (type->proxy == NULL) \
- return; \
- type->data = data; \
- switch (type->protocol) \
+#define VWL_FUNC_DATA_OFFER_EVENT_OFFER(offer_type) \
+ static void \
+ offer_type##_listener_event_offer( \
+ void *data, \
+ struct offer_type *offer UNUSED, \
+ const char *mime_type) \
{ \
- case VWL_DATA_PROTOCOL_WLR: \
- zwlr_data_control_##type##_v1_add_listener( type->proxy, \
- &zwlr_data_control_##type##_v1_listener, type); \
- break; \
- case VWL_DATA_PROTOCOL_EXT: \
- ext_data_control_##type##_v1_add_listener(type->proxy, \
- &ext_data_control_##type##_v1_listener, type); \
- break; \
- case VWL_DATA_PROTOCOL_CORE: \
- wl_data_##type##_add_listener(type->proxy, \
- &wl_data_##type##_listener, type); \
- break; \
- case VWL_DATA_PROTOCOL_PRIMARY: \
- zwp_primary_selection_##type##_v1_add_listener(type->proxy, \
- &zwp_primary_selection_##type##_v1_listener, type); \
- break; \
- default: \
- break; \
- } \
-} while (0)
+ vwl_data_offer_T *self = data; \
+ if (STRCMP(mime_type, wayland_vim_special_mime) == 0) \
+ self->from_vim = true; \
+ else if (!self->from_vim && \
+ self->listener->offer(self->data, self, mime_type)) \
+ { \
+ char *mime = (char *)vim_strsave((char_u *)mime_type); \
+ if (ga_grow(&self->mime_types, 1) == FAIL) \
+ vim_free(mime); \
+ else \
+ if (mime != NULL) \
+ ((char **)self->mime_types.ga_data) \
+ [self->mime_types.ga_len++] = mime; \
+ } \
+ }

- static void
-vwl_data_device_add_listener(vwl_data_device_T *device, void *data)
-{
- VWL_CODE_DATA_OBJECT_ADD_LISTENER(device);
-}
+VWL_FUNC_DATA_OFFER_EVENT_OFFER(ext_data_control_offer_v1)
+VWL_FUNC_DATA_OFFER_EVENT_OFFER(zwlr_data_control_offer_v1)
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+VWL_FUNC_DATA_OFFER_EVENT_OFFER(wl_data_offer)
+VWL_FUNC_DATA_OFFER_EVENT_OFFER(zwp_primary_selection_offer_v1)
+#endif

- static void
-vwl_data_source_add_listener(vwl_data_source_T *source, void *data)
-{
- VWL_CODE_DATA_OBJECT_ADD_LISTENER(source);
-}
+static const struct ext_data_control_offer_v1_listener
+ ext_data_control_offer_v1_listener = {
+ .offer = ext_data_control_offer_v1_listener_event_offer
+};
+static const struct zwlr_data_control_offer_v1_listener
+ zwlr_data_control_offer_v1_listener = {
+ .offer = zwlr_data_control_offer_v1_listener_event_offer
+};
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+static const struct wl_data_offer_listener
+ wl_data_offer_listener = {
+ .offer = wl_data_offer_listener_event_offer
+};
+static const struct zwp_primary_selection_offer_v1_listener
+ zwp_primary_selection_offer_v1_listener = {
+ .offer = zwp_primary_selection_offer_v1_listener_event_offer
+};
+#endif

- static void
-vwl_data_offer_add_listener(vwl_data_offer_T *offer, void *data)
-{
- VWL_CODE_DATA_OBJECT_ADD_LISTENER(offer);
-}
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+# define VWL_CODE_DATA_PROXY_FS_ADD_LISTENER(type) \
+ case VWL_DATA_PROTOCOL_CORE: \
+ wl_data_##type##_add_listener(self->proxy, \
+ &wl_data_##type##_listener, self); \
+ break; \
+ case VWL_DATA_PROTOCOL_PRIMARY: \
+ zwp_primary_selection_##type##_v1_add_listener(self->proxy, \
+ &zwp_primary_selection_##type##_v1_listener, self); \
+ break;
+#else
+# define VWL_CODE_DATA_PROXY_FS_ADD_LISTENER(type)
+#endif
+
+#define VWL_FUNC_DATA_PROXY_ADD_LISTENER(type) \
+ void \
+ vwl_data_##type##_add_listener( \
+ vwl_data_##type##_T *self, \
+ const vwl_data_##type##_listener_T *listener, \
+ void *data) \
+ { \
+ if (self == NULL) \
+ return; \
+ self->data = data; \
+ self->listener = listener; \
+ switch (self->protocol) \
+ { \
+ case VWL_DATA_PROTOCOL_EXT: \
+ ext_data_control_##type##_v1_add_listener(self->proxy, \
+ &ext_data_control_##type##_v1_listener, self); \
+ break; \
+ case VWL_DATA_PROTOCOL_WLR: \
+ zwlr_data_control_##type##_v1_add_listener(self->proxy, \
+ &zwlr_data_control_##type##_v1_listener, self); \
+ break; \
+ VWL_CODE_DATA_PROXY_FS_ADD_LISTENER(type) \
+ default: \
+ break; \
+ } \
+ }
+
+VWL_FUNC_DATA_PROXY_ADD_LISTENER(device)
+VWL_FUNC_DATA_PROXY_ADD_LISTENER(source)
+VWL_FUNC_DATA_PROXY_ADD_LISTENER(offer)

/*
- * Sets the selection using the given data device with the given selection. If
- * the device does not support the selection then nothing happens. For data
- * control protocols the serial argument is ignored.
+ * Set the given selection to source. If a data control protocol is being used,
+ * "serial" is ignored.
*/
- static void
+ void
vwl_data_device_set_selection(
- vwl_data_device_T *device,
- vwl_data_source_T *source,
- uint32_t serial,
- wayland_selection_T selection)
+ vwl_data_device_T *self,
+ vwl_data_source_T *source,
+ uint32_t serial UNUSED,
+ wayland_selection_T selection
+)
{
+ void *proxy = source == NULL ? NULL : source->proxy;
+
if (selection == WAYLAND_SELECTION_REGULAR)
{
- switch (device->protocol)
+ switch (self->protocol)
{
- case VWL_DATA_PROTOCOL_WLR:
- zwlr_data_control_device_v1_set_selection(
- device->proxy, source->proxy);
- break;
case VWL_DATA_PROTOCOL_EXT:
- ext_data_control_device_v1_set_selection(
- device->proxy, source->proxy);
+ ext_data_control_device_v1_set_selection(self->proxy, proxy);
+ break;
+ case VWL_DATA_PROTOCOL_WLR:
+ zwlr_data_control_device_v1_set_selection(self->proxy, proxy);
break;
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
case VWL_DATA_PROTOCOL_CORE:
- wl_data_device_set_selection(
- device->proxy, source->proxy, serial);
+ wl_data_device_set_selection(self->proxy, proxy, serial);
break;
+#endif
default:
break;
}
}
else if (selection == WAYLAND_SELECTION_PRIMARY)
{
- switch (device->protocol)
+ switch (self->protocol)
{
- case VWL_DATA_PROTOCOL_WLR:
- zwlr_data_control_device_v1_set_primary_selection(
- device->proxy, source->proxy);
- break;
case VWL_DATA_PROTOCOL_EXT:
ext_data_control_device_v1_set_primary_selection(
- device->proxy, source->proxy);
+ self->proxy, proxy
+ );
+ break;
+ case VWL_DATA_PROTOCOL_WLR:
+ zwlr_data_control_device_v1_set_primary_selection(
+ self->proxy, proxy
+ );
break;
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
case VWL_DATA_PROTOCOL_PRIMARY:
zwp_primary_selection_device_v1_set_selection(
- device->proxy, source->proxy, serial);
+ self->proxy, proxy, serial);
break;
+#endif
default:
break;
}
}
}

-/*
- * Start receiving data from offer object, which sends the given fd to the
- * source client to write into.
- */
- static void
-vwl_data_offer_receive(vwl_data_offer_T *offer, const char *mime_type, int fd)
+ void
+vwl_data_source_offer(vwl_data_source_T *self, const char *mime_type)
{
- switch (offer->protocol)
+ switch (self->protocol)
{
- case VWL_DATA_PROTOCOL_WLR:
- zwlr_data_control_offer_v1_receive(offer->proxy, mime_type, fd);
- break;
case VWL_DATA_PROTOCOL_EXT:
- ext_data_control_offer_v1_receive(offer->proxy, mime_type, fd);
+ ext_data_control_source_v1_offer(self->proxy, mime_type);
+ break;
+ case VWL_DATA_PROTOCOL_WLR:
+ zwlr_data_control_source_v1_offer(self->proxy, mime_type);
break;
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
case VWL_DATA_PROTOCOL_CORE:
- wl_data_offer_receive(offer->proxy, mime_type, fd);
+ wl_data_source_offer(self->proxy, mime_type);
break;
case VWL_DATA_PROTOCOL_PRIMARY:
- zwp_primary_selection_offer_v1_receive(offer->proxy, mime_type, fd);
+ zwp_primary_selection_source_v1_offer(self->proxy, mime_type);
break;
+#endif
default:
break;
}
}

-#define SET_MANAGER(manager_name, protocol_enum, focus) \
- do { \
- manager->proxy = vwl_gobjects.manager_name; \
- manager->protocol = protocol_enum; \
- return focus; \
- } while (0)
-
-/*
- * Get a data device manager that supports the given selection. If none if found
- * then the manager protocol is set to VWL_DATA_PROTOCOL_NONE. TRUE is returned
- * if the given data device manager requires focus to work else FALSE.
- */
- static int
-vwl_get_data_device_manager(
- vwl_data_device_manager_T *manager,
- wayland_selection_T selection)
-{
- // Prioritize data control protocols first then try using the focus steal
- // method with the core protocol data objects.
- if (force_fs)
- goto focus_steal;
-
- // Ext data control protocol supports both selections, try it first
- if (vwl_gobjects.ext_data_control_manager_v1 != NULL)
- SET_MANAGER(ext_data_control_manager_v1, VWL_DATA_PROTOCOL_EXT, FALSE);
- if (vwl_gobjects.zwlr_data_control_manager_v1 != NULL)
- {
- int ver = zwlr_data_control_manager_v1_get_version(
- vwl_gobjects.zwlr_data_control_manager_v1);
-
- // version 2 or greater supports the primary selection
- if ((selection == WAYLAND_SELECTION_PRIMARY && ver >= 2)
- || selection == WAYLAND_SELECTION_REGULAR)
- SET_MANAGER(zwlr_data_control_manager_v1,
- VWL_DATA_PROTOCOL_WLR, FALSE);
- }
-
-focus_steal:
- if (vwl_focus_stealing_available())
- {
- if (vwl_gobjects.wl_data_device_manager != NULL
- && selection == WAYLAND_SELECTION_REGULAR)
- SET_MANAGER(wl_data_device_manager, VWL_DATA_PROTOCOL_CORE, TRUE);
-
- else if (vwl_gobjects.zwp_primary_selection_device_manager_v1 != NULL
- && selection == WAYLAND_SELECTION_PRIMARY)
- SET_MANAGER(zwp_primary_selection_device_manager_v1,
- VWL_DATA_PROTOCOL_PRIMARY, TRUE);
- }
-
- manager->protocol = VWL_DATA_PROTOCOL_NONE;
-
- return FALSE;
-}
-
-/*
- * Get a data device that manages the given seat's selection.
- */
- static void
-vwl_get_data_device(
- vwl_data_device_manager_T *manager,
- vwl_seat_T *seat,
- vwl_data_device_T *device)
+ void
+vwl_data_offer_receive(
+ vwl_data_offer_T *self,
+ const char *mime_type,
+ int32_t fd)
{
- switch (manager->protocol)
+ switch (self->protocol)
{
- case VWL_DATA_PROTOCOL_WLR:
- device->proxy =
- zwlr_data_control_manager_v1_get_data_device(
- manager->proxy, seat->proxy);
- break;
case VWL_DATA_PROTOCOL_EXT:
- device->proxy =
- ext_data_control_manager_v1_get_data_device(
- manager->proxy, seat->proxy);
+ ext_data_control_offer_v1_receive(self->proxy, mime_type, fd);
+ break;
+ case VWL_DATA_PROTOCOL_WLR:
+ zwlr_data_control_offer_v1_receive(self->proxy, mime_type, fd);
break;
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
case VWL_DATA_PROTOCOL_CORE:
- device->proxy = wl_data_device_manager_get_data_device(
- manager->proxy, seat->proxy);
+ wl_data_offer_receive(self->proxy, mime_type, fd);
break;
case VWL_DATA_PROTOCOL_PRIMARY:
- device->proxy = zwp_primary_selection_device_manager_v1_get_device(
- manager->proxy, seat->proxy);
+ zwp_primary_selection_offer_v1_receive(self->proxy, mime_type, fd);
break;
+#endif
default:
- device->protocol = VWL_DATA_PROTOCOL_NONE;
- return;
+ break;
}
- device->protocol = manager->protocol;
-}
-
-/*
- * Create a data source
- */
- static void
-vwl_create_data_source(
- vwl_data_device_manager_T *manager,
- vwl_data_source_T *source)
-{
- switch (manager->protocol)
- {
- case VWL_DATA_PROTOCOL_WLR:
- source->proxy =
- zwlr_data_control_manager_v1_create_data_source(manager->proxy);
- break;
- case VWL_DATA_PROTOCOL_EXT:
- source->proxy =
- ext_data_control_manager_v1_create_data_source(manager->proxy);
- break;
- case VWL_DATA_PROTOCOL_CORE:
- source->proxy =
- wl_data_device_manager_create_data_source(manager->proxy);
- break;
- case VWL_DATA_PROTOCOL_PRIMARY:
- source->proxy =
- zwp_primary_selection_device_manager_v1_create_source(
- manager->proxy);
- break;
- default:
- source->protocol = VWL_DATA_PROTOCOL_NONE;
- return;
- }
- source->protocol = manager->protocol;
-}
-
-/*
- * Offer a new mime type to be advertised by us to other clients.
- */
- static void
-vwl_data_source_offer(vwl_data_source_T *source, const char *mime_type)
-{
- switch (source->protocol)
- {
- case VWL_DATA_PROTOCOL_WLR:
- zwlr_data_control_source_v1_offer(source->proxy, mime_type);
- break;
- case VWL_DATA_PROTOCOL_EXT:
- ext_data_control_source_v1_offer(source->proxy, mime_type);
- break;
- case VWL_DATA_PROTOCOL_CORE:
- wl_data_source_offer(source->proxy, mime_type);
- break;
- case VWL_DATA_PROTOCOL_PRIMARY:
- zwp_primary_selection_source_v1_offer(source->proxy, mime_type);
- break;
- default:
- break;
- }
-}
-
-/*
- * Free the mime types grow arrays in the given clip_sel struct.
- */
- static void
-vwl_clipboard_free_mime_types(vwl_clipboard_selection_T *clip_sel)
-{
- // Don't want to be double freeing
- if (clip_sel->mime_types.ga_data == clip_sel->tmp_mime_types.ga_data)
- {
- ga_clear_strings(&clip_sel->mime_types);
- ga_init(&vwl_clipboard.primary.tmp_mime_types);
- }
- else
- {
- ga_clear_strings(&clip_sel->mime_types);
- ga_clear_strings(&clip_sel->tmp_mime_types);
- }
-}
-
-/*
- * Setup required objects to interact with Wayland selections/clipboard on given
- * seat. Returns OK on success and FAIL on failure.
- */
- int
-wayland_cb_init(const char *seat)
-{
- vwl_clipboard.seat = vwl_get_seat(seat);
-
- if (vwl_clipboard.seat == NULL)
- return FAIL;
-
- // Get data device managers for each selection. If there wasn't any manager
- // that could be found that supports the given selection, then it will be
- // unavailable.
- vwl_clipboard.regular.requires_focus = vwl_get_data_device_manager(
- &vwl_clipboard.regular.manager,
- WAYLAND_SELECTION_REGULAR);
- vwl_clipboard.primary.requires_focus = vwl_get_data_device_manager(
- &vwl_clipboard.primary.manager,
- WAYLAND_SELECTION_PRIMARY);
-
- // Initialize shm pool and buffer if core data protocol is available
- if (vwl_focus_stealing_available() &&
- (vwl_clipboard.regular.requires_focus ||
- vwl_clipboard.primary.requires_focus))
- vwl_clipboard.fs_buffer = vwl_init_buffer_store(1, 1);
-
- // Get data devices for each selection. If one of the above function calls
- // results in an unavailable manager, then the device coming from it will
- // have its protocol set to VWL_DATA_PROTOCOL_NONE.
- vwl_get_data_device(
- &vwl_clipboard.regular.manager,
- vwl_clipboard.seat,
- &vwl_clipboard.regular.device);
- vwl_get_data_device(
- &vwl_clipboard.primary.manager,
- vwl_clipboard.seat,
- &vwl_clipboard.primary.device);
-
- // Initialize grow arrays for the offer mime types.
- // I find most applications to have below 10 mime types that they offer.
- ga_init2(&vwl_clipboard.regular.tmp_mime_types, sizeof(char*), 10);
- ga_init2(&vwl_clipboard.primary.tmp_mime_types, sizeof(char*), 10);
-
- // We dont need to use ga_init2 because tmp_mime_types will be copied over
- // to mime_types anyways.
- ga_init(&vwl_clipboard.regular.mime_types);
- ga_init(&vwl_clipboard.primary.mime_types);
-
- // Start listening for data offers/new selections. Don't do anything when we
- // get a new data offer other than saving the mime types and saving the data
- // offer. Then when we want the data we use the saved data offer to receive
- // data from it along with the saved mime_types. For each new selection just
- // destroy the previous offer/free mime_types, if any.
- vwl_data_device_add_listener(
- &vwl_clipboard.regular.device,
- &vwl_clipboard.regular);
- vwl_data_device_add_listener(
- &vwl_clipboard.primary.device,
- &vwl_clipboard.primary);
-
- if (vwl_display_roundtrip(&vwl_display) == FAIL)
- {
- wayland_cb_uninit();
- return FAIL;
- }
- clip_init(TRUE);
-
- return OK;
-}
-
-/*
- * Free up resources used for Wayland selections. Does not destroy global
- * objects such as data device managers.
- */
- void
-wayland_cb_uninit(void)
-{
- if (vwl_clipboard.fs_buffer != NULL)
- {
- vwl_destroy_buffer_store(vwl_clipboard.fs_buffer);
- vwl_clipboard.fs_buffer = NULL;
- }
-
- // Destroy the current offer if it exists
- vwl_data_offer_destroy(vwl_clipboard.regular.offer, TRUE);
- vwl_data_offer_destroy(vwl_clipboard.primary.offer, TRUE);
-
- // Destroy any devices or sources
- vwl_data_device_destroy(&vwl_clipboard.regular.device, FALSE);
- vwl_data_device_destroy(&vwl_clipboard.primary.device, FALSE);
- vwl_data_source_destroy(&vwl_clipboard.regular.source, FALSE);
- vwl_data_source_destroy(&vwl_clipboard.primary.source, FALSE);
-
- // Free mime types
- vwl_clipboard_free_mime_types(&vwl_clipboard.regular);
- vwl_clipboard_free_mime_types(&vwl_clipboard.primary);
-
- vwl_display_flush(&vwl_display);
-
- vim_memset(&vwl_clipboard, 0, sizeof(vwl_clipboard));
- vwl_clipboard.regular.selection = WAYLAND_SELECTION_REGULAR;
- vwl_clipboard.primary.selection = WAYLAND_SELECTION_PRIMARY;
-}
-
-/*
- * If the given selection can be used.
- */
- static int
-vwl_clipboard_selection_is_ready(vwl_clipboard_selection_T *clip_sel)
-{
- return clip_sel->manager.protocol != VWL_DATA_PROTOCOL_NONE &&
- clip_sel->device.protocol != VWL_DATA_PROTOCOL_NONE;
-}
-
-/*
- * Callback for data offer event. Start listening to the given offer immediately
- * in order to get mime types.
- */
- static void
-vwl_data_device_listener_data_offer(
- vwl_data_device_T *device,
- vwl_data_offer_T *offer)
-{
- vwl_clipboard_selection_T *clip_sel = device->data;
-
- // Get mime types and save them so we can use them when we want to paste the
- // selection.
- if (clip_sel->source.proxy != NULL)
- // We own the selection, no point in getting mime types
- return;
-
- vwl_data_offer_add_listener(offer, device->data);
-}
-
-/*
- * Callback for offer event. Save each mime type given to be used later.
- */
- static void
-vwl_data_offer_listener_offer(vwl_data_offer_T *offer, const char *mime_type)
-{
- vwl_clipboard_selection_T *clip_sel = offer->data;
-
- // Save string into temporary grow array, which will be finalized into the
- // actual grow array if the selection matches with the selection that the
- // device manages.
- ga_copy_string(&clip_sel->tmp_mime_types, (char_u*)mime_type);
-}
-
-/*
- * Callback for selection event, for either the regular or primary selection.
- * Don't try receiving data from the offer, instead destroy the previous offer
- * if any and set the current offer to the given offer, along with the
- * respective mime types.
- */
- static void
-vwl_data_device_listener_selection(
- vwl_data_device_T *device UNUSED,
- vwl_data_offer_T *offer,
- wayland_selection_T selection)
-{
- vwl_clipboard_selection_T *clip_sel = device->data;
- vwl_data_offer_T *prev_offer = clip_sel->offer;
-
- // Save offer if it selection and clip_sel match, else discard it
- if (clip_sel->selection == selection)
- clip_sel->offer = offer;
- else
- {
- // Example: selection event is for the primary selection but this device
- // is only for the regular selection, if so then just discard the offer
- // and tmp_mime_types.
- vwl_data_offer_destroy(offer, TRUE);
- tmp_vwl_offer = NULL;
- ga_clear_strings(&clip_sel->tmp_mime_types);
- return;
- }
-
- // There are two cases when clip_sel->offer is NULL
- // 1. No one owns the selection
- // 2. We own the selection (we'll just access the register directly)
- if (offer == NULL)
- {
- // Selection cleared/empty
- ga_clear_strings(&clip_sel->tmp_mime_types);
- clip_sel->offer = NULL;
- goto exit;
- }
- else if (clip_sel->source.proxy != NULL)
- {
- // We own the selection, ignore it
- vwl_data_offer_destroy(offer, TRUE);
- ga_clear_strings(&clip_sel->tmp_mime_types);
- clip_sel->offer = NULL;
- goto exit;
- }
-
-exit:
- // Destroy previous offer if any
- vwl_data_offer_destroy(prev_offer, TRUE);
- ga_clear_strings(&clip_sel->mime_types);
-
- // Copy the grow array over
- clip_sel->mime_types = clip_sel->tmp_mime_types;
-
- // Clear tmp_mime_types so next data_offer doesn't try to resize/grow it
- // (Don't free it though using ga_clear() because mime_types->ga_data is the
- // same pointer)r
- if (clip_sel->offer != NULL)
- ga_init(&clip_sel->tmp_mime_types);
-}
-
-/*
- * Callback for finished event. Destroy device and all related objects/resources
- * such as offers and mime types.
- */
- static void
-vwl_data_device_listener_finished(vwl_data_device_T *device)
-{
- vwl_clipboard_selection_T *clip_sel = device->data;
-
- vwl_data_device_destroy(&clip_sel->device, FALSE);
- vwl_data_offer_destroy(clip_sel->offer, TRUE);
- vwl_data_source_destroy(&clip_sel->source, FALSE);
- vwl_clipboard_free_mime_types(clip_sel);
-}
-
-/*
- * Return a pointer to a grow array of mime types that the current offer
- * supports sending. If the returned garray has NULL for ga_data or a ga_len of
- * 0, then the selection is cleared. If focus stealing is required, a surface
- * will be created to steal focus first.
- */
- garray_T *
-wayland_cb_get_mime_types(wayland_selection_T selection)
-{
- vwl_clipboard_selection_T *clip_sel;
-
- if (selection == WAYLAND_SELECTION_REGULAR)
- clip_sel = &vwl_clipboard.regular;
- else if (selection == WAYLAND_SELECTION_PRIMARY)
- clip_sel = &vwl_clipboard.primary;
- else
- return NULL;
-
- if (clip_sel->requires_focus)
- {
- // We don't care about the on_focus callback since once we gain focus
- // the data offer events will come immediately.
- if (vwl_init_fs_surface(vwl_clipboard.seat,
- vwl_clipboard.fs_buffer, NULL, NULL) == FAIL)
- return NULL;
- }
- else if (vwl_display_roundtrip(&vwl_display) == FAIL)
- return NULL;
-
- return &clip_sel->mime_types;
-}
-
-/*
- * Receive data from the given selection, and return the fd to read data from.
- * On failure -1 is returned.
- */
- int
-wayland_cb_receive_data(const char *mime_type, wayland_selection_T selection)
-{
- vwl_clipboard_selection_T *clip_sel;
-
- // Create pipe that source client will write to
- int fds[2];
-
- if (selection == WAYLAND_SELECTION_REGULAR)
- clip_sel = &vwl_clipboard.regular;
- else if (selection == WAYLAND_SELECTION_PRIMARY)
- clip_sel = &vwl_clipboard.primary;
- else
- return -1;
-
- if (!wayland_client_is_connected(FALSE) ||
- !vwl_clipboard_selection_is_ready(clip_sel))
- return -1;
-
- if (clip_sel->offer == NULL || clip_sel->offer->proxy == NULL)
- return -1;
-
- if (pipe(fds) == -1)
- return -1;
-
- vwl_data_offer_receive(clip_sel->offer, mime_type, fds[1]);
-
- close(fds[1]); // Close before we read data so that when the source client
- // closes their end we receive an EOF.
-
- if (vwl_display_flush(&vwl_display) == OK)
- return fds[0];
-
- close(fds[0]);
-
- return -1;
-}
-
-/*
- * Callback for send event. Just call the user callback which will handle it
- * and do the writing stuff.
- */
- static void
-vwl_data_source_listener_send(
- vwl_data_source_T *source,
- const char *mime_type,
- int32_t fd)
-{
- vwl_clipboard_selection_T *clip_sel = source->data;
-
- if (clip_sel->send_cb != NULL)
- clip_sel->send_cb(mime_type, fd, clip_sel->selection);
- close(fd);
-}
-
-/*
- * Callback for cancelled event, just call the user callback.
- */
- static void
-vwl_data_source_listener_cancelled(vwl_data_source_T *source)
-{
- vwl_clipboard_selection_T *clip_sel = source->data;
-
- if (clip_sel->send_cb != NULL)
- clip_sel->cancelled_cb(clip_sel->selection);
- vwl_data_source_destroy(source, FALSE);
-}
-
-/*
- * Set the selection when we gain focus
- */
- static void
-vwl_on_focus_set_selection(void *data, uint32_t serial)
-{
- vwl_clipboard_selection_T *clip_sel = data;
-
- vwl_data_device_set_selection(
- &clip_sel->device,
- &clip_sel->source,
- serial,
- clip_sel->selection);
- vwl_display_roundtrip(&vwl_display);
-}
-
-/*
- * Become the given selection's owner, and advertise to other clients the mime
- * types found in mime_types array. Returns FAIL on failure and OK on success.
- */
- int
-wayland_cb_own_selection(
- wayland_cb_send_data_func_T send_cb,
- wayland_cb_selection_cancelled_func_T cancelled_cb,
- const char **mime_types,
- int len,
- wayland_selection_T selection)
-{
- vwl_clipboard_selection_T *clip_sel;
-
- if (selection == WAYLAND_SELECTION_REGULAR)
- clip_sel = &vwl_clipboard.regular;
- else if (selection == WAYLAND_SELECTION_PRIMARY)
- clip_sel = &vwl_clipboard.primary;
- else
- return FAIL;
-
- if (clip_sel->source.proxy != NULL)
- {
- if (selection == WAYLAND_SELECTION_PRIMARY)
- // We already own the selection, ignore (only do this for primary
- // selection). We don't re set the selection because then we would
- // be setting the selection every time the user moves the visual
- // selection cursor, which is messy and inefficient.
- //
- // Vim doesn't have a mechanism to only set the selection
- // when the user stops selecting (such as the user releasing the
- // mouse button in graphical Wayland applications). So this
- // behaviour in Vim differs from other Wayland applications.
- return OK;
- else if (selection == WAYLAND_SELECTION_REGULAR)
- {
- // Technically we don't need to do this as we already own the
- // selection, however if a user yanks text a second time, the
- // text yanked won't appear in their clipboard manager if they are
- // using one.
- //
- // This can be unexpected behaviour for the user so its probably
- // better to do it this way. Additionally other Wayland applications
- // seem to set the selection every time.
- //
- // There should be no noticeable performance change since its not
- // like this is running in the background constantly in Vim, only
- // runs once when the user yanks text to the system clipboard.
- vwl_data_source_destroy(&clip_sel->source, FALSE);
- vwl_display_flush(&vwl_display);
- }
- else
- // Shouldn't happen
- return FAIL;
- }
-
- if (!wayland_client_is_connected(FALSE) ||
- !vwl_clipboard_selection_is_ready(clip_sel))
- return FAIL;
-
- clip_sel->send_cb = send_cb;
- clip_sel->cancelled_cb = cancelled_cb;
-
- vwl_create_data_source(&clip_sel->manager, &clip_sel->source);
-
- vwl_data_source_add_listener(&clip_sel->source, clip_sel);
-
- // Advertise mime types
- for (int i = 0; i < len; i++)
- vwl_data_source_offer(&clip_sel->source, mime_types[i]);
-
- if (clip_sel->requires_focus)
- {
- // Call set_selection later when we gain focus
- if (vwl_init_fs_surface(vwl_clipboard.seat, vwl_clipboard.fs_buffer,
- vwl_on_focus_set_selection, clip_sel) == FAIL)
- goto fail;
- }
- else
- {
- vwl_data_device_set_selection(&clip_sel->device,
- &clip_sel->source, 0, selection);
- if (vwl_display_roundtrip(&vwl_display) == FAIL)
- goto fail;
- }
-
- return OK;
-fail:
- vwl_data_source_destroy(&clip_sel->source, FALSE);
- return FAIL;
-}
-
-/*
- * Disown the given selection, so that we are not the source client that other
- * clients receive data from.
- */
- void
-wayland_cb_lose_selection(wayland_selection_T selection)
-{
- if (selection == WAYLAND_SELECTION_REGULAR)
- vwl_data_source_destroy(&vwl_clipboard.regular.source, FALSE);
- else if (selection == WAYLAND_SELECTION_PRIMARY)
- vwl_data_source_destroy(&vwl_clipboard.primary.source, FALSE);
- vwl_display_flush(&vwl_display);
-}
-
-/*
- * Return TRUE if the selection is owned by either us or another client.
- */
- int
-wayland_cb_selection_is_owned(wayland_selection_T selection)
-{
- vwl_display_roundtrip(&vwl_display);
-
- if (selection == WAYLAND_SELECTION_REGULAR)
- return vwl_clipboard.regular.source.proxy != NULL
- || vwl_clipboard.regular.offer != NULL;
- else if (selection == WAYLAND_SELECTION_PRIMARY)
- return vwl_clipboard.primary.source.proxy != NULL
- || vwl_clipboard.primary.offer != NULL;
- else
- return FALSE;
-}
-
-/*
- * Return TRUE if the Wayland clipboard/selections are ready to use.
- */
- int
-wayland_cb_is_ready(void)
-{
- vwl_display_roundtrip(&vwl_display);
-
- // Clipboard is ready if we have at least one selection available
- return wayland_client_is_connected(TRUE) &&
- (vwl_clipboard_selection_is_ready(&vwl_clipboard.regular) ||
- vwl_clipboard_selection_is_ready(&vwl_clipboard.primary));
-}
-
-/*
- * Reload Wayland clipboard, useful if changing seat.
- */
- int
-wayland_cb_reload(void)
-{
- // Lose any selections we own
- if (clipmethod == CLIPMETHOD_WAYLAND)
- {
- if (clip_star.owned)
- clip_lose_selection(&clip_star);
- if (clip_plus.owned)
- clip_lose_selection(&clip_plus);
- }
-
- wayland_cb_uninit();
-
- if (wayland_cb_init((char*)p_wse) == FAIL)
- return FAIL;
-
- choose_clipmethod();
- return OK;
}

#endif // FEAT_WAYLAND_CLIPBOARD

-static int wayland_ct_restore_count = 0;
-
-/*
- * Attempts to restore the Wayland display connection. Returns OK if display
- * connection was/is now valid, else FAIL if the display connection is invalid.
- */
- int
-wayland_may_restore_connection(void)
-{
- // No point if we still are already connected properly
- if (wayland_client_is_connected(TRUE))
- return OK;
-
- // No point in restoring the connection if we are exiting or dying.
- if (exiting || v_dying || wayland_ct_restore_count <= 0)
- {
- wayland_set_display("");
- return FAIL;
- }
-
- --wayland_ct_restore_count;
- wayland_uninit_client();
-
- return wayland_init_client(wayland_display_name);
-}
-
-/*
- * Disconnect then reconnect Wayland connection, and update clipmethod.
- */
- void
-ex_wlrestore(exarg_T *eap)
-{
- char *display;
-
- if (eap->arg == NULL || STRLEN(eap->arg) == 0)
- // Use current display name if none given
- display = wayland_display_name;
- else
- display = (char*)eap->arg;
-
- // Return early if shebang is not passed, we are still connected, and if not
- // changing to a new Wayland display.
- if (!eap->forceit && wayland_client_is_connected(TRUE) &&
- (display == wayland_display_name ||
- (wayland_display_name != NULL &&
- STRCMP(wayland_display_name, display) == 0)))
- return;
-
-#ifdef FEAT_WAYLAND_CLIPBOARD
- if (clipmethod == CLIPMETHOD_WAYLAND)
- {
- // Lose any selections we own
- if (clip_star.owned)
- clip_lose_selection(&clip_star);
- if (clip_plus.owned)
- clip_lose_selection(&clip_plus);
- }
-#endif
-
-
- if (display != NULL)
- display = (char*)vim_strsave((char_u*)display);
-
- wayland_uninit_client();
-
- // Reset amount of available tries to reconnect the display to 5
- wayland_ct_restore_count = 5;
-
- if (wayland_init_client(display) == OK)
- {
- smsg(_("restoring Wayland display %s"), wayland_display_name);
-
-#ifdef FEAT_WAYLAND_CLIPBOARD
- wayland_cb_init((char*)p_wse);
-#endif
- }
- else
- msg(_("failed restoring, lost connection to Wayland display"));
-
- vim_free(display);
-
- choose_clipmethod();
-}
-
-/*
- * Set wayland_display_name to display. Note that this allocate a copy of the
- * string, unless NULL is passed. If NULL is passed then v:wayland_display is
- * set to $WAYLAND_DISPLAY, but wayland_display_name is set to NULL.
- */
- static void
-wayland_set_display(const char *display)
-{
- if (display == NULL)
- display = (char*)mch_getenv((char_u*)"WAYLAND_DISPLAY");
- else if (display == wayland_display_name)
- // Don't want to be freeing vwl_display_strname then trying to copy it
- // after.
- goto exit;
-
- if (display == NULL)
- // $WAYLAND_DISPLAY is not set
- display = "";
-
- // Leave unchanged if display is empty (but not NULL)
- if (STRCMP(display, "") != 0)
- {
- vim_free(wayland_display_name);
- wayland_display_name = (char*)vim_strsave((char_u*)display);
- }
-
-exit:
-#ifdef FEAT_EVAL
- set_vim_var_string(VV_WAYLAND_DISPLAY, (char_u*)display, -1);
-#endif
-}
-
#endif // FEAT_WAYLAND
diff --git a/src/wayland.h b/src/wayland.h
new file mode 100644
index 000000000..d260af4a9
--- /dev/null
+++ b/src/wayland.h
@@ -0,0 +1,214 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * wayland.h: Common definitions for Wayland code
+ */
+
+
+#ifdef FEAT_WAYLAND
+
+#include <wayland-client.h>
+
+#ifdef FEAT_WAYLAND_CLIPBOARD
+# include "auto/wayland/wlr-data-control-unstable-v1.h"
+# include "auto/wayland/ext-data-control-v1.h"
+# ifdef FEAT_WAYLAND_CLIPBOARD_FS
+# include "auto/wayland/xdg-shell.h"
+# include "auto/wayland/primary-selection-unstable-v1.h"
+# endif
+#endif
+
+#ifdef FEAT_WAYLAND_CLIPBOARD
+
+// Wayland protocols for accessing the selection
+typedef enum {
+ VWL_DATA_PROTOCOL_NONE,
+ VWL_DATA_PROTOCOL_EXT,
+ VWL_DATA_PROTOCOL_WLR,
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ VWL_DATA_PROTOCOL_CORE,
+ VWL_DATA_PROTOCOL_PRIMARY
+#endif
+} vwl_data_protocol_T;
+
+#endif // FEAT_WAYLAND_CLIPBOARD
+
+// Struct that represents a seat. (Should be accessed via
+// vwl_get_seat()).
+struct vwl_seat_S {
+ struct wl_seat *proxy;
+ char *label; // Name of seat as text (e.g. seat0,
+ // seat1...).
+ uint32_t capabilities; // Bitmask of the capabilites of the seat
+ // (pointer, keyboard, touch).
+};
+
+// Struct wrapper for a Wayland connection
+struct vwl_connection_S {
+ struct {
+ struct wl_display *proxy;
+ int fd; // File descriptor for display
+ } display;
+
+ struct {
+ struct wl_registry *proxy;
+ } registry;
+
+ // Global objects
+ struct {
+ garray_T seats;
+
+#ifdef FEAT_WAYLAND_CLIPBOARD
+ struct zwlr_data_control_manager_v1 *zwlr_data_control_manager_v1;
+ struct ext_data_control_manager_v1 *ext_data_control_manager_v1;
+# ifdef FEAT_WAYLAND_CLIPBOARD_FS
+ struct wl_data_device_manager *wl_data_device_manager;
+ struct wl_shm *wl_shm;
+ struct wl_compositor *wl_compositor;
+ struct xdg_wm_base *xdg_wm_base;
+ struct zwp_primary_selection_device_manager_v1
+ *zwp_primary_selection_device_manager_v1;
+# endif
+#endif
+ } gobjects;
+};
+
+#ifdef FEAT_WAYLAND_CLIPBOARD
+
+// LISTENER WRAPPERS
+
+struct vwl_data_device_listener_S {
+ void (*data_offer)(void *data,
+ vwl_data_device_T *device,
+ vwl_data_offer_T *offer);
+ void (*selection)(void *data,
+ vwl_data_device_T *device,
+ vwl_data_offer_T *offer,
+ wayland_selection_T selection);
+
+ // This event is only relevant for data control protocols
+ void (*finished)(void *data, vwl_data_device_T *device);
+};
+
+struct vwl_data_source_listener_S {
+ void (*send)(void *data,
+ vwl_data_source_T *source,
+ const char *mime_type,
+ int fd);
+ void (*cancelled)(void *data, vwl_data_source_T *source);
+};
+
+struct vwl_data_offer_listener_S {
+ // Return TRUE to add mime type to internal array in data offer. Note that
+ // this is not called for the special Vim mime type
+ // (wayland_vim_special_mime), but offer->from_vim is set to true.
+ // Additionally when the special mime type is received, any offer events
+ // after are ignored.
+ bool (*offer)(void *data, vwl_data_offer_T *offer, const char *mime_type);
+};
+
+// DATA RELATED OBJECT WRAPPERS
+// These wrap around a proxy and act as a generic container.
+// The `data` member is used to pass other needed stuff around such as a
+// vwl_clipboard_selection_T pointer.
+
+struct vwl_data_offer_S {
+ void *proxy;
+ void *data; // Should be same as parent data
+ // device.
+ garray_T mime_types;
+ bool from_vim; // If offer came from us setting the
+ // selection.
+
+ const vwl_data_offer_listener_T *listener;
+ vwl_data_protocol_T protocol;
+};
+
+struct vwl_data_source_S {
+ void *proxy;
+ void *data;
+ const vwl_data_source_listener_T *listener;
+ vwl_data_protocol_T protocol;
+};
+
+struct vwl_data_device_S {
+ void *proxy;
+ void *data;
+ vwl_data_offer_T *offer;
+ const vwl_data_device_listener_T *listener;
+ vwl_data_protocol_T protocol;
+};
+
+struct vwl_data_device_manager_S {
+ void *proxy;
+ vwl_data_protocol_T protocol;
+};
+
+#ifdef FEAT_WAYLAND_CLIPBOARD_FS
+
+// Dummy functions to handle keyboard events we don't care about.
+
+#define VWL_FUNCS_DUMMY_KEYBOARD_EVENTS() \
+ static void \
+clip_wl_fs_keyboard_listener_keymap( \
+ void *data UNUSED, \
+ struct wl_keyboard *keyboard UNUSED, \
+ uint32_t format UNUSED, \
+ int fd, \
+ uint32_t size UNUSED) \
+{ \
+ close(fd); \
+} \
+ static void \
+clip_wl_fs_keyboard_listener_leave( \
+ void *data UNUSED, \
+ struct wl_keyboard *keyboard UNUSED, \
+ uint32_t serial UNUSED, \
+ struct wl_surface *surface UNUSED) \
+{ \
+} \
+ static void \
+clip_wl_fs_keyboard_listener_key( \
+ void *data UNUSED, \
+ struct wl_keyboard *keyboard UNUSED, \
+ uint32_t serial UNUSED, \
+ uint32_t time UNUSED, \
+ uint32_t key UNUSED, \
+ uint32_t state UNUSED) \
+{ \
+} \
+ static void \
+clip_wl_fs_keyboard_listener_modifiers( \
+ void *data UNUSED, \
+ struct wl_keyboard *keyboard UNUSED, \
+ uint32_t serial UNUSED, \
+ uint32_t mods_depressed UNUSED, \
+ uint32_t mods_latched UNUSED, \
+ uint32_t mods_locked UNUSED, \
+ uint32_t group UNUSED) \
+{ \
+} \
+ static void \
+clip_wl_fs_keyboard_listener_repeat_info( \
+ void *data UNUSED, \
+ struct wl_keyboard *keyboard UNUSED, \
+ int32_t rate UNUSED, \
+ int32_t delay UNUSED) \
+{ \
+}
+
+#endif
+
+#endif // FEAT_WAYLAND_CLIPBOARD
+
+// Global Wayland connection. Is also set to NULL when the connection is lost.
+extern vwl_connection_T *wayland_ct;
+
+#endif // FEAT_WAYLAND

Tony Mechelynck

unread,
Sep 22, 2025, 7:38:19 PMSep 22
to vim...@googlegroups.com
On Mon, Sep 22, 2025 at 9:15 PM Christian Brabandt <cbl...@256bit.org> wrote:
>
> patch 9.1.1784: Wayland code can be improved
>
> Commit: https://github.com/vim/vim/commit/368710abcfaadc8370a07cdd58303f2704f10282
> Author: Foxe Chen <chen...@gmail.com>
> Date: Mon Sep 22 19:06:58 2025 +0000
>
> patch 9.1.1784: Wayland code can be improved
>
> Problem: Wayland code can be improved
> Solution: Refactor Wayland Clipboard code (Foxe Chen).
>
> This the second attempt to refactor the Wayland code base:
> - Move clipboard code from wayland.c to clipboard.c
[...]

Build failure in clipboard.c in Normal build with Motif GUI *only*
(not in Huge with GTK3 GUI, and not in Tiny which has -wayland).

In file included from clipboard.c:36:
wayland.h:17:10: fatal error: wayland-client.h: No such file or directory
17 | #include <wayland-client.h>
| ^~~~~~~~~~~~~~~~~~
compilation terminated.
make: *** [Makefile:3238: objects/clipboard.o] Error 1

I believe that the same additional -I arguments should be added in
Normal build compile line for clipboard.c (with Motif GUI) as were
added previously in its wayland.c

Best regards,
Tony.

Christian Brabandt

unread,
Sep 23, 2025, 4:04:54 PMSep 23
to vim...@googlegroups.com
Hi Tony,

On Di, 23 Sep 2025, Tony Mechelynck wrote:

> On Mon, Sep 22, 2025 at 9:15 PM Christian Brabandt <cbl...@256bit.org> wrote:
> >
> > patch 9.1.1784: Wayland code can be improved
> >
> > Commit: https://github.com/vim/vim/commit/368710abcfaadc8370a07cdd58303f2704f10282
> > Author: Foxe Chen <chen...@gmail.com>
> > Date: Mon Sep 22 19:06:58 2025 +0000
> >
> > patch 9.1.1784: Wayland code can be improved
> >
> > Problem: Wayland code can be improved
> > Solution: Refactor Wayland Clipboard code (Foxe Chen).
> >
> > This the second attempt to refactor the Wayland code base:
> > - Move clipboard code from wayland.c to clipboard.c
> [...]
>
> Build failure in clipboard.c in Normal build with Motif GUI *only*
> (not in Huge with GTK3 GUI, and not in Tiny which has -wayland).
>
> In file included from clipboard.c:36:
> wayland.h:17:10: fatal error: wayland-client.h: No such file or directory
> 17 | #include <wayland-client.h>
> | ^~~~~~~~~~~~~~~~~~
> compilation terminated.
> make: *** [Makefile:3238: objects/clipboard.o] Error 1

does patch v9.1.1786 fixes it for you again?

Thanks,
Christian
--
Even though they raised the rate for first class mail in the United
States we really shouldn't complain -- it's still only two cents a day.

[and getting better! Soon it'll be down to a penny a day!]

Tony Mechelynck

unread,
Sep 23, 2025, 4:30:21 PMSep 23
to Christian Brabandt, vim...@googlegroups.com
No, it doesn't, not even if I use "make reconfig". I notice that
unlike at patch 9.1.1784, at patch 9.1.1786 configure is not run
automatically by make (for any featureset) and the clipboard.c compile
line (for Normal with Motif) is still

gcc -c -I. -Iproto -DHAVE_CONFIG_H -DFEAT_GUI_MOTIF -O2
-fno-strength-reduce -Wall -Wno-deprecated-declarations -D_REENTRANT
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 -o objects/clipboard.o
clipboard.c


with no "wayland" includes — indeed no includes at all other than -I. -Iproto

Best regards,
Tony.

Foxe Chen

unread,
Sep 23, 2025, 5:54:56 PMSep 23
to vim_dev
Does running make -j2 (parallel build) fix the issue?

Thanks,
Foxe 

Tony Mechelynck

unread,
Sep 23, 2025, 6:01:04 PMSep 23
to chen...@gmail.com, vim...@googlegroups.com
Normally I use -j4 and this is also what I used this time, first for
"make" and then for "make reconfig". The issue was present in both
cases.

Best regards,
Tony.

Foxe Chen

unread,
Sep 23, 2025, 6:03:54 PMSep 23
to vim_dev
Can you try out this patch? 

Thanks,
Foxe

diff --git a/src/Makefile b/src/Makefile
index c5697a256..033a2589d 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1625,7 +1625,7 @@ EXTRA_SRC = if_lua.c if_mzsch.c auto/if_perl.c if_perlsfio.c \
      gui_beval.c netbeans.c job.c channel.c \
      $(GRESOURCE_SRC)
 
-$(WAYLAND_SRC):
+$(WAYLAND_SRC) wayland.h:
  cd auto/wayland; $(MAKE)
 
 # Needed for parallel jobs to work
@@ -1633,6 +1633,7 @@ auto/wayland/ext-data-control-v1.h: auto/wayland/ext-data-control-v1.c
 auto/wayland/wlr-data-control-unstable-v1.h: auto/wayland/wlr-data-control-unstable-v1.c
 auto/wayland/primary-selection-unstable-v1.h: auto/wayland/primary-selection-unstable-v1.c
 auto/wayland/xdg-shell.h: auto/wayland/xdg-shell.c
+clipboard.c wayland.c: $(WAYLAND_SRC)
 
 # Unittest files
 JSON_TEST_SRC = json_test.c
@@ -3234,7 +3235,7 @@ objects/cindent.o: cindent.c
 objects/clientserver.o: clientserver.c
  $(CCC) -o $@ clientserver.c
 
-objects/clipboard.o: clipboard.c $(WAYLAND_SRC)
+objects/clipboard.o: clipboard.c
  $(CCC) -o $@ clipboard.c
 
 objects/cmdexpand.o: cmdexpand.c

Tony Mechelynck

unread,
Sep 24, 2025, 1:38:09 AMSep 24
to chen...@gmail.com, vim...@googlegroups.com
[...]

Hm. I'm not sure that I know how to apply it without f**king up my
repository. Can you try to compile Vim in a shadow repository with the
following configuration ?

export CONF_OPT_GUI='--enable-gui=motif'
export CONF_OPT_MULTIBYTE='--enable-multibyte'
export CONF_OPT_AUTOSERVE='--enable-autoservername'
export CONF_OPT_SODIUM='--enable-libsodium'
export CONF_OPT_FEAT='--with-features=normal'
export CONF_ARGS2='--with-vim-name=vim-normal'

If you can't, I'll add the following line then run "make reconfig":

export CONF_OPT_X='--without-wayland'

Best regards,
Tony.

Foxe Chen

unread,
Sep 24, 2025, 8:52:47 AMSep 24
to vim_dev
I tried compiling Vim in a shadow repo (make shadow) with that configuration, and it compiled without errors. Can you tell me why that patch is messing up your repo?

Thanks,
Foxe

Tony Mechelynck

unread,
Sep 24, 2025, 10:58:46 AMSep 24
to Foxe Chen, vim_dev
On Wed, Sep 24, 2025 at 3:15 PM Tony Mechelynck
<antoine.m...@gmail.com> wrote:
>
> On Wed, Sep 24, 2025 at 2:51 PM Foxe Chen <chen...@gmail.com> wrote:
> >
> > I tried compiling Vim in a shadow repo (make shadow) with that configuration, and it compiled without errors. Can you tell me why that patch is messing up your repo?
> >
> > Thanks,
> > Foxe
>
> I don't see in the gcc line for clipboard.c the Wayland-related -I
> argument(s) which were added to the line for wayland.c to make it find
> the .h include files required by wayland.h — as I said earlier in this
> thread, I see there no other -I arguments than -I. -Iproto; and since
> compilation proceeds in roughly alphabetical order, I don't see what
> the gcc line for wayland.c is now.
>
> As a "temporary workaround" I'm going to disable Wayland in my Normal
> configuration.
>
> Best regards,
> Tony.

P.S. Of course, with Wayland disabled, it builds without error, as expected.

Best regards,
Tony.

Foxe Chen

unread,
Sep 24, 2025, 1:04:54 PMSep 24
to vim_dev
There are no specific -I arguments for wayland, since the #includes in the wayland.h file are relative to the src directory. Do you mean the include directory for the header file of libwayland?

Christian Brabandt

unread,
Sep 24, 2025, 2:22:49 PMSep 24
to vim...@googlegroups.com, chen...@gmail.com

On Mi, 24 Sep 2025, Tony Mechelynck wrote:

>
> Hm. I'm not sure that I know how to apply it without f**king up my
> repository. Can you try to compile Vim in a shadow repository with the
> following configuration ?

That patch only changes a few lines. You could manually apply it and
test if this compiles. To revert to latest repository version you can
use hg revert --all or git reset --hard

Thanks,
Christian
--
A long memory is the most subversive idea in America.
Reply all
Reply to author
Forward
0 new messages