]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1784: Wayland code can be improved v9.1.1784
authorFoxe Chen <chen.foxe@gmail.com>
Mon, 22 Sep 2025 19:06:58 +0000 (19:06 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 22 Sep 2025 19:09:52 +0000 (19:09 +0000)
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.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
34 files changed:
Filelist
runtime/doc/builtin.txt
runtime/doc/gui_x11.txt
runtime/doc/options.txt
runtime/doc/tags
runtime/doc/various.txt
runtime/doc/wayland.txt
runtime/optwin.vim
src/Makefile
src/auto/configure
src/clipboard.c
src/config.h.in
src/configure.ac
src/evalfunc.c
src/feature.h
src/globals.h
src/main.c
src/message.c
src/option.c
src/option.h
src/optiondefs.h
src/optionstr.c
src/os_unix.c
src/po/vim.pot
src/proto/clipboard.pro
src/proto/message.pro
src/proto/wayland.pro
src/register.c
src/structs.h
src/testdir/test_wayland.vim
src/version.c
src/vim.h
src/wayland.c
src/wayland.h [new file with mode: 0644]

index e97e17ec0e89487b51a20638722cb72371b871fb..3a2e066e33d8cd79973e4c1e860e24fdfb1d4079 100644 (file)
--- 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 \
index 2e5edbbdc9060365bd335d44f7a71606d924c756..2e9535e650d11160cdee9e3b45ea17f2a6ba191b 100644 (file)
@@ -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)
index 89ba7c2ee4fb146e5f3b99e4bfb5398cc6834f3c..3b618aa5e298e55a71432835d3809c8589f42a2d 100644 (file)
@@ -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
index 7dad0c4ae3473426bcbd6a2352c8be1f7630d5ab..e1361d34d65c6ea1f3c32f16d4ff01f48c4fd722 100644 (file)
@@ -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|.
index b0662994ec5f41c473814d0a82af2266938f1b8b..7890b417e19f3e7550a127ed366b846c77608858 100644 (file)
@@ -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*
index e29fafdf72c53758588952e630779748053e7837..b91e3ef237c8b6a253e46d47f4ed64c66b2c8b9f 100644 (file)
@@ -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.
index adb7e694fa66488ab5be6a31fb4ce7c0057cc482..cbcdc0dc9c342ecaa4b7e9f3207cdfeb64b60730 100644 (file)
@@ -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
index 5b5727aa676b592a86c03d3136a6a431d912577b..538b44dad0428e594b4310016c1f9bbeb86bfd61 100644 (file)
@@ -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 <Bram@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
index 333a9121cd79454af73964f586c43012f9a145e2..b2b7a83b85a9a8fccdb0bf39400661417b19b4dc 100644 (file)
@@ -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
index 5c0d3d614f90d9feaf9bf4e59f3afc9a190b4ded..c99feb0507d3f717843aa8a4dd0d3546e8b1d855 100755 (executable)
@@ -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\n" "$as_me:${as_lineno-$LINENO}: checking for wayland" >&5
 printf %s "checking for wayland... " >&6; }
   if "$PKG_CONFIG" --exists 'wayland-client'; then
     { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 printf "%s\n" "yes" >&6; }
+
+    { printf "%s\n" "$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\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+          printf "%s\n" "#define FEAT_WAYLAND_CLIPBOARD_FS 1" >>confdefs.h
+
+else case e in #(
+  e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; } ;;
+esac
+fi
+
     printf "%s\n" "#define HAVE_WAYLAND 1" >>confdefs.h
 
     WAYLAND_CPPFLAGS=`$PKG_CONFIG --cflags-only-I wayland-client`
@@ -9288,16 +9318,23 @@ printf "%s\n" "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
+
+
 
 
 
index 4b2e6874a94a759cee067d0a7829e3ea7cafbcdc..66f3e321aeea7cb489b48bd25506dffbc4da38fc 100644 (file)
 #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] == '\n')
            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;
index e8775d98c38f3c804b2c4e553f2d6f67694e606d..983f186b8d1d7225dcbeb3ad8721bd495a8038b0 100644 (file)
@@ -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
 
index 983c5822471f5088ab6c7cd8929082d1425d3876..6a641c2f2b83d3e6aea843a2c590962ed25901d7 100644 (file)
@@ -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)
index da537779996ca79b5c7e1748dbcc625431d86f49..a8d190638bc6586581c6d91ffe32c474dea242bd 100644 (file)
@@ -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},
index 826adbe2bdcd43b25efb6cf8fb4a196982fa5a82..d7db96cd57810082b6360912afa88a7f2a350e48 100644 (file)
 # 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.
  */
index 8d031028eae201da23545d0b507d1233cd2c803b..653690b000102f0da4fa426d9d64901593f3924f 100644 (file)
@@ -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
 
index c77454a26fbb5cc2b5343c5face1ae85217dd390..50bc6f4f797f31eb5c65665ca3e16bdb0aac863f 100644 (file)
@@ -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
 
index 35f57e8db98eedf22e2ec25f7cb62d9234a6e20f..d5fc6b6240b064670e80a4e9caf823b2ab691c4d 100644 (file)
@@ -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)
index 7c5f4b99ddac1cb10b5ecced1f091fa3a8dfca00..88da9c09959ed25968e404f7d7e49bfa51891aba 100644 (file)
@@ -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;
 }
index 9e6aed355662fcf0ded783f5b24b4d0275a2f55a..77f3ceb3c7a1f2970944fe735996c5c80027c8f6 100644 (file)
@@ -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'
index 572b7aff13cb47a33251a3703b46f99ce930f24e..626823c161e108c09380d25335f9f9aa4fdd0012 100644 (file)
@@ -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
index 628842ef1a6ddd4e71e1f8fadc7bbed802a3c326..55cde57166265a6b2b898890d2fcddabc3a5fce4 100644 (file)
@@ -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;
index b2bd8adb30c7f36cf501328797d355076292c7d2..02116ee8c49962c505a3ac96a3908b2b989c3ae2 100644 (file)
@@ -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
index d5cb0795daad156383bbce27488c99c031ecf1c4..20571200d1da47d07c0645e39edc1a45c38d1b99 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Vim\n"
 "Report-Msgid-Bugs-To: vim-dev@vim.org\n"
-"POT-Creation-Date: 2025-09-21 18:48+0000\n"
+"POT-Creation-Date: 2025-09-22 19:04+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -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"
index 9ccfea90a63d24a1c8e1329739de038a6e6a2004..5727aa8b77e39004c7ccdd5f72510bfbb58f0b60 100644 (file)
@@ -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 : */
index 34b381866f5909030b6d12a7403c58fa7166f00a..5b6c80b4d8b70fc2a0b4a53568c19934d749f070 100644 (file)
@@ -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);
index a1f327dbc8f63cd84b2aff449fd34f1a4e1b426e..113bf82e478868a0ba3f71cad3b671adf0c04de8 100644 (file)
@@ -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 : */
index 818166df5e1711207549a2fe8f7d1841f7cebb64..a866321a3a7b2f1c161333c5da63237b21c2b3ba 100644 (file)
@@ -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
 
index e28c38340a82cbf344d27366357864c1994641b8..981db0e7cfc97cd6a38d9bfaf574ca960cf19494 100644 (file)
@@ -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
index 818574964242016e319e04fe63544615b68ad03f..155172a0fa93ed548609f2054cbc583850aefa9c 100644 (file)
@@ -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\%((\a\+)\)\=: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\n",
         \ 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
index efea7d85f36bf8d4ea89fed1eef7f7dc96329280..911b9139939a12ed73b56e25078b5f7a827c4ff1 100644 (file)
@@ -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,
 /**/
index dd4e782b328a16af797915df45cc4777d6e8214e..3c4dacb44d2712ea7198a11fb60b60394e90b526 100644 (file)
--- 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.
index 5aa8ec17ecb6efd8a61da52d61ed61e286dbea14..33c10987f618f891b1328c17a2e3c1800520ea79 100644 (file)
  */
 
 /*
- * 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 (file)
index 0000000..d260af4
--- /dev/null
@@ -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